Compare commits
12 Commits
master
...
dwelle/fix
Author | SHA1 | Date | |
---|---|---|---|
![]() |
913921631b | ||
![]() |
ec39094933 | ||
![]() |
ef20c8b9fa | ||
![]() |
1c55160e3f | ||
![]() |
e49db1dd3c | ||
![]() |
cd8bfb06b5 | ||
![]() |
7762b925ae | ||
![]() |
113dfc0023 | ||
![]() |
96b5cfc35d | ||
![]() |
9da26fb7e0 | ||
![]() |
392b362118 | ||
![]() |
0e02366dee |
@ -48,6 +48,3 @@ 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://ex.dylanbanta.com/api/v2/scenes/
|
VITE_APP_BACKEND_V2_GET_URL=https://json.excalidraw.com/api/v2/
|
||||||
VITE_APP_BACKEND_V2_POST_URL=https://ex.dylanbanta.com/api/v2/scenes/
|
VITE_APP_BACKEND_V2_POST_URL=https://json.excalidraw.com/api/v2/post/
|
||||||
|
|
||||||
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,43 +1,8 @@
|
|||||||
{
|
{
|
||||||
"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": ["error", { "prefer": "type-imports", "disallowTypeAnnotations": false, "fixStyle": "separate-type-imports" }]
|
||||||
"error",
|
|
||||||
{
|
|
||||||
"prefer": "type-imports",
|
|
||||||
"disallowTypeAnnotations": false,
|
|
||||||
"fixStyle": "separate-type-imports"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"no-restricted-imports": [
|
|
||||||
"error",
|
|
||||||
{
|
|
||||||
"name": "jotai",
|
|
||||||
"message": "Do not import from \"jotai\" directly. Use our app-specific modules (\"editor-jotai\" or \"app-jotai\")."
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"react/jsx-no-target-blank": [
|
|
||||||
"error",
|
|
||||||
{
|
|
||||||
"allowReferrer": true
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
1
.gitignore
vendored
@ -25,4 +25,5 @@ packages/excalidraw/types
|
|||||||
coverage
|
coverage
|
||||||
dev-dist
|
dev-dist
|
||||||
html
|
html
|
||||||
|
examples/**/bundle.*
|
||||||
meta*.json
|
meta*.json
|
14
README.md
@ -7,7 +7,7 @@
|
|||||||
|
|
||||||
<h4 align="center">
|
<h4 align="center">
|
||||||
<a href="https://excalidraw.com">Excalidraw Editor</a> |
|
<a href="https://excalidraw.com">Excalidraw Editor</a> |
|
||||||
<a href="https://plus.excalidraw.com/blog">Blog</a> |
|
<a href="https://blog.excalidraw.com">Blog</a> |
|
||||||
<a href="https://docs.excalidraw.com">Documentation</a> |
|
<a href="https://docs.excalidraw.com">Documentation</a> |
|
||||||
<a href="https://plus.excalidraw.com">Excalidraw+</a>
|
<a href="https://plus.excalidraw.com">Excalidraw+</a>
|
||||||
</h4>
|
</h4>
|
||||||
@ -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,11 +87,13 @@ We'll be adding these features as drop-in plugins for the npm package in the fut
|
|||||||
|
|
||||||
**Note:** following instructions are for installing the Excalidraw [npm package](https://www.npmjs.com/package/@excalidraw/excalidraw) when integrating Excalidraw into your own app. To run the repository locally for development, please refer to our [Development Guide](https://docs.excalidraw.com/docs/introduction/development).
|
**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 be 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 a valid React Node.
|
||||||
|
|
||||||
**Usage**
|
**Usage**
|
||||||
|
|
||||||
@ -25,7 +25,7 @@ function App() {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
This will only work for `Desktop` devices.
|
This will only for `Desktop` devices.
|
||||||
|
|
||||||
For `mobile` you will need to render it inside the [MainMenu](#mainmenu). You can use the [`useDevice`](#useDevice) hook to check the type of device, this will be available only inside the `children` of `Excalidraw` component.
|
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`. The default families are the following:
|
`FONT_FAMILY` contains all the font families used in `Excalidraw` as explained below
|
||||||
|
|
||||||
| Font Family | Description |
|
| Font Family | Description |
|
||||||
| ----------- | ---------------------- |
|
| ----------- | ---------------------- |
|
||||||
| `Excalifont` | The `Hand-drawn` font |
|
| `Virgil` | The `Hand-drawn` font |
|
||||||
| `Nunito` | The `Normal` Font |
|
| `Helvetica` | The `Normal` Font |
|
||||||
| `Comic Shanns` | The `Code` Font |
|
| `Cascadia` | The `Code` Font |
|
||||||
|
|
||||||
Pre-selected family is `FONT_FAMILY.Excalifont`, unless it's overriden with `initialData.appState.currentItemFontFamily`.
|
Defaults to `FONT_FAMILY.Virgil` unless passed in `initialData.appState.currentItemFontFamily`.
|
||||||
|
|
||||||
### THEME
|
### 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. |
|
||||||
| `captureUpdate` | [`CaptureUpdateAction`](https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/store.ts#L40) | Controls which updates should be captured by the `Store`. Captured updates are emmitted and listened to by other components, such as `History` for undo / redo purposes. |
|
| `commitToStore` | `boolean` | Implies if the change should be captured and commited to the `store`. Commited changes are emmitted and listened to by other components, such as `History` for undo / redo purposes. Defaults to `false`. |
|
||||||
|
|
||||||
```jsx live
|
```jsx live
|
||||||
function App() {
|
function App() {
|
||||||
@ -105,7 +105,6 @@ function App() {
|
|||||||
appState: {
|
appState: {
|
||||||
viewBackgroundColor: "#edf2ff",
|
viewBackgroundColor: "#edf2ff",
|
||||||
},
|
},
|
||||||
captureUpdate: CaptureUpdateAction.IMMEDIATELY,
|
|
||||||
};
|
};
|
||||||
excalidrawAPI.updateScene(sceneData);
|
excalidrawAPI.updateScene(sceneData);
|
||||||
};
|
};
|
||||||
@ -122,18 +121,6 @@ function App() {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
#### captureUpdate
|
|
||||||
|
|
||||||
You can use the `captureUpdate` to influence undo / redo behaviour.
|
|
||||||
|
|
||||||
> **NOTE**: Some updates are not observed by the store / history - i.e. updates to `collaborators` object or parts of `AppState` which are not observed (not `ObservedAppState`). Such updates will never make it to the undo / redo stacks, regardless of the passed `captureUpdate` value.
|
|
||||||
|
|
||||||
| | `captureUpdate` value | Notes |
|
|
||||||
| --- | --- | --- |
|
|
||||||
| _Immediately undoable_ | `CaptureUpdateAction.IMMEDIATELY` | Use for updates which should be captured. Should be used for most of the local updates. These updates will _immediately_ make it to the local undo / redo stacks. |
|
|
||||||
| _Eventually undoable_ | `CaptureUpdateAction.EVENTUALLY` | Use for updates which should not be captured immediately - likely exceptions which are part of some async multi-step process. Otherwise, all such updates would end up being captured with the next `CaptureUpdateAction.IMMEDIATELY` - triggered either by the next `updateScene` or internally by the editor. These updates will _eventually_ make it to the local undo / redo stacks. |
|
|
||||||
| _Never undoable_ | `CaptureUpdateAction.NEVER` | Use for updates which should never be recorded, such as remote updates or scene initialization. These updates will _never_ make it to the local undo / redo stacks. |
|
|
||||||
|
|
||||||
### updateLibrary
|
### updateLibrary
|
||||||
|
|
||||||
<pre>
|
<pre>
|
||||||
|
@ -5,33 +5,31 @@ 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 |
|
||||||
| [`onChange`](#onchange) | `function` | \_ | This callback is triggered whenever the component updates due to any change. This callback will receive the excalidraw `elements` and the current `app state`. |
|
| [`onChange`](#onchange) | `function` | _ | This callback is triggered whenever the component updates due to any change. This callback will receive the excalidraw `elements` and the current `app state`. |
|
||||||
| [`onPointerUpdate`](#onpointerupdate) | `function` | \_ | Callback triggered when mouse pointer is updated. |
|
| [`onPointerUpdate`](#onpointerupdate) | `function` | _ | Callback triggered when mouse pointer is updated. |
|
||||||
| [`onPointerDown`](#onpointerdown) | `function` | \_ | This prop if passed gets triggered on pointer down events |
|
| [`onPointerDown`](#onpointerdown) | `function` | _ | This prop if passed gets triggered on pointer down events |
|
||||||
| [`onScrollChange`](#onscrollchange) | `function` | \_ | This prop if passed gets triggered when scrolling the canvas. |
|
| [`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. |
|
| [`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 |
|
||||||
| [`renderCustomStats`](/docs/@excalidraw/excalidraw/api/props/render-props#rendercustomstats) | `function` | \_ | Render function that can be used to render custom stats on the stats dialog. |
|
| [`renderCustomStats`](/docs/@excalidraw/excalidraw/api/props/render-props#rendercustomstats) | `function` | _ | Render function that can be used to render custom stats on the stats dialog. |
|
||||||
| [`viewModeEnabled`](#viewmodeenabled) | `boolean` | \_ | This indicates if the app is in `view` mode. |
|
| [`viewModeEnabled`](#viewmodeenabled) | `boolean` | _ | This indicates if the app is in `view` mode. |
|
||||||
| [`zenModeEnabled`](#zenmodeenabled) | `boolean` | \_ | This indicates if the `zen` mode is enabled |
|
| [`zenModeEnabled`](#zenmodeenabled) | `boolean` | _ | This indicates if the `zen` mode is enabled |
|
||||||
| [`gridModeEnabled`](#gridmodeenabled) | `boolean` | \_ | This indicates if the `grid` mode is enabled |
|
| [`gridModeEnabled`](#gridmodeenabled) | `boolean` | _ | This indicates if the `grid` mode is enabled |
|
||||||
| [`libraryReturnUrl`](#libraryreturnurl) | `string` | \_ | What URL should [libraries.excalidraw.com](https://libraries.excalidraw.com) be installed to |
|
| [`libraryReturnUrl`](#libraryreturnurl) | `string` | _ | What URL should [libraries.excalidraw.com](https://libraries.excalidraw.com) be installed to |
|
||||||
| [`theme`](#theme) | `"light"` | `"dark"` | `"light"` | The theme of the Excalidraw component |
|
| [`theme`](#theme) | `"light"` | `"dark"` | `"light"` | The theme of the Excalidraw component |
|
||||||
| [`name`](#name) | `string` | | Name of the drawing |
|
| [`name`](#name) | `string` | | Name of the drawing |
|
||||||
| [`UIOptions`](/docs/@excalidraw/excalidraw/api/props/ui-options) | `object` | [DEFAULT UI OPTIONS](https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/constants.ts#L151) | To customise UI options. Currently we support customising [`canvas actions`](/docs/@excalidraw/excalidraw/api/props/ui-options#canvasactions) |
|
| [`UIOptions`](/docs/@excalidraw/excalidraw/api/props/ui-options) | `object` | [DEFAULT UI OPTIONS](https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/constants.ts#L151) | To customise UI options. Currently we support customising [`canvas actions`](/docs/@excalidraw/excalidraw/api/props/ui-options#canvasactions) |
|
||||||
| [`detectScroll`](#detectscroll) | `boolean` | `true` | Indicates whether to update the offsets when nearest ancestor is scrolled. |
|
| [`detectScroll`](#detectscroll) | `boolean` | `true` | Indicates whether to update the offsets when nearest ancestor is scrolled. |
|
||||||
| [`handleKeyboardGlobally`](#handlekeyboardglobally) | `boolean` | `false` | Indicates whether to bind the keyboard events to document. |
|
| [`handleKeyboardGlobally`](#handlekeyboardglobally) | `boolean` | `false` | Indicates whether to bind the keyboard events to document. |
|
||||||
| [`autoFocus`](#autofocus) | `boolean` | `false` | Indicates whether to focus the Excalidraw component on page load |
|
| [`autoFocus`](#autofocus) | `boolean` | `false` | Indicates whether to focus the Excalidraw component on page load |
|
||||||
| [`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[]` \| <code>((link: string) => boolean | undefined)</code> | \_ | use for custom src url validation |
|
| [`validateEmbeddable`](#validateEmbeddable) | string[] | `boolean | RegExp | RegExp[] | ((link: string) => boolean | undefined)` | \_ | use for custom src url validation |
|
||||||
| [`renderEmbeddable`](/docs/@excalidraw/excalidraw/api/props/render-props#renderEmbeddable) | `function` | \_ | Render function that can override the built-in `<iframe>` |
|
| [`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
|
||||||
|
|
||||||
@ -95,8 +93,9 @@ This callback is triggered when mouse pointer is updated.
|
|||||||
|
|
||||||
This prop if passed will be triggered on pointer down events and has the below signature.
|
This prop if passed will be triggered on pointer down events and has the below signature.
|
||||||
|
|
||||||
|
|
||||||
<pre>
|
<pre>
|
||||||
(activeTool:{" "}
|
(activeTool:{" "}
|
||||||
<a href="https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/types.ts#L115">
|
<a href="https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/types.ts#L115">
|
||||||
{" "}
|
{" "}
|
||||||
AppState["activeTool"]
|
AppState["activeTool"]
|
||||||
@ -144,14 +143,6 @@ This callback if supplied will get triggered when the library is updated and has
|
|||||||
|
|
||||||
It is invoked with empty items when user clears the library. You can use this callback when you want to do something additional when library is updated for example persisting it to local storage.
|
It is invoked with empty items when user clears the library. You can use this callback when you want to do something additional when library is updated for example persisting it to local storage.
|
||||||
|
|
||||||
### generateLinkForSelection
|
|
||||||
|
|
||||||
This prop if passed will be used to replace the default link generation function. The idea is that the host app can take over the creation of element links, which can be used to navigate to a particular element or a group. If the host app chooses a different key for element link id, then the host app should also take care of the handling and the navigation in `onLinkOpen`.
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
(id: string, type: "element" | "group") => string;
|
|
||||||
```
|
|
||||||
|
|
||||||
### onLinkOpen
|
### onLinkOpen
|
||||||
|
|
||||||
This prop if passed will be triggered when clicked on `link`. To handle the redirect yourself (such as when using your own router for internal links), you must call `event.preventDefault()`.
|
This prop if passed will be triggered when clicked on `link`. To handle the redirect yourself (such as when using your own router for internal links), you must call `event.preventDefault()`.
|
||||||
@ -216,7 +207,8 @@ This prop indicates whether the shows the grid. When supplied, the value takes p
|
|||||||
|
|
||||||
### libraryReturnUrl
|
### libraryReturnUrl
|
||||||
|
|
||||||
If supplied, this URL will be used when user tries to install a library from [libraries.excalidraw.com](https://libraries.excalidraw.com). Defaults to _window.location.origin + window.location.pathname_. To install the libraries in the same tab from which it was opened, you need to set `window.name` (to any alphanumeric string) — if it's not set it will open in a new tab.
|
If supplied, this URL will be used when user tries to install a library from [libraries.excalidraw.com](https://libraries.excalidraw.com).
|
||||||
|
Defaults to _window.location.origin + window.location.pathname_. To install the libraries in the same tab from which it was opened, you need to set `window.name` (to any alphanumeric string) — if it's not set it will open in a new tab.
|
||||||
|
|
||||||
### theme
|
### theme
|
||||||
|
|
||||||
@ -228,6 +220,7 @@ You can use [`THEME`](/docs/@excalidraw/excalidraw/api/utils#theme) to specify t
|
|||||||
|
|
||||||
This prop sets the `name` of the drawing which will be used when exporting the drawing. When supplied, the value takes precedence over _intialData.appState.name_, the `name` will be fully controlled by host app and the users won't be able to edit from within Excalidraw.
|
This prop sets the `name` of the drawing which will be used when exporting the drawing. When supplied, the value takes precedence over _intialData.appState.name_, the `name` will be fully controlled by host app and the users won't be able to edit from within Excalidraw.
|
||||||
|
|
||||||
|
|
||||||
### detectScroll
|
### detectScroll
|
||||||
|
|
||||||
Indicates whether Excalidraw should listen for `scroll` event on the nearest scrollable container in the DOM tree and recompute the coordinates (e.g. to correctly handle the cursor) when the component's position changes. You can disable this when you either know this doesn't affect your app or you want to take care of it yourself (calling the [`refresh()`](#ref) method).
|
Indicates whether Excalidraw should listen for `scroll` event on the nearest scrollable container in the DOM tree and recompute the coordinates (e.g. to correctly handle the cursor) when the component's position changes. You can disable this when you either know this doesn't affect your app or you want to take care of it yourself (calling the [`refresh()`](#ref) method).
|
||||||
|
@ -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, normalizeIndices?: boolean }<br/>
|
opts: { refreshDimensions?: boolean, repairBindings?: boolean }<br/>
|
||||||
)
|
)
|
||||||
</pre>
|
</pre>
|
||||||
|
|
||||||
@ -51,9 +51,8 @@ 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_**
|
||||||
|
|
||||||
@ -74,7 +73,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, normalizeIndices?: boolean }<br/>
|
opts: { refreshDimensions?: boolean, repairBindings?: 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
|
||||||
yarn
|
cd packages/excalidraw && yarn
|
||||||
```
|
```
|
||||||
|
|
||||||
2. Start the example app
|
2. Start the example app
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
yarn start:example
|
yarn start
|
||||||
```
|
```
|
||||||
|
|
||||||
[http://localhost:3001](http://localhost:3001) will open in your default browser.
|
[http://localhost:3001](http://localhost:3001) will open in your default browser.
|
||||||
|
|
||||||
This is the same example as the [CodeSandbox](https://codesandbox.io/p/sandbox/github/excalidraw/excalidraw/tree/master/examples/with-script-in-browser) example.
|
The example is same as the [codesandbox example](https://ehlz3.csb.app/)
|
||||||
|
|
||||||
## Releasing
|
## Releasing
|
||||||
|
|
||||||
|
@ -17,9 +17,11 @@ 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,12 +1,16 @@
|
|||||||
# Installation
|
# Installation
|
||||||
|
|
||||||
**Excalidraw** is exported as a component to be directly embedded in your project.
|
**Excalidraw** is published to npm as a component you can directly embed in your projects.
|
||||||
|
|
||||||
Use `npm` or `yarn` to install the package.
|
Using `npm`:
|
||||||
|
|
||||||
```bash
|
```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
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -16,40 +20,24 @@ yarn add react react-dom @excalidraw/excalidraw
|
|||||||
|
|
||||||
:::
|
:::
|
||||||
|
|
||||||
### Self-hosting fonts
|
### Static assets
|
||||||
|
|
||||||
By default, Excalidraw will try to download all the used fonts from the [CDN](https://esm.run/@excalidraw/excalidraw/dist/prod).
|
Excalidraw depends on assets such as localization files (if you opt to use them), fonts, and others.
|
||||||
|
|
||||||
For self-hosting purposes, you'll have to copy the content of the folder `node_modules/@excalidraw/excalidraw/dist/prod/fonts` to the path where your assets should be served from (i.e. `public/` directory in your project). In that case, you should also set `window.EXCALIDRAW_ASSET_PATH` to the very same path, i.e. `/` in case it's in the root:
|
By default these assets are loaded from a public CDN [`https://unpkg.com/@excalidraw/excalidraw/dist/`](https://unpkg.com/@excalidraw/excalidraw/dist), so you don't need to do anything on your end.
|
||||||
|
|
||||||
|
However, if you want to host these files yourself, you can find them in your `node_modules/@excalidraw/excalidraw/dist` directory, in folders `excalidraw-assets` (for production) and `excalidraw-assets-dev` (for development).
|
||||||
|
|
||||||
|
Copy these folders to your static assets directory, and add a `window.EXCALIDRAW_ASSET_PATH` variable in your `index.html` or `index.js` entry file pointing to your public assets path (relative). For example, if you serve your assets from the root of your hostname, you would do:
|
||||||
|
|
||||||
```js
|
```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
|
||||||
|
|
||||||
Go to [CodeSandbox](https://codesandbox.io/p/sandbox/github/excalidraw/excalidraw/tree/master/examples/with-script-in-browser) example.
|
[Try here](https://codesandbox.io/s/excalidraw-ehlz3).
|
||||||
|
@ -12,7 +12,7 @@ import { Excalidraw } from "@excalidraw/excalidraw";
|
|||||||
|
|
||||||
Throughout the documentation we use live, editable Excalidraw examples like the one shown below.
|
Throughout the documentation we use live, editable Excalidraw examples like the one shown below.
|
||||||
|
|
||||||
While we aim for the examples to closely reflect what you'd get if you rendered it yourself, we actually initialize it with some props behind the scenes.
|
While we aim for the examples to closely reflect what you'd get if you rendered it yourself, we actually initialize it with some props behind the scenes.
|
||||||
For example, we're passing a `theme` prop to it based on the current color theme of the docs you're just reading.
|
For example, we're passing a `theme` prop to it based on the current color theme of the docs you're just reading.
|
||||||
|
|
||||||
:::
|
:::
|
||||||
@ -70,9 +70,9 @@ If you are using `pages router` then importing the wrapper dynamically would wor
|
|||||||
height: 141.9765625,
|
height: 141.9765625,
|
||||||
},]));
|
},]));
|
||||||
return (
|
return (
|
||||||
<div style={{height:"500px", width:"500px"}}>
|
<div style={{height:"500px", width:"500px"}}>
|
||||||
<Excalidraw />
|
<Excalidraw />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
export default ExcalidrawWrapper;
|
export default ExcalidrawWrapper;
|
||||||
@ -84,8 +84,8 @@ If you are using `pages router` then importing the wrapper dynamically would wor
|
|||||||
|
|
||||||
```jsx showLineNumbers
|
```jsx showLineNumbers
|
||||||
import dynamic from "next/dynamic";
|
import dynamic from "next/dynamic";
|
||||||
|
|
||||||
// Since client components get prerenderd on server as well hence importing
|
// Since client components get prerenderd on server as well hence importing
|
||||||
// the excalidraw stuff dynamically with ssr false
|
// the excalidraw stuff dynamically with ssr false
|
||||||
|
|
||||||
const ExcalidrawWrapper = dynamic(
|
const ExcalidrawWrapper = dynamic(
|
||||||
@ -97,7 +97,7 @@ If you are using `pages router` then importing the wrapper dynamically would wor
|
|||||||
|
|
||||||
export default function Page() {
|
export default function Page() {
|
||||||
return (
|
return (
|
||||||
<ExcalidrawWrapper />
|
<ExcalidrawWrapper />
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
@ -108,7 +108,7 @@ If you are using `pages router` then importing the wrapper dynamically would wor
|
|||||||
```jsx showLineNumbers
|
```jsx showLineNumbers
|
||||||
import dynamic from "next/dynamic";
|
import dynamic from "next/dynamic";
|
||||||
|
|
||||||
// Since client components get prerenderd on server as well hence importing
|
// Since client components get prerenderd on server as well hence importing
|
||||||
// the excalidraw stuff dynamically with ssr false
|
// the excalidraw stuff dynamically with ssr false
|
||||||
|
|
||||||
const ExcalidrawWrapper = dynamic(
|
const ExcalidrawWrapper = dynamic(
|
||||||
@ -128,10 +128,11 @@ 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/).
|
|
||||||
|
|
||||||
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.
|
Here is a [source code](https://github.com/excalidraw/excalidraw/tree/master/examples/excalidraw/with-nextjs) for the example with app and pages router. You you can try it out [here](https://excalidraw-package-example-with-nextjs-gh6smrdnq-excalidraw.vercel.app/).
|
||||||
|
|
||||||
|
|
||||||
|
The `types` are available at `@excalidraw/excalidraw/types`, you can view [example for typescript](https://codesandbox.io/s/excalidraw-types-9h2dm)
|
||||||
|
|
||||||
### Preact
|
### Preact
|
||||||
|
|
||||||
@ -152,13 +153,31 @@ Since Vite removes env variables by default, you can update the vite config to e
|
|||||||
"process.env.IS_PREACT": JSON.stringify("true"),
|
"process.env.IS_PREACT": JSON.stringify("true"),
|
||||||
},
|
},
|
||||||
```
|
```
|
||||||
:::
|
:::
|
||||||
|
|
||||||
## Browser
|
## Browser
|
||||||
|
|
||||||
To use it Excalidraw in a browser directly, use the following setup :point_down:
|
To use it in a browser directly:
|
||||||
|
|
||||||
> **Note**: We rely on import maps to de-duplicate `react`, `react-dom` and `react/jsx-runtime` versions.
|
For development use :point_down:
|
||||||
|
|
||||||
|
```js
|
||||||
|
<script
|
||||||
|
type="text/javascript"
|
||||||
|
src="https://unpkg.com/@excalidraw/excalidraw/dist/excalidraw.development.js"
|
||||||
|
></script>
|
||||||
|
```
|
||||||
|
|
||||||
|
For production use :point_down:
|
||||||
|
|
||||||
|
```js
|
||||||
|
<script
|
||||||
|
type="text/javascript"
|
||||||
|
src="https://unpkg.com/@excalidraw/excalidraw/dist/excalidraw.production.min.js"
|
||||||
|
></script>
|
||||||
|
```
|
||||||
|
|
||||||
|
You will need to make sure `react`, `react-dom` is available as shown in the below example. For prod please use the production versions of `react`, `react-dom`.
|
||||||
|
|
||||||
import Tabs from "@theme/Tabs";
|
import Tabs from "@theme/Tabs";
|
||||||
import TabItem from "@theme/TabItem";
|
import TabItem from "@theme/TabItem";
|
||||||
@ -172,23 +191,13 @@ 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" />
|
||||||
<link
|
<script src="https://unpkg.com/react@18.2.0/umd/react.development.js"></script>
|
||||||
rel="stylesheet"
|
<script src="https://unpkg.com/react-dom@18.2.0/umd/react-dom.development.js"></script>
|
||||||
href="https://esm.sh/@excalidraw/excalidraw@0.18.0/dist/dev/index.css"
|
|
||||||
/>
|
<script
|
||||||
<link rel="stylesheet" href="./index.css" />
|
type="text/javascript"
|
||||||
<script>
|
src="https://unpkg.com/@excalidraw/excalidraw/dist/excalidraw.development.js"
|
||||||
window.EXCALIDRAW_ASSET_PATH = "https://esm.sh/@excalidraw/excalidraw@0.18.0/dist/prod/";
|
></script>
|
||||||
</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>
|
||||||
@ -205,14 +214,6 @@ 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,
|
||||||
@ -234,5 +235,3 @@ root.render(React.createElement(App));
|
|||||||
|
|
||||||
</TabItem>
|
</TabItem>
|
||||||
</Tabs>
|
</Tabs>
|
||||||
|
|
||||||
You can try it out [here](https://jsfiddle.net/vfn6dm14/3/).
|
|
||||||
|
@ -66,7 +66,7 @@ const config = {
|
|||||||
label: "Docs",
|
label: "Docs",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
to: "https://plus.excalidraw.com/blog",
|
to: "https://blog.excalidraw.com",
|
||||||
label: "Blog",
|
label: "Blog",
|
||||||
position: "left",
|
position: "left",
|
||||||
},
|
},
|
||||||
@ -111,7 +111,7 @@ const config = {
|
|||||||
items: [
|
items: [
|
||||||
{
|
{
|
||||||
label: "Blog",
|
label: "Blog",
|
||||||
to: "https://plus.excalidraw.com/blog",
|
to: "https://blog.excalidraw.com",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "GitHub",
|
label: "GitHub",
|
||||||
@ -149,29 +149,6 @@ 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.18.0",
|
"@excalidraw/excalidraw": "0.17.6",
|
||||||
"@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,6 +1,5 @@
|
|||||||
import clsx from "clsx";
|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
|
import clsx from "clsx";
|
||||||
import styles from "./styles.module.css";
|
import styles from "./styles.module.css";
|
||||||
|
|
||||||
const FeatureList = [
|
const FeatureList = [
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
import clsx from "clsx";
|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
|
import clsx from "clsx";
|
||||||
import styles from "./styles.module.css";
|
import styles from "./styles.module.css";
|
||||||
|
|
||||||
type FeatureItem = {
|
type FeatureItem = {
|
||||||
|
@ -1,11 +1,10 @@
|
|||||||
|
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 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";
|
import styles from "./index.module.css";
|
||||||
|
import HomepageFeatures from "@site/src/components/Homepage";
|
||||||
|
|
||||||
function HomepageHeader() {
|
function HomepageHeader() {
|
||||||
const { siteConfig } = useDocusaurusContext();
|
const { siteConfig } = useDocusaurusContext();
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
// Import the original mapper
|
// Import the original mapper
|
||||||
import Highlight from "@site/src/components/Highlight";
|
|
||||||
import MDXComponents from "@theme-original/MDXComponents";
|
import MDXComponents from "@theme-original/MDXComponents";
|
||||||
|
import Highlight from "@site/src/components/Highlight";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
// Re-use the default mapping
|
// Re-use the default mapping
|
||||||
|
@ -3,18 +3,11 @@ 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,7 +1,5 @@
|
|||||||
import React from "react";
|
|
||||||
|
|
||||||
import type * as TExcalidraw from "@excalidraw/excalidraw";
|
import type * as TExcalidraw from "@excalidraw/excalidraw";
|
||||||
import type { ExcalidrawImperativeAPI } from "@excalidraw/excalidraw/types";
|
import type { ExcalidrawImperativeAPI } from "@excalidraw/excalidraw/dist/excalidraw/types";
|
||||||
|
|
||||||
const COMMENT_SVG = (
|
const COMMENT_SVG = (
|
||||||
<svg
|
<svg
|
@ -52,7 +52,7 @@
|
|||||||
transform: none;
|
transform: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.excalidraw .selected-shape-actions {
|
.excalidraw .panelColumn {
|
||||||
text-align: left;
|
text-align: left;
|
||||||
}
|
}
|
||||||
|
|
@ -1,4 +1,3 @@
|
|||||||
import { nanoid } from "nanoid";
|
|
||||||
import React, {
|
import React, {
|
||||||
useEffect,
|
useEffect,
|
||||||
useState,
|
useState,
|
||||||
@ -7,24 +6,13 @@ 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 initialData from "../initialData";
|
import { nanoid } from "nanoid";
|
||||||
|
|
||||||
|
import type { ResolvablePromise } from "../utils";
|
||||||
import {
|
import {
|
||||||
resolvablePromise,
|
resolvablePromise,
|
||||||
distance2d,
|
distance2d,
|
||||||
@ -35,12 +23,25 @@ import {
|
|||||||
|
|
||||||
import CustomFooter from "./CustomFooter";
|
import CustomFooter from "./CustomFooter";
|
||||||
import MobileFooter from "./MobileFooter";
|
import MobileFooter from "./MobileFooter";
|
||||||
import ExampleSidebar from "./sidebar/ExampleSidebar";
|
import initialData from "../initialData";
|
||||||
|
|
||||||
|
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;
|
||||||
@ -104,7 +105,6 @@ 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,7 +193,6 @@ 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",
|
||||||
@ -370,10 +369,12 @@ export default function ExampleApp({
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
await exportToClipboard({
|
await exportToClipboard({
|
||||||
elements: excalidrawAPI.getSceneElements(),
|
data: {
|
||||||
appState: excalidrawAPI.getAppState(),
|
elements: excalidrawAPI.getSceneElements(),
|
||||||
files: excalidrawAPI.getFiles(),
|
appState: excalidrawAPI.getAppState(),
|
||||||
type,
|
files: excalidrawAPI.getFiles(),
|
||||||
|
},
|
||||||
|
type: "json",
|
||||||
});
|
});
|
||||||
window.alert(`Copied to clipboard as ${type} successfully`);
|
window.alert(`Copied to clipboard as ${type} successfully`);
|
||||||
};
|
};
|
||||||
@ -712,14 +713,6 @@ 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"
|
||||||
@ -826,15 +819,17 @@ export default function ExampleApp({
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const svg = await exportToSvg({
|
const svg = await exportToSvg({
|
||||||
elements: excalidrawAPI?.getSceneElements(),
|
data: {
|
||||||
appState: {
|
elements: excalidrawAPI?.getSceneElements(),
|
||||||
...initialData.appState,
|
appState: {
|
||||||
exportWithDarkMode,
|
...initialData.appState,
|
||||||
exportEmbedScene,
|
exportWithDarkMode,
|
||||||
width: 300,
|
exportEmbedScene,
|
||||||
height: 100,
|
width: 300,
|
||||||
|
height: 100,
|
||||||
|
},
|
||||||
|
files: excalidrawAPI?.getFiles(),
|
||||||
},
|
},
|
||||||
files: excalidrawAPI?.getFiles(),
|
|
||||||
});
|
});
|
||||||
appRef.current.querySelector(".export-svg").innerHTML =
|
appRef.current.querySelector(".export-svg").innerHTML =
|
||||||
svg.outerHTML;
|
svg.outerHTML;
|
||||||
@ -850,14 +845,18 @@ export default function ExampleApp({
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const blob = await exportToBlob({
|
const blob = await exportToBlob({
|
||||||
elements: excalidrawAPI?.getSceneElements(),
|
data: {
|
||||||
mimeType: "image/png",
|
elements: excalidrawAPI?.getSceneElements(),
|
||||||
appState: {
|
appState: {
|
||||||
...initialData.appState,
|
...initialData.appState,
|
||||||
exportEmbedScene,
|
exportEmbedScene,
|
||||||
exportWithDarkMode,
|
exportWithDarkMode,
|
||||||
|
},
|
||||||
|
files: excalidrawAPI?.getFiles(),
|
||||||
|
},
|
||||||
|
config: {
|
||||||
|
mimeType: "image/png",
|
||||||
},
|
},
|
||||||
files: excalidrawAPI?.getFiles(),
|
|
||||||
});
|
});
|
||||||
setBlobUrl(window.URL.createObjectURL(blob));
|
setBlobUrl(window.URL.createObjectURL(blob));
|
||||||
}}
|
}}
|
||||||
@ -873,12 +872,14 @@ export default function ExampleApp({
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const canvas = await exportToCanvas({
|
const canvas = await exportToCanvas({
|
||||||
elements: excalidrawAPI.getSceneElements(),
|
data: {
|
||||||
appState: {
|
elements: excalidrawAPI.getSceneElements(),
|
||||||
...initialData.appState,
|
appState: {
|
||||||
exportWithDarkMode,
|
...initialData.appState,
|
||||||
|
exportWithDarkMode,
|
||||||
|
},
|
||||||
|
files: excalidrawAPI.getFiles(),
|
||||||
},
|
},
|
||||||
files: excalidrawAPI.getFiles(),
|
|
||||||
});
|
});
|
||||||
const ctx = canvas.getContext("2d")!;
|
const ctx = canvas.getContext("2d")!;
|
||||||
ctx.font = "30px Excalifont";
|
ctx.font = "30px Excalifont";
|
||||||
@ -894,12 +895,14 @@ export default function ExampleApp({
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const canvas = await exportToCanvas({
|
const canvas = await exportToCanvas({
|
||||||
elements: excalidrawAPI.getSceneElements(),
|
data: {
|
||||||
appState: {
|
elements: excalidrawAPI.getSceneElements(),
|
||||||
...initialData.appState,
|
appState: {
|
||||||
exportWithDarkMode,
|
...initialData.appState,
|
||||||
|
exportWithDarkMode,
|
||||||
|
},
|
||||||
|
files: excalidrawAPI.getFiles(),
|
||||||
},
|
},
|
||||||
files: excalidrawAPI.getFiles(),
|
|
||||||
});
|
});
|
||||||
const ctx = canvas.getContext("2d")!;
|
const ctx = canvas.getContext("2d")!;
|
||||||
ctx.font = "30px Excalifont";
|
ctx.font = "30px Excalifont";
|
@ -1,9 +1,6 @@
|
|||||||
import React from "react";
|
import type { ExcalidrawImperativeAPI } from "@excalidraw/excalidraw/dist/excalidraw/types";
|
||||||
|
|
||||||
import type * as TExcalidraw from "@excalidraw/excalidraw";
|
|
||||||
import type { ExcalidrawImperativeAPI } from "@excalidraw/excalidraw/types";
|
|
||||||
|
|
||||||
import CustomFooter from "./CustomFooter";
|
import CustomFooter from "./CustomFooter";
|
||||||
|
import type * as TExcalidraw from "@excalidraw/excalidraw";
|
||||||
|
|
||||||
const MobileFooter = ({
|
const MobileFooter = ({
|
||||||
excalidrawAPI,
|
excalidrawAPI,
|
@ -1,5 +1,4 @@
|
|||||||
import React, { useState } from "react";
|
import { 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 }) {
|
13
examples/excalidraw/package.json
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"name": "examples",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"private": true,
|
||||||
|
"dependencies": {
|
||||||
|
"react": "18.2.0",
|
||||||
|
"react-dom": "18.2.0",
|
||||||
|
"@excalidraw/excalidraw": "*"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"typescript": "^5"
|
||||||
|
}
|
||||||
|
}
|
3
examples/excalidraw/tsconfig.json
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"extends": "../../tsconfig"
|
||||||
|
}
|
@ -1,6 +1,7 @@
|
|||||||
import { MIME_TYPES } from "@excalidraw/excalidraw";
|
|
||||||
import { fileOpen as _fileOpen } from "browser-fs-access";
|
|
||||||
import { unstable_batchedUpdates } from "react-dom";
|
import { unstable_batchedUpdates } from "react-dom";
|
||||||
|
import { fileOpen as _fileOpen } from "browser-fs-access";
|
||||||
|
import { MIME_TYPES } from "@excalidraw/excalidraw";
|
||||||
|
import { AbortError } from "../../packages/excalidraw/errors";
|
||||||
|
|
||||||
type FILE_EXTENSION = Exclude<keyof typeof MIME_TYPES, "binary">;
|
type FILE_EXTENSION = Exclude<keyof typeof MIME_TYPES, "binary">;
|
||||||
|
|
||||||
@ -84,7 +85,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 Error("Request Aborted"));
|
rejectPromise(new AbortError());
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
},
|
},
|
@ -4,21 +4,22 @@
|
|||||||
"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": "19.0.0",
|
"react": "18.2.0",
|
||||||
"react-dom": "19.0.0"
|
"react-dom": "18.2.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/node": "^20",
|
"@types/node": "^20",
|
||||||
"@types/react": "19.0.10",
|
"@types/react": "18.2.0",
|
||||||
"@types/react-dom": "19.0.4",
|
"@types/react-dom": "18.2.0",
|
||||||
"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,6 +1,5 @@
|
|||||||
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,11 +1,10 @@
|
|||||||
"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,5 +1,4 @@
|
|||||||
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
|
@ -11,15 +11,17 @@
|
|||||||
<title>React App</title>
|
<title>React App</title>
|
||||||
<script>
|
<script>
|
||||||
window.name = "codesandbox";
|
window.name = "codesandbox";
|
||||||
window.EXCALIDRAW_ASSET_PATH =
|
window.EXCALIDRAW_ASSET_PATH = window.origin;
|
||||||
"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,11 +1,10 @@
|
|||||||
|
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 App from "./components/ExampleApp";
|
import "@excalidraw/excalidraw/index.css";
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
interface Window {
|
interface Window {
|
21
examples/excalidraw/with-script-in-browser/package.json
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
{
|
||||||
|
"name": "with-script-in-browser",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"private": true,
|
||||||
|
"dependencies": {
|
||||||
|
"react": "18.2.0",
|
||||||
|
"react-dom": "18.2.0",
|
||||||
|
"@excalidraw/excalidraw": "*"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"vite": "5.0.12",
|
||||||
|
"typescript": "^5"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"build:workspace": "yarn workspace @excalidraw/excalidraw run build:esm && yarn copy:assets",
|
||||||
|
"copy:assets": "cp -r ../../../packages/excalidraw/dist/prod/fonts ./public",
|
||||||
|
"start": "yarn build:workspace && vite",
|
||||||
|
"build": "yarn build:workspace && vite build",
|
||||||
|
"build:preview": "yarn build && vite preview --port 5002"
|
||||||
|
}
|
||||||
|
}
|
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 |
4
examples/excalidraw/with-script-in-browser/vercel.json
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"outputDirectory": "dist",
|
||||||
|
"installCommand": "yarn install"
|
||||||
|
}
|
11
examples/excalidraw/with-script-in-browser/vite.config.mts
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
import { defineConfig } from "vite";
|
||||||
|
|
||||||
|
// https://vitejs.dev/config/
|
||||||
|
export default defineConfig({
|
||||||
|
server: {
|
||||||
|
port: 3001,
|
||||||
|
// open the browser
|
||||||
|
open: true,
|
||||||
|
},
|
||||||
|
publicDir: "public",
|
||||||
|
});
|
313
examples/excalidraw/yarn.lock
Normal file
@ -0,0 +1,313 @@
|
|||||||
|
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
|
||||||
|
# yarn lockfile v1
|
||||||
|
|
||||||
|
|
||||||
|
"@esbuild/aix-ppc64@0.19.11":
|
||||||
|
version "0.19.11"
|
||||||
|
resolved "https://registry.yarnpkg.com/@esbuild/aix-ppc64/-/aix-ppc64-0.19.11.tgz#2acd20be6d4f0458bc8c784103495ff24f13b1d3"
|
||||||
|
integrity sha512-FnzU0LyE3ySQk7UntJO4+qIiQgI7KoODnZg5xzXIrFJlKd2P2gwHsHY4927xj9y5PJmJSzULiUCWmv7iWnNa7g==
|
||||||
|
|
||||||
|
"@esbuild/android-arm64@0.19.11":
|
||||||
|
version "0.19.11"
|
||||||
|
resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.19.11.tgz#b45d000017385c9051a4f03e17078abb935be220"
|
||||||
|
integrity sha512-aiu7K/5JnLj//KOnOfEZ0D90obUkRzDMyqd/wNAUQ34m4YUPVhRZpnqKV9uqDGxT7cToSDnIHsGooyIczu9T+Q==
|
||||||
|
|
||||||
|
"@esbuild/android-arm@0.19.11":
|
||||||
|
version "0.19.11"
|
||||||
|
resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.19.11.tgz#f46f55414e1c3614ac682b29977792131238164c"
|
||||||
|
integrity sha512-5OVapq0ClabvKvQ58Bws8+wkLCV+Rxg7tUVbo9xu034Nm536QTII4YzhaFriQ7rMrorfnFKUsArD2lqKbFY4vw==
|
||||||
|
|
||||||
|
"@esbuild/android-x64@0.19.11":
|
||||||
|
version "0.19.11"
|
||||||
|
resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.19.11.tgz#bfc01e91740b82011ef503c48f548950824922b2"
|
||||||
|
integrity sha512-eccxjlfGw43WYoY9QgB82SgGgDbibcqyDTlk3l3C0jOVHKxrjdc9CTwDUQd0vkvYg5um0OH+GpxYvp39r+IPOg==
|
||||||
|
|
||||||
|
"@esbuild/darwin-arm64@0.19.11":
|
||||||
|
version "0.19.11"
|
||||||
|
resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.19.11.tgz#533fb7f5a08c37121d82c66198263dcc1bed29bf"
|
||||||
|
integrity sha512-ETp87DRWuSt9KdDVkqSoKoLFHYTrkyz2+65fj9nfXsaV3bMhTCjtQfw3y+um88vGRKRiF7erPrh/ZuIdLUIVxQ==
|
||||||
|
|
||||||
|
"@esbuild/darwin-x64@0.19.11":
|
||||||
|
version "0.19.11"
|
||||||
|
resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.19.11.tgz#62f3819eff7e4ddc656b7c6815a31cf9a1e7d98e"
|
||||||
|
integrity sha512-fkFUiS6IUK9WYUO/+22omwetaSNl5/A8giXvQlcinLIjVkxwTLSktbF5f/kJMftM2MJp9+fXqZ5ezS7+SALp4g==
|
||||||
|
|
||||||
|
"@esbuild/freebsd-arm64@0.19.11":
|
||||||
|
version "0.19.11"
|
||||||
|
resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.19.11.tgz#d478b4195aa3ca44160272dab85ef8baf4175b4a"
|
||||||
|
integrity sha512-lhoSp5K6bxKRNdXUtHoNc5HhbXVCS8V0iZmDvyWvYq9S5WSfTIHU2UGjcGt7UeS6iEYp9eeymIl5mJBn0yiuxA==
|
||||||
|
|
||||||
|
"@esbuild/freebsd-x64@0.19.11":
|
||||||
|
version "0.19.11"
|
||||||
|
resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.19.11.tgz#7bdcc1917409178257ca6a1a27fe06e797ec18a2"
|
||||||
|
integrity sha512-JkUqn44AffGXitVI6/AbQdoYAq0TEullFdqcMY/PCUZ36xJ9ZJRtQabzMA+Vi7r78+25ZIBosLTOKnUXBSi1Kw==
|
||||||
|
|
||||||
|
"@esbuild/linux-arm64@0.19.11":
|
||||||
|
version "0.19.11"
|
||||||
|
resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.19.11.tgz#58ad4ff11685fcc735d7ff4ca759ab18fcfe4545"
|
||||||
|
integrity sha512-LneLg3ypEeveBSMuoa0kwMpCGmpu8XQUh+mL8XXwoYZ6Be2qBnVtcDI5azSvh7vioMDhoJFZzp9GWp9IWpYoUg==
|
||||||
|
|
||||||
|
"@esbuild/linux-arm@0.19.11":
|
||||||
|
version "0.19.11"
|
||||||
|
resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.19.11.tgz#ce82246d873b5534d34de1e5c1b33026f35e60e3"
|
||||||
|
integrity sha512-3CRkr9+vCV2XJbjwgzjPtO8T0SZUmRZla+UL1jw+XqHZPkPgZiyWvbDvl9rqAN8Zl7qJF0O/9ycMtjU67HN9/Q==
|
||||||
|
|
||||||
|
"@esbuild/linux-ia32@0.19.11":
|
||||||
|
version "0.19.11"
|
||||||
|
resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.19.11.tgz#cbae1f313209affc74b80f4390c4c35c6ab83fa4"
|
||||||
|
integrity sha512-caHy++CsD8Bgq2V5CodbJjFPEiDPq8JJmBdeyZ8GWVQMjRD0sU548nNdwPNvKjVpamYYVL40AORekgfIubwHoA==
|
||||||
|
|
||||||
|
"@esbuild/linux-loong64@0.19.11":
|
||||||
|
version "0.19.11"
|
||||||
|
resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.19.11.tgz#5f32aead1c3ec8f4cccdb7ed08b166224d4e9121"
|
||||||
|
integrity sha512-ppZSSLVpPrwHccvC6nQVZaSHlFsvCQyjnvirnVjbKSHuE5N24Yl8F3UwYUUR1UEPaFObGD2tSvVKbvR+uT1Nrg==
|
||||||
|
|
||||||
|
"@esbuild/linux-mips64el@0.19.11":
|
||||||
|
version "0.19.11"
|
||||||
|
resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.19.11.tgz#38eecf1cbb8c36a616261de858b3c10d03419af9"
|
||||||
|
integrity sha512-B5x9j0OgjG+v1dF2DkH34lr+7Gmv0kzX6/V0afF41FkPMMqaQ77pH7CrhWeR22aEeHKaeZVtZ6yFwlxOKPVFyg==
|
||||||
|
|
||||||
|
"@esbuild/linux-ppc64@0.19.11":
|
||||||
|
version "0.19.11"
|
||||||
|
resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.19.11.tgz#9c5725a94e6ec15b93195e5a6afb821628afd912"
|
||||||
|
integrity sha512-MHrZYLeCG8vXblMetWyttkdVRjQlQUb/oMgBNurVEnhj4YWOr4G5lmBfZjHYQHHN0g6yDmCAQRR8MUHldvvRDA==
|
||||||
|
|
||||||
|
"@esbuild/linux-riscv64@0.19.11":
|
||||||
|
version "0.19.11"
|
||||||
|
resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.19.11.tgz#2dc4486d474a2a62bbe5870522a9a600e2acb916"
|
||||||
|
integrity sha512-f3DY++t94uVg141dozDu4CCUkYW+09rWtaWfnb3bqe4w5NqmZd6nPVBm+qbz7WaHZCoqXqHz5p6CM6qv3qnSSQ==
|
||||||
|
|
||||||
|
"@esbuild/linux-s390x@0.19.11":
|
||||||
|
version "0.19.11"
|
||||||
|
resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.19.11.tgz#4ad8567df48f7dd4c71ec5b1753b6f37561a65a8"
|
||||||
|
integrity sha512-A5xdUoyWJHMMlcSMcPGVLzYzpcY8QP1RtYzX5/bS4dvjBGVxdhuiYyFwp7z74ocV7WDc0n1harxmpq2ePOjI0Q==
|
||||||
|
|
||||||
|
"@esbuild/linux-x64@0.19.11":
|
||||||
|
version "0.19.11"
|
||||||
|
resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.19.11.tgz#b7390c4d5184f203ebe7ddaedf073df82a658766"
|
||||||
|
integrity sha512-grbyMlVCvJSfxFQUndw5mCtWs5LO1gUlwP4CDi4iJBbVpZcqLVT29FxgGuBJGSzyOxotFG4LoO5X+M1350zmPA==
|
||||||
|
|
||||||
|
"@esbuild/netbsd-x64@0.19.11":
|
||||||
|
version "0.19.11"
|
||||||
|
resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.19.11.tgz#d633c09492a1721377f3bccedb2d821b911e813d"
|
||||||
|
integrity sha512-13jvrQZJc3P230OhU8xgwUnDeuC/9egsjTkXN49b3GcS5BKvJqZn86aGM8W9pd14Kd+u7HuFBMVtrNGhh6fHEQ==
|
||||||
|
|
||||||
|
"@esbuild/openbsd-x64@0.19.11":
|
||||||
|
version "0.19.11"
|
||||||
|
resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.19.11.tgz#17388c76e2f01125bf831a68c03a7ffccb65d1a2"
|
||||||
|
integrity sha512-ysyOGZuTp6SNKPE11INDUeFVVQFrhcNDVUgSQVDzqsqX38DjhPEPATpid04LCoUr2WXhQTEZ8ct/EgJCUDpyNw==
|
||||||
|
|
||||||
|
"@esbuild/sunos-x64@0.19.11":
|
||||||
|
version "0.19.11"
|
||||||
|
resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.19.11.tgz#e320636f00bb9f4fdf3a80e548cb743370d41767"
|
||||||
|
integrity sha512-Hf+Sad9nVwvtxy4DXCZQqLpgmRTQqyFyhT3bZ4F2XlJCjxGmRFF0Shwn9rzhOYRB61w9VMXUkxlBy56dk9JJiQ==
|
||||||
|
|
||||||
|
"@esbuild/win32-arm64@0.19.11":
|
||||||
|
version "0.19.11"
|
||||||
|
resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.19.11.tgz#c778b45a496e90b6fc373e2a2bb072f1441fe0ee"
|
||||||
|
integrity sha512-0P58Sbi0LctOMOQbpEOvOL44Ne0sqbS0XWHMvvrg6NE5jQ1xguCSSw9jQeUk2lfrXYsKDdOe6K+oZiwKPilYPQ==
|
||||||
|
|
||||||
|
"@esbuild/win32-ia32@0.19.11":
|
||||||
|
version "0.19.11"
|
||||||
|
resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.19.11.tgz#481a65fee2e5cce74ec44823e6b09ecedcc5194c"
|
||||||
|
integrity sha512-6YOrWS+sDJDmshdBIQU+Uoyh7pQKrdykdefC1avn76ss5c+RN6gut3LZA4E2cH5xUEp5/cA0+YxRaVtRAb0xBg==
|
||||||
|
|
||||||
|
"@esbuild/win32-x64@0.19.11":
|
||||||
|
version "0.19.11"
|
||||||
|
resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.19.11.tgz#a5d300008960bb39677c46bf16f53ec70d8dee04"
|
||||||
|
integrity sha512-vfkhltrjCAb603XaFhqhAF4LGDi2M4OrCRrFusyQ+iTLQ/o60QQXxc9cZC/FFpihBI9N1Grn6SMKVJ4KP7Fuiw==
|
||||||
|
|
||||||
|
"@rollup/rollup-android-arm-eabi@4.9.5":
|
||||||
|
version "4.9.5"
|
||||||
|
resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.9.5.tgz#b752b6c88a14ccfcbdf3f48c577ccc3a7f0e66b9"
|
||||||
|
integrity sha512-idWaG8xeSRCfRq9KpRysDHJ/rEHBEXcHuJ82XY0yYFIWnLMjZv9vF/7DOq8djQ2n3Lk6+3qfSH8AqlmHlmi1MA==
|
||||||
|
|
||||||
|
"@rollup/rollup-android-arm64@4.9.5":
|
||||||
|
version "4.9.5"
|
||||||
|
resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.9.5.tgz#33757c3a448b9ef77b6f6292d8b0ec45c87e9c1a"
|
||||||
|
integrity sha512-f14d7uhAMtsCGjAYwZGv6TwuS3IFaM4ZnGMUn3aCBgkcHAYErhV1Ad97WzBvS2o0aaDv4mVz+syiN0ElMyfBPg==
|
||||||
|
|
||||||
|
"@rollup/rollup-darwin-arm64@4.9.5":
|
||||||
|
version "4.9.5"
|
||||||
|
resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.9.5.tgz#5234ba62665a3f443143bc8bcea9df2cc58f55fb"
|
||||||
|
integrity sha512-ndoXeLx455FffL68OIUrVr89Xu1WLzAG4n65R8roDlCoYiQcGGg6MALvs2Ap9zs7AHg8mpHtMpwC8jBBjZrT/w==
|
||||||
|
|
||||||
|
"@rollup/rollup-darwin-x64@4.9.5":
|
||||||
|
version "4.9.5"
|
||||||
|
resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.9.5.tgz#981256c054d3247b83313724938d606798a919d1"
|
||||||
|
integrity sha512-UmElV1OY2m/1KEEqTlIjieKfVwRg0Zwg4PLgNf0s3glAHXBN99KLpw5A5lrSYCa1Kp63czTpVll2MAqbZYIHoA==
|
||||||
|
|
||||||
|
"@rollup/rollup-linux-arm-gnueabihf@4.9.5":
|
||||||
|
version "4.9.5"
|
||||||
|
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.9.5.tgz#120678a5a2b3a283a548dbb4d337f9187a793560"
|
||||||
|
integrity sha512-Q0LcU61v92tQB6ae+udZvOyZ0wfpGojtAKrrpAaIqmJ7+psq4cMIhT/9lfV6UQIpeItnq/2QDROhNLo00lOD1g==
|
||||||
|
|
||||||
|
"@rollup/rollup-linux-arm64-gnu@4.9.5":
|
||||||
|
version "4.9.5"
|
||||||
|
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.9.5.tgz#c99d857e2372ece544b6f60b85058ad259f64114"
|
||||||
|
integrity sha512-dkRscpM+RrR2Ee3eOQmRWFjmV/payHEOrjyq1VZegRUa5OrZJ2MAxBNs05bZuY0YCtpqETDy1Ix4i/hRqX98cA==
|
||||||
|
|
||||||
|
"@rollup/rollup-linux-arm64-musl@4.9.5":
|
||||||
|
version "4.9.5"
|
||||||
|
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.9.5.tgz#3064060f568a5718c2a06858cd6e6d24f2ff8632"
|
||||||
|
integrity sha512-QaKFVOzzST2xzY4MAmiDmURagWLFh+zZtttuEnuNn19AiZ0T3fhPyjPPGwLNdiDT82ZE91hnfJsUiDwF9DClIQ==
|
||||||
|
|
||||||
|
"@rollup/rollup-linux-riscv64-gnu@4.9.5":
|
||||||
|
version "4.9.5"
|
||||||
|
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.9.5.tgz#987d30b5d2b992fff07d055015991a57ff55fbad"
|
||||||
|
integrity sha512-HeGqmRJuyVg6/X6MpE2ur7GbymBPS8Np0S/vQFHDmocfORT+Zt76qu+69NUoxXzGqVP1pzaY6QIi0FJWLC3OPA==
|
||||||
|
|
||||||
|
"@rollup/rollup-linux-x64-gnu@4.9.5":
|
||||||
|
version "4.9.5"
|
||||||
|
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.9.5.tgz#85946ee4d068bd12197aeeec2c6f679c94978a49"
|
||||||
|
integrity sha512-Dq1bqBdLaZ1Gb/l2e5/+o3B18+8TI9ANlA1SkejZqDgdU/jK/ThYaMPMJpVMMXy2uRHvGKbkz9vheVGdq3cJfA==
|
||||||
|
|
||||||
|
"@rollup/rollup-linux-x64-musl@4.9.5":
|
||||||
|
version "4.9.5"
|
||||||
|
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.9.5.tgz#fe0b20f9749a60eb1df43d20effa96c756ddcbd4"
|
||||||
|
integrity sha512-ezyFUOwldYpj7AbkwyW9AJ203peub81CaAIVvckdkyH8EvhEIoKzaMFJj0G4qYJ5sw3BpqhFrsCc30t54HV8vg==
|
||||||
|
|
||||||
|
"@rollup/rollup-win32-arm64-msvc@4.9.5":
|
||||||
|
version "4.9.5"
|
||||||
|
resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.9.5.tgz#422661ef0e16699a234465d15b2c1089ef963b2a"
|
||||||
|
integrity sha512-aHSsMnUw+0UETB0Hlv7B/ZHOGY5bQdwMKJSzGfDfvyhnpmVxLMGnQPGNE9wgqkLUs3+gbG1Qx02S2LLfJ5GaRQ==
|
||||||
|
|
||||||
|
"@rollup/rollup-win32-ia32-msvc@4.9.5":
|
||||||
|
version "4.9.5"
|
||||||
|
resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.9.5.tgz#7b73a145891c202fbcc08759248983667a035d85"
|
||||||
|
integrity sha512-AiqiLkb9KSf7Lj/o1U3SEP9Zn+5NuVKgFdRIZkvd4N0+bYrTOovVd0+LmYCPQGbocT4kvFyK+LXCDiXPBF3fyA==
|
||||||
|
|
||||||
|
"@rollup/rollup-win32-x64-msvc@4.9.5":
|
||||||
|
version "4.9.5"
|
||||||
|
resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.9.5.tgz#10491ccf4f63c814d4149e0316541476ea603602"
|
||||||
|
integrity sha512-1q+mykKE3Vot1kaFJIDoUFv5TuW+QQVaf2FmTT9krg86pQrGStOSJJ0Zil7CFagyxDuouTepzt5Y5TVzyajOdQ==
|
||||||
|
|
||||||
|
"@types/estree@1.0.5":
|
||||||
|
version "1.0.5"
|
||||||
|
resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.5.tgz#a6ce3e556e00fd9895dd872dd172ad0d4bd687f4"
|
||||||
|
integrity sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==
|
||||||
|
|
||||||
|
esbuild@^0.19.3:
|
||||||
|
version "0.19.11"
|
||||||
|
resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.19.11.tgz#4a02dca031e768b5556606e1b468fe72e3325d96"
|
||||||
|
integrity sha512-HJ96Hev2hX/6i5cDVwcqiJBBtuo9+FeIJOtZ9W1kA5M6AMJRHUZlpYZ1/SbEwtO0ioNAW8rUooVpC/WehY2SfA==
|
||||||
|
optionalDependencies:
|
||||||
|
"@esbuild/aix-ppc64" "0.19.11"
|
||||||
|
"@esbuild/android-arm" "0.19.11"
|
||||||
|
"@esbuild/android-arm64" "0.19.11"
|
||||||
|
"@esbuild/android-x64" "0.19.11"
|
||||||
|
"@esbuild/darwin-arm64" "0.19.11"
|
||||||
|
"@esbuild/darwin-x64" "0.19.11"
|
||||||
|
"@esbuild/freebsd-arm64" "0.19.11"
|
||||||
|
"@esbuild/freebsd-x64" "0.19.11"
|
||||||
|
"@esbuild/linux-arm" "0.19.11"
|
||||||
|
"@esbuild/linux-arm64" "0.19.11"
|
||||||
|
"@esbuild/linux-ia32" "0.19.11"
|
||||||
|
"@esbuild/linux-loong64" "0.19.11"
|
||||||
|
"@esbuild/linux-mips64el" "0.19.11"
|
||||||
|
"@esbuild/linux-ppc64" "0.19.11"
|
||||||
|
"@esbuild/linux-riscv64" "0.19.11"
|
||||||
|
"@esbuild/linux-s390x" "0.19.11"
|
||||||
|
"@esbuild/linux-x64" "0.19.11"
|
||||||
|
"@esbuild/netbsd-x64" "0.19.11"
|
||||||
|
"@esbuild/openbsd-x64" "0.19.11"
|
||||||
|
"@esbuild/sunos-x64" "0.19.11"
|
||||||
|
"@esbuild/win32-arm64" "0.19.11"
|
||||||
|
"@esbuild/win32-ia32" "0.19.11"
|
||||||
|
"@esbuild/win32-x64" "0.19.11"
|
||||||
|
|
||||||
|
fsevents@~2.3.2, fsevents@~2.3.3:
|
||||||
|
version "2.3.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6"
|
||||||
|
integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==
|
||||||
|
|
||||||
|
"js-tokens@^3.0.0 || ^4.0.0":
|
||||||
|
version "4.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499"
|
||||||
|
integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==
|
||||||
|
|
||||||
|
loose-envify@^1.1.0:
|
||||||
|
version "1.4.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf"
|
||||||
|
integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==
|
||||||
|
dependencies:
|
||||||
|
js-tokens "^3.0.0 || ^4.0.0"
|
||||||
|
|
||||||
|
nanoid@^3.3.7:
|
||||||
|
version "3.3.7"
|
||||||
|
resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.7.tgz#d0c301a691bc8d54efa0a2226ccf3fe2fd656bd8"
|
||||||
|
integrity sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==
|
||||||
|
|
||||||
|
picocolors@^1.0.0:
|
||||||
|
version "1.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.0.tgz#cb5bdc74ff3f51892236eaf79d68bc44564ab81c"
|
||||||
|
integrity sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==
|
||||||
|
|
||||||
|
postcss@^8.4.32:
|
||||||
|
version "8.4.33"
|
||||||
|
resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.33.tgz#1378e859c9f69bf6f638b990a0212f43e2aaa742"
|
||||||
|
integrity sha512-Kkpbhhdjw2qQs2O2DGX+8m5OVqEcbB9HRBvuYM9pgrjEFUg30A9LmXNlTAUj4S9kgtGyrMbTzVjH7E+s5Re2yg==
|
||||||
|
dependencies:
|
||||||
|
nanoid "^3.3.7"
|
||||||
|
picocolors "^1.0.0"
|
||||||
|
source-map-js "^1.0.2"
|
||||||
|
|
||||||
|
react-dom@18.2.0:
|
||||||
|
version "18.2.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-18.2.0.tgz#22aaf38708db2674ed9ada224ca4aa708d821e3d"
|
||||||
|
integrity sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==
|
||||||
|
dependencies:
|
||||||
|
loose-envify "^1.1.0"
|
||||||
|
scheduler "^0.23.0"
|
||||||
|
|
||||||
|
react@18.2.0:
|
||||||
|
version "18.2.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/react/-/react-18.2.0.tgz#555bd98592883255fa00de14f1151a917b5d77d5"
|
||||||
|
integrity sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==
|
||||||
|
dependencies:
|
||||||
|
loose-envify "^1.1.0"
|
||||||
|
|
||||||
|
rollup@^4.2.0:
|
||||||
|
version "4.9.5"
|
||||||
|
resolved "https://registry.yarnpkg.com/rollup/-/rollup-4.9.5.tgz#62999462c90f4c8b5d7c38fc7161e63b29101b05"
|
||||||
|
integrity sha512-E4vQW0H/mbNMw2yLSqJyjtkHY9dslf/p0zuT1xehNRqUTBOFMqEjguDvqhXr7N7r/4ttb2jr4T41d3dncmIgbQ==
|
||||||
|
dependencies:
|
||||||
|
"@types/estree" "1.0.5"
|
||||||
|
optionalDependencies:
|
||||||
|
"@rollup/rollup-android-arm-eabi" "4.9.5"
|
||||||
|
"@rollup/rollup-android-arm64" "4.9.5"
|
||||||
|
"@rollup/rollup-darwin-arm64" "4.9.5"
|
||||||
|
"@rollup/rollup-darwin-x64" "4.9.5"
|
||||||
|
"@rollup/rollup-linux-arm-gnueabihf" "4.9.5"
|
||||||
|
"@rollup/rollup-linux-arm64-gnu" "4.9.5"
|
||||||
|
"@rollup/rollup-linux-arm64-musl" "4.9.5"
|
||||||
|
"@rollup/rollup-linux-riscv64-gnu" "4.9.5"
|
||||||
|
"@rollup/rollup-linux-x64-gnu" "4.9.5"
|
||||||
|
"@rollup/rollup-linux-x64-musl" "4.9.5"
|
||||||
|
"@rollup/rollup-win32-arm64-msvc" "4.9.5"
|
||||||
|
"@rollup/rollup-win32-ia32-msvc" "4.9.5"
|
||||||
|
"@rollup/rollup-win32-x64-msvc" "4.9.5"
|
||||||
|
fsevents "~2.3.2"
|
||||||
|
|
||||||
|
scheduler@^0.23.0:
|
||||||
|
version "0.23.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.23.0.tgz#ba8041afc3d30eb206a487b6b384002e4e61fdfe"
|
||||||
|
integrity sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==
|
||||||
|
dependencies:
|
||||||
|
loose-envify "^1.1.0"
|
||||||
|
|
||||||
|
source-map-js@^1.0.2:
|
||||||
|
version "1.0.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.0.2.tgz#adbc361d9c62df380125e7f161f71c826f1e490c"
|
||||||
|
integrity sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==
|
||||||
|
|
||||||
|
vite@5.0.6:
|
||||||
|
version "5.0.6"
|
||||||
|
resolved "https://registry.yarnpkg.com/vite/-/vite-5.0.6.tgz#f9e13503a4c5ccd67312c67803dec921f3bdea7c"
|
||||||
|
integrity sha512-MD3joyAEBtV7QZPl2JVVUai6zHms3YOmLR+BpMzLlX2Yzjfcc4gTgNi09d/Rua3F4EtC8zdwPU8eQYyib4vVMQ==
|
||||||
|
dependencies:
|
||||||
|
esbuild "^0.19.3"
|
||||||
|
postcss "^8.4.32"
|
||||||
|
rollup "^4.2.0"
|
||||||
|
optionalDependencies:
|
||||||
|
fsevents "~2.3.3"
|
@ -1,5 +0,0 @@
|
|||||||
FROM node:18-bullseye
|
|
||||||
|
|
||||||
# Vite wants to open the browser using `open`, so we
|
|
||||||
# need to install those utils.
|
|
||||||
RUN apt update -y && apt install -y xdg-utils
|
|
@ -1,35 +0,0 @@
|
|||||||
{
|
|
||||||
// These tasks will run in order when initializing your CodeSandbox project.
|
|
||||||
"setupTasks": [
|
|
||||||
{
|
|
||||||
"name": "Install Dependencies",
|
|
||||||
"command": "yarn install"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
|
|
||||||
// These tasks can be run from CodeSandbox. Running one will open a log in the app.
|
|
||||||
"tasks": {
|
|
||||||
"build": {
|
|
||||||
"name": "Build",
|
|
||||||
"command": "yarn build",
|
|
||||||
"runAtStart": false
|
|
||||||
},
|
|
||||||
"start": {
|
|
||||||
"name": "Start Example",
|
|
||||||
"command": "yarn start",
|
|
||||||
"runAtStart": true,
|
|
||||||
"preview": {
|
|
||||||
"port": 3001
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"install-deps": {
|
|
||||||
"name": "Install Dependencies",
|
|
||||||
"command": "yarn install",
|
|
||||||
"restartOn": {
|
|
||||||
"files": ["yarn.lock"],
|
|
||||||
"branch": false,
|
|
||||||
"resume": false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,22 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "with-script-in-browser",
|
|
||||||
"version": "1.0.0",
|
|
||||||
"private": true,
|
|
||||||
"dependencies": {
|
|
||||||
"react": "19.0.0",
|
|
||||||
"react-dom": "19.0.0",
|
|
||||||
"@excalidraw/excalidraw": "*",
|
|
||||||
"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"
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,9 +0,0 @@
|
|||||||
{
|
|
||||||
"compilerOptions": {
|
|
||||||
"module": "ES2022",
|
|
||||||
"moduleResolution": "Bundler",
|
|
||||||
"lib": ["ESNext", "DOM", "DOM.Iterable"],
|
|
||||||
"jsx": "react-jsx",
|
|
||||||
"skipLibCheck": true
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,5 +0,0 @@
|
|||||||
{
|
|
||||||
"outputDirectory": "dist",
|
|
||||||
"installCommand": "yarn install",
|
|
||||||
"buildCommand": "yarn build:package && yarn build"
|
|
||||||
}
|
|
@ -1,19 +0,0 @@
|
|||||||
import { defineConfig } from "vite";
|
|
||||||
|
|
||||||
// https://vitejs.dev/config/
|
|
||||||
export default defineConfig({
|
|
||||||
server: {
|
|
||||||
port: 3001,
|
|
||||||
// open the browser
|
|
||||||
open: true,
|
|
||||||
},
|
|
||||||
publicDir: "public",
|
|
||||||
optimizeDeps: {
|
|
||||||
esbuildOptions: {
|
|
||||||
// Bumping to 2022 due to "Arbitrary module namespace identifier names" not being
|
|
||||||
// supported in Vite's default browser target https://github.com/vitejs/vite/issues/13556
|
|
||||||
target: "es2022",
|
|
||||||
treeShaking: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
@ -1,27 +1,46 @@
|
|||||||
import {
|
import polyfill from "../packages/excalidraw/polyfill";
|
||||||
Excalidraw,
|
import { useCallback, useEffect, useRef, useState } from "react";
|
||||||
LiveCollaborationTrigger,
|
import { trackEvent } from "../packages/excalidraw/analytics";
|
||||||
TTDDialogTrigger,
|
import { getDefaultAppState } from "../packages/excalidraw/appState";
|
||||||
CaptureUpdateAction,
|
import { ErrorDialog } from "../packages/excalidraw/components/ErrorDialog";
|
||||||
reconcileElements,
|
import { TopErrorBoundary } from "./components/TopErrorBoundary";
|
||||||
} 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,
|
||||||
|
exportToCanvas,
|
||||||
|
exportToSvg,
|
||||||
|
} from "../packages/excalidraw";
|
||||||
|
import {
|
||||||
|
exportToBlob,
|
||||||
|
getNonDeletedElements,
|
||||||
|
} from "../packages/excalidraw/index";
|
||||||
|
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,
|
||||||
@ -29,14 +48,71 @@ import {
|
|||||||
preventUnload,
|
preventUnload,
|
||||||
resolvablePromise,
|
resolvablePromise,
|
||||||
isRunningInIframe,
|
isRunningInIframe,
|
||||||
isDevEnv,
|
} from "../packages/excalidraw/utils";
|
||||||
} from "@excalidraw/common";
|
import {
|
||||||
import polyfill from "@excalidraw/excalidraw/polyfill";
|
FIREBASE_STORAGE_PREFIXES,
|
||||||
import { useCallback, useEffect, useRef, useState } from "react";
|
isExcalidrawPlusSignedUser,
|
||||||
import { loadFromBlob } from "@excalidraw/excalidraw/data/blob";
|
STORAGE_KEYS,
|
||||||
import { useCallbackRefState } from "@excalidraw/excalidraw/hooks/useCallbackRefState";
|
SYNC_BROWSER_TABS_TIMEOUT,
|
||||||
import { t } from "@excalidraw/excalidraw/i18n";
|
} from "./app_constants";
|
||||||
|
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 } from "jotai";
|
||||||
|
import { useAtomWithInitialValue } from "../packages/excalidraw/jotai";
|
||||||
|
import { 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,
|
||||||
@ -46,85 +122,8 @@ import {
|
|||||||
exportToPlus,
|
exportToPlus,
|
||||||
share,
|
share,
|
||||||
youtubeIcon,
|
youtubeIcon,
|
||||||
} from "@excalidraw/excalidraw/components/icons";
|
} from "../packages/excalidraw/components/icons";
|
||||||
import { isElementLink } from "@excalidraw/element";
|
import { appThemeAtom, useHandleAppTheme } from "./useHandleAppTheme";
|
||||||
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 { 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";
|
||||||
import DebugCanvas, {
|
import DebugCanvas, {
|
||||||
@ -134,10 +133,9 @@ 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 { fileSave } from "../packages/excalidraw/data/filesystem";
|
||||||
import "./index.scss";
|
import { type ExportSceneConfig } from "../packages/excalidraw/scene/export";
|
||||||
|
import { round } from "../packages/math";
|
||||||
import type { CollabAPI } from "./collab/Collab";
|
|
||||||
|
|
||||||
polyfill();
|
polyfill();
|
||||||
|
|
||||||
@ -338,7 +336,8 @@ const ExcalidrawWrapper = () => {
|
|||||||
const [errorMessage, setErrorMessage] = useState("");
|
const [errorMessage, setErrorMessage] = useState("");
|
||||||
const isCollabDisabled = isRunningInIframe();
|
const isCollabDisabled = isRunningInIframe();
|
||||||
|
|
||||||
const { editorTheme, appTheme, setAppTheme } = useHandleAppTheme();
|
const [appTheme, setAppTheme] = useAtom(appThemeAtom);
|
||||||
|
const { editorTheme } = useHandleAppTheme();
|
||||||
|
|
||||||
const [langCode, setLangCode] = useAppLangCode();
|
const [langCode, setLangCode] = useAppLangCode();
|
||||||
|
|
||||||
@ -383,7 +382,7 @@ const ExcalidrawWrapper = () => {
|
|||||||
const [, forceRefresh] = useState(false);
|
const [, forceRefresh] = useState(false);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (isDevEnv()) {
|
if (import.meta.env.DEV) {
|
||||||
const debugState = loadSavedDebugState();
|
const debugState = loadSavedDebugState();
|
||||||
|
|
||||||
if (debugState.enabled && !window.visualDebug) {
|
if (debugState.enabled && !window.visualDebug) {
|
||||||
@ -492,7 +491,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 }),
|
||||||
captureUpdate: CaptureUpdateAction.IMMEDIATELY,
|
storeAction: StoreAction.CAPTURE,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -519,7 +518,7 @@ const ExcalidrawWrapper = () => {
|
|||||||
setLangCode(getPreferredLanguage());
|
setLangCode(getPreferredLanguage());
|
||||||
excalidrawAPI.updateScene({
|
excalidrawAPI.updateScene({
|
||||||
...localDataState,
|
...localDataState,
|
||||||
captureUpdate: CaptureUpdateAction.NEVER,
|
storeAction: StoreAction.UPDATE,
|
||||||
});
|
});
|
||||||
LibraryIndexedDBAdapter.load().then((data) => {
|
LibraryIndexedDBAdapter.load().then((data) => {
|
||||||
if (data) {
|
if (data) {
|
||||||
@ -608,13 +607,7 @@ const ExcalidrawWrapper = () => {
|
|||||||
excalidrawAPI.getSceneElements(),
|
excalidrawAPI.getSceneElements(),
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
if (import.meta.env.VITE_APP_DISABLE_PREVENT_UNLOAD !== "true") {
|
preventUnload(event);
|
||||||
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);
|
||||||
@ -623,6 +616,19 @@ const ExcalidrawWrapper = () => {
|
|||||||
};
|
};
|
||||||
}, [excalidrawAPI]);
|
}, [excalidrawAPI]);
|
||||||
|
|
||||||
|
const canvasPreviewContainerRef = useRef<HTMLDivElement>(null);
|
||||||
|
const svgPreviewContainerRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
|
const [config, setConfig] = useState<ExportSceneConfig>({
|
||||||
|
scale: 1,
|
||||||
|
position: "center",
|
||||||
|
fit: "contain",
|
||||||
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
localStorage.setItem("_exportConfig", JSON.stringify(config));
|
||||||
|
}, [config]);
|
||||||
|
|
||||||
const onChange = (
|
const onChange = (
|
||||||
elements: readonly OrderedExcalidrawElement[],
|
elements: readonly OrderedExcalidrawElement[],
|
||||||
appState: AppState,
|
appState: AppState,
|
||||||
@ -632,6 +638,84 @@ const ExcalidrawWrapper = () => {
|
|||||||
collabAPI.syncElements(elements);
|
collabAPI.syncElements(elements);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const nonDeletedElements = getNonDeletedElements(elements);
|
||||||
|
|
||||||
|
const frame = nonDeletedElements.find(
|
||||||
|
(el) => el.strokeStyle === "dashed" && el.type === "rectangle",
|
||||||
|
);
|
||||||
|
|
||||||
|
exportToCanvas({
|
||||||
|
data: {
|
||||||
|
elements: nonDeletedElements.filter((x) => x.id !== frame?.id),
|
||||||
|
// .concat(
|
||||||
|
// restoreElements(
|
||||||
|
// [
|
||||||
|
// // @ts-ignore
|
||||||
|
// {
|
||||||
|
// type: "rectangle",
|
||||||
|
// width: appState.width / zoom,
|
||||||
|
// height: appState.height / zoom,
|
||||||
|
// x: -appState.scrollX,
|
||||||
|
// y: -appState.scrollY,
|
||||||
|
// fillStyle: "solid",
|
||||||
|
// strokeColor: "transparent",
|
||||||
|
// backgroundColor: "rgba(0,0,0,0.05)",
|
||||||
|
// roundness: { type: ROUNDNESS.ADAPTIVE_RADIUS, value: 40 },
|
||||||
|
// },
|
||||||
|
// ],
|
||||||
|
// null,
|
||||||
|
// ),
|
||||||
|
// ),
|
||||||
|
appState,
|
||||||
|
files,
|
||||||
|
},
|
||||||
|
config: {
|
||||||
|
...(frame
|
||||||
|
? {
|
||||||
|
...config,
|
||||||
|
width: frame.width,
|
||||||
|
height: frame.height,
|
||||||
|
x: frame.x,
|
||||||
|
y: frame.y,
|
||||||
|
}
|
||||||
|
: config),
|
||||||
|
},
|
||||||
|
}).then((canvas) => {
|
||||||
|
if (canvasPreviewContainerRef.current) {
|
||||||
|
canvasPreviewContainerRef.current.replaceChildren(canvas);
|
||||||
|
document.querySelector(
|
||||||
|
".canvas_dims",
|
||||||
|
)!.innerHTML = `${canvas.width}x${canvas.height} (canvas)`;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
exportToSvg({
|
||||||
|
data: {
|
||||||
|
elements: nonDeletedElements.filter((x) => x.id !== frame?.id),
|
||||||
|
appState,
|
||||||
|
files,
|
||||||
|
},
|
||||||
|
config: {
|
||||||
|
...(frame
|
||||||
|
? {
|
||||||
|
...config,
|
||||||
|
width: frame.width,
|
||||||
|
height: frame.height,
|
||||||
|
x: frame.x,
|
||||||
|
y: frame.y,
|
||||||
|
}
|
||||||
|
: config),
|
||||||
|
},
|
||||||
|
}).then((svg) => {
|
||||||
|
if (svgPreviewContainerRef.current) {
|
||||||
|
svgPreviewContainerRef.current.replaceChildren(svg);
|
||||||
|
document.querySelector(".svg_dims")!.innerHTML = `${round(
|
||||||
|
parseFloat(svg.getAttribute("width") ?? ""),
|
||||||
|
0,
|
||||||
|
)}x${round(parseFloat(svg.getAttribute("height") ?? ""), 0)} (svg)`;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// this check is redundant, but since this is a hot path, it's best
|
// this check is redundant, but since this is a hot path, it's best
|
||||||
// not to evaludate the nested expression every time
|
// not to evaludate the nested expression every time
|
||||||
if (!LocalData.isSavePaused()) {
|
if (!LocalData.isSavePaused()) {
|
||||||
@ -657,7 +741,7 @@ const ExcalidrawWrapper = () => {
|
|||||||
if (didChange) {
|
if (didChange) {
|
||||||
excalidrawAPI.updateScene({
|
excalidrawAPI.updateScene({
|
||||||
elements,
|
elements,
|
||||||
captureUpdate: CaptureUpdateAction.NEVER,
|
storeAction: StoreAction.UPDATE,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -864,12 +948,6 @@ const ExcalidrawWrapper = () => {
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
onLinkOpen={(element, event) => {
|
|
||||||
if (element.link && isElementLink(element.link)) {
|
|
||||||
event.preventDefault();
|
|
||||||
excalidrawAPI?.scrollToContent(element.link, { animate: true });
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
<AppMainMenu
|
<AppMainMenu
|
||||||
onCollabDialogOpen={onCollabDialogOpen}
|
onCollabDialogOpen={onCollabDialogOpen}
|
||||||
@ -926,21 +1004,16 @@ const ExcalidrawWrapper = () => {
|
|||||||
<ShareDialog
|
<ShareDialog
|
||||||
collabAPI={collabAPI}
|
collabAPI={collabAPI}
|
||||||
onExportToBackend={async () => {
|
onExportToBackend={async () => {
|
||||||
if (!excalidrawAPI) {
|
if (excalidrawAPI) {
|
||||||
return;
|
try {
|
||||||
}
|
await onExportToBackend(
|
||||||
try {
|
excalidrawAPI.getSceneElements(),
|
||||||
const { url, errorMessage } = await exportToBackend(
|
excalidrawAPI.getAppState(),
|
||||||
excalidrawAPI.getSceneElements(),
|
excalidrawAPI.getFiles(),
|
||||||
excalidrawAPI.getAppState(),
|
);
|
||||||
excalidrawAPI.getFiles(),
|
} catch (error: any) {
|
||||||
);
|
setErrorMessage(error.message);
|
||||||
if (errorMessage) {
|
|
||||||
throw new Error(errorMessage);
|
|
||||||
}
|
}
|
||||||
setLatestShareableLink(url);
|
|
||||||
} catch (error: any) {
|
|
||||||
setErrorMessage(error.message);
|
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
@ -1148,6 +1221,258 @@ const ExcalidrawWrapper = () => {
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</Excalidraw>
|
</Excalidraw>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: "flex",
|
||||||
|
flexDirection: "column",
|
||||||
|
position: "fixed",
|
||||||
|
bottom: 60,
|
||||||
|
right: 60,
|
||||||
|
zIndex: 9999999999,
|
||||||
|
color: "black",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div style={{ display: "flex", gap: "1rem", flexDirection: "column" }}>
|
||||||
|
<div style={{ display: "flex", gap: "1rem" }}>
|
||||||
|
<label>
|
||||||
|
center{" "}
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
checked={config.position === "center"}
|
||||||
|
onChange={() =>
|
||||||
|
setConfig((s) => ({
|
||||||
|
...s,
|
||||||
|
position: s.position === "center" ? "topLeft" : "center",
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
<label>
|
||||||
|
fit{" "}
|
||||||
|
<select
|
||||||
|
value={config.fit}
|
||||||
|
onChange={(event) =>
|
||||||
|
setConfig((s) => ({
|
||||||
|
...s,
|
||||||
|
fit: event.target.value as any,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<option value="none">none</option>
|
||||||
|
<option value="contain">contain</option>
|
||||||
|
</select>
|
||||||
|
</label>
|
||||||
|
<label>
|
||||||
|
padding{" "}
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
max={600}
|
||||||
|
style={{ width: "3rem" }}
|
||||||
|
value={config.padding}
|
||||||
|
onChange={(event) =>
|
||||||
|
setConfig((s) => ({
|
||||||
|
...s,
|
||||||
|
padding: !event.target.value.trim()
|
||||||
|
? undefined
|
||||||
|
: Math.min(parseInt(event.target.value as any), 600),
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
<label>
|
||||||
|
scale{" "}
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
max={4}
|
||||||
|
style={{ width: "3rem" }}
|
||||||
|
value={config.scale}
|
||||||
|
onChange={(event) =>
|
||||||
|
setConfig((s) => ({
|
||||||
|
...s,
|
||||||
|
scale: !event.target.value.trim()
|
||||||
|
? undefined
|
||||||
|
: Math.min(parseFloat(event.target.value as any), 4),
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div style={{ display: "flex", gap: "1rem" }}>
|
||||||
|
<label
|
||||||
|
style={{
|
||||||
|
opacity:
|
||||||
|
config.maxWidthOrHeight != null ||
|
||||||
|
config.widthOrHeight != null
|
||||||
|
? 0.5
|
||||||
|
: undefined,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
width{" "}
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
max={600}
|
||||||
|
style={{ width: "3rem" }}
|
||||||
|
value={config.width}
|
||||||
|
onChange={(event) =>
|
||||||
|
setConfig((s) => ({
|
||||||
|
...s,
|
||||||
|
width: !event.target.value.trim()
|
||||||
|
? undefined
|
||||||
|
: Math.min(parseInt(event.target.value as any), 600),
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
<label
|
||||||
|
style={{
|
||||||
|
opacity:
|
||||||
|
config.maxWidthOrHeight != null ||
|
||||||
|
config.widthOrHeight != null
|
||||||
|
? 0.5
|
||||||
|
: undefined,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
height{" "}
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
max={600}
|
||||||
|
style={{ width: "3rem" }}
|
||||||
|
value={config.height}
|
||||||
|
onChange={(event) =>
|
||||||
|
setConfig((s) => ({
|
||||||
|
...s,
|
||||||
|
height: !event.target.value.trim()
|
||||||
|
? undefined
|
||||||
|
: Math.min(parseInt(event.target.value as any), 600),
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
<label>
|
||||||
|
x{" "}
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
style={{ width: "3rem" }}
|
||||||
|
value={config.x}
|
||||||
|
onChange={(event) =>
|
||||||
|
setConfig((s) => ({
|
||||||
|
...s,
|
||||||
|
x: !event.target.value.trim()
|
||||||
|
? undefined
|
||||||
|
: parseFloat(event.target.value as any) ?? undefined,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
<label>
|
||||||
|
y{" "}
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
style={{ width: "3rem" }}
|
||||||
|
value={config.y}
|
||||||
|
onChange={(event) =>
|
||||||
|
setConfig((s) => ({
|
||||||
|
...s,
|
||||||
|
y: !event.target.value.trim()
|
||||||
|
? undefined
|
||||||
|
: parseFloat(event.target.value as any) ?? undefined,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
<label
|
||||||
|
style={{
|
||||||
|
opacity: config.widthOrHeight != null ? 0.5 : undefined,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
maxWH{" "}
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
// max={600}
|
||||||
|
style={{ width: "3rem" }}
|
||||||
|
value={config.maxWidthOrHeight}
|
||||||
|
onChange={(event) =>
|
||||||
|
setConfig((s) => ({
|
||||||
|
...s,
|
||||||
|
maxWidthOrHeight: !event.target.value.trim()
|
||||||
|
? undefined
|
||||||
|
: parseInt(event.target.value as any),
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
<label>
|
||||||
|
widthOrHeight{" "}
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
max={600}
|
||||||
|
style={{ width: "3rem" }}
|
||||||
|
value={config.widthOrHeight}
|
||||||
|
onChange={(event) =>
|
||||||
|
setConfig((s) => ({
|
||||||
|
...s,
|
||||||
|
widthOrHeight: !event.target.value.trim()
|
||||||
|
? undefined
|
||||||
|
: Math.min(parseInt(event.target.value as any), 600),
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="canvas_dims">0x0</div>
|
||||||
|
<div
|
||||||
|
ref={canvasPreviewContainerRef}
|
||||||
|
onClick={() => {
|
||||||
|
exportToBlob({
|
||||||
|
data: {
|
||||||
|
elements: excalidrawAPI!.getSceneElements(),
|
||||||
|
files: excalidrawAPI?.getFiles() || null,
|
||||||
|
},
|
||||||
|
config,
|
||||||
|
}).then((blob) => {
|
||||||
|
fileSave(blob, {
|
||||||
|
name: "xx",
|
||||||
|
extension: "png",
|
||||||
|
description: "xxx",
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
style={{
|
||||||
|
borderRadius: 12,
|
||||||
|
border: "1px solid #777",
|
||||||
|
overflow: "hidden",
|
||||||
|
padding: 10,
|
||||||
|
backgroundColor: "pink",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<div className="svg_dims">0x0</div>
|
||||||
|
<div
|
||||||
|
ref={svgPreviewContainerRef}
|
||||||
|
onClick={() => {
|
||||||
|
exportToBlob({
|
||||||
|
data: {
|
||||||
|
elements: excalidrawAPI!.getSceneElements(),
|
||||||
|
files: excalidrawAPI?.getFiles() || null,
|
||||||
|
},
|
||||||
|
config,
|
||||||
|
}).then((blob) => {
|
||||||
|
fileSave(blob, {
|
||||||
|
name: "xx",
|
||||||
|
extension: "png",
|
||||||
|
description: "xxx",
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
style={{
|
||||||
|
borderRadius: 12,
|
||||||
|
border: "1px solid #777",
|
||||||
|
overflow: "hidden",
|
||||||
|
padding: 10,
|
||||||
|
backgroundColor: "pink",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@ -1161,7 +1486,7 @@ const ExcalidrawApp = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<TopErrorBoundary>
|
<TopErrorBoundary>
|
||||||
<Provider store={appJotaiStore}>
|
<Provider unstable_createStore={() => appJotaiStore}>
|
||||||
<ExcalidrawWrapper />
|
<ExcalidrawWrapper />
|
||||||
</Provider>
|
</Provider>
|
||||||
</TopErrorBoundary>
|
</TopErrorBoundary>
|
||||||
|
@ -1,21 +1,15 @@
|
|||||||
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,15 +1,13 @@
|
|||||||
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 "@excalidraw/element/types";
|
} from "../packages/excalidraw/element/types";
|
||||||
import type { AppState, BinaryFileData } from "@excalidraw/excalidraw/types";
|
import type { AppState, BinaryFileData } from "../packages/excalidraw/types";
|
||||||
|
import { ExcalidrawError } from "../packages/excalidraw/errors";
|
||||||
import { STORAGE_KEYS } from "./app_constants";
|
import { base64urlToString } from "../packages/excalidraw/data/encode";
|
||||||
import { LocalData } from "./data/LocalData";
|
|
||||||
|
|
||||||
const EVENT_REQUEST_SCENE = "REQUEST_SCENE";
|
const EVENT_REQUEST_SCENE = "REQUEST_SCENE";
|
||||||
|
|
||||||
|
@ -1,37 +1,3 @@
|
|||||||
// eslint-disable-next-line no-restricted-imports
|
import { unstable_createStore } from "jotai";
|
||||||
import {
|
|
||||||
atom,
|
|
||||||
Provider,
|
|
||||||
useAtom,
|
|
||||||
useAtomValue,
|
|
||||||
useSetAtom,
|
|
||||||
createStore,
|
|
||||||
type PrimitiveAtom,
|
|
||||||
} from "jotai";
|
|
||||||
import { useLayoutEffect } from "react";
|
|
||||||
|
|
||||||
export const appJotaiStore = createStore();
|
export const appJotaiStore = unstable_createStore();
|
||||||
|
|
||||||
export { atom, Provider, useAtom, useAtomValue, useSetAtom };
|
|
||||||
|
|
||||||
export const useAtomWithInitialValue = <
|
|
||||||
T extends unknown,
|
|
||||||
A extends PrimitiveAtom<T>,
|
|
||||||
>(
|
|
||||||
atom: A,
|
|
||||||
initialValue: T | (() => T),
|
|
||||||
) => {
|
|
||||||
const [value, setValue] = useAtom(atom);
|
|
||||||
|
|
||||||
useLayoutEffect(() => {
|
|
||||||
if (typeof initialValue === "function") {
|
|
||||||
// @ts-ignore
|
|
||||||
setValue(initialValue());
|
|
||||||
} else {
|
|
||||||
setValue(initialValue);
|
|
||||||
}
|
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
return [value, setValue] as const;
|
|
||||||
};
|
|
||||||
|
@ -1,8 +1,6 @@
|
|||||||
import { useI18n, languages } from "@excalidraw/excalidraw/i18n";
|
import { useSetAtom } from "jotai";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
|
import { useI18n, languages } from "../../packages/excalidraw/i18n";
|
||||||
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,7 +1,5 @@
|
|||||||
|
import { atom, useAtom } from "jotai";
|
||||||
import { useEffect } from "react";
|
import { useEffect } from "react";
|
||||||
|
|
||||||
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,55 +1,33 @@
|
|||||||
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 {
|
||||||
ReconciledExcalidrawElement,
|
BinaryFileData,
|
||||||
RemoteExcalidrawElement,
|
ExcalidrawImperativeAPI,
|
||||||
} from "@excalidraw/excalidraw/data/reconcile";
|
SocketId,
|
||||||
import type { ImportedDataState } from "@excalidraw/excalidraw/data/types";
|
} from "../../packages/excalidraw/types";
|
||||||
|
import { ErrorDialog } from "../../packages/excalidraw/components/ErrorDialog";
|
||||||
|
import { APP_NAME, ENV, EVENT } from "../../packages/excalidraw/constants";
|
||||||
|
import type { ImportedDataState } from "../../packages/excalidraw/data/types";
|
||||||
import type {
|
import type {
|
||||||
ExcalidrawElement,
|
ExcalidrawElement,
|
||||||
FileId,
|
FileId,
|
||||||
InitializedExcalidrawImageElement,
|
InitializedExcalidrawImageElement,
|
||||||
OrderedExcalidrawElement,
|
OrderedExcalidrawElement,
|
||||||
} from "@excalidraw/element/types";
|
} from "../../packages/excalidraw/element/types";
|
||||||
import type {
|
import {
|
||||||
BinaryFileData,
|
StoreAction,
|
||||||
ExcalidrawImperativeAPI,
|
getSceneVersion,
|
||||||
SocketId,
|
restoreElements,
|
||||||
Collaborator,
|
zoomToFitBounds,
|
||||||
Gesture,
|
reconcileElements,
|
||||||
} from "@excalidraw/excalidraw/types";
|
} from "../../packages/excalidraw";
|
||||||
import type { Mutable, ValueOf } from "@excalidraw/common/utility-types";
|
import type { Collaborator, Gesture } from "../../packages/excalidraw/types";
|
||||||
|
import {
|
||||||
import { appJotaiStore, atom } from "../app-jotai";
|
assertNever,
|
||||||
|
preventUnload,
|
||||||
|
resolvablePromise,
|
||||||
|
throttleRAF,
|
||||||
|
} from "../../packages/excalidraw/utils";
|
||||||
import {
|
import {
|
||||||
CURSOR_SYNC_TIMEOUT,
|
CURSOR_SYNC_TIMEOUT,
|
||||||
FILE_UPLOAD_MAX_BYTES,
|
FILE_UPLOAD_MAX_BYTES,
|
||||||
@ -60,17 +38,15 @@ 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,
|
||||||
@ -82,15 +58,37 @@ import {
|
|||||||
importUsernameFromLocalStorage,
|
importUsernameFromLocalStorage,
|
||||||
saveUsernameToLocalStorage,
|
saveUsernameToLocalStorage,
|
||||||
} from "../data/localStorage";
|
} from "../data/localStorage";
|
||||||
import { resetBrowserStateVersions } from "../data/tabSync";
|
|
||||||
|
|
||||||
import { collabErrorIndicatorAtom } from "./CollabError";
|
|
||||||
import Portal from "./Portal";
|
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 { LocalData } from "../data/LocalData";
|
||||||
|
import { atom } from "jotai";
|
||||||
|
import { appJotaiStore } from "../app-jotai";
|
||||||
|
import type { Mutable, ValueOf } from "../../packages/excalidraw/utility-types";
|
||||||
|
import { getVisibleSceneBounds } from "../../packages/excalidraw/element/bounds";
|
||||||
|
import { withBatchedUpdates } from "../../packages/excalidraw/reactUtils";
|
||||||
|
import { collabErrorIndicatorAtom } from "./CollabError";
|
||||||
import type {
|
import type {
|
||||||
SocketUpdateDataSource,
|
ReconciledExcalidrawElement,
|
||||||
SyncableExcalidrawElement,
|
RemoteExcalidrawElement,
|
||||||
} from "../data";
|
} from "../../packages/excalidraw/data/reconcile";
|
||||||
|
|
||||||
export const collabAPIAtom = atom<CollabAPI | null>(null);
|
export const collabAPIAtom = atom<CollabAPI | null>(null);
|
||||||
export const isCollaboratingAtom = atom(false);
|
export const isCollaboratingAtom = atom(false);
|
||||||
@ -238,7 +236,7 @@ class Collab extends PureComponent<CollabProps, CollabState> {
|
|||||||
|
|
||||||
appJotaiStore.set(collabAPIAtom, collabAPI);
|
appJotaiStore.set(collabAPIAtom, collabAPI);
|
||||||
|
|
||||||
if (isTestEnv() || isDevEnv()) {
|
if (import.meta.env.MODE === ENV.TEST || import.meta.env.DEV) {
|
||||||
window.collab = window.collab || ({} as Window["collab"]);
|
window.collab = window.collab || ({} as Window["collab"]);
|
||||||
Object.defineProperties(window, {
|
Object.defineProperties(window, {
|
||||||
collab: {
|
collab: {
|
||||||
@ -298,13 +296,7 @@ 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);
|
||||||
|
|
||||||
if (import.meta.env.VITE_APP_DISABLE_PREVENT_UNLOAD !== "true") {
|
preventUnload(event);
|
||||||
preventUnload(event);
|
|
||||||
} else {
|
|
||||||
console.warn(
|
|
||||||
"preventing unload disabled (VITE_APP_DISABLE_PREVENT_UNLOAD)",
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -392,7 +384,7 @@ class Collab extends PureComponent<CollabProps, CollabState> {
|
|||||||
|
|
||||||
this.excalidrawAPI.updateScene({
|
this.excalidrawAPI.updateScene({
|
||||||
elements,
|
elements,
|
||||||
captureUpdate: CaptureUpdateAction.NEVER,
|
storeAction: StoreAction.UPDATE,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -543,7 +535,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,
|
||||||
captureUpdate: CaptureUpdateAction.NEVER,
|
storeAction: StoreAction.UPDATE,
|
||||||
});
|
});
|
||||||
|
|
||||||
this.saveCollabRoomToFirebase(getSyncableElements(elements));
|
this.saveCollabRoomToFirebase(getSyncableElements(elements));
|
||||||
@ -781,7 +773,7 @@ class Collab extends PureComponent<CollabProps, CollabState> {
|
|||||||
) => {
|
) => {
|
||||||
this.excalidrawAPI.updateScene({
|
this.excalidrawAPI.updateScene({
|
||||||
elements,
|
elements,
|
||||||
captureUpdate: CaptureUpdateAction.NEVER,
|
storeAction: StoreAction.UPDATE,
|
||||||
});
|
});
|
||||||
|
|
||||||
this.loadImageFiles();
|
this.loadImageFiles();
|
||||||
@ -1017,7 +1009,7 @@ declare global {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isTestEnv() || isDevEnv()) {
|
if (import.meta.env.MODE === ENV.TEST || import.meta.env.DEV) {
|
||||||
window.collab = window.collab || ({} as Window["collab"]);
|
window.collab = window.collab || ({} as Window["collab"]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,11 +1,10 @@
|
|||||||
import { Tooltip } from "@excalidraw/excalidraw/components/Tooltip";
|
import { Tooltip } from "../../packages/excalidraw/components/Tooltip";
|
||||||
import { warning } from "@excalidraw/excalidraw/components/icons";
|
import { warning } from "../../packages/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 "./CollabError.scss";
|
import "./CollabError.scss";
|
||||||
|
import { atom } from "jotai";
|
||||||
|
|
||||||
type ErrorIndicator = {
|
type ErrorIndicator = {
|
||||||
message: string | null;
|
message: string | null;
|
||||||
@ -20,16 +19,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>(0);
|
const clearAnimationRef = useRef<string | number | NodeJS.Timeout>();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setIsAnimating(true);
|
setIsAnimating(true);
|
||||||
clearAnimationRef.current = window.setTimeout(() => {
|
clearAnimationRef.current = setTimeout(() => {
|
||||||
setIsAnimating(false);
|
setIsAnimating(false);
|
||||||
}, 1000);
|
}, 1000);
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
window.clearTimeout(clearAnimationRef.current);
|
clearTimeout(clearAnimationRef.current);
|
||||||
};
|
};
|
||||||
}, [collabError.message, collabError.nonce]);
|
}, [collabError.message, collabError.nonce]);
|
||||||
|
|
||||||
|
@ -1,26 +1,25 @@
|
|||||||
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;
|
||||||
@ -134,7 +133,7 @@ class Portal {
|
|||||||
if (isChanged) {
|
if (isChanged) {
|
||||||
this.collab.excalidrawAPI.updateScene({
|
this.collab.excalidrawAPI.updateScene({
|
||||||
elements: newElements,
|
elements: newElements,
|
||||||
captureUpdate: CaptureUpdateAction.NEVER,
|
storeAction: StoreAction.UPDATE,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}, FILE_UPLOAD_TIMEOUT);
|
}, FILE_UPLOAD_TIMEOUT);
|
||||||
|
@ -1,14 +1,13 @@
|
|||||||
|
import type { ExcalidrawImperativeAPI } from "../../packages/excalidraw/types";
|
||||||
import {
|
import {
|
||||||
DiagramToCodePlugin,
|
DiagramToCodePlugin,
|
||||||
exportToBlob,
|
exportToBlob,
|
||||||
getTextFromElements,
|
getTextFromElements,
|
||||||
MIME_TYPES,
|
MIME_TYPES,
|
||||||
TTDDialog,
|
TTDDialog,
|
||||||
} from "@excalidraw/excalidraw";
|
} from "../../packages/excalidraw";
|
||||||
import { getDataURL } from "@excalidraw/excalidraw/data/blob";
|
import { getDataURL } from "../../packages/excalidraw/data/blob";
|
||||||
import { safelyParseJSON } from "@excalidraw/common";
|
import { safelyParseJSON } from "../../packages/excalidraw/utils";
|
||||||
|
|
||||||
import type { ExcalidrawImperativeAPI } from "@excalidraw/excalidraw/types";
|
|
||||||
|
|
||||||
export const AIComponents = ({
|
export const AIComponents = ({
|
||||||
excalidrawAPI,
|
excalidrawAPI,
|
||||||
@ -22,15 +21,19 @@ export const AIComponents = ({
|
|||||||
const appState = excalidrawAPI.getAppState();
|
const appState = excalidrawAPI.getAppState();
|
||||||
|
|
||||||
const blob = await exportToBlob({
|
const blob = await exportToBlob({
|
||||||
elements: children,
|
data: {
|
||||||
appState: {
|
elements: children,
|
||||||
...appState,
|
appState: {
|
||||||
exportBackground: true,
|
...appState,
|
||||||
viewBackgroundColor: appState.viewBackgroundColor,
|
exportBackground: true,
|
||||||
|
viewBackgroundColor: appState.viewBackgroundColor,
|
||||||
|
},
|
||||||
|
files: excalidrawAPI.getFiles(),
|
||||||
|
},
|
||||||
|
config: {
|
||||||
|
exportingFrame: frame,
|
||||||
|
mimeType: MIME_TYPES.jpg,
|
||||||
},
|
},
|
||||||
exportingFrame: frame,
|
|
||||||
files: excalidrawAPI.getFiles(),
|
|
||||||
mimeType: MIME_TYPES.jpg,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const dataURL = await getDataURL(blob);
|
const dataURL = await getDataURL(blob);
|
||||||
@ -73,7 +76,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="noopener">Excalidraw+</a> to get more requests.</div>
|
}/plus?utm_source=excalidraw&utm_medium=app&utm_content=d2c" target="_blank" rel="noreferrer noopener">Excalidraw+</a> to get more requests.</div>
|
||||||
</div>
|
</div>
|
||||||
</body>
|
</body>
|
||||||
</html>`,
|
</html>`,
|
||||||
|
@ -1,11 +1,9 @@
|
|||||||
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,18 +1,13 @@
|
|||||||
|
import React from "react";
|
||||||
import {
|
import {
|
||||||
loginIcon,
|
loginIcon,
|
||||||
ExcalLogo,
|
ExcalLogo,
|
||||||
eyeIcon,
|
eyeIcon,
|
||||||
} from "@excalidraw/excalidraw/components/icons";
|
} from "../../packages/excalidraw/components/icons";
|
||||||
import { MainMenu } from "@excalidraw/excalidraw/index";
|
import type { Theme } from "../../packages/excalidraw/element/types";
|
||||||
import React from "react";
|
import { MainMenu } from "../../packages/excalidraw/index";
|
||||||
|
|
||||||
import { isDevEnv } from "@excalidraw/common";
|
|
||||||
|
|
||||||
import type { Theme } from "@excalidraw/element/types";
|
|
||||||
|
|
||||||
import { LanguageList } from "../app-language/LanguageList";
|
|
||||||
import { isExcalidrawPlusSignedUser } from "../app_constants";
|
import { isExcalidrawPlusSignedUser } from "../app_constants";
|
||||||
|
import { LanguageList } from "../app-language/LanguageList";
|
||||||
import { saveDebugState } from "./DebugCanvas";
|
import { saveDebugState } from "./DebugCanvas";
|
||||||
|
|
||||||
export const AppMainMenu: React.FC<{
|
export const AppMainMenu: React.FC<{
|
||||||
@ -59,7 +54,7 @@ export const AppMainMenu: React.FC<{
|
|||||||
>
|
>
|
||||||
{isExcalidrawPlusSignedUser ? "Sign in" : "Sign up"}
|
{isExcalidrawPlusSignedUser ? "Sign in" : "Sign up"}
|
||||||
</MainMenu.ItemLink>
|
</MainMenu.ItemLink>
|
||||||
{isDevEnv() && (
|
{import.meta.env.DEV && (
|
||||||
<MainMenu.Item
|
<MainMenu.Item
|
||||||
icon={eyeIcon}
|
icon={eyeIcon}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
|
@ -1,10 +1,9 @@
|
|||||||
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,28 +1,22 @@
|
|||||||
|
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 "@excalidraw/excalidraw/components/icons";
|
} from "../../packages/excalidraw/components/icons";
|
||||||
import {
|
import { STORAGE_KEYS } from "../app_constants";
|
||||||
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 "@excalidraw/math";
|
} from "../../packages/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,
|
||||||
@ -39,28 +33,6 @@ 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();
|
||||||
@ -88,16 +60,6 @@ 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)}`);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@ -122,7 +84,7 @@ const _debugRenderer = (
|
|||||||
scale,
|
scale,
|
||||||
normalizedWidth,
|
normalizedWidth,
|
||||||
normalizedHeight,
|
normalizedHeight,
|
||||||
viewBackgroundColor: "transparent",
|
canvasBackgroundColor: "transparent",
|
||||||
});
|
});
|
||||||
|
|
||||||
// Apply zoom
|
// Apply zoom
|
||||||
@ -314,35 +276,36 @@ export const DebugFooter = ({ onChange }: { onChange: () => void }) => {
|
|||||||
interface DebugCanvasProps {
|
interface DebugCanvasProps {
|
||||||
appState: AppState;
|
appState: AppState;
|
||||||
scale: number;
|
scale: number;
|
||||||
ref?: React.Ref<HTMLCanvasElement>;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const DebugCanvas = ({ appState, scale, ref }: DebugCanvasProps) => {
|
const DebugCanvas = forwardRef<HTMLCanvasElement, DebugCanvasProps>(
|
||||||
const { width, height } = appState;
|
({ appState, scale }, ref) => {
|
||||||
|
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 { Tooltip } from "@excalidraw/excalidraw/components/Tooltip";
|
import { shield } from "../../packages/excalidraw/components/icons";
|
||||||
import { shield } from "@excalidraw/excalidraw/components/icons";
|
import { Tooltip } from "../../packages/excalidraw/components/Tooltip";
|
||||||
import { useI18n } from "@excalidraw/excalidraw/i18n";
|
import { useI18n } from "../../packages/excalidraw/i18n";
|
||||||
|
|
||||||
export const EncryptedIcon = () => {
|
export const EncryptedIcon = () => {
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
@ -8,9 +8,9 @@ export const EncryptedIcon = () => {
|
|||||||
return (
|
return (
|
||||||
<a
|
<a
|
||||||
className="encrypted-icon tooltip"
|
className="encrypted-icon tooltip"
|
||||||
href="https://plus.excalidraw.com/blog/end-to-end-encryption"
|
href="https://blog.excalidraw.com/end-to-end-encryption/"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener"
|
rel="noopener noreferrer"
|
||||||
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="noopener"
|
rel="noreferrer"
|
||||||
className="plus-button"
|
className="plus-button"
|
||||||
>
|
>
|
||||||
Go to Excalidraw+
|
Go to Excalidraw+
|
||||||
|
@ -1,33 +1,30 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { uploadBytes, ref } from "firebase/storage";
|
import { Card } from "../../packages/excalidraw/components/Card";
|
||||||
import { nanoid } from "nanoid";
|
import { ToolButton } from "../../packages/excalidraw/components/ToolButton";
|
||||||
|
import { serializeAsJSON } from "../../packages/excalidraw/data/json";
|
||||||
import { trackEvent } from "@excalidraw/excalidraw/analytics";
|
import { loadFirebaseStorage, saveFilesToFirebase } from "../data/firebase";
|
||||||
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 "@excalidraw/element/types";
|
} from "../../packages/excalidraw/element/types";
|
||||||
import type {
|
import type {
|
||||||
AppState,
|
AppState,
|
||||||
BinaryFileData,
|
BinaryFileData,
|
||||||
BinaryFiles,
|
BinaryFiles,
|
||||||
} from "@excalidraw/excalidraw/types";
|
} from "../../packages/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 { loadFirebaseStorage, saveFilesToFirebase } from "../data/firebase";
|
import { MIME_TYPES } from "../../packages/excalidraw/constants";
|
||||||
|
import { trackEvent } from "../../packages/excalidraw/analytics";
|
||||||
|
import { getFrame } from "../../packages/excalidraw/utils";
|
||||||
|
import { ExcalidrawLogo } from "../../packages/excalidraw/components/ExcalidrawLogo";
|
||||||
|
|
||||||
export const exportToExcalidrawPlus = async (
|
export const exportToExcalidrawPlus = async (
|
||||||
elements: readonly NonDeletedExcalidrawElement[],
|
elements: readonly NonDeletedExcalidrawElement[],
|
||||||
@ -35,7 +32,7 @@ export const exportToExcalidrawPlus = async (
|
|||||||
files: BinaryFiles,
|
files: BinaryFiles,
|
||||||
name: string,
|
name: string,
|
||||||
) => {
|
) => {
|
||||||
const storage = await loadFirebaseStorage();
|
const firebase = await loadFirebaseStorage();
|
||||||
|
|
||||||
const id = `${nanoid(12)}`;
|
const id = `${nanoid(12)}`;
|
||||||
|
|
||||||
@ -52,13 +49,15 @@ export const exportToExcalidrawPlus = async (
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
const storageRef = ref(storage, `/migrations/scenes/${id}`);
|
await firebase
|
||||||
await uploadBytes(storageRef, blob, {
|
.storage()
|
||||||
customMetadata: {
|
.ref(`/migrations/scenes/${id}`)
|
||||||
data: JSON.stringify({ version: 2, name }),
|
.put(blob, {
|
||||||
created: Date.now().toString(),
|
customMetadata: {
|
||||||
},
|
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,8 +1,7 @@
|
|||||||
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 "@excalidraw/element/types";
|
import type { Theme } from "../../packages/excalidraw/element/types";
|
||||||
|
|
||||||
// https://github.com/tholman/github-corners
|
// https://github.com/tholman/github-corners
|
||||||
export const GitHubCorner = React.memo(
|
export const GitHubCorner = React.memo(
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import Trans from "@excalidraw/excalidraw/components/Trans";
|
|
||||||
import { t } from "@excalidraw/excalidraw/i18n";
|
|
||||||
import * as Sentry from "@sentry/browser";
|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
|
import * as Sentry from "@sentry/browser";
|
||||||
|
import { t } from "../../packages/excalidraw/i18n";
|
||||||
|
import Trans from "../../packages/excalidraw/components/Trans";
|
||||||
|
|
||||||
interface TopErrorBoundaryState {
|
interface TopErrorBoundaryState {
|
||||||
hasError: boolean;
|
hasError: boolean;
|
||||||
|
@ -1,21 +1,20 @@
|
|||||||
import { CaptureUpdateAction } from "@excalidraw/excalidraw";
|
import { StoreAction } from "../../packages/excalidraw";
|
||||||
import { compressData } from "@excalidraw/excalidraw/data/encode";
|
import { compressData } from "../../packages/excalidraw/data/encode";
|
||||||
import { newElementWith } from "@excalidraw/element";
|
import { newElementWith } from "../../packages/excalidraw/element/mutateElement";
|
||||||
import { isInitializedImageElement } from "@excalidraw/element";
|
import { isInitializedImageElement } from "../../packages/excalidraw/element/typeChecks";
|
||||||
import { t } from "@excalidraw/excalidraw/i18n";
|
|
||||||
|
|
||||||
import type {
|
import type {
|
||||||
ExcalidrawElement,
|
ExcalidrawElement,
|
||||||
ExcalidrawImageElement,
|
ExcalidrawImageElement,
|
||||||
FileId,
|
FileId,
|
||||||
InitializedExcalidrawImageElement,
|
InitializedExcalidrawImageElement,
|
||||||
} from "@excalidraw/element/types";
|
} from "../../packages/excalidraw/element/types";
|
||||||
|
import { t } from "../../packages/excalidraw/i18n";
|
||||||
import type {
|
import type {
|
||||||
BinaryFileData,
|
BinaryFileData,
|
||||||
BinaryFileMetadata,
|
BinaryFileMetadata,
|
||||||
ExcalidrawImperativeAPI,
|
ExcalidrawImperativeAPI,
|
||||||
BinaryFiles,
|
BinaryFiles,
|
||||||
} from "@excalidraw/excalidraw/types";
|
} from "../../packages/excalidraw/types";
|
||||||
|
|
||||||
type FileVersion = Required<BinaryFileData>["version"];
|
type FileVersion = Required<BinaryFileData>["version"];
|
||||||
|
|
||||||
@ -269,6 +268,6 @@ export const updateStaleImageStatuses = (params: {
|
|||||||
}
|
}
|
||||||
return element;
|
return element;
|
||||||
}),
|
}),
|
||||||
captureUpdate: CaptureUpdateAction.NEVER,
|
storeAction: StoreAction.UPDATE,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
@ -10,13 +10,6 @@
|
|||||||
* (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,
|
||||||
@ -26,19 +19,26 @@ import {
|
|||||||
setMany,
|
setMany,
|
||||||
get,
|
get,
|
||||||
} from "idb-keyval";
|
} from "idb-keyval";
|
||||||
|
import { clearAppStateForLocalStorage } from "../../packages/excalidraw/appState";
|
||||||
import type { LibraryPersistedData } from "@excalidraw/excalidraw/data/library";
|
import {
|
||||||
import type { ImportedDataState } from "@excalidraw/excalidraw/data/types";
|
CANVAS_SEARCH_TAB,
|
||||||
import type { ExcalidrawElement, FileId } from "@excalidraw/element/types";
|
DEFAULT_SIDEBAR,
|
||||||
|
} 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 "@excalidraw/excalidraw/types";
|
} from "../../packages/excalidraw/types";
|
||||||
import type { MaybePromise } from "@excalidraw/common/utility-types";
|
import type { MaybePromise } from "../../packages/excalidraw/utility-types";
|
||||||
|
import { debounce } from "../../packages/excalidraw/utils";
|
||||||
import { SAVE_TO_LOCAL_STORAGE_TIMEOUT, STORAGE_KEYS } from "../app_constants";
|
import { 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,42 +1,30 @@
|
|||||||
import { reconcileElements } from "@excalidraw/excalidraw";
|
import { reconcileElements } from "../../packages/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 "@excalidraw/element/types";
|
} from "../../packages/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 "@excalidraw/excalidraw/types";
|
} from "../../packages/excalidraw/types";
|
||||||
|
|
||||||
import { FILE_CACHE_MAX_AGE_SEC } from "../app_constants";
|
import { FILE_CACHE_MAX_AGE_SEC } from "../app_constants";
|
||||||
|
import { decompressData } from "../../packages/excalidraw/data/encode";
|
||||||
import { getSyncableElements } from ".";
|
import {
|
||||||
|
encryptData,
|
||||||
|
decryptData,
|
||||||
|
} from "../../packages/excalidraw/data/encryption";
|
||||||
|
import { MIME_TYPES } from "../../packages/excalidraw/constants";
|
||||||
import type { SyncableExcalidrawElement } from ".";
|
import type { SyncableExcalidrawElement } from ".";
|
||||||
import type Portal from "../collab/Portal";
|
import { getSyncableElements } from ".";
|
||||||
|
import type { ResolutionType } from "../../packages/excalidraw/utility-types";
|
||||||
import type { Socket } from "socket.io-client";
|
import type { Socket } from "socket.io-client";
|
||||||
|
import type { RemoteExcalidrawElement } from "../../packages/excalidraw/data/reconcile";
|
||||||
|
|
||||||
// private
|
// private
|
||||||
// -----------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------
|
||||||
@ -53,43 +41,81 @@ try {
|
|||||||
FIREBASE_CONFIG = {};
|
FIREBASE_CONFIG = {};
|
||||||
}
|
}
|
||||||
|
|
||||||
let firebaseApp: ReturnType<typeof initializeApp> | null = null;
|
let firebasePromise: Promise<typeof import("firebase/app").default> | null =
|
||||||
let firestore: ReturnType<typeof getFirestore> | null = null;
|
null;
|
||||||
let firebaseStorage: ReturnType<typeof getStorage> | null = null;
|
let firestorePromise: Promise<any> | null | true = null;
|
||||||
|
let firebaseStoragePromise: Promise<any> | null | true = null;
|
||||||
|
|
||||||
const _initializeFirebase = () => {
|
let isFirebaseInitialized = false;
|
||||||
if (!firebaseApp) {
|
|
||||||
firebaseApp = initializeApp(FIREBASE_CONFIG);
|
const _loadFirebase = async () => {
|
||||||
|
const firebase = (
|
||||||
|
await import(/* webpackChunkName: "firebase" */ "firebase/app")
|
||||||
|
).default;
|
||||||
|
|
||||||
|
if (!isFirebaseInitialized) {
|
||||||
|
try {
|
||||||
|
firebase.initializeApp(FIREBASE_CONFIG);
|
||||||
|
} catch (error: any) {
|
||||||
|
// trying initialize again throws. Usually this is harmless, and happens
|
||||||
|
// mainly in dev (HMR)
|
||||||
|
if (error.code === "app/duplicate-app") {
|
||||||
|
console.warn(error.name, error.code);
|
||||||
|
} else {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
isFirebaseInitialized = true;
|
||||||
}
|
}
|
||||||
return firebaseApp;
|
|
||||||
|
return firebase;
|
||||||
};
|
};
|
||||||
|
|
||||||
const _getFirestore = () => {
|
const _getFirebase = async (): Promise<
|
||||||
if (!firestore) {
|
typeof import("firebase/app").default
|
||||||
firestore = getFirestore(_initializeFirebase());
|
> => {
|
||||||
|
if (!firebasePromise) {
|
||||||
|
firebasePromise = _loadFirebase();
|
||||||
}
|
}
|
||||||
return firestore;
|
return firebasePromise;
|
||||||
};
|
|
||||||
|
|
||||||
const _getStorage = () => {
|
|
||||||
if (!firebaseStorage) {
|
|
||||||
firebaseStorage = getStorage(_initializeFirebase());
|
|
||||||
}
|
|
||||||
return firebaseStorage;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// -----------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------
|
||||||
|
|
||||||
export const loadFirebaseStorage = async () => {
|
const loadFirestore = async () => {
|
||||||
return _getStorage();
|
const firebase = await _getFirebase();
|
||||||
|
if (!firestorePromise) {
|
||||||
|
firestorePromise = import(
|
||||||
|
/* webpackChunkName: "firestore" */ "firebase/firestore"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (firestorePromise !== true) {
|
||||||
|
await firestorePromise;
|
||||||
|
firestorePromise = true;
|
||||||
|
}
|
||||||
|
return firebase;
|
||||||
};
|
};
|
||||||
|
|
||||||
type FirebaseStoredScene = {
|
export const loadFirebaseStorage = async () => {
|
||||||
sceneVersion: number;
|
const firebase = await _getFirebase();
|
||||||
iv: Bytes;
|
if (!firebaseStoragePromise) {
|
||||||
ciphertext: Bytes;
|
firebaseStoragePromise = import(
|
||||||
|
/* webpackChunkName: "storage" */ "firebase/storage"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (firebaseStoragePromise !== true) {
|
||||||
|
await firebaseStoragePromise;
|
||||||
|
firebaseStoragePromise = true;
|
||||||
|
}
|
||||||
|
return firebase;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
interface FirebaseStoredScene {
|
||||||
|
sceneVersion: number;
|
||||||
|
iv: firebase.default.firestore.Blob;
|
||||||
|
ciphertext: firebase.default.firestore.Blob;
|
||||||
|
}
|
||||||
|
|
||||||
const encryptElements = async (
|
const encryptElements = async (
|
||||||
key: string,
|
key: string,
|
||||||
elements: readonly ExcalidrawElement[],
|
elements: readonly ExcalidrawElement[],
|
||||||
@ -149,7 +175,7 @@ export const saveFilesToFirebase = async ({
|
|||||||
prefix: string;
|
prefix: string;
|
||||||
files: { id: FileId; buffer: Uint8Array }[];
|
files: { id: FileId; buffer: Uint8Array }[];
|
||||||
}) => {
|
}) => {
|
||||||
const storage = await loadFirebaseStorage();
|
const firebase = await loadFirebaseStorage();
|
||||||
|
|
||||||
const erroredFiles: FileId[] = [];
|
const erroredFiles: FileId[] = [];
|
||||||
const savedFiles: FileId[] = [];
|
const savedFiles: FileId[] = [];
|
||||||
@ -157,10 +183,17 @@ export const saveFilesToFirebase = async ({
|
|||||||
await Promise.all(
|
await Promise.all(
|
||||||
files.map(async ({ id, buffer }) => {
|
files.map(async ({ id, buffer }) => {
|
||||||
try {
|
try {
|
||||||
const storageRef = ref(storage, `${prefix}/${id}`);
|
await firebase
|
||||||
await uploadBytes(storageRef, buffer, {
|
.storage()
|
||||||
cacheControl: `public, max-age=${FILE_CACHE_MAX_AGE_SEC}`,
|
.ref(`${prefix}/${id}`)
|
||||||
});
|
.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);
|
||||||
@ -172,6 +205,7 @@ export const saveFilesToFirebase = async ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
const createFirebaseSceneDocument = async (
|
const createFirebaseSceneDocument = async (
|
||||||
|
firebase: ResolutionType<typeof loadFirestore>,
|
||||||
elements: readonly SyncableExcalidrawElement[],
|
elements: readonly SyncableExcalidrawElement[],
|
||||||
roomKey: string,
|
roomKey: string,
|
||||||
) => {
|
) => {
|
||||||
@ -179,8 +213,10 @@ const createFirebaseSceneDocument = async (
|
|||||||
const { ciphertext, iv } = await encryptElements(roomKey, elements);
|
const { ciphertext, iv } = await encryptElements(roomKey, elements);
|
||||||
return {
|
return {
|
||||||
sceneVersion,
|
sceneVersion,
|
||||||
ciphertext: Bytes.fromUint8Array(new Uint8Array(ciphertext)),
|
ciphertext: firebase.firestore.Blob.fromUint8Array(
|
||||||
iv: Bytes.fromUint8Array(iv),
|
new Uint8Array(ciphertext),
|
||||||
|
),
|
||||||
|
iv: firebase.firestore.Blob.fromUint8Array(iv),
|
||||||
} as FirebaseStoredScene;
|
} as FirebaseStoredScene;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -200,14 +236,20 @@ export const saveToFirebase = async (
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const firestore = _getFirestore();
|
const firebase = await loadFirestore();
|
||||||
const docRef = doc(firestore, "scenes", roomId);
|
const firestore = firebase.firestore();
|
||||||
|
|
||||||
const storedScene = await runTransaction(firestore, async (transaction) => {
|
const docRef = firestore.collection("scenes").doc(roomId);
|
||||||
|
|
||||||
|
const storedScene = await firestore.runTransaction(async (transaction) => {
|
||||||
const snapshot = await transaction.get(docRef);
|
const snapshot = await transaction.get(docRef);
|
||||||
|
|
||||||
if (!snapshot.exists()) {
|
if (!snapshot.exists) {
|
||||||
const storedScene = await createFirebaseSceneDocument(elements, roomKey);
|
const storedScene = await createFirebaseSceneDocument(
|
||||||
|
firebase,
|
||||||
|
elements,
|
||||||
|
roomKey,
|
||||||
|
);
|
||||||
|
|
||||||
transaction.set(docRef, storedScene);
|
transaction.set(docRef, storedScene);
|
||||||
|
|
||||||
@ -227,6 +269,7 @@ export const saveToFirebase = async (
|
|||||||
);
|
);
|
||||||
|
|
||||||
const storedScene = await createFirebaseSceneDocument(
|
const storedScene = await createFirebaseSceneDocument(
|
||||||
|
firebase,
|
||||||
reconciledElements,
|
reconciledElements,
|
||||||
roomKey,
|
roomKey,
|
||||||
);
|
);
|
||||||
@ -251,13 +294,15 @@ export const loadFromFirebase = async (
|
|||||||
roomKey: string,
|
roomKey: string,
|
||||||
socket: Socket | null,
|
socket: Socket | null,
|
||||||
): Promise<readonly SyncableExcalidrawElement[] | null> => {
|
): Promise<readonly SyncableExcalidrawElement[] | null> => {
|
||||||
const firestore = _getFirestore();
|
const firebase = await loadFirestore();
|
||||||
const docRef = doc(firestore, "scenes", roomId);
|
const db = firebase.firestore();
|
||||||
const docSnap = await getDoc(docRef);
|
|
||||||
if (!docSnap.exists()) {
|
const docRef = db.collection("scenes").doc(roomId);
|
||||||
|
const doc = await docRef.get();
|
||||||
|
if (!doc.exists) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
const storedScene = docSnap.data() as FirebaseStoredScene;
|
const storedScene = doc.data() as FirebaseStoredScene;
|
||||||
const elements = getSyncableElements(
|
const elements = getSyncableElements(
|
||||||
restoreElements(await decryptElements(storedScene, roomKey), null),
|
restoreElements(await decryptElements(storedScene, roomKey), null),
|
||||||
);
|
);
|
||||||
|
@ -1,46 +1,42 @@
|
|||||||
import {
|
import {
|
||||||
compressData,
|
compressData,
|
||||||
decompressData,
|
decompressData,
|
||||||
} from "@excalidraw/excalidraw/data/encode";
|
} from "../../packages/excalidraw/data/encode";
|
||||||
import {
|
import {
|
||||||
decryptData,
|
decryptData,
|
||||||
generateEncryptionKey,
|
generateEncryptionKey,
|
||||||
IV_LENGTH_BYTES,
|
IV_LENGTH_BYTES,
|
||||||
} from "@excalidraw/excalidraw/data/encryption";
|
} from "../../packages/excalidraw/data/encryption";
|
||||||
import { serializeAsJSON } from "@excalidraw/excalidraw/data/json";
|
import { serializeAsJSON } from "../../packages/excalidraw/data/json";
|
||||||
import { restore } from "@excalidraw/excalidraw/data/restore";
|
import { restore } from "../../packages/excalidraw/data/restore";
|
||||||
import { isInvisiblySmallElement } from "@excalidraw/element";
|
import type { ImportedDataState } from "../../packages/excalidraw/data/types";
|
||||||
import { isInitializedImageElement } from "@excalidraw/element";
|
import type { SceneBounds } from "../../packages/excalidraw/element/bounds";
|
||||||
import { t } from "@excalidraw/excalidraw/i18n";
|
import { isInvisiblySmallElement } from "../../packages/excalidraw/element/sizeHelpers";
|
||||||
import { bytesToHexString } from "@excalidraw/common";
|
import { isInitializedImageElement } from "../../packages/excalidraw/element/typeChecks";
|
||||||
|
|
||||||
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 "@excalidraw/element/types";
|
} from "../../packages/excalidraw/element/types";
|
||||||
|
import { t } from "../../packages/excalidraw/i18n";
|
||||||
import type {
|
import type {
|
||||||
AppState,
|
AppState,
|
||||||
BinaryFileData,
|
BinaryFileData,
|
||||||
BinaryFiles,
|
BinaryFiles,
|
||||||
SocketId,
|
SocketId,
|
||||||
} from "@excalidraw/excalidraw/types";
|
UserIdleState,
|
||||||
import type { MakeBrand } from "@excalidraw/common/utility-types";
|
} from "../../packages/excalidraw/types";
|
||||||
|
import type { MakeBrand } from "../../packages/excalidraw/utility-types";
|
||||||
|
import { bytesToHexString } from "../../packages/excalidraw/utils";
|
||||||
|
import type { WS_SUBTYPES } from "../app_constants";
|
||||||
import {
|
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,12 +1,10 @@
|
|||||||
|
import type { ExcalidrawElement } from "../../packages/excalidraw/element/types";
|
||||||
|
import type { AppState } from "../../packages/excalidraw/types";
|
||||||
import {
|
import {
|
||||||
clearAppStateForLocalStorage,
|
clearAppStateForLocalStorage,
|
||||||
getDefaultAppState,
|
getDefaultAppState,
|
||||||
} from "@excalidraw/excalidraw/appState";
|
} from "../../packages/excalidraw/appState";
|
||||||
import { clearElementsForLocalStorage } from "@excalidraw/element";
|
import { clearElementsForLocalStorage } from "../../packages/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,6 +1,3 @@
|
|||||||
import "@excalidraw/excalidraw/global";
|
|
||||||
import "@excalidraw/excalidraw/css";
|
|
||||||
|
|
||||||
interface Window {
|
interface Window {
|
||||||
__EXCALIDRAW_SHA__: string | undefined;
|
__EXCALIDRAW_SHA__: string | undefined;
|
||||||
}
|
}
|
||||||
|
@ -54,8 +54,6 @@
|
|||||||
content="https://excalidraw.com/og-image-3.png"
|
content="https://excalidraw.com/og-image-3.png"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<link rel="canonical" href="https://excalidraw.com" />
|
|
||||||
|
|
||||||
<!------------------------------------------------------------------------->
|
<!------------------------------------------------------------------------->
|
||||||
<!-- to minimize white flash on load when user has dark mode enabled -->
|
<!-- to minimize white flash on load when user has dark mode enabled -->
|
||||||
<script>
|
<script>
|
||||||
|
@ -1,11 +1,9 @@
|
|||||||
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);
|
||||||
|
@ -27,22 +27,22 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@excalidraw/random-username": "1.0.0",
|
"@excalidraw/random-username": "1.0.0",
|
||||||
"@sentry/browser": "9.0.1",
|
"@sentry/browser": "6.2.5",
|
||||||
"callsites": "4.2.0",
|
"@sentry/integrations": "6.2.5",
|
||||||
"firebase": "11.3.1",
|
"firebase": "8.3.3",
|
||||||
"i18next-browser-languagedetector": "6.1.4",
|
"i18next-browser-languagedetector": "6.1.4",
|
||||||
"idb-keyval": "6.0.3",
|
"idb-keyval": "6.0.3",
|
||||||
"jotai": "2.11.0",
|
"jotai": "1.13.1",
|
||||||
"react": "19.0.0",
|
"react": "18.2.0",
|
||||||
"react-dom": "19.0.0",
|
"react-dom": "18.2.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": "vite build",
|
"build:app:docker": "cross-env VITE_APP_DISABLE_SENTRY=true vite build",
|
||||||
"build:app": "vite build",
|
"build:app": "cross-env VITE_APP_GIT_SHA=$VERCEL_GIT_COMMIT_SHA cross-env VITE_APP_ENABLE_TRACKING=true 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,9 +1,8 @@
|
|||||||
import * as Sentry from "@sentry/browser";
|
import * as Sentry from "@sentry/browser";
|
||||||
import callsites from "callsites";
|
import * as SentryIntegrations from "@sentry/integrations";
|
||||||
|
|
||||||
const SentryEnvHostnameMap: { [key: string]: string } = {
|
const SentryEnvHostnameMap: { [key: string]: string } = {
|
||||||
"excalidraw.com": "production",
|
"excalidraw.com": "production",
|
||||||
"staging.excalidraw.com": "staging",
|
|
||||||
"vercel.app": "staging",
|
"vercel.app": "staging",
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -24,13 +23,9 @@ Sentry.init({
|
|||||||
release: import.meta.env.VITE_APP_GIT_SHA,
|
release: import.meta.env.VITE_APP_GIT_SHA,
|
||||||
ignoreErrors: [
|
ignoreErrors: [
|
||||||
"undefined is not an object (evaluating 'window.__pad.performLoop')", // Only happens on Safari, but spams our servers. Doesn't break anything
|
"undefined is not an object (evaluating 'window.__pad.performLoop')", // Only happens on Safari, but spams our servers. Doesn't break anything
|
||||||
"InvalidStateError: Failed to execute 'transaction' on 'IDBDatabase': The database connection is closing.", // Not much we can do about the IndexedDB closing error
|
|
||||||
/(Failed to fetch|(fetch|loading) dynamically imported module)/i, // This is happening when a service worker tries to load an old asset
|
|
||||||
/QuotaExceededError: (The quota has been exceeded|.*setItem.*Storage)/i, // localStorage quota exceeded
|
|
||||||
"Internal error opening backing store for indexedDB.open", // Private mode and disabled indexedDB
|
|
||||||
],
|
],
|
||||||
integrations: [
|
integrations: [
|
||||||
Sentry.captureConsoleIntegration({
|
new SentryIntegrations.CaptureConsole({
|
||||||
levels: ["error"],
|
levels: ["error"],
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
@ -38,44 +33,6 @@ Sentry.init({
|
|||||||
if (event.request?.url) {
|
if (event.request?.url) {
|
||||||
event.request.url = event.request.url.replace(/#.*$/, "");
|
event.request.url = event.request.url.replace(/#.*$/, "");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!event.exception) {
|
|
||||||
event.exception = {
|
|
||||||
values: [
|
|
||||||
{
|
|
||||||
type: "ConsoleError",
|
|
||||||
value: event.message ?? "Unknown error",
|
|
||||||
stacktrace: {
|
|
||||||
frames: callsites()
|
|
||||||
.slice(1)
|
|
||||||
.filter(
|
|
||||||
(frame) =>
|
|
||||||
frame.getFileName() &&
|
|
||||||
!frame.getFileName()?.includes("@sentry_browser.js"),
|
|
||||||
)
|
|
||||||
.map((frame) => ({
|
|
||||||
filename: frame.getFileName() ?? undefined,
|
|
||||||
function: frame.getFunctionName() ?? undefined,
|
|
||||||
in_app: !(
|
|
||||||
frame.getFileName()?.includes("node_modules") ?? false
|
|
||||||
),
|
|
||||||
lineno: frame.getLineNumber() ?? undefined,
|
|
||||||
colno: frame.getColumnNumber() ?? undefined,
|
|
||||||
})),
|
|
||||||
},
|
|
||||||
mechanism: {
|
|
||||||
type: "instrument",
|
|
||||||
handled: true,
|
|
||||||
data: {
|
|
||||||
function: "console.error",
|
|
||||||
handler: "Sentry.beforeSend",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return event;
|
return event;
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
@ -1,8 +1,10 @@
|
|||||||
import { trackEvent } from "@excalidraw/excalidraw/analytics";
|
import { useEffect, useRef, useState } from "react";
|
||||||
import { copyTextToSystemClipboard } from "@excalidraw/excalidraw/clipboard";
|
import { copyTextToSystemClipboard } from "../../packages/excalidraw/clipboard";
|
||||||
import { Dialog } from "@excalidraw/excalidraw/components/Dialog";
|
import { trackEvent } from "../../packages/excalidraw/analytics";
|
||||||
import { FilledButton } from "@excalidraw/excalidraw/components/FilledButton";
|
import { getFrame } from "../../packages/excalidraw/utils";
|
||||||
import { TextField } from "@excalidraw/excalidraw/components/TextField";
|
import { useI18n } from "../../packages/excalidraw/i18n";
|
||||||
|
import { KEYS } from "../../packages/excalidraw/keys";
|
||||||
|
import { Dialog } from "../../packages/excalidraw/components/Dialog";
|
||||||
import {
|
import {
|
||||||
copyIcon,
|
copyIcon,
|
||||||
LinkIcon,
|
LinkIcon,
|
||||||
@ -11,19 +13,16 @@ import {
|
|||||||
share,
|
share,
|
||||||
shareIOS,
|
shareIOS,
|
||||||
shareWindows,
|
shareWindows,
|
||||||
} from "@excalidraw/excalidraw/components/icons";
|
} from "../../packages/excalidraw/components/icons";
|
||||||
import { useUIAppState } from "@excalidraw/excalidraw/context/ui-appState";
|
import { TextField } from "../../packages/excalidraw/components/TextField";
|
||||||
import { useCopyStatus } from "@excalidraw/excalidraw/hooks/useCopiedIndicator";
|
import { FilledButton } from "../../packages/excalidraw/components/FilledButton";
|
||||||
import { useI18n } from "@excalidraw/excalidraw/i18n";
|
import type { CollabAPI } from "../collab/Collab";
|
||||||
import { KEYS, getFrame } from "@excalidraw/common";
|
|
||||||
import { useEffect, useRef, useState } from "react";
|
|
||||||
|
|
||||||
import { atom, useAtom, useAtomValue } from "../app-jotai";
|
|
||||||
import { activeRoomLinkAtom } from "../collab/Collab";
|
import { activeRoomLinkAtom } from "../collab/Collab";
|
||||||
|
import { atom, useAtom, useAtomValue } from "jotai";
|
||||||
|
|
||||||
import "./ShareDialog.scss";
|
import "./ShareDialog.scss";
|
||||||
|
import { useUIAppState } from "../../packages/excalidraw/context/ui-appState";
|
||||||
import type { CollabAPI } from "../collab/Collab";
|
import { useCopyStatus } from "../../packages/excalidraw/hooks/useCopiedIndicator";
|
||||||
|
|
||||||
type OnExportToBackend = () => void;
|
type OnExportToBackend = () => void;
|
||||||
type ShareDialogType = "share" | "collaborationOnly";
|
type ShareDialogType = "share" | "collaborationOnly";
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
import { defaultLang } from "@excalidraw/excalidraw/i18n";
|
import { defaultLang } from "../../packages/excalidraw/i18n";
|
||||||
import { UI } from "@excalidraw/excalidraw/tests/helpers/ui";
|
import { UI } from "../../packages/excalidraw/tests/helpers/ui";
|
||||||
import {
|
import {
|
||||||
screen,
|
screen,
|
||||||
fireEvent,
|
fireEvent,
|
||||||
waitFor,
|
waitFor,
|
||||||
render,
|
render,
|
||||||
} from "@excalidraw/excalidraw/tests/test-utils";
|
} from "../../packages/excalidraw/tests/test-utils";
|
||||||
|
|
||||||
import ExcalidrawApp from "../App";
|
import ExcalidrawApp from "../App";
|
||||||
|
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
import { UI } from "@excalidraw/excalidraw/tests/helpers/ui";
|
import ExcalidrawApp from "../App";
|
||||||
import {
|
import {
|
||||||
mockBoundingClientRect,
|
mockBoundingClientRect,
|
||||||
render,
|
render,
|
||||||
restoreOriginalGetBoundingClientRect,
|
restoreOriginalGetBoundingClientRect,
|
||||||
} from "@excalidraw/excalidraw/tests/test-utils";
|
} from "../../packages/excalidraw/tests/test-utils";
|
||||||
|
|
||||||
import ExcalidrawApp from "../App";
|
import { UI } from "../../packages/excalidraw/tests/helpers/ui";
|
||||||
|
|
||||||
describe("Test MobileMenu", () => {
|
describe("Test MobileMenu", () => {
|
||||||
const { h } = window;
|
const { h } = window;
|
||||||
|