Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 332bc4d732 |
@@ -1,5 +1,3 @@
|
||||
MODE="development"
|
||||
|
||||
VITE_APP_BACKEND_V2_GET_URL=https://json-dev.excalidraw.com/api/v2/
|
||||
VITE_APP_BACKEND_V2_POST_URL=https://json-dev.excalidraw.com/api/v2/post/
|
||||
|
||||
@@ -50,6 +48,3 @@ UNWEjuqNMi/lwAErS9fFa2oJlWyT8U7zzv/5kQREkxZI6y9v0AF3qcbsy2731FnD
|
||||
s9ChJvOUW9toIab2gsIdrKW8ZNpu084ZFVKb6LNjvIXI1Se4oMTHeszXzNptzlot
|
||||
kdxxjOoaQMAyfljFSot1F1FlU6MQlag7UnFGvFjRHN1JI5q4K+n3a67DX+TMyRqS
|
||||
HQIDAQAB'
|
||||
|
||||
# set to true in .env.development.local to disable the prevent unload dialog
|
||||
VITE_APP_DISABLE_PREVENT_UNLOAD=
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
MODE="production"
|
||||
|
||||
VITE_APP_BACKEND_V2_GET_URL=https://json.excalidraw.com/api/v2/
|
||||
VITE_APP_BACKEND_V2_POST_URL=https://json.excalidraw.com/api/v2/post/
|
||||
|
||||
|
||||
@@ -1,21 +1,6 @@
|
||||
{
|
||||
"extends": ["@excalidraw/eslint-config", "react-app"],
|
||||
"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",
|
||||
"no-restricted-globals": "off",
|
||||
"@typescript-eslint/consistent-type-imports": [
|
||||
@@ -32,12 +17,6 @@
|
||||
"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,45 +0,0 @@
|
||||
# Project coding standards
|
||||
|
||||
## Generic Communication Guidelines
|
||||
|
||||
- Be succint and be aware that expansive generative AI answers are costly and slow
|
||||
- Avoid providing explanations, trying to teach unless asked for, your chat partner is an expert
|
||||
- Stop apologising if corrected, just provide the correct information or code
|
||||
- Prefer code unless asked for explanation
|
||||
- Stop summarizing what you've changed after modifications unless asked for
|
||||
|
||||
## TypeScript Guidelines
|
||||
|
||||
- Use TypeScript for all new code
|
||||
- Where possible, prefer implementations without allocation
|
||||
- When there is an option, opt for more performant solutions and trade RAM usage for less CPU cycles
|
||||
- Prefer immutable data (const, readonly)
|
||||
- Use optional chaining (?.) and nullish coalescing (??) operators
|
||||
|
||||
## React Guidelines
|
||||
|
||||
- Use functional components with hooks
|
||||
- Follow the React hooks rules (no conditional hooks)
|
||||
- Keep components small and focused
|
||||
- Use CSS modules for component styling
|
||||
|
||||
## Naming Conventions
|
||||
|
||||
- Use PascalCase for component names, interfaces, and type aliases
|
||||
- Use camelCase for variables, functions, and methods
|
||||
- Use ALL_CAPS for constants
|
||||
|
||||
## Error Handling
|
||||
|
||||
- Use try/catch blocks for async operations
|
||||
- Implement proper error boundaries in React components
|
||||
- Always log errors with contextual information
|
||||
|
||||
## Testing
|
||||
|
||||
- Always attempt to fix #problems
|
||||
- Always offer to run `yarn test:app` in the project root after modifications are complete and attempt fixing the issues reported
|
||||
|
||||
## Types
|
||||
|
||||
- Always include `packages/math/src/types.ts` in the context when your write math related code and always use the Point type instead of { x, y}
|
||||
@@ -12,10 +12,10 @@ jobs:
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
fetch-depth: 2
|
||||
- name: Setup Node.js
|
||||
- name: Setup Node.js 18.x
|
||||
uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: 20.x
|
||||
node-version: 18.x
|
||||
- name: Set up publish access
|
||||
run: |
|
||||
npm config set //registry.npmjs.org/:_authToken ${NPM_TOKEN}
|
||||
@@ -24,4 +24,4 @@ jobs:
|
||||
- name: Auto release
|
||||
run: |
|
||||
yarn add @actions/core -W
|
||||
yarn release --tag=next --non-interactive
|
||||
yarn autorelease
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
name: Auto release excalidraw preview
|
||||
on:
|
||||
issue_comment:
|
||||
types: [created, edited]
|
||||
|
||||
jobs:
|
||||
Auto-release-excalidraw-preview:
|
||||
name: Auto release preview
|
||||
if: github.event.comment.body == '@excalibot trigger release' && github.event.issue.pull_request
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: React to release comment
|
||||
uses: peter-evans/create-or-update-comment@v1
|
||||
with:
|
||||
token: ${{ secrets.PUSH_TRANSLATIONS_COVERAGE_PAT }}
|
||||
comment-id: ${{ github.event.comment.id }}
|
||||
reactions: "+1"
|
||||
- name: Get PR SHA
|
||||
id: sha
|
||||
uses: actions/github-script@v4
|
||||
with:
|
||||
result-encoding: string
|
||||
script: |
|
||||
const { owner, repo, number } = context.issue;
|
||||
const pr = await github.pulls.get({
|
||||
owner,
|
||||
repo,
|
||||
pull_number: number,
|
||||
});
|
||||
return pr.data.head.sha
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
ref: ${{ steps.sha.outputs.result }}
|
||||
fetch-depth: 2
|
||||
- name: Setup Node.js 18.x
|
||||
uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: 18.x
|
||||
- name: Set up publish access
|
||||
run: |
|
||||
npm config set //registry.npmjs.org/:_authToken ${NPM_TOKEN}
|
||||
env:
|
||||
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||
- name: Auto release preview
|
||||
id: "autorelease"
|
||||
run: |
|
||||
yarn add @actions/core -W
|
||||
yarn autorelease preview ${{ github.event.issue.number }}
|
||||
- name: Post comment post release
|
||||
if: always()
|
||||
uses: peter-evans/create-or-update-comment@v1
|
||||
with:
|
||||
token: ${{ secrets.PUSH_TRANSLATIONS_COVERAGE_PAT }}
|
||||
issue-number: ${{ github.event.issue.number }}
|
||||
body: "@${{ github.event.comment.user.login }} ${{ steps.autorelease.outputs.result }}"
|
||||
@@ -9,10 +9,10 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- name: Setup Node.js
|
||||
- name: Setup Node.js 18.x
|
||||
uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: 20.x
|
||||
node-version: 18.x
|
||||
|
||||
- name: Install and lint
|
||||
run: |
|
||||
|
||||
@@ -14,10 +14,10 @@ jobs:
|
||||
with:
|
||||
token: ${{ secrets.PUSH_TRANSLATIONS_COVERAGE_PAT }}
|
||||
|
||||
- name: Setup Node.js
|
||||
- name: Setup Node.js 18.x
|
||||
uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: 20.x
|
||||
node-version: 18.x
|
||||
|
||||
- name: Create report file
|
||||
run: |
|
||||
|
||||
@@ -17,14 +17,9 @@ jobs:
|
||||
with:
|
||||
username: ${{ secrets.DOCKER_USERNAME }}
|
||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v3
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
- name: Build and push
|
||||
uses: docker/build-push-action@v5
|
||||
uses: docker/build-push-action@v3
|
||||
with:
|
||||
context: .
|
||||
push: true
|
||||
tags: excalidraw/excalidraw:latest
|
||||
platforms: linux/amd64, linux/arm64, linux/arm/v7
|
||||
|
||||
@@ -10,10 +10,10 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Setup Node.js
|
||||
- name: Setup Node.js 18.x
|
||||
uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: 20.x
|
||||
node-version: 18.x
|
||||
- name: Install and build
|
||||
run: |
|
||||
yarn --frozen-lockfile
|
||||
|
||||
@@ -11,10 +11,10 @@ jobs:
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v3
|
||||
- name: Setup Node.js
|
||||
- name: Setup Node.js 18.x
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 20.x
|
||||
node-version: 18.x
|
||||
- name: Install in packages/excalidraw
|
||||
run: yarn
|
||||
working-directory: packages/excalidraw
|
||||
|
||||
+1
-2
@@ -25,5 +25,4 @@ packages/excalidraw/types
|
||||
coverage
|
||||
dev-dist
|
||||
html
|
||||
meta*.json
|
||||
.claude
|
||||
meta*.json
|
||||
@@ -1,34 +0,0 @@
|
||||
# CLAUDE.md
|
||||
|
||||
## Project Structure
|
||||
|
||||
Excalidraw is a **monorepo** with a clear separation between the core library and the application:
|
||||
|
||||
- **`packages/excalidraw/`** - Main React component library published to npm as `@excalidraw/excalidraw`
|
||||
- **`excalidraw-app/`** - Full-featured web application (excalidraw.com) that uses the library
|
||||
- **`packages/`** - Core packages: `@excalidraw/common`, `@excalidraw/element`, `@excalidraw/math`, `@excalidraw/utils`
|
||||
- **`examples/`** - Integration examples (NextJS, browser script)
|
||||
|
||||
## Development Workflow
|
||||
|
||||
1. **Package Development**: Work in `packages/*` for editor features
|
||||
2. **App Development**: Work in `excalidraw-app/` for app-specific features
|
||||
3. **Testing**: Always run `yarn test:update` before committing
|
||||
4. **Type Safety**: Use `yarn test:typecheck` to verify TypeScript
|
||||
|
||||
## Development Commands
|
||||
|
||||
```bash
|
||||
yarn test:typecheck # TypeScript type checking
|
||||
yarn test:update # Run all tests (with snapshot updates)
|
||||
yarn fix # Auto-fix formatting and linting issues
|
||||
```
|
||||
|
||||
## Architecture Notes
|
||||
|
||||
### Package System
|
||||
|
||||
- Uses Yarn workspaces for monorepo management
|
||||
- Internal packages use path aliases (see `vitest.config.mts`)
|
||||
- Build system uses esbuild for packages, Vite for the app
|
||||
- TypeScript throughout with strict configuration
|
||||
+4
-5
@@ -1,4 +1,4 @@
|
||||
FROM --platform=${BUILDPLATFORM} node:18 AS build
|
||||
FROM node:18 AS build
|
||||
|
||||
WORKDIR /opt/node_app
|
||||
|
||||
@@ -6,14 +6,13 @@ COPY . .
|
||||
|
||||
# do not ignore optional dependencies:
|
||||
# Error: Cannot find module @rollup/rollup-linux-x64-gnu
|
||||
RUN --mount=type=cache,target=/root/.cache/yarn \
|
||||
npm_config_target_arch=${TARGETARCH} yarn --network-timeout 600000
|
||||
RUN yarn --network-timeout 600000
|
||||
|
||||
ARG NODE_ENV=production
|
||||
|
||||
RUN npm_config_target_arch=${TARGETARCH} yarn build:app:docker
|
||||
RUN yarn build:app:docker
|
||||
|
||||
FROM --platform=${TARGETPLATFORM} nginx:1.27-alpine
|
||||
FROM nginx:1.27-alpine
|
||||
|
||||
COPY --from=build /opt/node_app/excalidraw-app/build /usr/share/nginx/html
|
||||
|
||||
|
||||
@@ -23,17 +23,20 @@
|
||||
<br />
|
||||
<p align="center">
|
||||
<a href="https://github.com/excalidraw/excalidraw/blob/master/LICENSE">
|
||||
<img alt="Excalidraw is released under the MIT license." src="https://img.shields.io/badge/license-MIT-blue.svg" /></a>
|
||||
<img alt="Excalidraw is released under the MIT license." src="https://img.shields.io/badge/license-MIT-blue.svg" />
|
||||
</a>
|
||||
<a href="https://www.npmjs.com/package/@excalidraw/excalidraw">
|
||||
<img alt="npm downloads/month" src="https://img.shields.io/npm/dm/@excalidraw/excalidraw" /></a>
|
||||
<img alt="npm downloads/month" src="https://img.shields.io/npm/dm/@excalidraw/excalidraw" />
|
||||
</a>
|
||||
<a href="https://docs.excalidraw.com/docs/introduction/contributing">
|
||||
<img alt="PRs welcome!" src="https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat" /></a>
|
||||
<img alt="PRs welcome!" src="https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat" />
|
||||
</a>
|
||||
<a href="https://discord.gg/UexuTaE">
|
||||
<img alt="Chat on Discord" src="https://img.shields.io/discord/723672430744174682?color=738ad6&label=Chat%20on%20Discord&logo=discord&logoColor=ffffff&widge=false"/></a>
|
||||
<a href="https://deepwiki.com/excalidraw/excalidraw">
|
||||
<img alt="Ask DeepWiki" src="https://deepwiki.com/badge.svg" /></a>
|
||||
<img alt="Chat on Discord" src="https://img.shields.io/discord/723672430744174682?color=738ad6&label=Chat%20on%20Discord&logo=discord&logoColor=ffffff&widge=false"/>
|
||||
</a>
|
||||
<a href="https://twitter.com/excalidraw">
|
||||
<img alt="Follow Excalidraw on Twitter" src="https://img.shields.io/twitter/follow/excalidraw.svg?label=follow+@excalidraw&style=social&logo=twitter"/></a>
|
||||
<img alt="Follow Excalidraw on Twitter" src="https://img.shields.io/twitter/follow/excalidraw.svg?label=follow+@excalidraw&style=social&logo=twitter"/>
|
||||
</a>
|
||||
</p>
|
||||
|
||||
<div align="center">
|
||||
@@ -60,7 +63,7 @@ The Excalidraw editor (npm package) supports:
|
||||
- 🏗️ Customizable.
|
||||
- 📷 Image support.
|
||||
- 😀 Shape libraries support.
|
||||
- 🌐 Localization (i18n) support.
|
||||
- 👅 Localization (i18n) support.
|
||||
- 🖼️ Export to PNG, SVG & clipboard.
|
||||
- 💾 Open format - export drawings as an `.excalidraw` json file.
|
||||
- ⚒️ Wide range of tools - rectangle, circle, diamond, arrow, line, free-draw, eraser...
|
||||
|
||||
@@ -2,14 +2,14 @@
|
||||
|
||||
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**
|
||||
|
||||
```jsx live
|
||||
function App() {
|
||||
return (
|
||||
<div style={{ height: "500px" }}>
|
||||
<div style={{ height: "500px"}}>
|
||||
<Excalidraw>
|
||||
<Footer>
|
||||
<button
|
||||
@@ -25,21 +25,21 @@ 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 [`useEditorInterface`](#useEditorInterface) 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.
|
||||
|
||||
Open the `Menu` in the below playground and you will see the `custom footer` rendered.
|
||||
|
||||
```jsx live noInline
|
||||
const MobileFooter = ({}) => {
|
||||
const editorInterface = useEditorInterface();
|
||||
if (editorInterface.formFactor === "phone") {
|
||||
const device = useDevice();
|
||||
if (device.editor.isMobile) {
|
||||
return (
|
||||
<Footer>
|
||||
<button
|
||||
className="custom-footer"
|
||||
style={{ marginLeft: "20px", height: "2rem" }}
|
||||
style= {{ marginLeft: '20px', height: '2rem'}}
|
||||
onClick={() => alert("This is custom footer in mobile menu")}
|
||||
>
|
||||
custom footer
|
||||
@@ -65,4 +65,4 @@ const App = () => (
|
||||
// Need to render when code is span across multiple components
|
||||
// in Live Code blocks editor
|
||||
render(<App />);
|
||||
```
|
||||
```
|
||||
@@ -363,7 +363,13 @@ This API has the below signature. It sets the `tool` passed in param as the acti
|
||||
```ts
|
||||
(
|
||||
tool: (
|
||||
| { type: ToolType }
|
||||
| (
|
||||
| { type: Exclude<ToolType, "image"> }
|
||||
| {
|
||||
type: Extract<ToolType, "image">;
|
||||
insertOnCanvasDirectly?: boolean;
|
||||
}
|
||||
)
|
||||
| { type: "custom"; customType: string }
|
||||
) & { locked?: boolean },
|
||||
) => {};
|
||||
@@ -371,7 +377,7 @@ This API has the below signature. It sets the `tool` passed in param as the acti
|
||||
|
||||
| Name | Type | Default | Description |
|
||||
| --- | --- | --- | --- |
|
||||
| `type` | [ToolType](https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/types.ts#L91) | `selection` | The tool type which should be set as active tool |
|
||||
| `type` | [ToolType](https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/types.ts#L91) | `selection` | The tool type which should be set as active tool. When setting `image` as active tool, the insertion onto canvas when using image tool is disabled by default, so you can enable it by setting `insertOnCanvasDirectly` to `true` |
|
||||
| `locked` | `boolean` | `false` | Indicates whether the the active tool should be locked. It behaves the same way when using the `lock` tool in the editor interface |
|
||||
|
||||
## setCursor
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
All `props` are _optional_.
|
||||
|
||||
| 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. |
|
||||
| [`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 |
|
||||
@@ -13,7 +13,7 @@ All `props` are _optional_.
|
||||
| [`onScrollChange`](#onscrollchange) | `function` | \_ | This prop if passed gets triggered when scrolling the canvas. |
|
||||
| [`onPaste`](#onpaste) | `function` | \_ | Callback to be triggered if passed when something is pasted into the scene |
|
||||
| [`onLibraryChange`](#onlibrarychange) | `function` | \_ | The callback if supplied is triggered when the library is updated and receives the library items. |
|
||||
| [`generateLinkForSelection`](#generatelinkforselection) | `function` | \_ | Allows you to override `url` generation when linking to Excalidraw elements. |
|
||||
| [`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. |
|
||||
| [`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 |
|
||||
@@ -29,9 +29,8 @@ All `props` are _optional_.
|
||||
| [`handleKeyboardGlobally`](#handlekeyboardglobally) | `boolean` | `false` | Indicates whether to bind the keyboard events to document. |
|
||||
| [`autoFocus`](#autofocus) | `boolean` | `false` | Indicates whether to focus the Excalidraw component on page load |
|
||||
| [`generateIdForFile`](#generateidforfile) | `function` | \_ | Allows you to override `id` generation for files added on canvas |
|
||||
| [`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>` |
|
||||
| [`renderScrollbars`] | `boolean`| | `false` | Indicates whether scrollbars will be shown
|
||||
|
||||
### Storing custom data on Excalidraw elements
|
||||
|
||||
|
||||
@@ -292,7 +292,7 @@ viewportCoordsToSceneCoords({ clientX: number, clientY: number },<br/> 
|
||||
appState: <a href="https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/types.ts#L95">AppState</a><br/>): {x: number, y: number}
|
||||
</pre>
|
||||
|
||||
### useEditorInterface
|
||||
### useDevice
|
||||
|
||||
This hook can be used to check the type of device which is being used. It can only be used inside the `children` of `Excalidraw` component.
|
||||
|
||||
@@ -300,8 +300,8 @@ Open the `main menu` in the below example to view the footer.
|
||||
|
||||
```jsx live noInline
|
||||
const MobileFooter = ({}) => {
|
||||
const editorInterface = useEditorInterface();
|
||||
if (editorInterface.formFactor === "phone") {
|
||||
const device = useDevice();
|
||||
if (device.editor.isMobile) {
|
||||
return (
|
||||
<Footer>
|
||||
<button
|
||||
@@ -336,20 +336,12 @@ render(<App />);
|
||||
The `device` has the following `attributes`, some grouped into `viewport` and `editor` objects, per context.
|
||||
|
||||
| Name | Type | Description |
|
||||
| ---- | ---- | ----------- |
|
||||
|
||||
The `EditorInterface` object has the following properties:
|
||||
|
||||
| Name | Type | Description |
|
||||
| --- | --- | --- | --- | --- | --- |
|
||||
| `formFactor` | `'phone' | 'tablet' | 'desktop'` | Indicates the device type based on screen size |
|
||||
| `desktopUIMode` | `'compact' | 'full'` | UI mode for desktop form factor |
|
||||
| `userAgent.raw` | `string` | Raw user agent string |
|
||||
| `userAgent.isMobileDevice` | `boolean` | True if device is mobile |
|
||||
| `userAgent.platform` | `'ios' | 'android' | 'other' | 'unknown'` | Device platform |
|
||||
| `isTouchScreen` | `boolean` | True if touch events are detected |
|
||||
| `canFitSidebar` | `boolean` | True if sidebar can fit in the viewport |
|
||||
| `isLandscape` | `boolean` | True if viewport is in landscape mode |
|
||||
| --- | --- | --- |
|
||||
| `viewport.isMobile` | `boolean` | Set to `true` when viewport is in `mobile` breakpoint |
|
||||
| `viewport.isLandscape` | `boolean` | Set to `true` when the viewport is in `landscape` mode |
|
||||
| `editor.canFitSidebar` | `boolean` | Set to `true` if there's enough space to fit the `sidebar` |
|
||||
| `editor.isMobile` | `boolean` | Set to `true` when editor container is in `mobile` breakpoint |
|
||||
| `isTouchScreen` | `boolean` | Set to `true` for `touch` when touch event detected |
|
||||
|
||||
### i18n
|
||||
|
||||
|
||||
@@ -24,16 +24,36 @@ To start the example app using the `@excalidraw/excalidraw` package, follow the
|
||||
|
||||
[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.
|
||||
This is the same example as the [CodeSandbox](https://codesandbox.io/p/sandbox/github/excalidraw/excalidraw/tree/mrazator/release-v18/examples/with-script-in-browser) example.
|
||||
|
||||
## Releasing
|
||||
|
||||
### Create a test release
|
||||
|
||||
You can create a test release by posting the below comment in your pull request:
|
||||
|
||||
```bash
|
||||
@excalibot trigger release
|
||||
```
|
||||
|
||||
Once the version is released `@excalibot` will post a comment with the release version.
|
||||
|
||||
### Creating a production release
|
||||
|
||||
To release the next stable version follow the below steps:
|
||||
|
||||
```bash
|
||||
yarn release --tag=latest --version=0.19.0
|
||||
yarn prerelease:excalidraw
|
||||
```
|
||||
|
||||
You will need to pass the `latest` tag with `version` for which you want to create the release. This will make the changes needed before publishing the packages into NPM, like updating dependencies of all `@excalidraw/*` packages, generating new entries in `CHANGELOG.md` and more.
|
||||
You need to pass the `version` for which you want to create the release. This will make the changes needed before making the release like updating `package.json`, `changelog` and more.
|
||||
|
||||
The next step is to run the `release` script:
|
||||
|
||||
```bash
|
||||
yarn release:excalidraw
|
||||
```
|
||||
|
||||
This will publish the package.
|
||||
|
||||
Right now there are two steps to create a production release but once this works fine these scripts will be combined and more automation will be done.
|
||||
|
||||
@@ -52,4 +52,4 @@ Excalidraw takes _100%_ of `width` and `height` of the containing block so make
|
||||
|
||||
## Demo
|
||||
|
||||
Go to [CodeSandbox](https://codesandbox.io/p/sandbox/github/excalidraw/excalidraw/tree/master/examples/with-script-in-browser) example.
|
||||
Go to [CodeSandbox](https://codesandbox.io/p/sandbox/github/excalidraw/excalidraw/tree/mrazator/release-v18/examples/with-script-in-browser) example.
|
||||
|
||||
@@ -38,8 +38,6 @@ If you want to only import `Excalidraw` component you can do :point_down:
|
||||
|
||||
```jsx showLineNumbers
|
||||
import dynamic from "next/dynamic";
|
||||
import "@excalidraw/excalidraw/index.css";
|
||||
|
||||
const Excalidraw = dynamic(
|
||||
async () => (await import("@excalidraw/excalidraw")).Excalidraw,
|
||||
{
|
||||
@@ -133,7 +131,7 @@ If you are using `pages router` then importing the wrapper dynamically would wor
|
||||
{/* 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.
|
||||
The `types` are available at `@excalidraw/excalidraw/types`, check [CodeSandbox](https://codesandbox.io/p/sandbox/github/excalidraw/excalidraw/tree/mrazator/release-v18/examples/with-script-in-browser) example for details.
|
||||
|
||||
### Preact
|
||||
|
||||
@@ -208,7 +206,7 @@ import TabItem from "@theme/TabItem";
|
||||
|
||||
```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 * as ExcalidrawLib from 'https://esm.sh/@excalidraw/excalidraw@0.18.0-rc.1/dist/dev/index.js?external=react,react-dom';
|
||||
import React from "https://esm.sh/react@19.0.0";
|
||||
import ReactDOM from "https://esm.sh/react-dom@19.0.0"
|
||||
|
||||
@@ -237,4 +235,4 @@ root.render(React.createElement(App));
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
You can try it out [here](https://jsfiddle.net/vfn6dm14/3/).
|
||||
You can try it out [here](https://jsfiddle.net/64y130L8/1/).
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
"@docusaurus/core": "2.2.0",
|
||||
"@docusaurus/preset-classic": "2.2.0",
|
||||
"@docusaurus/theme-live-codeblock": "2.2.0",
|
||||
"@excalidraw/excalidraw": "0.18.0",
|
||||
"@excalidraw/excalidraw": "0.18.0-rc.5",
|
||||
"@mdx-js/react": "^1.6.22",
|
||||
"clsx": "^1.2.1",
|
||||
"docusaurus-plugin-sass": "0.2.3",
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import clsx from "clsx";
|
||||
import React from "react";
|
||||
|
||||
import clsx from "clsx";
|
||||
import styles from "./styles.module.css";
|
||||
|
||||
const FeatureList = [
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import clsx from "clsx";
|
||||
import React from "react";
|
||||
|
||||
import clsx from "clsx";
|
||||
import styles from "./styles.module.css";
|
||||
|
||||
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 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 HomepageFeatures from "@site/src/components/Homepage";
|
||||
|
||||
function HomepageHeader() {
|
||||
const { siteConfig } = useDocusaurusContext();
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// Import the original mapper
|
||||
import Highlight from "@site/src/components/Highlight";
|
||||
import MDXComponents from "@theme-original/MDXComponents";
|
||||
import Highlight from "@site/src/components/Highlight";
|
||||
|
||||
export default {
|
||||
// Re-use the default mapping
|
||||
|
||||
@@ -12,7 +12,7 @@ if (ExecutionEnvironment.canUseDOM) {
|
||||
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/";
|
||||
"https://esm.sh/@excalidraw/excalidraw@0.18.0-rc.5/dist/prod/";
|
||||
}
|
||||
|
||||
const { colorMode } = useColorMode();
|
||||
@@ -33,7 +33,6 @@ const ExcalidrawScope = {
|
||||
initialData,
|
||||
useI18n: ExcalidrawComp.useI18n,
|
||||
convertToExcalidrawElements: ExcalidrawComp.convertToExcalidrawElements,
|
||||
CaptureUpdateAction: ExcalidrawComp.CaptureUpdateAction,
|
||||
};
|
||||
|
||||
export default ExcalidrawScope;
|
||||
|
||||
+130
-186
@@ -1735,16 +1735,16 @@
|
||||
url-loader "^4.1.1"
|
||||
webpack "^5.73.0"
|
||||
|
||||
"@excalidraw/excalidraw@0.18.0":
|
||||
version "0.18.0"
|
||||
resolved "https://registry.yarnpkg.com/@excalidraw/excalidraw/-/excalidraw-0.18.0.tgz#9f818e2df80a8735af54f8cc21da67997785532f"
|
||||
integrity sha512-QkIiS+5qdy8lmDWTKsuy0sK/fen/LRDtbhm2lc2xcFcqhv2/zdg95bYnl+wnwwXGHo7kEmP65BSiMHE7PJ3Zpw==
|
||||
"@excalidraw/excalidraw@0.18.0-rc.5":
|
||||
version "0.18.0-rc.5"
|
||||
resolved "https://registry.yarnpkg.com/@excalidraw/excalidraw/-/excalidraw-0.18.0-rc.5.tgz#c55598e01808693702251322e59bf9dba931b6e0"
|
||||
integrity sha512-f6Z6cWlx+FWl1nxCu5F6OdKm9ooV/FPjndjIfFhGLVyERKATXhuNwZUHWwqsEW+SIGmiPG2515NG9KIOhjGd5g==
|
||||
dependencies:
|
||||
"@braintree/sanitize-url" "6.0.2"
|
||||
"@excalidraw/laser-pointer" "1.3.1"
|
||||
"@excalidraw/mermaid-to-excalidraw" "1.1.2"
|
||||
"@excalidraw/random-username" "1.1.0"
|
||||
"@radix-ui/react-popover" "1.1.6"
|
||||
"@radix-ui/react-popover" "1.0.3"
|
||||
"@radix-ui/react-tabs" "1.0.2"
|
||||
browser-fs-access "0.29.1"
|
||||
canvas-roundrect-polyfill "0.0.1"
|
||||
@@ -1796,32 +1796,25 @@
|
||||
resolved "https://registry.yarnpkg.com/@excalidraw/random-username/-/random-username-1.1.0.tgz#6f388d6a9708cf655b8c9c6aa3fa569ee71ecf0f"
|
||||
integrity sha512-nULYsQxkWHnbmHvcs+efMkJ4/9TtvNyFeLyHdeGxW0zHs6P+jYVqcRff9A6Vq9w9JXeDRnRh2VKvTtS19GW2qA==
|
||||
|
||||
"@floating-ui/core@^1.6.0":
|
||||
version "1.6.9"
|
||||
resolved "https://registry.yarnpkg.com/@floating-ui/core/-/core-1.6.9.tgz#64d1da251433019dafa091de9b2886ff35ec14e6"
|
||||
integrity sha512-uMXCuQ3BItDUbAMhIXw7UPXRfAlOAvZzdK9BWpE60MCn+Svt3aLn9jsPTi/WNGlRUu2uI0v5S7JiIUsbsvh3fw==
|
||||
dependencies:
|
||||
"@floating-ui/utils" "^0.2.9"
|
||||
"@floating-ui/core@^0.7.3":
|
||||
version "0.7.3"
|
||||
resolved "https://registry.yarnpkg.com/@floating-ui/core/-/core-0.7.3.tgz#d274116678ffae87f6b60e90f88cc4083eefab86"
|
||||
integrity sha512-buc8BXHmG9l82+OQXOFU3Kr2XQx9ys01U/Q9HMIrZ300iLc8HLMgh7dcCqgYzAzf4BkoQvDcXf5Y+CuEZ5JBYg==
|
||||
|
||||
"@floating-ui/dom@^1.0.0":
|
||||
version "1.6.13"
|
||||
resolved "https://registry.yarnpkg.com/@floating-ui/dom/-/dom-1.6.13.tgz#a8a938532aea27a95121ec16e667a7cbe8c59e34"
|
||||
integrity sha512-umqzocjDgNRGTuO7Q8CU32dkHkECqI8ZdMZ5Swb6QAM0t5rnlrN3lGo1hdpscRd3WS8T6DKYK4ephgIH9iRh3w==
|
||||
"@floating-ui/dom@^0.5.3":
|
||||
version "0.5.4"
|
||||
resolved "https://registry.yarnpkg.com/@floating-ui/dom/-/dom-0.5.4.tgz#4eae73f78bcd4bd553ae2ade30e6f1f9c73fe3f1"
|
||||
integrity sha512-419BMceRLq0RrmTSDxn8hf9R3VCJv2K9PUfugh5JyEFmdjzDo+e8U5EdR8nzKq8Yj1htzLm3b6eQEEam3/rrtg==
|
||||
dependencies:
|
||||
"@floating-ui/core" "^1.6.0"
|
||||
"@floating-ui/utils" "^0.2.9"
|
||||
"@floating-ui/core" "^0.7.3"
|
||||
|
||||
"@floating-ui/react-dom@^2.0.0":
|
||||
version "2.1.2"
|
||||
resolved "https://registry.yarnpkg.com/@floating-ui/react-dom/-/react-dom-2.1.2.tgz#a1349bbf6a0e5cb5ded55d023766f20a4d439a31"
|
||||
integrity sha512-06okr5cgPzMNBy+Ycse2A6udMi4bqwW/zgBF/rwjcNqWkyr82Mcg8b0vjX8OJpZFy/FKjJmw6wV7t44kK6kW7A==
|
||||
"@floating-ui/react-dom@0.7.2":
|
||||
version "0.7.2"
|
||||
resolved "https://registry.yarnpkg.com/@floating-ui/react-dom/-/react-dom-0.7.2.tgz#0bf4ceccb777a140fc535c87eb5d6241c8e89864"
|
||||
integrity sha512-1T0sJcpHgX/u4I1OzIEhlcrvkUN8ln39nz7fMoE/2HDHrPiMFoOGR7++GYyfUmIQHkkrTinaeQsO3XWubjSvGg==
|
||||
dependencies:
|
||||
"@floating-ui/dom" "^1.0.0"
|
||||
|
||||
"@floating-ui/utils@^0.2.9":
|
||||
version "0.2.9"
|
||||
resolved "https://registry.yarnpkg.com/@floating-ui/utils/-/utils-0.2.9.tgz#50dea3616bc8191fb8e112283b49eaff03e78429"
|
||||
integrity sha512-MDWhGtE+eHw5JW7lq4qhc5yRLS11ERl1c7Z6Xd0a58DozHES6EnNNwUWbMiG4J9Cgj053Bhk8zvlhFYKVhULwg==
|
||||
"@floating-ui/dom" "^0.5.3"
|
||||
use-isomorphic-layout-effect "^1.1.1"
|
||||
|
||||
"@hapi/hoek@^9.0.0":
|
||||
version "9.3.0"
|
||||
@@ -1989,17 +1982,13 @@
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.13.10"
|
||||
|
||||
"@radix-ui/primitive@1.1.1":
|
||||
version "1.1.1"
|
||||
resolved "https://registry.yarnpkg.com/@radix-ui/primitive/-/primitive-1.1.1.tgz#fc169732d755c7fbad33ba8d0cd7fd10c90dc8e3"
|
||||
integrity sha512-SJ31y+Q/zAyShtXJc8x83i9TYdbAfHZ++tUZnvjJJqFjzsdUnKsxPL6IEtBlxKkU7yzer//GQtZSV4GbldL3YA==
|
||||
|
||||
"@radix-ui/react-arrow@1.1.2":
|
||||
version "1.1.2"
|
||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-arrow/-/react-arrow-1.1.2.tgz#30c0d574d7bb10eed55cd7007b92d38b03c6b2ab"
|
||||
integrity sha512-G+KcpzXHq24iH0uGG/pF8LyzpFJYGD4RfLjCIBfGdSLXvjLHST31RUiRVrupIBMvIppMgSzQ6l66iAxl03tdlg==
|
||||
"@radix-ui/react-arrow@1.0.1":
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-arrow/-/react-arrow-1.0.1.tgz#5246adf79e97f89e819af68da51ddcf349ecf1c4"
|
||||
integrity sha512-1yientwXqXcErDHEv8av9ZVNEBldH8L9scVR3is20lL+jOCfcJyMFZFEY5cgIrgexsq1qggSXqiEL/d/4f+QXA==
|
||||
dependencies:
|
||||
"@radix-ui/react-primitive" "2.0.2"
|
||||
"@babel/runtime" "^7.13.10"
|
||||
"@radix-ui/react-primitive" "1.0.1"
|
||||
|
||||
"@radix-ui/react-collection@1.0.1":
|
||||
version "1.0.1"
|
||||
@@ -2019,11 +2008,6 @@
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.13.10"
|
||||
|
||||
"@radix-ui/react-compose-refs@1.1.1":
|
||||
version "1.1.1"
|
||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.1.tgz#6f766faa975f8738269ebb8a23bad4f5a8d2faec"
|
||||
integrity sha512-Y9VzoRDSJtgFMUCoiZBDVo084VQ5hfpXxVE+NgkdNsjiDBByiImMZKKhxMwCbdHvhlENG6a833CbFkOQvTricw==
|
||||
|
||||
"@radix-ui/react-context@1.0.0":
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-context/-/react-context-1.0.0.tgz#f38e30c5859a9fb5e9aa9a9da452ee3ed9e0aee0"
|
||||
@@ -2031,11 +2015,6 @@
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.13.10"
|
||||
|
||||
"@radix-ui/react-context@1.1.1":
|
||||
version "1.1.1"
|
||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-context/-/react-context-1.1.1.tgz#82074aa83a472353bb22e86f11bcbd1c61c4c71a"
|
||||
integrity sha512-UASk9zi+crv9WteK/NU4PLvOoL3OuE6BWVKNF6hPRBtYBDXQ2u5iu3O59zUlJiTVvkyuycnqrztsHVJwcK9K+Q==
|
||||
|
||||
"@radix-ui/react-direction@1.0.0":
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-direction/-/react-direction-1.0.0.tgz#a2e0b552352459ecf96342c79949dd833c1e6e45"
|
||||
@@ -2043,30 +2022,34 @@
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.13.10"
|
||||
|
||||
"@radix-ui/react-dismissable-layer@1.1.5":
|
||||
version "1.1.5"
|
||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.5.tgz#96dde2be078c694a621e55e047406c58cd5fe774"
|
||||
integrity sha512-E4TywXY6UsXNRhFrECa5HAvE5/4BFcGyfTyK36gP+pAW1ed7UTK4vKwdr53gAJYwqbfCWC6ATvJa3J3R/9+Qrg==
|
||||
"@radix-ui/react-dismissable-layer@1.0.2":
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.0.2.tgz#f04d1061bddf00b1ca304148516b9ddc62e45fb2"
|
||||
integrity sha512-WjJzMrTWROozDqLB0uRWYvj4UuXsM/2L19EmQ3Au+IJWqwvwq9Bwd+P8ivo0Deg9JDPArR1I6MbWNi1CmXsskg==
|
||||
dependencies:
|
||||
"@radix-ui/primitive" "1.1.1"
|
||||
"@radix-ui/react-compose-refs" "1.1.1"
|
||||
"@radix-ui/react-primitive" "2.0.2"
|
||||
"@radix-ui/react-use-callback-ref" "1.1.0"
|
||||
"@radix-ui/react-use-escape-keydown" "1.1.0"
|
||||
"@babel/runtime" "^7.13.10"
|
||||
"@radix-ui/primitive" "1.0.0"
|
||||
"@radix-ui/react-compose-refs" "1.0.0"
|
||||
"@radix-ui/react-primitive" "1.0.1"
|
||||
"@radix-ui/react-use-callback-ref" "1.0.0"
|
||||
"@radix-ui/react-use-escape-keydown" "1.0.2"
|
||||
|
||||
"@radix-ui/react-focus-guards@1.1.1":
|
||||
version "1.1.1"
|
||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-focus-guards/-/react-focus-guards-1.1.1.tgz#8635edd346304f8b42cae86b05912b61aef27afe"
|
||||
integrity sha512-pSIwfrT1a6sIoDASCSpFwOasEwKTZWDw/iBdtnqKO7v6FeOzYJ7U53cPzYFVR3geGGXgVHaH+CdngrrAzqUGxg==
|
||||
|
||||
"@radix-ui/react-focus-scope@1.1.2":
|
||||
version "1.1.2"
|
||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-focus-scope/-/react-focus-scope-1.1.2.tgz#c0a4519cd95c772606a82fc5b96226cd7fdd2602"
|
||||
integrity sha512-zxwE80FCU7lcXUGWkdt6XpTTCKPitG1XKOwViTxHVKIJhZl9MvIl2dVHeZENCWD9+EdWv05wlaEkRXUykU27RA==
|
||||
"@radix-ui/react-focus-guards@1.0.0":
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-focus-guards/-/react-focus-guards-1.0.0.tgz#339c1c69c41628c1a5e655f15f7020bf11aa01fa"
|
||||
integrity sha512-UagjDk4ijOAnGu4WMUPj9ahi7/zJJqNZ9ZAiGPp7waUWJO0O1aWXi/udPphI0IUjvrhBsZJGSN66dR2dsueLWQ==
|
||||
dependencies:
|
||||
"@radix-ui/react-compose-refs" "1.1.1"
|
||||
"@radix-ui/react-primitive" "2.0.2"
|
||||
"@radix-ui/react-use-callback-ref" "1.1.0"
|
||||
"@babel/runtime" "^7.13.10"
|
||||
|
||||
"@radix-ui/react-focus-scope@1.0.1":
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-focus-scope/-/react-focus-scope-1.0.1.tgz#faea8c25f537c5a5c38c50914b63722db0e7f951"
|
||||
integrity sha512-Ej2MQTit8IWJiS2uuujGUmxXjF/y5xZptIIQnyd2JHLwtV0R2j9NRVoRj/1j/gJ7e3REdaBw4Hjf4a1ImhkZcQ==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.13.10"
|
||||
"@radix-ui/react-compose-refs" "1.0.0"
|
||||
"@radix-ui/react-primitive" "1.0.1"
|
||||
"@radix-ui/react-use-callback-ref" "1.0.0"
|
||||
|
||||
"@radix-ui/react-id@1.0.0":
|
||||
version "1.0.0"
|
||||
@@ -2076,57 +2059,52 @@
|
||||
"@babel/runtime" "^7.13.10"
|
||||
"@radix-ui/react-use-layout-effect" "1.0.0"
|
||||
|
||||
"@radix-ui/react-id@1.1.0":
|
||||
"@radix-ui/react-popover@1.0.3":
|
||||
version "1.0.3"
|
||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-popover/-/react-popover-1.0.3.tgz#65ae2ee1fca2d7fd750308549eb8e0857c6160fe"
|
||||
integrity sha512-YwedSukfWsyJs3/yP3yXUq44k4/JBe3jqU63Z8v2i19qZZ3dsx32oma17ztgclWPNuqp3A+Xa9UiDlZHyVX8Vg==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.13.10"
|
||||
"@radix-ui/primitive" "1.0.0"
|
||||
"@radix-ui/react-compose-refs" "1.0.0"
|
||||
"@radix-ui/react-context" "1.0.0"
|
||||
"@radix-ui/react-dismissable-layer" "1.0.2"
|
||||
"@radix-ui/react-focus-guards" "1.0.0"
|
||||
"@radix-ui/react-focus-scope" "1.0.1"
|
||||
"@radix-ui/react-id" "1.0.0"
|
||||
"@radix-ui/react-popper" "1.1.0"
|
||||
"@radix-ui/react-portal" "1.0.1"
|
||||
"@radix-ui/react-presence" "1.0.0"
|
||||
"@radix-ui/react-primitive" "1.0.1"
|
||||
"@radix-ui/react-slot" "1.0.1"
|
||||
"@radix-ui/react-use-controllable-state" "1.0.0"
|
||||
aria-hidden "^1.1.1"
|
||||
react-remove-scroll "2.5.5"
|
||||
|
||||
"@radix-ui/react-popper@1.1.0":
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-id/-/react-id-1.1.0.tgz#de47339656594ad722eb87f94a6b25f9cffae0ed"
|
||||
integrity sha512-EJUrI8yYh7WOjNOqpoJaf1jlFIH2LvtgAl+YcFqNCa+4hj64ZXmPkAKOFs/ukjz3byN6bdb/AVUqHkI8/uWWMA==
|
||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-popper/-/react-popper-1.1.0.tgz#2be7e4c0cd4581f54277ca33a981c9037d2a8e60"
|
||||
integrity sha512-07U7jpI0dZcLRAxT7L9qs6HecSoPhDSJybF7mEGHJDBDv+ZoGCvIlva0s+WxMXwJEav+ckX3hAlXBtnHmuvlCQ==
|
||||
dependencies:
|
||||
"@radix-ui/react-use-layout-effect" "1.1.0"
|
||||
"@babel/runtime" "^7.13.10"
|
||||
"@floating-ui/react-dom" "0.7.2"
|
||||
"@radix-ui/react-arrow" "1.0.1"
|
||||
"@radix-ui/react-compose-refs" "1.0.0"
|
||||
"@radix-ui/react-context" "1.0.0"
|
||||
"@radix-ui/react-primitive" "1.0.1"
|
||||
"@radix-ui/react-use-callback-ref" "1.0.0"
|
||||
"@radix-ui/react-use-layout-effect" "1.0.0"
|
||||
"@radix-ui/react-use-rect" "1.0.0"
|
||||
"@radix-ui/react-use-size" "1.0.0"
|
||||
"@radix-ui/rect" "1.0.0"
|
||||
|
||||
"@radix-ui/react-popover@1.1.6":
|
||||
version "1.1.6"
|
||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-popover/-/react-popover-1.1.6.tgz#699634dbc7899429f657bb590d71fb3ca0904087"
|
||||
integrity sha512-NQouW0x4/GnkFJ/pRqsIS3rM/k97VzKnVb2jB7Gq7VEGPy5g7uNV1ykySFt7eWSp3i2uSGFwaJcvIRJBAHmmFg==
|
||||
"@radix-ui/react-portal@1.0.1":
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-portal/-/react-portal-1.0.1.tgz#169c5a50719c2bb0079cf4c91a27aa6d37e5dd33"
|
||||
integrity sha512-NY2vUWI5WENgAT1nfC6JS7RU5xRYBfjZVLq0HmgEN1Ezy3rk/UruMV4+Rd0F40PEaFC5SrLS1ixYvcYIQrb4Ig==
|
||||
dependencies:
|
||||
"@radix-ui/primitive" "1.1.1"
|
||||
"@radix-ui/react-compose-refs" "1.1.1"
|
||||
"@radix-ui/react-context" "1.1.1"
|
||||
"@radix-ui/react-dismissable-layer" "1.1.5"
|
||||
"@radix-ui/react-focus-guards" "1.1.1"
|
||||
"@radix-ui/react-focus-scope" "1.1.2"
|
||||
"@radix-ui/react-id" "1.1.0"
|
||||
"@radix-ui/react-popper" "1.2.2"
|
||||
"@radix-ui/react-portal" "1.1.4"
|
||||
"@radix-ui/react-presence" "1.1.2"
|
||||
"@radix-ui/react-primitive" "2.0.2"
|
||||
"@radix-ui/react-slot" "1.1.2"
|
||||
"@radix-ui/react-use-controllable-state" "1.1.0"
|
||||
aria-hidden "^1.2.4"
|
||||
react-remove-scroll "^2.6.3"
|
||||
|
||||
"@radix-ui/react-popper@1.2.2":
|
||||
version "1.2.2"
|
||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-popper/-/react-popper-1.2.2.tgz#d2e1ee5a9b24419c5936a1b7f6f472b7b412b029"
|
||||
integrity sha512-Rvqc3nOpwseCyj/rgjlJDYAgyfw7OC1tTkKn2ivhaMGcYt8FSBlahHOZak2i3QwkRXUXgGgzeEe2RuqeEHuHgA==
|
||||
dependencies:
|
||||
"@floating-ui/react-dom" "^2.0.0"
|
||||
"@radix-ui/react-arrow" "1.1.2"
|
||||
"@radix-ui/react-compose-refs" "1.1.1"
|
||||
"@radix-ui/react-context" "1.1.1"
|
||||
"@radix-ui/react-primitive" "2.0.2"
|
||||
"@radix-ui/react-use-callback-ref" "1.1.0"
|
||||
"@radix-ui/react-use-layout-effect" "1.1.0"
|
||||
"@radix-ui/react-use-rect" "1.1.0"
|
||||
"@radix-ui/react-use-size" "1.1.0"
|
||||
"@radix-ui/rect" "1.1.0"
|
||||
|
||||
"@radix-ui/react-portal@1.1.4":
|
||||
version "1.1.4"
|
||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-portal/-/react-portal-1.1.4.tgz#ff5401ff63c8a825c46eea96d3aef66074b8c0c8"
|
||||
integrity sha512-sn2O9k1rPFYVyKd5LAJfo96JlSGVFpa1fS6UuBJfrZadudiw5tAmru+n1x7aMRQ84qDM71Zh1+SzK5QwU0tJfA==
|
||||
dependencies:
|
||||
"@radix-ui/react-primitive" "2.0.2"
|
||||
"@radix-ui/react-use-layout-effect" "1.1.0"
|
||||
"@babel/runtime" "^7.13.10"
|
||||
"@radix-ui/react-primitive" "1.0.1"
|
||||
|
||||
"@radix-ui/react-presence@1.0.0":
|
||||
version "1.0.0"
|
||||
@@ -2137,14 +2115,6 @@
|
||||
"@radix-ui/react-compose-refs" "1.0.0"
|
||||
"@radix-ui/react-use-layout-effect" "1.0.0"
|
||||
|
||||
"@radix-ui/react-presence@1.1.2":
|
||||
version "1.1.2"
|
||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-presence/-/react-presence-1.1.2.tgz#bb764ed8a9118b7ec4512da5ece306ded8703cdc"
|
||||
integrity sha512-18TFr80t5EVgL9x1SwF/YGtfG+l0BS0PRAlCWBDoBEiDQjeKgnNZRVJp/oVBl24sr3Gbfwc/Qpj4OcWTQMsAEg==
|
||||
dependencies:
|
||||
"@radix-ui/react-compose-refs" "1.1.1"
|
||||
"@radix-ui/react-use-layout-effect" "1.1.0"
|
||||
|
||||
"@radix-ui/react-primitive@1.0.1":
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-primitive/-/react-primitive-1.0.1.tgz#c1ebcce283dd2f02e4fbefdaa49d1cb13dbc990a"
|
||||
@@ -2153,13 +2123,6 @@
|
||||
"@babel/runtime" "^7.13.10"
|
||||
"@radix-ui/react-slot" "1.0.1"
|
||||
|
||||
"@radix-ui/react-primitive@2.0.2":
|
||||
version "2.0.2"
|
||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-primitive/-/react-primitive-2.0.2.tgz#ac8b7854d87b0d7af388d058268d9a7eb64ca8ef"
|
||||
integrity sha512-Ec/0d38EIuvDF+GZjcMU/Ze6MxntVJYO/fRlCPhCaVUyPY9WTalHJw54tp9sXeJo3tlShWpy41vQRgLRGOuz+w==
|
||||
dependencies:
|
||||
"@radix-ui/react-slot" "1.1.2"
|
||||
|
||||
"@radix-ui/react-roving-focus@1.0.2":
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-roving-focus/-/react-roving-focus-1.0.2.tgz#d8ac2e3b8006697bdfc2b0eb06bef7e15b6245de"
|
||||
@@ -2184,13 +2147,6 @@
|
||||
"@babel/runtime" "^7.13.10"
|
||||
"@radix-ui/react-compose-refs" "1.0.0"
|
||||
|
||||
"@radix-ui/react-slot@1.1.2":
|
||||
version "1.1.2"
|
||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-slot/-/react-slot-1.1.2.tgz#daffff7b2bfe99ade63b5168407680b93c00e1c6"
|
||||
integrity sha512-YAKxaiGsSQJ38VzKH86/BPRC4rh+b1Jpa+JneA5LRE7skmLPNAyeG8kPJj/oo4STLvlrs8vkf/iYyc3A5stYCQ==
|
||||
dependencies:
|
||||
"@radix-ui/react-compose-refs" "1.1.1"
|
||||
|
||||
"@radix-ui/react-tabs@1.0.2":
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-tabs/-/react-tabs-1.0.2.tgz#8f5ec73ca41b151a413bdd6e00553408ff34ce07"
|
||||
@@ -2213,11 +2169,6 @@
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.13.10"
|
||||
|
||||
"@radix-ui/react-use-callback-ref@1.1.0":
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.0.tgz#bce938ca413675bc937944b0d01ef6f4a6dc5bf1"
|
||||
integrity sha512-CasTfvsy+frcFkbXtSJ2Zu9JHpN8TYKxkgJGWbjiZhFivxaeW7rMeZt7QELGVLaYVfFMsKHjb7Ak0nMEe+2Vfw==
|
||||
|
||||
"@radix-ui/react-use-controllable-state@1.0.0":
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.0.0.tgz#a64deaafbbc52d5d407afaa22d493d687c538b7f"
|
||||
@@ -2226,19 +2177,13 @@
|
||||
"@babel/runtime" "^7.13.10"
|
||||
"@radix-ui/react-use-callback-ref" "1.0.0"
|
||||
|
||||
"@radix-ui/react-use-controllable-state@1.1.0":
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.1.0.tgz#1321446857bb786917df54c0d4d084877aab04b0"
|
||||
integrity sha512-MtfMVJiSr2NjzS0Aa90NPTnvTSg6C/JLCV7ma0W6+OMV78vd8OyRpID+Ng9LxzsPbLeuBnWBA1Nq30AtBIDChw==
|
||||
"@radix-ui/react-use-escape-keydown@1.0.2":
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-use-escape-keydown/-/react-use-escape-keydown-1.0.2.tgz#09ab6455ab240b4f0a61faf06d4e5132c4d639f6"
|
||||
integrity sha512-DXGim3x74WgUv+iMNCF+cAo8xUHHeqvjx8zs7trKf+FkQKPQXLk2sX7Gx1ysH7Q76xCpZuxIJE7HLPxRE+Q+GA==
|
||||
dependencies:
|
||||
"@radix-ui/react-use-callback-ref" "1.1.0"
|
||||
|
||||
"@radix-ui/react-use-escape-keydown@1.1.0":
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-use-escape-keydown/-/react-use-escape-keydown-1.1.0.tgz#31a5b87c3b726504b74e05dac1edce7437b98754"
|
||||
integrity sha512-L7vwWlR1kTTQ3oh7g1O0CBF3YCyyTj8NmhLR+phShpyA50HCfBFKVJTpshm9PzLiKmehsrQzTYTpX9HvmC9rhw==
|
||||
dependencies:
|
||||
"@radix-ui/react-use-callback-ref" "1.1.0"
|
||||
"@babel/runtime" "^7.13.10"
|
||||
"@radix-ui/react-use-callback-ref" "1.0.0"
|
||||
|
||||
"@radix-ui/react-use-layout-effect@1.0.0":
|
||||
version "1.0.0"
|
||||
@@ -2247,29 +2192,28 @@
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.13.10"
|
||||
|
||||
"@radix-ui/react-use-layout-effect@1.1.0":
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.1.0.tgz#3c2c8ce04827b26a39e442ff4888d9212268bd27"
|
||||
integrity sha512-+FPE0rOdziWSrH9athwI1R0HDVbWlEhd+FR+aSDk4uWGmSJ9Z54sdZVDQPZAinJhJXwfT+qnj969mCsT2gfm5w==
|
||||
|
||||
"@radix-ui/react-use-rect@1.1.0":
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-use-rect/-/react-use-rect-1.1.0.tgz#13b25b913bd3e3987cc9b073a1a164bb1cf47b88"
|
||||
integrity sha512-0Fmkebhr6PiseyZlYAOtLS+nb7jLmpqTrJyv61Pe68MKYW6OWdRE2kI70TaYY27u7H0lajqM3hSMMLFq18Z7nQ==
|
||||
"@radix-ui/react-use-rect@1.0.0":
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-use-rect/-/react-use-rect-1.0.0.tgz#b040cc88a4906b78696cd3a32b075ed5b1423b3e"
|
||||
integrity sha512-TB7pID8NRMEHxb/qQJpvSt3hQU4sqNPM1VCTjTRjEOa7cEop/QMuq8S6fb/5Tsz64kqSvB9WnwsDHtjnrM9qew==
|
||||
dependencies:
|
||||
"@radix-ui/rect" "1.1.0"
|
||||
"@babel/runtime" "^7.13.10"
|
||||
"@radix-ui/rect" "1.0.0"
|
||||
|
||||
"@radix-ui/react-use-size@1.1.0":
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-use-size/-/react-use-size-1.1.0.tgz#b4dba7fbd3882ee09e8d2a44a3eed3a7e555246b"
|
||||
integrity sha512-XW3/vWuIXHa+2Uwcc2ABSfcCledmXhhQPlGbfcRXbiUQI5Icjcg19BGCZVKKInYbvUCut/ufbbLLPFC5cbb1hw==
|
||||
"@radix-ui/react-use-size@1.0.0":
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-use-size/-/react-use-size-1.0.0.tgz#a0b455ac826749419f6354dc733e2ca465054771"
|
||||
integrity sha512-imZ3aYcoYCKhhgNpkNDh/aTiU05qw9hX+HHI1QDBTyIlcFjgeFlKKySNGMwTp7nYFLQg/j0VA2FmCY4WPDDHMg==
|
||||
dependencies:
|
||||
"@radix-ui/react-use-layout-effect" "1.1.0"
|
||||
"@babel/runtime" "^7.13.10"
|
||||
"@radix-ui/react-use-layout-effect" "1.0.0"
|
||||
|
||||
"@radix-ui/rect@1.1.0":
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/@radix-ui/rect/-/rect-1.1.0.tgz#f817d1d3265ac5415dadc67edab30ae196696438"
|
||||
integrity sha512-A9+lCBZoaMJlVKcRBz2YByCG+Cp2t6nAnMnNba+XiWxnj6r4JUFqfsgwocMBZU9LPtdxC6wB56ySYpc7LQIoJg==
|
||||
"@radix-ui/rect@1.0.0":
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@radix-ui/rect/-/rect-1.0.0.tgz#0dc8e6a829ea2828d53cbc94b81793ba6383bf3c"
|
||||
integrity sha512-d0O68AYy/9oeEy1DdC07bz1/ZXX+DqCskRd3i4JzLSTXwefzaepQrKjXC7aNM8lTHjFLDO0pDgaEiQ7jEk+HVg==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.13.10"
|
||||
|
||||
"@sideway/address@^4.1.3":
|
||||
version "4.1.4"
|
||||
@@ -3008,7 +2952,7 @@ argparse@^2.0.1:
|
||||
resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38"
|
||||
integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==
|
||||
|
||||
aria-hidden@^1.2.4:
|
||||
aria-hidden@^1.1.1:
|
||||
version "1.2.4"
|
||||
resolved "https://registry.yarnpkg.com/aria-hidden/-/aria-hidden-1.2.4.tgz#b78e383fdbc04d05762c78b4a25a501e736c4522"
|
||||
integrity sha512-y+CcFFwelSXpLZk/7fMB2mUbGtX9lKycf1MWJ7CaTIERyitVlyQx6C+sxcROU2BAJ24OiZyK+8wj2i8AlBoS3A==
|
||||
@@ -7539,7 +7483,7 @@ react-loadable-ssr-addon-v5-slorber@^1.0.1:
|
||||
"@types/react" "*"
|
||||
prop-types "^15.6.2"
|
||||
|
||||
react-remove-scroll-bar@^2.3.7:
|
||||
react-remove-scroll-bar@^2.3.3:
|
||||
version "2.3.8"
|
||||
resolved "https://registry.yarnpkg.com/react-remove-scroll-bar/-/react-remove-scroll-bar-2.3.8.tgz#99c20f908ee467b385b68a3469b4a3e750012223"
|
||||
integrity sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q==
|
||||
@@ -7547,16 +7491,16 @@ react-remove-scroll-bar@^2.3.7:
|
||||
react-style-singleton "^2.2.2"
|
||||
tslib "^2.0.0"
|
||||
|
||||
react-remove-scroll@^2.6.3:
|
||||
version "2.6.3"
|
||||
resolved "https://registry.yarnpkg.com/react-remove-scroll/-/react-remove-scroll-2.6.3.tgz#df02cde56d5f2731e058531f8ffd7f9adec91ac2"
|
||||
integrity sha512-pnAi91oOk8g8ABQKGF5/M9qxmmOPxaAnopyTHYfqYEwJhyFrbbBtHuSgtKEoH0jpcxx5o3hXqH1mNd9/Oi+8iQ==
|
||||
react-remove-scroll@2.5.5:
|
||||
version "2.5.5"
|
||||
resolved "https://registry.yarnpkg.com/react-remove-scroll/-/react-remove-scroll-2.5.5.tgz#1e31a1260df08887a8a0e46d09271b52b3a37e77"
|
||||
integrity sha512-ImKhrzJJsyXJfBZ4bzu8Bwpka14c/fQt0k+cyFp/PBhTfyDnU5hjOtM4AG/0AMyy8oKzOTR0lDgJIM7pYXI0kw==
|
||||
dependencies:
|
||||
react-remove-scroll-bar "^2.3.7"
|
||||
react-style-singleton "^2.2.3"
|
||||
react-remove-scroll-bar "^2.3.3"
|
||||
react-style-singleton "^2.2.1"
|
||||
tslib "^2.1.0"
|
||||
use-callback-ref "^1.3.3"
|
||||
use-sidecar "^1.1.3"
|
||||
use-callback-ref "^1.3.0"
|
||||
use-sidecar "^1.1.2"
|
||||
|
||||
react-router-config@^5.1.1:
|
||||
version "5.1.1"
|
||||
@@ -7599,7 +7543,7 @@ react-simple-code-editor@^0.10.0:
|
||||
resolved "https://registry.yarnpkg.com/react-simple-code-editor/-/react-simple-code-editor-0.10.0.tgz#73e7ac550a928069715482aeb33ccba36efe2373"
|
||||
integrity sha512-bL5W5mAxSW6+cLwqqVWY47Silqgy2DKDTR4hDBrLrUqC5BXc29YVx17l2IZk5v36VcDEq1Bszu2oHm1qBwKqBA==
|
||||
|
||||
react-style-singleton@^2.2.2, react-style-singleton@^2.2.3:
|
||||
react-style-singleton@^2.2.1, react-style-singleton@^2.2.2:
|
||||
version "2.2.3"
|
||||
resolved "https://registry.yarnpkg.com/react-style-singleton/-/react-style-singleton-2.2.3.tgz#4265608be69a4d70cfe3047f2c6c88b2c3ace388"
|
||||
integrity sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ==
|
||||
@@ -8861,7 +8805,7 @@ url-parse-lax@^3.0.0:
|
||||
dependencies:
|
||||
prepend-http "^2.0.0"
|
||||
|
||||
use-callback-ref@^1.3.3:
|
||||
use-callback-ref@^1.3.0:
|
||||
version "1.3.3"
|
||||
resolved "https://registry.yarnpkg.com/use-callback-ref/-/use-callback-ref-1.3.3.tgz#98d9fab067075841c5b2c6852090d5d0feabe2bf"
|
||||
integrity sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg==
|
||||
@@ -8885,7 +8829,7 @@ use-latest@^1.2.1:
|
||||
dependencies:
|
||||
use-isomorphic-layout-effect "^1.1.1"
|
||||
|
||||
use-sidecar@^1.1.3:
|
||||
use-sidecar@^1.1.2:
|
||||
version "1.1.3"
|
||||
resolved "https://registry.yarnpkg.com/use-sidecar/-/use-sidecar-1.1.3.tgz#10e7fd897d130b896e2c546c63a5e8233d00efdb"
|
||||
integrity sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ==
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
version: "3.8"
|
||||
|
||||
services:
|
||||
excalidraw:
|
||||
build:
|
||||
|
||||
@@ -3,8 +3,7 @@
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"build:packages": "yarn --cwd ../../ build:packages",
|
||||
"build:workspace": "yarn build:packages && 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",
|
||||
"dev": "yarn build:workspace && next dev -p 3005",
|
||||
"build": "yarn build:workspace && next build",
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import dynamic from "next/dynamic";
|
||||
import Script from "next/script";
|
||||
|
||||
import "../common.scss";
|
||||
|
||||
// Since client components get prerenderd on server as well hence importing the excalidraw stuff dynamically
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
"use client";
|
||||
import * as excalidrawLib from "@excalidraw/excalidraw";
|
||||
import { Excalidraw } from "@excalidraw/excalidraw";
|
||||
import App from "../../with-script-in-browser/components/ExampleApp";
|
||||
|
||||
import "@excalidraw/excalidraw/index.css";
|
||||
|
||||
import App from "../../with-script-in-browser/components/ExampleApp";
|
||||
|
||||
const ExcalidrawWrapper: React.FC = () => {
|
||||
return (
|
||||
<>
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import dynamic from "next/dynamic";
|
||||
|
||||
import "../common.scss";
|
||||
|
||||
// Since client components get prerenderd on server as well hence importing the excalidraw stuff dynamically
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import React from "react";
|
||||
|
||||
import type * as TExcalidraw from "@excalidraw/excalidraw";
|
||||
import type { ExcalidrawImperativeAPI } from "@excalidraw/excalidraw/types";
|
||||
|
||||
|
||||
@@ -52,7 +52,7 @@
|
||||
transform: none;
|
||||
}
|
||||
|
||||
.excalidraw .selected-shape-actions {
|
||||
.excalidraw .panelColumn {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { nanoid } from "nanoid";
|
||||
import React, {
|
||||
useEffect,
|
||||
useState,
|
||||
@@ -7,24 +6,13 @@ import React, {
|
||||
Children,
|
||||
cloneElement,
|
||||
} from "react";
|
||||
import ExampleSidebar from "./sidebar/ExampleSidebar";
|
||||
|
||||
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 {
|
||||
resolvablePromise,
|
||||
distance2d,
|
||||
@@ -35,12 +23,25 @@ import {
|
||||
|
||||
import CustomFooter from "./CustomFooter";
|
||||
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/types";
|
||||
import type {
|
||||
NonDeletedExcalidrawElement,
|
||||
Theme,
|
||||
} from "@excalidraw/excalidraw/element/types";
|
||||
import type { ImportedLibraryData } from "@excalidraw/excalidraw/data/types";
|
||||
|
||||
import "./ExampleApp.scss";
|
||||
|
||||
import type { ResolvablePromise } from "../utils";
|
||||
|
||||
type Comment = {
|
||||
x: number;
|
||||
y: number;
|
||||
@@ -104,7 +105,6 @@ export default function ExampleApp({
|
||||
const [viewModeEnabled, setViewModeEnabled] = useState(false);
|
||||
const [zenModeEnabled, setZenModeEnabled] = useState(false);
|
||||
const [gridModeEnabled, setGridModeEnabled] = useState(false);
|
||||
const [renderScrollbars, setRenderScrollbars] = useState(false);
|
||||
const [blobUrl, setBlobUrl] = useState<string>("");
|
||||
const [canvasUrl, setCanvasUrl] = useState<string>("");
|
||||
const [exportWithDarkMode, setExportWithDarkMode] = useState(false);
|
||||
@@ -193,7 +193,6 @@ export default function ExampleApp({
|
||||
}) => setPointerData(payload),
|
||||
viewModeEnabled,
|
||||
zenModeEnabled,
|
||||
renderScrollbars,
|
||||
gridModeEnabled,
|
||||
theme,
|
||||
name: "Custom name of drawing",
|
||||
@@ -712,14 +711,6 @@ export default function ExampleApp({
|
||||
/>
|
||||
Grid mode
|
||||
</label>
|
||||
<label>
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={renderScrollbars}
|
||||
onChange={() => setRenderScrollbars(!renderScrollbars)}
|
||||
/>
|
||||
Render scrollbars
|
||||
</label>
|
||||
<label>
|
||||
<input
|
||||
type="checkbox"
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
import React from "react";
|
||||
|
||||
import type * as TExcalidraw from "@excalidraw/excalidraw";
|
||||
import type { ExcalidrawImperativeAPI } from "@excalidraw/excalidraw/types";
|
||||
|
||||
import CustomFooter from "./CustomFooter";
|
||||
import type * as TExcalidraw from "@excalidraw/excalidraw";
|
||||
|
||||
const MobileFooter = ({
|
||||
excalidrawAPI,
|
||||
@@ -12,10 +10,10 @@ const MobileFooter = ({
|
||||
excalidrawAPI: ExcalidrawImperativeAPI;
|
||||
excalidrawLib: typeof TExcalidraw;
|
||||
}) => {
|
||||
const { useEditorInterface, Footer } = excalidrawLib;
|
||||
const { useDevice, Footer } = excalidrawLib;
|
||||
|
||||
const editorInterface = useEditorInterface();
|
||||
if (editorInterface.formFactor === "phone") {
|
||||
const device = useDevice();
|
||||
if (device.editor.isMobile) {
|
||||
return (
|
||||
<Footer>
|
||||
<CustomFooter
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import React, { useState } from "react";
|
||||
|
||||
import "./ExampleSidebar.scss";
|
||||
|
||||
export default function Sidebar({ children }: { children: React.ReactNode }) {
|
||||
|
||||
@@ -12,8 +12,9 @@
|
||||
<script>
|
||||
window.name = "codesandbox";
|
||||
window.EXCALIDRAW_ASSET_PATH =
|
||||
"https://esm.sh/@excalidraw/excalidraw@0.18.0/dist/prod/";
|
||||
"https://esm.sh/@excalidraw/excalidraw@0.18.0-rc.5/dist/prod/";
|
||||
</script>
|
||||
<link rel="stylesheet" href="/dist/dev/index.css" />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
import App from "./components/ExampleApp";
|
||||
import React, { StrictMode } from "react";
|
||||
import { createRoot } from "react-dom/client";
|
||||
|
||||
import "@excalidraw/excalidraw/index.css";
|
||||
|
||||
import type * as TExcalidraw from "@excalidraw/excalidraw";
|
||||
|
||||
import App from "./components/ExampleApp";
|
||||
import "@excalidraw/excalidraw/index.css";
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
"dependencies": {
|
||||
"react": "19.0.0",
|
||||
"react-dom": "19.0.0",
|
||||
"@excalidraw/excalidraw": "*",
|
||||
"@excalidraw/excalidraw": "0.18.0-rc.5",
|
||||
"browser-fs-access": "0.29.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
@@ -15,8 +15,6 @@
|
||||
"scripts": {
|
||||
"start": "vite",
|
||||
"build": "vite build",
|
||||
"preview": "vite preview --port 5002",
|
||||
"build:preview": "yarn build && yarn preview",
|
||||
"build:packages": "yarn --cwd ../../ build:packages"
|
||||
"build:preview": "yarn build && vite preview --port 5002"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { MIME_TYPES } from "@excalidraw/excalidraw";
|
||||
import { fileOpen as _fileOpen } from "browser-fs-access";
|
||||
import { unstable_batchedUpdates } from "react-dom";
|
||||
import { fileOpen as _fileOpen } from "browser-fs-access";
|
||||
import { MIME_TYPES } from "@excalidraw/excalidraw";
|
||||
|
||||
type FILE_EXTENSION = Exclude<keyof typeof MIME_TYPES, "binary">;
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
{
|
||||
"outputDirectory": "dist",
|
||||
"installCommand": "yarn install",
|
||||
"buildCommand": "yarn build:packages && yarn build"
|
||||
"installCommand": "yarn install"
|
||||
}
|
||||
|
||||
+109
-138
@@ -1,27 +1,40 @@
|
||||
import polyfill from "@excalidraw/excalidraw/polyfill";
|
||||
import { useCallback, useEffect, useRef, useState } from "react";
|
||||
import { trackEvent } from "@excalidraw/excalidraw/analytics";
|
||||
import { getDefaultAppState } from "@excalidraw/excalidraw/appState";
|
||||
import { ErrorDialog } from "@excalidraw/excalidraw/components/ErrorDialog";
|
||||
import { TopErrorBoundary } from "./components/TopErrorBoundary";
|
||||
import {
|
||||
APP_NAME,
|
||||
EVENT,
|
||||
THEME,
|
||||
TITLE_TIMEOUT,
|
||||
VERSION_TIMEOUT,
|
||||
} from "@excalidraw/excalidraw/constants";
|
||||
import { loadFromBlob } from "@excalidraw/excalidraw/data/blob";
|
||||
import type {
|
||||
FileId,
|
||||
NonDeletedExcalidrawElement,
|
||||
OrderedExcalidrawElement,
|
||||
} from "@excalidraw/excalidraw/element/types";
|
||||
import { useCallbackRefState } from "@excalidraw/excalidraw/hooks/useCallbackRefState";
|
||||
import { t } from "@excalidraw/excalidraw/i18n";
|
||||
import {
|
||||
Excalidraw,
|
||||
LiveCollaborationTrigger,
|
||||
TTDDialogTrigger,
|
||||
CaptureUpdateAction,
|
||||
reconcileElements,
|
||||
useEditorInterface,
|
||||
} from "@excalidraw/excalidraw";
|
||||
import { trackEvent } from "@excalidraw/excalidraw/analytics";
|
||||
import { getDefaultAppState } from "@excalidraw/excalidraw/appState";
|
||||
import type {
|
||||
AppState,
|
||||
ExcalidrawImperativeAPI,
|
||||
BinaryFiles,
|
||||
ExcalidrawInitialDataState,
|
||||
UIAppState,
|
||||
} from "@excalidraw/excalidraw/types";
|
||||
import type { ResolvablePromise } from "@excalidraw/excalidraw/utils";
|
||||
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 {
|
||||
APP_NAME,
|
||||
EVENT,
|
||||
THEME,
|
||||
VERSION_TIMEOUT,
|
||||
debounce,
|
||||
getVersion,
|
||||
getFrame,
|
||||
@@ -29,14 +42,75 @@ import {
|
||||
preventUnload,
|
||||
resolvablePromise,
|
||||
isRunningInIframe,
|
||||
isDevEnv,
|
||||
} from "@excalidraw/common";
|
||||
import polyfill from "@excalidraw/excalidraw/polyfill";
|
||||
import { useCallback, useEffect, useRef, useState } from "react";
|
||||
import { loadFromBlob } from "@excalidraw/excalidraw/data/blob";
|
||||
import { useCallbackRefState } from "@excalidraw/excalidraw/hooks/useCallbackRefState";
|
||||
import { t } from "@excalidraw/excalidraw/i18n";
|
||||
} from "@excalidraw/excalidraw/utils";
|
||||
import {
|
||||
FIREBASE_STORAGE_PREFIXES,
|
||||
isExcalidrawPlusSignedUser,
|
||||
STORAGE_KEYS,
|
||||
SYNC_BROWSER_TABS_TIMEOUT,
|
||||
} 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 "@excalidraw/excalidraw/data/restore";
|
||||
import { restore, restoreAppState } from "@excalidraw/excalidraw/data/restore";
|
||||
import {
|
||||
ExportToExcalidrawPlus,
|
||||
exportToExcalidrawPlus,
|
||||
} from "./components/ExportToExcalidrawPlus";
|
||||
import { updateStaleImageStatuses } from "./data/FileManager";
|
||||
import { newElementWith } from "@excalidraw/excalidraw/element/mutateElement";
|
||||
import { isInitializedImageElement } from "@excalidraw/excalidraw/element/typeChecks";
|
||||
import { loadFilesFromFirebase } from "./data/firebase";
|
||||
import {
|
||||
LibraryIndexedDBAdapter,
|
||||
LibraryLocalStorageMigrationAdapter,
|
||||
LocalData,
|
||||
} from "./data/LocalData";
|
||||
import { isBrowserStorageStateNewer } from "./data/tabSync";
|
||||
import clsx from "clsx";
|
||||
import {
|
||||
parseLibraryTokensFromUrl,
|
||||
useHandleLibrary,
|
||||
} from "@excalidraw/excalidraw/data/library";
|
||||
import { AppMainMenu } from "./components/AppMainMenu";
|
||||
import { AppWelcomeScreen } from "./components/AppWelcomeScreen";
|
||||
import { AppFooter } from "./components/AppFooter";
|
||||
import {
|
||||
Provider,
|
||||
useAtom,
|
||||
useAtomValue,
|
||||
useAtomWithInitialValue,
|
||||
appJotaiStore,
|
||||
} from "./app-jotai";
|
||||
|
||||
import "./index.scss";
|
||||
import type { ResolutionType } from "@excalidraw/excalidraw/utility-types";
|
||||
import { ShareableLinkDialog } from "@excalidraw/excalidraw/components/ShareableLinkDialog";
|
||||
import { openConfirmModal } from "@excalidraw/excalidraw/components/OverwriteConfirm/OverwriteConfirmState";
|
||||
import { OverwriteConfirmDialog } from "@excalidraw/excalidraw/components/OverwriteConfirm/OverwriteConfirm";
|
||||
import Trans from "@excalidraw/excalidraw/components/Trans";
|
||||
import { ShareDialog, shareDialogStateAtom } from "./share/ShareDialog";
|
||||
import CollabError, { collabErrorIndicatorAtom } from "./collab/CollabError";
|
||||
import type { RemoteExcalidrawElement } from "@excalidraw/excalidraw/data/reconcile";
|
||||
import {
|
||||
CommandPalette,
|
||||
DEFAULT_CATEGORIES,
|
||||
} from "@excalidraw/excalidraw/components/CommandPalette/CommandPalette";
|
||||
import {
|
||||
GithubIcon,
|
||||
XBrandIcon,
|
||||
@@ -47,84 +121,6 @@ import {
|
||||
share,
|
||||
youtubeIcon,
|
||||
} from "@excalidraw/excalidraw/components/icons";
|
||||
import { isElementLink } from "@excalidraw/element";
|
||||
import { restore, restoreAppState } from "@excalidraw/excalidraw/data/restore";
|
||||
import { newElementWith } from "@excalidraw/element";
|
||||
import { isInitializedImageElement } from "@excalidraw/element";
|
||||
import clsx from "clsx";
|
||||
import {
|
||||
parseLibraryTokensFromUrl,
|
||||
useHandleLibrary,
|
||||
} from "@excalidraw/excalidraw/data/library";
|
||||
|
||||
import type { RemoteExcalidrawElement } from "@excalidraw/excalidraw/data/reconcile";
|
||||
import type { RestoredDataState } from "@excalidraw/excalidraw/data/restore";
|
||||
import type {
|
||||
FileId,
|
||||
NonDeletedExcalidrawElement,
|
||||
OrderedExcalidrawElement,
|
||||
} from "@excalidraw/element/types";
|
||||
import type {
|
||||
AppState,
|
||||
ExcalidrawImperativeAPI,
|
||||
BinaryFiles,
|
||||
ExcalidrawInitialDataState,
|
||||
UIAppState,
|
||||
} from "@excalidraw/excalidraw/types";
|
||||
import type { ResolutionType } from "@excalidraw/common/utility-types";
|
||||
import type { ResolvablePromise } from "@excalidraw/common/utils";
|
||||
|
||||
import CustomStats from "./CustomStats";
|
||||
import {
|
||||
Provider,
|
||||
useAtom,
|
||||
useAtomValue,
|
||||
useAtomWithInitialValue,
|
||||
appJotaiStore,
|
||||
} from "./app-jotai";
|
||||
import {
|
||||
FIREBASE_STORAGE_PREFIXES,
|
||||
isExcalidrawPlusSignedUser,
|
||||
STORAGE_KEYS,
|
||||
SYNC_BROWSER_TABS_TIMEOUT,
|
||||
} from "./app_constants";
|
||||
import Collab, {
|
||||
collabAPIAtom,
|
||||
isCollaboratingAtom,
|
||||
isOfflineAtom,
|
||||
} from "./collab/Collab";
|
||||
import { AppFooter } from "./components/AppFooter";
|
||||
import { AppMainMenu } from "./components/AppMainMenu";
|
||||
import { AppWelcomeScreen } from "./components/AppWelcomeScreen";
|
||||
import {
|
||||
ExportToExcalidrawPlus,
|
||||
exportToExcalidrawPlus,
|
||||
} from "./components/ExportToExcalidrawPlus";
|
||||
import { TopErrorBoundary } from "./components/TopErrorBoundary";
|
||||
|
||||
import {
|
||||
exportToBackend,
|
||||
getCollaborationLinkData,
|
||||
isCollaborationLink,
|
||||
loadScene,
|
||||
} from "./data";
|
||||
|
||||
import { updateStaleImageStatuses } from "./data/FileManager";
|
||||
import {
|
||||
importFromLocalStorage,
|
||||
importUsernameFromLocalStorage,
|
||||
} from "./data/localStorage";
|
||||
|
||||
import { loadFilesFromFirebase } from "./data/firebase";
|
||||
import {
|
||||
LibraryIndexedDBAdapter,
|
||||
LibraryLocalStorageMigrationAdapter,
|
||||
LocalData,
|
||||
localStorageQuotaExceededAtom,
|
||||
} 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 { useAppLangCode } from "./app-language/language-state";
|
||||
@@ -135,13 +131,7 @@ import DebugCanvas, {
|
||||
} from "./components/DebugCanvas";
|
||||
import { AIComponents } from "./components/AI";
|
||||
import { ExcalidrawPlusIframeExport } from "./ExcalidrawPlusIframeExport";
|
||||
|
||||
import "./index.scss";
|
||||
|
||||
import { ExcalidrawPlusPromoBanner } from "./components/ExcalidrawPlusPromoBanner";
|
||||
import { AppSidebar } from "./components/AppSidebar";
|
||||
|
||||
import type { CollabAPI } from "./collab/Collab";
|
||||
import { isElementLink } from "@excalidraw/excalidraw/element/elementLink";
|
||||
|
||||
polyfill();
|
||||
|
||||
@@ -346,8 +336,6 @@ const ExcalidrawWrapper = () => {
|
||||
|
||||
const [langCode, setLangCode] = useAppLangCode();
|
||||
|
||||
const editorInterface = useEditorInterface();
|
||||
|
||||
// initial state
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
@@ -389,7 +377,7 @@ const ExcalidrawWrapper = () => {
|
||||
const [, forceRefresh] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (isDevEnv()) {
|
||||
if (import.meta.env.DEV) {
|
||||
const debugState = loadSavedDebugState();
|
||||
|
||||
if (debugState.enabled && !window.visualDebug) {
|
||||
@@ -505,6 +493,11 @@ const ExcalidrawWrapper = () => {
|
||||
}
|
||||
};
|
||||
|
||||
const titleTimeout = setTimeout(
|
||||
() => (document.title = APP_NAME),
|
||||
TITLE_TIMEOUT,
|
||||
);
|
||||
|
||||
const syncData = debounce(() => {
|
||||
if (isTestEnv()) {
|
||||
return;
|
||||
@@ -595,6 +588,7 @@ const ExcalidrawWrapper = () => {
|
||||
visibilityChange,
|
||||
false,
|
||||
);
|
||||
clearTimeout(titleTimeout);
|
||||
};
|
||||
}, [isCollabDisabled, collabAPI, excalidrawAPI, setLangCode]);
|
||||
|
||||
@@ -608,13 +602,7 @@ const ExcalidrawWrapper = () => {
|
||||
excalidrawAPI.getSceneElements(),
|
||||
)
|
||||
) {
|
||||
if (import.meta.env.VITE_APP_DISABLE_PREVENT_UNLOAD !== "true") {
|
||||
preventUnload(event);
|
||||
} else {
|
||||
console.warn(
|
||||
"preventing unload disabled (VITE_APP_DISABLE_PREVENT_UNLOAD)",
|
||||
);
|
||||
}
|
||||
preventUnload(event);
|
||||
}
|
||||
};
|
||||
window.addEventListener(EVENT.BEFORE_UNLOAD, unloadHandler);
|
||||
@@ -669,8 +657,8 @@ const ExcalidrawWrapper = () => {
|
||||
debugRenderer(
|
||||
debugCanvasRef.current,
|
||||
appState,
|
||||
elements,
|
||||
window.devicePixelRatio,
|
||||
() => forceRefresh((prev) => !prev),
|
||||
);
|
||||
}
|
||||
};
|
||||
@@ -734,8 +722,6 @@ const ExcalidrawWrapper = () => {
|
||||
|
||||
const isOffline = useAtomValue(isOfflineAtom);
|
||||
|
||||
const localStorageQuotaExceeded = useAtomValue(localStorageQuotaExceededAtom);
|
||||
|
||||
const onCollabDialogOpen = useCallback(
|
||||
() => setShareDialogState({ isOpen: true, type: "collaborationOnly" }),
|
||||
[setShareDialogState],
|
||||
@@ -854,22 +840,14 @@ const ExcalidrawWrapper = () => {
|
||||
if (isMobile || !collabAPI || isCollabDisabled) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="excalidraw-ui-top-right">
|
||||
{excalidrawAPI?.getEditorInterface().formFactor === "desktop" && (
|
||||
<ExcalidrawPlusPromoBanner
|
||||
isSignedIn={isExcalidrawPlusSignedUser}
|
||||
/>
|
||||
)}
|
||||
|
||||
<div className="top-right-ui">
|
||||
{collabError.message && <CollabError collabError={collabError} />}
|
||||
<LiveCollaborationTrigger
|
||||
isCollaborating={isCollaborating}
|
||||
onSelect={() =>
|
||||
setShareDialogState({ isOpen: true, type: "share" })
|
||||
}
|
||||
editorInterface={editorInterface}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
@@ -918,15 +896,10 @@ const ExcalidrawWrapper = () => {
|
||||
|
||||
<TTDDialogTrigger />
|
||||
{isCollaborating && isOffline && (
|
||||
<div className="alertalert--warning">
|
||||
<div className="collab-offline-warning">
|
||||
{t("alerts.collabOfflineWarning")}
|
||||
</div>
|
||||
)}
|
||||
{localStorageQuotaExceeded && (
|
||||
<div className="alert alert--danger">
|
||||
{t("alerts.localStorageQuotaExceeded")}
|
||||
</div>
|
||||
)}
|
||||
{latestShareableLink && (
|
||||
<ShareableLinkDialog
|
||||
link={latestShareableLink}
|
||||
@@ -955,8 +928,6 @@ const ExcalidrawWrapper = () => {
|
||||
}}
|
||||
/>
|
||||
|
||||
<AppSidebar />
|
||||
|
||||
{errorMessage && (
|
||||
<ErrorDialog onClose={() => setErrorMessage("")}>
|
||||
{errorMessage}
|
||||
|
||||
@@ -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 type { NonDeletedExcalidrawElement } from "@excalidraw/element/types";
|
||||
import type { UIAppState } from "@excalidraw/excalidraw/types";
|
||||
|
||||
import { debounce, getVersion, nFormatter } from "@excalidraw/excalidraw/utils";
|
||||
import {
|
||||
getElementsStorageSize,
|
||||
getTotalStorageSize,
|
||||
} from "./data/localStorage";
|
||||
import { DEFAULT_VERSION } from "@excalidraw/excalidraw/constants";
|
||||
import { t } from "@excalidraw/excalidraw/i18n";
|
||||
import { copyTextToSystemClipboard } from "@excalidraw/excalidraw/clipboard";
|
||||
import type { NonDeletedExcalidrawElement } from "@excalidraw/excalidraw/element/types";
|
||||
import type { UIAppState } from "@excalidraw/excalidraw/types";
|
||||
import { Stats } from "@excalidraw/excalidraw";
|
||||
|
||||
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 { STORAGE_KEYS } from "./app_constants";
|
||||
import { LocalData } from "./data/LocalData";
|
||||
import type {
|
||||
FileId,
|
||||
OrderedExcalidrawElement,
|
||||
} from "@excalidraw/element/types";
|
||||
} from "@excalidraw/excalidraw/element/types";
|
||||
import type { AppState, BinaryFileData } from "@excalidraw/excalidraw/types";
|
||||
|
||||
import { STORAGE_KEYS } from "./app_constants";
|
||||
import { LocalData } from "./data/LocalData";
|
||||
import { ExcalidrawError } from "@excalidraw/excalidraw/errors";
|
||||
import { base64urlToString } from "@excalidraw/excalidraw/data/encode";
|
||||
|
||||
const EVENT_REQUEST_SCENE = "REQUEST_SCENE";
|
||||
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
import { useI18n, languages } from "@excalidraw/excalidraw/i18n";
|
||||
import React from "react";
|
||||
|
||||
import { useI18n, languages } from "@excalidraw/excalidraw/i18n";
|
||||
import { useSetAtom } from "../app-jotai";
|
||||
|
||||
import { appLangCodeAtom } from "./language-state";
|
||||
|
||||
export const LanguageList = ({ style }: { style?: React.CSSProperties }) => {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { defaultLang, languages } from "@excalidraw/excalidraw";
|
||||
import LanguageDetector from "i18next-browser-languagedetector";
|
||||
import { defaultLang, languages } from "@excalidraw/excalidraw";
|
||||
|
||||
export const languageDetector = new LanguageDetector();
|
||||
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
import { useEffect } from "react";
|
||||
|
||||
import { atom, useAtom } from "../app-jotai";
|
||||
|
||||
import { getPreferredLanguage, languageDetector } from "./language-detector";
|
||||
|
||||
export const appLangCodeAtom = atom(getPreferredLanguage());
|
||||
|
||||
@@ -8,8 +8,7 @@ export const SYNC_BROWSER_TABS_TIMEOUT = 50;
|
||||
export const CURSOR_SYNC_TIMEOUT = 33; // ~30fps
|
||||
export const DELETED_ELEMENT_TIMEOUT = 24 * 60 * 60 * 1000; // 1 day
|
||||
|
||||
// should be aligned with MAX_ALLOWED_FILE_BYTES
|
||||
export const FILE_UPLOAD_MAX_BYTES = 4 * 1024 * 1024; // 4 MiB
|
||||
export const FILE_UPLOAD_MAX_BYTES = 3 * 1024 * 1024; // 3 MiB
|
||||
// 1 year (https://stackoverflow.com/a/25201898/927631)
|
||||
export const FILE_CACHE_MAX_AGE_SEC = 31536000;
|
||||
|
||||
|
||||
@@ -1,45 +1,5 @@
|
||||
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 { PureComponent } from "react";
|
||||
|
||||
import type {
|
||||
ReconciledExcalidrawElement,
|
||||
RemoteExcalidrawElement,
|
||||
} from "@excalidraw/excalidraw/data/reconcile";
|
||||
import type { ImportedDataState } from "@excalidraw/excalidraw/data/types";
|
||||
import type {
|
||||
ExcalidrawElement,
|
||||
FileId,
|
||||
InitializedExcalidrawImageElement,
|
||||
OrderedExcalidrawElement,
|
||||
} from "@excalidraw/element/types";
|
||||
import type {
|
||||
BinaryFileData,
|
||||
ExcalidrawImperativeAPI,
|
||||
@@ -47,9 +7,28 @@ import type {
|
||||
Collaborator,
|
||||
Gesture,
|
||||
} from "@excalidraw/excalidraw/types";
|
||||
import type { Mutable, ValueOf } from "@excalidraw/common/utility-types";
|
||||
|
||||
import { appJotaiStore, atom } from "../app-jotai";
|
||||
import { ErrorDialog } from "@excalidraw/excalidraw/components/ErrorDialog";
|
||||
import { APP_NAME, ENV, EVENT } from "@excalidraw/excalidraw/constants";
|
||||
import type { ImportedDataState } from "@excalidraw/excalidraw/data/types";
|
||||
import type {
|
||||
ExcalidrawElement,
|
||||
FileId,
|
||||
InitializedExcalidrawImageElement,
|
||||
OrderedExcalidrawElement,
|
||||
} from "@excalidraw/excalidraw/element/types";
|
||||
import {
|
||||
CaptureUpdateAction,
|
||||
getSceneVersion,
|
||||
restoreElements,
|
||||
zoomToFitBounds,
|
||||
reconcileElements,
|
||||
} from "@excalidraw/excalidraw";
|
||||
import {
|
||||
assertNever,
|
||||
preventUnload,
|
||||
resolvablePromise,
|
||||
throttleRAF,
|
||||
} from "@excalidraw/excalidraw/utils";
|
||||
import {
|
||||
CURSOR_SYNC_TIMEOUT,
|
||||
FILE_UPLOAD_MAX_BYTES,
|
||||
@@ -60,17 +39,15 @@ import {
|
||||
SYNC_FULL_SCENE_INTERVAL_MS,
|
||||
WS_EVENTS,
|
||||
} from "../app_constants";
|
||||
import type {
|
||||
SocketUpdateDataSource,
|
||||
SyncableExcalidrawElement,
|
||||
} from "../data";
|
||||
import {
|
||||
generateCollaborationLinkData,
|
||||
getCollaborationLink,
|
||||
getSyncableElements,
|
||||
} from "../data";
|
||||
import {
|
||||
encodeFilesForUpload,
|
||||
FileManager,
|
||||
updateStaleImageStatuses,
|
||||
} from "../data/FileManager";
|
||||
import { LocalData } from "../data/LocalData";
|
||||
import {
|
||||
isSavedToFirebase,
|
||||
loadFilesFromFirebase,
|
||||
@@ -82,15 +59,36 @@ import {
|
||||
importUsernameFromLocalStorage,
|
||||
saveUsernameToLocalStorage,
|
||||
} from "../data/localStorage";
|
||||
import { resetBrowserStateVersions } from "../data/tabSync";
|
||||
|
||||
import { collabErrorIndicatorAtom } from "./CollabError";
|
||||
import Portal from "./Portal";
|
||||
|
||||
import { t } from "@excalidraw/excalidraw/i18n";
|
||||
import {
|
||||
IDLE_THRESHOLD,
|
||||
ACTIVE_THRESHOLD,
|
||||
UserIdleState,
|
||||
} from "@excalidraw/excalidraw/constants";
|
||||
import {
|
||||
encodeFilesForUpload,
|
||||
FileManager,
|
||||
updateStaleImageStatuses,
|
||||
} from "../data/FileManager";
|
||||
import { AbortError } from "@excalidraw/excalidraw/errors";
|
||||
import {
|
||||
isImageElement,
|
||||
isInitializedImageElement,
|
||||
} from "@excalidraw/excalidraw/element/typeChecks";
|
||||
import { newElementWith } from "@excalidraw/excalidraw/element/mutateElement";
|
||||
import { decryptData } from "@excalidraw/excalidraw/data/encryption";
|
||||
import { resetBrowserStateVersions } from "../data/tabSync";
|
||||
import { LocalData } from "../data/LocalData";
|
||||
import { appJotaiStore, atom } from "../app-jotai";
|
||||
import type { Mutable, ValueOf } from "@excalidraw/excalidraw/utility-types";
|
||||
import { getVisibleSceneBounds } from "@excalidraw/excalidraw/element/bounds";
|
||||
import { withBatchedUpdates } from "@excalidraw/excalidraw/reactUtils";
|
||||
import { collabErrorIndicatorAtom } from "./CollabError";
|
||||
import type {
|
||||
SocketUpdateDataSource,
|
||||
SyncableExcalidrawElement,
|
||||
} from "../data";
|
||||
ReconciledExcalidrawElement,
|
||||
RemoteExcalidrawElement,
|
||||
} from "@excalidraw/excalidraw/data/reconcile";
|
||||
|
||||
export const collabAPIAtom = atom<CollabAPI | null>(null);
|
||||
export const isCollaboratingAtom = atom(false);
|
||||
@@ -238,7 +236,7 @@ class Collab extends PureComponent<CollabProps, CollabState> {
|
||||
|
||||
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"]);
|
||||
Object.defineProperties(window, {
|
||||
collab: {
|
||||
@@ -298,13 +296,7 @@ class Collab extends PureComponent<CollabProps, CollabState> {
|
||||
// the purpose is to run in immediately after user decides to stay
|
||||
this.saveCollabRoomToFirebase(syncableElements);
|
||||
|
||||
if (import.meta.env.VITE_APP_DISABLE_PREVENT_UNLOAD !== "true") {
|
||||
preventUnload(event);
|
||||
} else {
|
||||
console.warn(
|
||||
"preventing unload disabled (VITE_APP_DISABLE_PREVENT_UNLOAD)",
|
||||
);
|
||||
}
|
||||
preventUnload(event);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -441,7 +433,7 @@ class Collab extends PureComponent<CollabProps, CollabState> {
|
||||
};
|
||||
|
||||
private decryptPayload = async (
|
||||
iv: Uint8Array<ArrayBuffer>,
|
||||
iv: Uint8Array,
|
||||
encryptedData: ArrayBuffer,
|
||||
decryptionKey: string,
|
||||
): Promise<ValueOf<SocketUpdateDataSource>> => {
|
||||
@@ -530,10 +522,7 @@ class Collab extends PureComponent<CollabProps, CollabState> {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (existingRoomLinkData) {
|
||||
// when joining existing room, don't merge it with current scene data
|
||||
this.excalidrawAPI.resetScene();
|
||||
} else {
|
||||
if (!existingRoomLinkData) {
|
||||
const elements = this.excalidrawAPI.getSceneElements().map((element) => {
|
||||
if (isImageElement(element) && element.status === "saved") {
|
||||
return newElementWith(element, { status: "pending" });
|
||||
@@ -562,7 +551,7 @@ class Collab extends PureComponent<CollabProps, CollabState> {
|
||||
// All socket listeners are moving to Portal
|
||||
this.portal.socket.on(
|
||||
"client-broadcast",
|
||||
async (encryptedData: ArrayBuffer, iv: Uint8Array<ArrayBuffer>) => {
|
||||
async (encryptedData: ArrayBuffer, iv: Uint8Array) => {
|
||||
if (!this.portal.roomKey) {
|
||||
return;
|
||||
}
|
||||
@@ -746,10 +735,7 @@ class Collab extends PureComponent<CollabProps, CollabState> {
|
||||
): ReconciledExcalidrawElement[] => {
|
||||
const localElements = this.getSceneElementsIncludingDeleted();
|
||||
const appState = this.excalidrawAPI.getAppState();
|
||||
const restoredRemoteElements = restoreElements(
|
||||
remoteElements,
|
||||
this.excalidrawAPI.getSceneElementsMapIncludingDeleted(),
|
||||
);
|
||||
const restoredRemoteElements = restoreElements(remoteElements, null);
|
||||
const reconciledElements = reconcileElements(
|
||||
localElements,
|
||||
restoredRemoteElements as RemoteExcalidrawElement[],
|
||||
@@ -1023,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"]);
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,6 @@ import { Tooltip } from "@excalidraw/excalidraw/components/Tooltip";
|
||||
import { warning } from "@excalidraw/excalidraw/components/icons";
|
||||
import clsx from "clsx";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
|
||||
import { atom } from "../app-jotai";
|
||||
|
||||
import "./CollabError.scss";
|
||||
|
||||
@@ -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 {
|
||||
SocketUpdateData,
|
||||
SocketUpdateDataSource,
|
||||
SyncableExcalidrawElement,
|
||||
} from "../data";
|
||||
import { isSyncableElement } from "../data";
|
||||
|
||||
import type { TCollabClass } from "./Collab";
|
||||
|
||||
import type { OrderedExcalidrawElement } from "@excalidraw/excalidraw/element/types";
|
||||
import { WS_EVENTS, FILE_UPLOAD_TIMEOUT, WS_SUBTYPES } from "../app_constants";
|
||||
import type {
|
||||
OnUserFollowedPayload,
|
||||
SocketId,
|
||||
} from "@excalidraw/excalidraw/types";
|
||||
import type { UserIdleState } from "@excalidraw/excalidraw/constants";
|
||||
import { trackEvent } from "@excalidraw/excalidraw/analytics";
|
||||
import throttle from "lodash.throttle";
|
||||
import { newElementWith } from "@excalidraw/excalidraw/element/mutateElement";
|
||||
import { encryptData } from "@excalidraw/excalidraw/data/encryption";
|
||||
import type { Socket } from "socket.io-client";
|
||||
import { CaptureUpdateAction } from "@excalidraw/excalidraw";
|
||||
|
||||
class Portal {
|
||||
collab: TCollabClass;
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import type { ExcalidrawImperativeAPI } from "@excalidraw/excalidraw/types";
|
||||
import {
|
||||
DiagramToCodePlugin,
|
||||
exportToBlob,
|
||||
@@ -6,9 +7,7 @@ import {
|
||||
TTDDialog,
|
||||
} from "@excalidraw/excalidraw";
|
||||
import { getDataURL } from "@excalidraw/excalidraw/data/blob";
|
||||
import { safelyParseJSON } from "@excalidraw/common";
|
||||
|
||||
import type { ExcalidrawImperativeAPI } from "@excalidraw/excalidraw/types";
|
||||
import { safelyParseJSON } from "@excalidraw/excalidraw/utils";
|
||||
|
||||
export const AIComponents = ({
|
||||
excalidrawAPI,
|
||||
@@ -73,7 +72,7 @@ export const AIComponents = ({
|
||||
</br>
|
||||
<div>You can also try <a href="${
|
||||
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>
|
||||
</body>
|
||||
</html>`,
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
import { Footer } from "@excalidraw/excalidraw/index";
|
||||
import React from "react";
|
||||
|
||||
import { isExcalidrawPlusSignedUser } from "../app_constants";
|
||||
|
||||
import { DebugFooter, isVisualDebuggerEnabled } from "./DebugCanvas";
|
||||
import { Footer } from "@excalidraw/excalidraw/index";
|
||||
import { EncryptedIcon } from "./EncryptedIcon";
|
||||
import { ExcalidrawPlusAppLink } from "./ExcalidrawPlusAppLink";
|
||||
import { isExcalidrawPlusSignedUser } from "../app_constants";
|
||||
import { DebugFooter, isVisualDebuggerEnabled } from "./DebugCanvas";
|
||||
|
||||
export const AppFooter = React.memo(
|
||||
({ onChange }: { onChange: () => void }) => {
|
||||
@@ -18,7 +17,11 @@ export const AppFooter = React.memo(
|
||||
}}
|
||||
>
|
||||
{isVisualDebuggerEnabled() && <DebugFooter onChange={onChange} />}
|
||||
{!isExcalidrawPlusSignedUser && <EncryptedIcon />}
|
||||
{isExcalidrawPlusSignedUser ? (
|
||||
<ExcalidrawPlusAppLink />
|
||||
) : (
|
||||
<EncryptedIcon />
|
||||
)}
|
||||
</div>
|
||||
</Footer>
|
||||
);
|
||||
|
||||
@@ -1,18 +1,13 @@
|
||||
import React from "react";
|
||||
import {
|
||||
loginIcon,
|
||||
ExcalLogo,
|
||||
eyeIcon,
|
||||
} from "@excalidraw/excalidraw/components/icons";
|
||||
import type { Theme } from "@excalidraw/excalidraw/element/types";
|
||||
import { MainMenu } from "@excalidraw/excalidraw/index";
|
||||
import React from "react";
|
||||
|
||||
import { isDevEnv } from "@excalidraw/common";
|
||||
|
||||
import type { Theme } from "@excalidraw/element/types";
|
||||
|
||||
import { LanguageList } from "../app-language/LanguageList";
|
||||
import { isExcalidrawPlusSignedUser } from "../app_constants";
|
||||
|
||||
import { LanguageList } from "../app-language/LanguageList";
|
||||
import { saveDebugState } from "./DebugCanvas";
|
||||
|
||||
export const AppMainMenu: React.FC<{
|
||||
@@ -59,7 +54,7 @@ export const AppMainMenu: React.FC<{
|
||||
>
|
||||
{isExcalidrawPlusSignedUser ? "Sign in" : "Sign up"}
|
||||
</MainMenu.ItemLink>
|
||||
{isDevEnv() && (
|
||||
{import.meta.env.DEV && (
|
||||
<MainMenu.Item
|
||||
icon={eyeIcon}
|
||||
onClick={() => {
|
||||
|
||||
@@ -1,36 +0,0 @@
|
||||
.excalidraw {
|
||||
.app-sidebar-promo-container {
|
||||
padding: 0.75rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
text-align: center;
|
||||
gap: 1rem;
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
|
||||
.app-sidebar-promo-image {
|
||||
margin: 1rem 0;
|
||||
|
||||
height: 16.25rem;
|
||||
background-size: contain;
|
||||
background-position: center;
|
||||
background-repeat: no-repeat;
|
||||
|
||||
background-image: radial-gradient(
|
||||
circle,
|
||||
transparent 60%,
|
||||
var(--sidebar-bg-color) 100%
|
||||
),
|
||||
var(--image-source);
|
||||
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.app-sidebar-promo-text {
|
||||
padding: 0 2rem;
|
||||
}
|
||||
|
||||
.link-button {
|
||||
margin: 0 auto;
|
||||
}
|
||||
}
|
||||
@@ -1,79 +0,0 @@
|
||||
import { DefaultSidebar, Sidebar, THEME } from "@excalidraw/excalidraw";
|
||||
import {
|
||||
messageCircleIcon,
|
||||
presentationIcon,
|
||||
} from "@excalidraw/excalidraw/components/icons";
|
||||
import { LinkButton } from "@excalidraw/excalidraw/components/LinkButton";
|
||||
import { useUIAppState } from "@excalidraw/excalidraw/context/ui-appState";
|
||||
|
||||
import "./AppSidebar.scss";
|
||||
|
||||
export const AppSidebar = () => {
|
||||
const { theme, openSidebar } = useUIAppState();
|
||||
|
||||
return (
|
||||
<DefaultSidebar>
|
||||
<DefaultSidebar.TabTriggers>
|
||||
<Sidebar.TabTrigger
|
||||
tab="comments"
|
||||
style={{ opacity: openSidebar?.tab === "comments" ? 1 : 0.4 }}
|
||||
>
|
||||
{messageCircleIcon}
|
||||
</Sidebar.TabTrigger>
|
||||
<Sidebar.TabTrigger
|
||||
tab="presentation"
|
||||
style={{ opacity: openSidebar?.tab === "presentation" ? 1 : 0.4 }}
|
||||
>
|
||||
{presentationIcon}
|
||||
</Sidebar.TabTrigger>
|
||||
</DefaultSidebar.TabTriggers>
|
||||
<Sidebar.Tab tab="comments">
|
||||
<div className="app-sidebar-promo-container">
|
||||
<div
|
||||
className="app-sidebar-promo-image"
|
||||
style={{
|
||||
["--image-source" as any]: `url(/oss_promo_comments_${
|
||||
theme === THEME.DARK ? "dark" : "light"
|
||||
}.jpg)`,
|
||||
opacity: 0.7,
|
||||
}}
|
||||
/>
|
||||
<div className="app-sidebar-promo-text">
|
||||
Make comments with Excalidraw+
|
||||
</div>
|
||||
<LinkButton
|
||||
href={`${
|
||||
import.meta.env.VITE_APP_PLUS_LP
|
||||
}/plus?utm_source=excalidraw&utm_medium=app&utm_content=comments_promo#excalidraw-redirect`}
|
||||
>
|
||||
Sign up now
|
||||
</LinkButton>
|
||||
</div>
|
||||
</Sidebar.Tab>
|
||||
<Sidebar.Tab tab="presentation" className="px-3">
|
||||
<div className="app-sidebar-promo-container">
|
||||
<div
|
||||
className="app-sidebar-promo-image"
|
||||
style={{
|
||||
["--image-source" as any]: `url(/oss_promo_presentations_${
|
||||
theme === THEME.DARK ? "dark" : "light"
|
||||
}.svg)`,
|
||||
backgroundSize: "60%",
|
||||
opacity: 0.4,
|
||||
}}
|
||||
/>
|
||||
<div className="app-sidebar-promo-text">
|
||||
Create presentations with Excalidraw+
|
||||
</div>
|
||||
<LinkButton
|
||||
href={`${
|
||||
import.meta.env.VITE_APP_PLUS_LP
|
||||
}/plus?utm_source=excalidraw&utm_medium=app&utm_content=presentations_promo#excalidraw-redirect`}
|
||||
>
|
||||
Sign up now
|
||||
</LinkButton>
|
||||
</div>
|
||||
</Sidebar.Tab>
|
||||
</DefaultSidebar>
|
||||
);
|
||||
};
|
||||
@@ -1,10 +1,9 @@
|
||||
import React from "react";
|
||||
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 { isExcalidrawPlusSignedUser } from "../app_constants";
|
||||
import { POINTER_EVENTS } from "@excalidraw/excalidraw/constants";
|
||||
|
||||
export const AppWelcomeScreen: React.FC<{
|
||||
onCollabDialogOpen: () => any;
|
||||
|
||||
@@ -1,42 +1,24 @@
|
||||
import { useCallback, useImperativeHandle, useRef } from "react";
|
||||
import { type AppState } from "@excalidraw/excalidraw/types";
|
||||
import { throttleRAF } from "@excalidraw/excalidraw/utils";
|
||||
import {
|
||||
bootstrapCanvas,
|
||||
getNormalizedCanvasDimensions,
|
||||
} from "@excalidraw/excalidraw/renderer/helpers";
|
||||
import type { DebugElement } from "@excalidraw/excalidraw/visualdebug";
|
||||
import {
|
||||
ArrowheadArrowIcon,
|
||||
CloseIcon,
|
||||
TrashIcon,
|
||||
} from "@excalidraw/excalidraw/components/icons";
|
||||
import {
|
||||
bootstrapCanvas,
|
||||
getNormalizedCanvasDimensions,
|
||||
} from "@excalidraw/excalidraw/renderer/helpers";
|
||||
import { type AppState } from "@excalidraw/excalidraw/types";
|
||||
import { arrayToMap, throttleRAF } from "@excalidraw/common";
|
||||
import { useCallback } from "react";
|
||||
|
||||
import {
|
||||
getGlobalFixedPointForBindableElement,
|
||||
isArrowElement,
|
||||
isBindableElement,
|
||||
} from "@excalidraw/element";
|
||||
|
||||
import { STORAGE_KEYS } from "../app_constants";
|
||||
import type { Curve } from "../../packages/math";
|
||||
import {
|
||||
isLineSegment,
|
||||
type GlobalPoint,
|
||||
type LineSegment,
|
||||
} from "@excalidraw/math";
|
||||
import { isCurve } from "@excalidraw/math/curve";
|
||||
|
||||
import React from "react";
|
||||
|
||||
import type { Curve } from "@excalidraw/math";
|
||||
import type { DebugElement } from "@excalidraw/common";
|
||||
import type {
|
||||
ElementsMap,
|
||||
ExcalidrawArrowElement,
|
||||
ExcalidrawBindableElement,
|
||||
FixedPointBinding,
|
||||
OrderedExcalidrawElement,
|
||||
} from "@excalidraw/element/types";
|
||||
|
||||
import { STORAGE_KEYS } from "../app_constants";
|
||||
} from "../../packages/math";
|
||||
import { isCurve } from "../../packages/math/curve";
|
||||
|
||||
const renderLine = (
|
||||
context: CanvasRenderingContext2D,
|
||||
@@ -87,176 +69,6 @@ const renderOrigin = (context: CanvasRenderingContext2D, zoom: number) => {
|
||||
context.save();
|
||||
};
|
||||
|
||||
const _renderBinding = (
|
||||
context: CanvasRenderingContext2D,
|
||||
binding: FixedPointBinding,
|
||||
elementsMap: ElementsMap,
|
||||
zoom: number,
|
||||
width: number,
|
||||
height: number,
|
||||
color: string,
|
||||
) => {
|
||||
if (!binding.fixedPoint) {
|
||||
console.warn("Binding must have a fixedPoint");
|
||||
return;
|
||||
}
|
||||
|
||||
const bindable = elementsMap.get(
|
||||
binding.elementId,
|
||||
) as ExcalidrawBindableElement;
|
||||
const [x, y] = getGlobalFixedPointForBindableElement(
|
||||
binding.fixedPoint,
|
||||
bindable,
|
||||
elementsMap,
|
||||
);
|
||||
|
||||
context.save();
|
||||
context.strokeStyle = color;
|
||||
context.lineWidth = 1;
|
||||
context.beginPath();
|
||||
context.moveTo(x * zoom, y * zoom);
|
||||
context.bezierCurveTo(
|
||||
x * zoom - width,
|
||||
y * zoom - height,
|
||||
x * zoom - width,
|
||||
y * zoom + height,
|
||||
x * zoom,
|
||||
y * zoom,
|
||||
);
|
||||
context.stroke();
|
||||
context.restore();
|
||||
};
|
||||
|
||||
const _renderBindableBinding = (
|
||||
binding: FixedPointBinding,
|
||||
context: CanvasRenderingContext2D,
|
||||
elementsMap: ElementsMap,
|
||||
zoom: number,
|
||||
width: number,
|
||||
height: number,
|
||||
color: string,
|
||||
) => {
|
||||
const bindable = elementsMap.get(
|
||||
binding.elementId,
|
||||
) as ExcalidrawBindableElement;
|
||||
if (!binding.fixedPoint) {
|
||||
console.warn("Binding must have a fixedPoint");
|
||||
return;
|
||||
}
|
||||
|
||||
const [x, y] = getGlobalFixedPointForBindableElement(
|
||||
binding.fixedPoint,
|
||||
bindable,
|
||||
elementsMap,
|
||||
);
|
||||
|
||||
context.save();
|
||||
context.strokeStyle = color;
|
||||
context.lineWidth = 1;
|
||||
context.beginPath();
|
||||
context.moveTo(x * zoom, y * zoom);
|
||||
context.bezierCurveTo(
|
||||
x * zoom + width,
|
||||
y * zoom + height,
|
||||
x * zoom + width,
|
||||
y * zoom - height,
|
||||
x * zoom,
|
||||
y * zoom,
|
||||
);
|
||||
context.stroke();
|
||||
context.restore();
|
||||
};
|
||||
|
||||
const renderBindings = (
|
||||
context: CanvasRenderingContext2D,
|
||||
elements: readonly OrderedExcalidrawElement[],
|
||||
zoom: number,
|
||||
) => {
|
||||
const elementsMap = arrayToMap(elements);
|
||||
const dim = 16;
|
||||
elements.forEach((element) => {
|
||||
if (element.isDeleted) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (isArrowElement(element)) {
|
||||
if (element.startBinding) {
|
||||
if (
|
||||
!elementsMap
|
||||
.get(element.startBinding.elementId)
|
||||
?.boundElements?.find((e) => e.id === element.id)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
_renderBinding(
|
||||
context,
|
||||
element.startBinding,
|
||||
elementsMap,
|
||||
zoom,
|
||||
dim,
|
||||
dim,
|
||||
element.startBinding?.mode === "orbit" ? "red" : "black",
|
||||
);
|
||||
}
|
||||
|
||||
if (element.endBinding) {
|
||||
if (
|
||||
!elementsMap
|
||||
.get(element.endBinding.elementId)
|
||||
?.boundElements?.find((e) => e.id === element.id)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
_renderBinding(
|
||||
context,
|
||||
element.endBinding,
|
||||
elementsMap,
|
||||
zoom,
|
||||
dim,
|
||||
dim,
|
||||
element.endBinding?.mode === "orbit" ? "red" : "black",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (isBindableElement(element) && element.boundElements?.length) {
|
||||
element.boundElements.forEach((boundElement) => {
|
||||
if (boundElement.type !== "arrow") {
|
||||
return;
|
||||
}
|
||||
|
||||
const arrow = elementsMap.get(
|
||||
boundElement.id,
|
||||
) as ExcalidrawArrowElement;
|
||||
|
||||
if (arrow && arrow.startBinding?.elementId === element.id) {
|
||||
_renderBindableBinding(
|
||||
arrow.startBinding,
|
||||
context,
|
||||
elementsMap,
|
||||
zoom,
|
||||
dim,
|
||||
dim,
|
||||
"green",
|
||||
);
|
||||
}
|
||||
if (arrow && arrow.endBinding?.elementId === element.id) {
|
||||
_renderBindableBinding(
|
||||
arrow.endBinding,
|
||||
context,
|
||||
elementsMap,
|
||||
zoom,
|
||||
dim,
|
||||
dim,
|
||||
"green",
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const render = (
|
||||
frame: DebugElement[],
|
||||
context: CanvasRenderingContext2D,
|
||||
@@ -289,14 +101,18 @@ const render = (
|
||||
const _debugRenderer = (
|
||||
canvas: HTMLCanvasElement,
|
||||
appState: AppState,
|
||||
elements: readonly OrderedExcalidrawElement[],
|
||||
scale: number,
|
||||
refresh: () => void,
|
||||
) => {
|
||||
const [normalizedWidth, normalizedHeight] = getNormalizedCanvasDimensions(
|
||||
canvas,
|
||||
scale,
|
||||
);
|
||||
|
||||
if (appState.height !== canvas.height || appState.width !== canvas.width) {
|
||||
refresh();
|
||||
}
|
||||
|
||||
const context = bootstrapCanvas({
|
||||
canvas,
|
||||
scale,
|
||||
@@ -313,7 +129,6 @@ const _debugRenderer = (
|
||||
);
|
||||
|
||||
renderOrigin(context, appState.zoom.value);
|
||||
renderBindings(context, elements, appState.zoom.value);
|
||||
|
||||
if (
|
||||
window.visualDebug?.currentFrame &&
|
||||
@@ -365,10 +180,10 @@ export const debugRenderer = throttleRAF(
|
||||
(
|
||||
canvas: HTMLCanvasElement,
|
||||
appState: AppState,
|
||||
elements: readonly OrderedExcalidrawElement[],
|
||||
scale: number,
|
||||
refresh: () => void,
|
||||
) => {
|
||||
_debugRenderer(canvas, appState, elements, scale);
|
||||
_debugRenderer(canvas, appState, scale, refresh);
|
||||
},
|
||||
{ trailing: true },
|
||||
);
|
||||
@@ -495,29 +310,35 @@ export const DebugFooter = ({ onChange }: { onChange: () => void }) => {
|
||||
interface DebugCanvasProps {
|
||||
appState: AppState;
|
||||
scale: number;
|
||||
ref?: React.Ref<HTMLCanvasElement>;
|
||||
}
|
||||
|
||||
const DebugCanvas = React.forwardRef<HTMLCanvasElement, DebugCanvasProps>(
|
||||
({ appState, scale }, ref) => {
|
||||
const { width, height } = appState;
|
||||
const DebugCanvas = ({ appState, scale, ref }: DebugCanvasProps) => {
|
||||
const { width, height } = appState;
|
||||
|
||||
return (
|
||||
<canvas
|
||||
style={{
|
||||
width,
|
||||
height,
|
||||
position: "absolute",
|
||||
zIndex: 2,
|
||||
pointerEvents: "none",
|
||||
}}
|
||||
width={width * scale}
|
||||
height={height * scale}
|
||||
ref={ref}
|
||||
>
|
||||
Debug Canvas
|
||||
</canvas>
|
||||
);
|
||||
},
|
||||
);
|
||||
const canvasRef = useRef<HTMLCanvasElement>(null);
|
||||
useImperativeHandle<HTMLCanvasElement | null, HTMLCanvasElement | null>(
|
||||
ref,
|
||||
() => canvasRef.current,
|
||||
[canvasRef],
|
||||
);
|
||||
|
||||
return (
|
||||
<canvas
|
||||
style={{
|
||||
width,
|
||||
height,
|
||||
position: "absolute",
|
||||
zIndex: 2,
|
||||
pointerEvents: "none",
|
||||
}}
|
||||
width={width * scale}
|
||||
height={height * scale}
|
||||
ref={canvasRef}
|
||||
>
|
||||
Debug Canvas
|
||||
</canvas>
|
||||
);
|
||||
};
|
||||
|
||||
export default DebugCanvas;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Tooltip } from "@excalidraw/excalidraw/components/Tooltip";
|
||||
import { shield } from "@excalidraw/excalidraw/components/icons";
|
||||
import { Tooltip } from "@excalidraw/excalidraw/components/Tooltip";
|
||||
import { useI18n } from "@excalidraw/excalidraw/i18n";
|
||||
|
||||
export const EncryptedIcon = () => {
|
||||
@@ -10,7 +10,7 @@ export const EncryptedIcon = () => {
|
||||
className="encrypted-icon tooltip"
|
||||
href="https://plus.excalidraw.com/blog/end-to-end-encryption"
|
||||
target="_blank"
|
||||
rel="noopener"
|
||||
rel="noopener noreferrer"
|
||||
aria-label={t("encrypted.link")}
|
||||
>
|
||||
<Tooltip label={t("encrypted.tooltip")} long={true}>
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
import { isExcalidrawPlusSignedUser } from "../app_constants";
|
||||
|
||||
export const ExcalidrawPlusAppLink = () => {
|
||||
if (!isExcalidrawPlusSignedUser) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<a
|
||||
href={`${
|
||||
import.meta.env.VITE_APP_PLUS_APP
|
||||
}?utm_source=excalidraw&utm_medium=app&utm_content=signedInUserRedirectButton#excalidraw-redirect`}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
className="plus-button"
|
||||
>
|
||||
Go to Excalidraw+
|
||||
</a>
|
||||
);
|
||||
};
|
||||
@@ -1,22 +0,0 @@
|
||||
export const ExcalidrawPlusPromoBanner = ({
|
||||
isSignedIn,
|
||||
}: {
|
||||
isSignedIn: boolean;
|
||||
}) => {
|
||||
return (
|
||||
<a
|
||||
href={
|
||||
isSignedIn
|
||||
? import.meta.env.VITE_APP_PLUS_APP
|
||||
: `${
|
||||
import.meta.env.VITE_APP_PLUS_LP
|
||||
}/plus?utm_source=excalidraw&utm_medium=app&utm_content=guestBanner#excalidraw-redirect`
|
||||
}
|
||||
target="_blank"
|
||||
rel="noopener"
|
||||
className="plus-banner"
|
||||
>
|
||||
Excalidraw+
|
||||
</a>
|
||||
);
|
||||
};
|
||||
@@ -1,33 +1,31 @@
|
||||
import React from "react";
|
||||
import { uploadBytes, ref } from "firebase/storage";
|
||||
import { nanoid } from "nanoid";
|
||||
|
||||
import { trackEvent } from "@excalidraw/excalidraw/analytics";
|
||||
import { Card } from "@excalidraw/excalidraw/components/Card";
|
||||
import { ExcalidrawLogo } from "@excalidraw/excalidraw/components/ExcalidrawLogo";
|
||||
import { ToolButton } from "@excalidraw/excalidraw/components/ToolButton";
|
||||
import { MIME_TYPES, getFrame } from "@excalidraw/common";
|
||||
import {
|
||||
encryptData,
|
||||
generateEncryptionKey,
|
||||
} from "@excalidraw/excalidraw/data/encryption";
|
||||
import { serializeAsJSON } from "@excalidraw/excalidraw/data/json";
|
||||
import { isInitializedImageElement } from "@excalidraw/element";
|
||||
import { useI18n } from "@excalidraw/excalidraw/i18n";
|
||||
|
||||
import { loadFirebaseStorage, saveFilesToFirebase } from "../data/firebase";
|
||||
import type {
|
||||
FileId,
|
||||
NonDeletedExcalidrawElement,
|
||||
} from "@excalidraw/element/types";
|
||||
} from "@excalidraw/excalidraw/element/types";
|
||||
import type {
|
||||
AppState,
|
||||
BinaryFileData,
|
||||
BinaryFiles,
|
||||
} from "@excalidraw/excalidraw/types";
|
||||
|
||||
import { nanoid } from "nanoid";
|
||||
import { useI18n } from "@excalidraw/excalidraw/i18n";
|
||||
import {
|
||||
encryptData,
|
||||
generateEncryptionKey,
|
||||
} from "@excalidraw/excalidraw/data/encryption";
|
||||
import { isInitializedImageElement } from "@excalidraw/excalidraw/element/typeChecks";
|
||||
import { FILE_UPLOAD_MAX_BYTES } from "../app_constants";
|
||||
import { encodeFilesForUpload } from "../data/FileManager";
|
||||
import { loadFirebaseStorage, saveFilesToFirebase } from "../data/firebase";
|
||||
import { uploadBytes, ref } from "firebase/storage";
|
||||
import { MIME_TYPES } from "@excalidraw/excalidraw/constants";
|
||||
import { trackEvent } from "@excalidraw/excalidraw/analytics";
|
||||
import { getFrame } from "@excalidraw/excalidraw/utils";
|
||||
import { ExcalidrawLogo } from "@excalidraw/excalidraw/components/ExcalidrawLogo";
|
||||
|
||||
export const exportToExcalidrawPlus = async (
|
||||
elements: readonly NonDeletedExcalidrawElement[],
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import { THEME } from "@excalidraw/common";
|
||||
import oc from "open-color";
|
||||
import React from "react";
|
||||
|
||||
import type { Theme } from "@excalidraw/element/types";
|
||||
import { THEME } from "@excalidraw/excalidraw/constants";
|
||||
import type { Theme } from "@excalidraw/excalidraw/element/types";
|
||||
|
||||
// https://github.com/tholman/github-corners
|
||||
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 * as Sentry from "@sentry/browser";
|
||||
import { t } from "@excalidraw/excalidraw/i18n";
|
||||
import Trans from "@excalidraw/excalidraw/components/Trans";
|
||||
|
||||
interface TopErrorBoundaryState {
|
||||
hasError: boolean;
|
||||
|
||||
@@ -1,15 +1,14 @@
|
||||
import { CaptureUpdateAction } from "@excalidraw/excalidraw";
|
||||
import { compressData } from "@excalidraw/excalidraw/data/encode";
|
||||
import { newElementWith } from "@excalidraw/element";
|
||||
import { isInitializedImageElement } from "@excalidraw/element";
|
||||
import { t } from "@excalidraw/excalidraw/i18n";
|
||||
|
||||
import { newElementWith } from "@excalidraw/excalidraw/element/mutateElement";
|
||||
import { isInitializedImageElement } from "@excalidraw/excalidraw/element/typeChecks";
|
||||
import type {
|
||||
ExcalidrawElement,
|
||||
ExcalidrawImageElement,
|
||||
FileId,
|
||||
InitializedExcalidrawImageElement,
|
||||
} from "@excalidraw/element/types";
|
||||
} from "@excalidraw/excalidraw/element/types";
|
||||
import { t } from "@excalidraw/excalidraw/i18n";
|
||||
import type {
|
||||
BinaryFileData,
|
||||
BinaryFileMetadata,
|
||||
|
||||
@@ -10,12 +10,6 @@
|
||||
* (localStorage, indexedDB).
|
||||
*/
|
||||
|
||||
import { clearAppStateForLocalStorage } from "@excalidraw/excalidraw/appState";
|
||||
import {
|
||||
CANVAS_SEARCH_TAB,
|
||||
DEFAULT_SIDEBAR,
|
||||
debounce,
|
||||
} from "@excalidraw/common";
|
||||
import {
|
||||
createStore,
|
||||
entries,
|
||||
@@ -25,30 +19,32 @@ import {
|
||||
setMany,
|
||||
get,
|
||||
} from "idb-keyval";
|
||||
|
||||
import { appJotaiStore, atom } from "excalidraw-app/app-jotai";
|
||||
import { getNonDeletedElements } from "@excalidraw/element";
|
||||
|
||||
import { clearAppStateForLocalStorage } from "@excalidraw/excalidraw/appState";
|
||||
import {
|
||||
CANVAS_SEARCH_TAB,
|
||||
DEFAULT_SIDEBAR,
|
||||
} from "@excalidraw/excalidraw/constants";
|
||||
import type { LibraryPersistedData } from "@excalidraw/excalidraw/data/library";
|
||||
import type { ImportedDataState } from "@excalidraw/excalidraw/data/types";
|
||||
import type { ExcalidrawElement, FileId } from "@excalidraw/element/types";
|
||||
import { clearElementsForLocalStorage } from "@excalidraw/excalidraw/element";
|
||||
import type {
|
||||
ExcalidrawElement,
|
||||
FileId,
|
||||
} from "@excalidraw/excalidraw/element/types";
|
||||
import type {
|
||||
AppState,
|
||||
BinaryFileData,
|
||||
BinaryFiles,
|
||||
} from "@excalidraw/excalidraw/types";
|
||||
import type { MaybePromise } from "@excalidraw/common/utility-types";
|
||||
|
||||
import type { MaybePromise } from "@excalidraw/excalidraw/utility-types";
|
||||
import { debounce } from "@excalidraw/excalidraw/utils";
|
||||
import { SAVE_TO_LOCAL_STORAGE_TIMEOUT, STORAGE_KEYS } from "../app_constants";
|
||||
|
||||
import { FileManager } from "./FileManager";
|
||||
import { Locker } from "./Locker";
|
||||
import { updateBrowserStateVersion } from "./tabSync";
|
||||
|
||||
const filesStore = createStore("files-db", "files-store");
|
||||
|
||||
export const localStorageQuotaExceededAtom = atom(false);
|
||||
|
||||
class LocalFileManager extends FileManager {
|
||||
clearObsoleteFiles = async (opts: { currentFileIds: FileId[] }) => {
|
||||
await entries(filesStore).then((entries) => {
|
||||
@@ -73,9 +69,6 @@ const saveDataStateToLocalStorage = (
|
||||
elements: readonly ExcalidrawElement[],
|
||||
appState: AppState,
|
||||
) => {
|
||||
const localStorageQuotaExceeded = appJotaiStore.get(
|
||||
localStorageQuotaExceededAtom,
|
||||
);
|
||||
try {
|
||||
const _appState = clearAppStateForLocalStorage(appState);
|
||||
|
||||
@@ -88,29 +81,19 @@ const saveDataStateToLocalStorage = (
|
||||
|
||||
localStorage.setItem(
|
||||
STORAGE_KEYS.LOCAL_STORAGE_ELEMENTS,
|
||||
JSON.stringify(getNonDeletedElements(elements)),
|
||||
JSON.stringify(clearElementsForLocalStorage(elements)),
|
||||
);
|
||||
localStorage.setItem(
|
||||
STORAGE_KEYS.LOCAL_STORAGE_APP_STATE,
|
||||
JSON.stringify(_appState),
|
||||
);
|
||||
updateBrowserStateVersion(STORAGE_KEYS.VERSION_DATA_STATE);
|
||||
if (localStorageQuotaExceeded) {
|
||||
appJotaiStore.set(localStorageQuotaExceededAtom, false);
|
||||
}
|
||||
} catch (error: any) {
|
||||
// Unable to access window.localStorage
|
||||
console.error(error);
|
||||
if (isQuotaExceededError(error) && !localStorageQuotaExceeded) {
|
||||
appJotaiStore.set(localStorageQuotaExceededAtom, true);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const isQuotaExceededError = (error: any) => {
|
||||
return error instanceof DOMException && error.name === "QuotaExceededError";
|
||||
};
|
||||
|
||||
type SavingLockTypes = "collaboration";
|
||||
|
||||
export class LocalData {
|
||||
|
||||
@@ -1,12 +1,27 @@
|
||||
import { reconcileElements } from "@excalidraw/excalidraw";
|
||||
import { MIME_TYPES } from "@excalidraw/common";
|
||||
import type {
|
||||
ExcalidrawElement,
|
||||
FileId,
|
||||
OrderedExcalidrawElement,
|
||||
} from "@excalidraw/excalidraw/element/types";
|
||||
import { getSceneVersion } from "@excalidraw/excalidraw/element";
|
||||
import type Portal from "../collab/Portal";
|
||||
import { restoreElements } from "@excalidraw/excalidraw/data/restore";
|
||||
import type {
|
||||
AppState,
|
||||
BinaryFileData,
|
||||
BinaryFileMetadata,
|
||||
DataURL,
|
||||
} from "@excalidraw/excalidraw/types";
|
||||
import { FILE_CACHE_MAX_AGE_SEC } from "../app_constants";
|
||||
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 { MIME_TYPES } from "@excalidraw/excalidraw/constants";
|
||||
import type { SyncableExcalidrawElement } from ".";
|
||||
import { getSyncableElements } from ".";
|
||||
import { initializeApp } from "firebase/app";
|
||||
import {
|
||||
getFirestore,
|
||||
@@ -16,27 +31,8 @@ import {
|
||||
Bytes,
|
||||
} from "firebase/firestore";
|
||||
import { getStorage, ref, uploadBytes } from "firebase/storage";
|
||||
|
||||
import type { RemoteExcalidrawElement } from "@excalidraw/excalidraw/data/reconcile";
|
||||
import type {
|
||||
ExcalidrawElement,
|
||||
FileId,
|
||||
OrderedExcalidrawElement,
|
||||
} from "@excalidraw/element/types";
|
||||
import type {
|
||||
AppState,
|
||||
BinaryFileData,
|
||||
BinaryFileMetadata,
|
||||
DataURL,
|
||||
} from "@excalidraw/excalidraw/types";
|
||||
|
||||
import { FILE_CACHE_MAX_AGE_SEC } from "../app_constants";
|
||||
|
||||
import { getSyncableElements } from ".";
|
||||
|
||||
import type { SyncableExcalidrawElement } from ".";
|
||||
import type Portal from "../collab/Portal";
|
||||
import type { Socket } from "socket.io-client";
|
||||
import type { RemoteExcalidrawElement } from "@excalidraw/excalidraw/data/reconcile";
|
||||
|
||||
// private
|
||||
// -----------------------------------------------------------------------------
|
||||
@@ -105,8 +101,8 @@ const decryptElements = async (
|
||||
data: FirebaseStoredScene,
|
||||
roomKey: string,
|
||||
): Promise<readonly ExcalidrawElement[]> => {
|
||||
const ciphertext = data.ciphertext.toUint8Array() as Uint8Array<ArrayBuffer>;
|
||||
const iv = data.iv.toUint8Array() as Uint8Array<ArrayBuffer>;
|
||||
const ciphertext = data.ciphertext.toUint8Array();
|
||||
const iv = data.iv.toUint8Array();
|
||||
|
||||
const decrypted = await decryptData(iv, ciphertext, roomKey);
|
||||
const decodedData = new TextDecoder("utf-8").decode(
|
||||
@@ -259,9 +255,7 @@ export const loadFromFirebase = async (
|
||||
}
|
||||
const storedScene = docSnap.data() as FirebaseStoredScene;
|
||||
const elements = getSyncableElements(
|
||||
restoreElements(await decryptElements(storedScene, roomKey), null, {
|
||||
deleteInvisibleElements: true,
|
||||
}),
|
||||
restoreElements(await decryptElements(storedScene, roomKey), null),
|
||||
);
|
||||
|
||||
if (socket) {
|
||||
|
||||
@@ -9,38 +9,34 @@ import {
|
||||
} from "@excalidraw/excalidraw/data/encryption";
|
||||
import { serializeAsJSON } from "@excalidraw/excalidraw/data/json";
|
||||
import { restore } from "@excalidraw/excalidraw/data/restore";
|
||||
import { isInvisiblySmallElement } from "@excalidraw/element";
|
||||
import { isInitializedImageElement } from "@excalidraw/element";
|
||||
import { t } from "@excalidraw/excalidraw/i18n";
|
||||
import { bytesToHexString } from "@excalidraw/common";
|
||||
|
||||
import type { UserIdleState } from "@excalidraw/common";
|
||||
import type { ImportedDataState } from "@excalidraw/excalidraw/data/types";
|
||||
import type { SceneBounds } from "@excalidraw/element";
|
||||
import type { SceneBounds } from "@excalidraw/excalidraw/element/bounds";
|
||||
import { isInvisiblySmallElement } from "@excalidraw/excalidraw/element/sizeHelpers";
|
||||
import { isInitializedImageElement } from "@excalidraw/excalidraw/element/typeChecks";
|
||||
import type {
|
||||
ExcalidrawElement,
|
||||
FileId,
|
||||
OrderedExcalidrawElement,
|
||||
} from "@excalidraw/element/types";
|
||||
} from "@excalidraw/excalidraw/element/types";
|
||||
import { t } from "@excalidraw/excalidraw/i18n";
|
||||
import type {
|
||||
AppState,
|
||||
BinaryFileData,
|
||||
BinaryFiles,
|
||||
SocketId,
|
||||
} from "@excalidraw/excalidraw/types";
|
||||
import type { MakeBrand } from "@excalidraw/common/utility-types";
|
||||
|
||||
import type { UserIdleState } from "@excalidraw/excalidraw/constants";
|
||||
import type { MakeBrand } from "@excalidraw/excalidraw/utility-types";
|
||||
import { bytesToHexString } from "@excalidraw/excalidraw/utils";
|
||||
import type { WS_SUBTYPES } from "../app_constants";
|
||||
import {
|
||||
DELETED_ELEMENT_TIMEOUT,
|
||||
FILE_UPLOAD_MAX_BYTES,
|
||||
ROOM_ID_BYTES,
|
||||
} from "../app_constants";
|
||||
|
||||
import { encodeFilesForUpload } from "./FileManager";
|
||||
import { saveFilesToFirebase } from "./firebase";
|
||||
|
||||
import type { WS_SUBTYPES } from "../app_constants";
|
||||
|
||||
export type SyncableExcalidrawElement = OrderedExcalidrawElement &
|
||||
MakeBrand<"SyncableExcalidrawElement">;
|
||||
|
||||
@@ -258,16 +254,11 @@ export const loadScene = async (
|
||||
await importFromBackend(id, privateKey),
|
||||
localDataState?.appState,
|
||||
localDataState?.elements,
|
||||
{
|
||||
repairBindings: true,
|
||||
refreshDimensions: false,
|
||||
deleteInvisibleElements: true,
|
||||
},
|
||||
{ repairBindings: true, refreshDimensions: false },
|
||||
);
|
||||
} else {
|
||||
data = restore(localDataState || null, null, null, {
|
||||
repairBindings: true,
|
||||
deleteInvisibleElements: true,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
import type { ExcalidrawElement } from "@excalidraw/excalidraw/element/types";
|
||||
import type { AppState } from "@excalidraw/excalidraw/types";
|
||||
import {
|
||||
clearAppStateForLocalStorage,
|
||||
getDefaultAppState,
|
||||
} from "@excalidraw/excalidraw/appState";
|
||||
|
||||
import type { ExcalidrawElement } from "@excalidraw/element/types";
|
||||
import type { AppState } from "@excalidraw/excalidraw/types";
|
||||
|
||||
import { clearElementsForLocalStorage } from "@excalidraw/excalidraw/element";
|
||||
import { STORAGE_KEYS } from "../app_constants";
|
||||
|
||||
export const saveUsernameToLocalStorage = (username: string) => {
|
||||
@@ -49,7 +48,7 @@ export const importFromLocalStorage = () => {
|
||||
let elements: ExcalidrawElement[] = [];
|
||||
if (savedElements) {
|
||||
try {
|
||||
elements = JSON.parse(savedElements);
|
||||
elements = clearElementsForLocalStorage(JSON.parse(savedElements));
|
||||
} catch (error: any) {
|
||||
console.error(error);
|
||||
// Do nothing because elements array is already empty
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>Excalidraw Whiteboard</title>
|
||||
<title>Excalidraw | Hand-drawn look & feel • Collaborative • Secure</title>
|
||||
<meta
|
||||
name="viewport"
|
||||
content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no, viewport-fit=cover, shrink-to-fit=no"
|
||||
@@ -14,7 +14,7 @@
|
||||
<!-- Primary Meta Tags -->
|
||||
<meta
|
||||
name="title"
|
||||
content="Free, collaborative whiteboard • Hand-drawn look & feel | Excalidraw"
|
||||
content="Excalidraw — Collaborative whiteboarding made easy"
|
||||
/>
|
||||
<meta
|
||||
name="description"
|
||||
|
||||
+14
-27
@@ -1,5 +1,3 @@
|
||||
@import "../packages/excalidraw/css/variables.module.scss";
|
||||
|
||||
.excalidraw {
|
||||
--color-primary-contrast-offset: #625ee0; // to offset Chubb illusion
|
||||
|
||||
@@ -7,6 +5,12 @@
|
||||
--color-primary-contrast-offset: #726dff; // to offset Chubb illusion
|
||||
}
|
||||
|
||||
.top-right-ui {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.footer-center {
|
||||
justify-content: flex-end;
|
||||
margin-top: auto;
|
||||
@@ -54,7 +58,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
.alert {
|
||||
.collab-offline-warning {
|
||||
pointer-events: none;
|
||||
position: absolute;
|
||||
top: 6.5rem;
|
||||
@@ -65,18 +69,10 @@
|
||||
text-align: center;
|
||||
line-height: 1.5;
|
||||
border-radius: var(--border-radius-md);
|
||||
background-color: var(--color-warning);
|
||||
color: var(--color-text-warning);
|
||||
z-index: 6;
|
||||
white-space: pre;
|
||||
|
||||
&--warning {
|
||||
background-color: var(--color-warning);
|
||||
color: var(--color-text-warning);
|
||||
}
|
||||
|
||||
&--danger {
|
||||
background-color: var(--color-danger-dark);
|
||||
color: var(--color-danger-text);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -86,31 +82,22 @@
|
||||
}
|
||||
}
|
||||
|
||||
.plus-banner {
|
||||
.plus-button {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
align-items: center;
|
||||
border: 1px solid var(--color-primary);
|
||||
padding: 0.5rem 0.875rem;
|
||||
padding: 0.5rem 0.75rem;
|
||||
border-radius: var(--border-radius-lg);
|
||||
background-color: var(--island-bg-color);
|
||||
color: var(--color-primary) !important;
|
||||
text-decoration: none !important;
|
||||
|
||||
font-family: var(--ui-font);
|
||||
font-size: 0.8333rem;
|
||||
font-size: 0.75rem;
|
||||
box-sizing: border-box;
|
||||
height: var(--lg-button-size);
|
||||
|
||||
border: none;
|
||||
box-shadow: 0 0 0 1px var(--color-surface-lowest);
|
||||
background-color: var(--color-surface-low);
|
||||
color: var(--button-color, var(--color-on-surface)) !important;
|
||||
|
||||
&:active {
|
||||
box-shadow: 0 0 0 1px var(--color-brand-active);
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: var(--color-primary);
|
||||
color: white !important;
|
||||
@@ -122,7 +109,7 @@
|
||||
}
|
||||
|
||||
.theme--dark {
|
||||
.plus-banner {
|
||||
.plus-button {
|
||||
&:hover {
|
||||
color: black !important;
|
||||
}
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
import { StrictMode } from "react";
|
||||
import { createRoot } from "react-dom/client";
|
||||
import ExcalidrawApp from "./App";
|
||||
import { registerSW } from "virtual:pwa-register";
|
||||
|
||||
import "../excalidraw-app/sentry";
|
||||
|
||||
import ExcalidrawApp from "./App";
|
||||
|
||||
window.__EXCALIDRAW_SHA__ = import.meta.env.VITE_APP_GIT_SHA;
|
||||
const rootElement = document.getElementById("root")!;
|
||||
const root = createRoot(rootElement);
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
]
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18.0.0"
|
||||
"node": "18.0.0 - 22.x.x"
|
||||
},
|
||||
"dependencies": {
|
||||
"@excalidraw/random-username": "1.0.0",
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { getFeatureFlag } from "@excalidraw/common";
|
||||
import * as Sentry from "@sentry/browser";
|
||||
import callsites from "callsites";
|
||||
|
||||
@@ -34,7 +33,6 @@ Sentry.init({
|
||||
Sentry.captureConsoleIntegration({
|
||||
levels: ["error"],
|
||||
}),
|
||||
Sentry.featureFlagsIntegration(),
|
||||
],
|
||||
beforeSend(event) {
|
||||
if (event.request?.url) {
|
||||
@@ -81,14 +79,3 @@ Sentry.init({
|
||||
return event;
|
||||
},
|
||||
});
|
||||
|
||||
const flagsIntegration =
|
||||
Sentry.getClient()?.getIntegrationByName<Sentry.FeatureFlagsIntegration>(
|
||||
"FeatureFlags",
|
||||
);
|
||||
if (flagsIntegration) {
|
||||
flagsIntegration.addFeatureFlag(
|
||||
"COMPLEX_BINDINGS",
|
||||
getFeatureFlag("COMPLEX_BINDINGS"),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import { trackEvent } from "@excalidraw/excalidraw/analytics";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import { copyTextToSystemClipboard } from "@excalidraw/excalidraw/clipboard";
|
||||
import { trackEvent } from "@excalidraw/excalidraw/analytics";
|
||||
import { getFrame } from "@excalidraw/excalidraw/utils";
|
||||
import { useI18n } from "@excalidraw/excalidraw/i18n";
|
||||
import { KEYS } from "@excalidraw/excalidraw/keys";
|
||||
import { Dialog } from "@excalidraw/excalidraw/components/Dialog";
|
||||
import { FilledButton } from "@excalidraw/excalidraw/components/FilledButton";
|
||||
import { TextField } from "@excalidraw/excalidraw/components/TextField";
|
||||
import {
|
||||
copyIcon,
|
||||
LinkIcon,
|
||||
@@ -12,19 +14,16 @@ import {
|
||||
shareIOS,
|
||||
shareWindows,
|
||||
} from "@excalidraw/excalidraw/components/icons";
|
||||
import { TextField } from "@excalidraw/excalidraw/components/TextField";
|
||||
import { FilledButton } from "@excalidraw/excalidraw/components/FilledButton";
|
||||
import type { CollabAPI } from "../collab/Collab";
|
||||
import { activeRoomLinkAtom } from "../collab/Collab";
|
||||
import { useUIAppState } from "@excalidraw/excalidraw/context/ui-appState";
|
||||
import { useCopyStatus } from "@excalidraw/excalidraw/hooks/useCopiedIndicator";
|
||||
import { useI18n } from "@excalidraw/excalidraw/i18n";
|
||||
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 "./ShareDialog.scss";
|
||||
|
||||
import type { CollabAPI } from "../collab/Collab";
|
||||
|
||||
type OnExportToBackend = () => void;
|
||||
type ShareDialogType = "share" | "collaborationOnly";
|
||||
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import { UI } from "@excalidraw/excalidraw/tests/helpers/ui";
|
||||
import ExcalidrawApp from "../App";
|
||||
import {
|
||||
mockBoundingClientRect,
|
||||
render,
|
||||
restoreOriginalGetBoundingClientRect,
|
||||
} from "@excalidraw/excalidraw/tests/test-utils";
|
||||
|
||||
import ExcalidrawApp from "../App";
|
||||
import { UI } from "@excalidraw/excalidraw/tests/helpers/ui";
|
||||
|
||||
describe("Test MobileMenu", () => {
|
||||
const { h } = window;
|
||||
@@ -17,15 +17,30 @@ describe("Test MobileMenu", () => {
|
||||
|
||||
beforeEach(async () => {
|
||||
await render(<ExcalidrawApp />);
|
||||
h.app.refreshEditorInterface();
|
||||
// @ts-ignore
|
||||
h.app.refreshViewportBreakpoints();
|
||||
// @ts-ignore
|
||||
h.app.refreshEditorBreakpoints();
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
restoreOriginalGetBoundingClientRect();
|
||||
});
|
||||
|
||||
it("should set editor interface correctly", () => {
|
||||
expect(h.app.editorInterface.formFactor).toBe("phone");
|
||||
it("should set device correctly", () => {
|
||||
expect(h.app.device).toMatchInlineSnapshot(`
|
||||
{
|
||||
"editor": {
|
||||
"canFitSidebar": false,
|
||||
"isMobile": true,
|
||||
},
|
||||
"isTouchScreen": false,
|
||||
"viewport": {
|
||||
"isLandscape": false,
|
||||
"isMobile": true,
|
||||
},
|
||||
}
|
||||
`);
|
||||
});
|
||||
|
||||
it("should initialize with welcome screen and hide once user interacts", async () => {
|
||||
|
||||
@@ -198,7 +198,7 @@ exports[`Test MobileMenu > should initialize with welcome screen and hide once u
|
||||
<a
|
||||
class="welcome-screen-menu-item "
|
||||
href="undefined/plus?utm_source=excalidraw&utm_medium=app&utm_content=welcomeScreenGuest"
|
||||
rel="noopener"
|
||||
rel="noreferrer"
|
||||
target="_blank"
|
||||
>
|
||||
<div
|
||||
|
||||
@@ -1,18 +1,13 @@
|
||||
import { CaptureUpdateAction, newElementWith } from "@excalidraw/excalidraw";
|
||||
import { vi } from "vitest";
|
||||
import { act, render, waitFor } from "@excalidraw/excalidraw/tests/test-utils";
|
||||
import ExcalidrawApp from "../App";
|
||||
import { API } from "@excalidraw/excalidraw/tests/helpers/api";
|
||||
import { syncInvalidIndices } from "@excalidraw/excalidraw/fractionalIndex";
|
||||
import {
|
||||
createRedoAction,
|
||||
createUndoAction,
|
||||
} from "@excalidraw/excalidraw/actions/actionHistory";
|
||||
import { syncInvalidIndices } from "@excalidraw/element";
|
||||
import { API } from "@excalidraw/excalidraw/tests/helpers/api";
|
||||
import { act, render, waitFor } from "@excalidraw/excalidraw/tests/test-utils";
|
||||
import { vi } from "vitest";
|
||||
|
||||
import { StoreIncrement } from "@excalidraw/element";
|
||||
|
||||
import type { DurableIncrement, EphemeralIncrement } from "@excalidraw/element";
|
||||
|
||||
import ExcalidrawApp from "../App";
|
||||
import { CaptureUpdateAction, newElementWith } from "@excalidraw/excalidraw";
|
||||
|
||||
const { h } = window;
|
||||
|
||||
@@ -69,79 +64,6 @@ vi.mock("socket.io-client", () => {
|
||||
* i.e. multiplayer history tests could be a good first candidate, as we could test both history stacks simultaneously.
|
||||
*/
|
||||
describe("collaboration", () => {
|
||||
it("should emit two ephemeral increments even though updates get batched", async () => {
|
||||
const durableIncrements: DurableIncrement[] = [];
|
||||
const ephemeralIncrements: EphemeralIncrement[] = [];
|
||||
|
||||
await render(<ExcalidrawApp />);
|
||||
|
||||
h.store.onStoreIncrementEmitter.on((increment) => {
|
||||
if (StoreIncrement.isDurable(increment)) {
|
||||
durableIncrements.push(increment);
|
||||
} else {
|
||||
ephemeralIncrements.push(increment);
|
||||
}
|
||||
});
|
||||
|
||||
// eslint-disable-next-line dot-notation
|
||||
expect(h.store["scheduledMicroActions"].length).toBe(0);
|
||||
expect(durableIncrements.length).toBe(0);
|
||||
expect(ephemeralIncrements.length).toBe(0);
|
||||
|
||||
const rectProps = {
|
||||
type: "rectangle",
|
||||
id: "A",
|
||||
height: 200,
|
||||
width: 100,
|
||||
x: 0,
|
||||
y: 0,
|
||||
} as const;
|
||||
|
||||
const rect = API.createElement({ ...rectProps });
|
||||
|
||||
API.updateScene({
|
||||
elements: [rect],
|
||||
captureUpdate: CaptureUpdateAction.IMMEDIATELY,
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
// expect(commitSpy).toHaveBeenCalledTimes(1);
|
||||
expect(durableIncrements.length).toBe(1);
|
||||
});
|
||||
|
||||
// simulate two batched remote updates
|
||||
act(() => {
|
||||
h.app.updateScene({
|
||||
elements: [newElementWith(h.elements[0], { x: 100 })],
|
||||
captureUpdate: CaptureUpdateAction.NEVER,
|
||||
});
|
||||
h.app.updateScene({
|
||||
elements: [newElementWith(h.elements[0], { x: 200 })],
|
||||
captureUpdate: CaptureUpdateAction.NEVER,
|
||||
});
|
||||
|
||||
// we scheduled two micro actions,
|
||||
// which confirms they are going to be executed as part of one batched component update
|
||||
// eslint-disable-next-line dot-notation
|
||||
expect(h.store["scheduledMicroActions"].length).toBe(2);
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
// altough the updates get batched,
|
||||
// we expect two ephemeral increments for each update,
|
||||
// and each such update should have the expected change
|
||||
expect(ephemeralIncrements.length).toBe(2);
|
||||
expect(ephemeralIncrements[0].change.elements.A).toEqual(
|
||||
expect.objectContaining({ x: 100 }),
|
||||
);
|
||||
expect(ephemeralIncrements[1].change.elements.A).toEqual(
|
||||
expect.objectContaining({ x: 200 }),
|
||||
);
|
||||
// eslint-disable-next-line dot-notation
|
||||
expect(h.store["scheduledMicroActions"].length).toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
it("should allow to undo / redo even on force-deleted elements", async () => {
|
||||
await render(<ExcalidrawApp />);
|
||||
const rect1Props = {
|
||||
@@ -199,13 +121,12 @@ describe("collaboration", () => {
|
||||
expect(h.elements).toEqual([expect.objectContaining(rect1Props)]);
|
||||
});
|
||||
|
||||
const undoAction = createUndoAction(h.history);
|
||||
const undoAction = createUndoAction(h.history, h.store);
|
||||
act(() => h.app.actionManager.executeAction(undoAction));
|
||||
|
||||
// with explicit undo (as addition) we expect our item to be restored from the snapshot!
|
||||
await waitFor(() => {
|
||||
expect(API.getUndoStack().length).toBe(1);
|
||||
expect(API.getRedoStack().length).toBe(1);
|
||||
expect(API.getSnapshot()).toEqual([
|
||||
expect.objectContaining(rect1Props),
|
||||
expect.objectContaining({ ...rect2Props, isDeleted: false }),
|
||||
@@ -232,7 +153,7 @@ describe("collaboration", () => {
|
||||
expect(h.elements).toEqual([expect.objectContaining(rect1Props)]);
|
||||
});
|
||||
|
||||
const redoAction = createRedoAction(h.history);
|
||||
const redoAction = createRedoAction(h.history, h.store);
|
||||
act(() => h.app.actionManager.executeAction(redoAction));
|
||||
|
||||
// with explicit redo (as removal) we again restore the element from the snapshot!
|
||||
@@ -248,5 +169,79 @@ describe("collaboration", () => {
|
||||
expect.objectContaining({ ...rect2Props, isDeleted: true }),
|
||||
]);
|
||||
});
|
||||
|
||||
act(() => h.app.actionManager.executeAction(undoAction));
|
||||
|
||||
// simulate local update
|
||||
API.updateScene({
|
||||
elements: syncInvalidIndices([
|
||||
h.elements[0],
|
||||
newElementWith(h.elements[1], { x: 100 }),
|
||||
]),
|
||||
captureUpdate: CaptureUpdateAction.IMMEDIATELY,
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(API.getUndoStack().length).toBe(2);
|
||||
expect(API.getRedoStack().length).toBe(0);
|
||||
expect(API.getSnapshot()).toEqual([
|
||||
expect.objectContaining(rect1Props),
|
||||
expect.objectContaining({ ...rect2Props, isDeleted: false, x: 100 }),
|
||||
]);
|
||||
expect(h.elements).toEqual([
|
||||
expect.objectContaining(rect1Props),
|
||||
expect.objectContaining({ ...rect2Props, isDeleted: false, x: 100 }),
|
||||
]);
|
||||
});
|
||||
|
||||
act(() => h.app.actionManager.executeAction(undoAction));
|
||||
|
||||
// we expect to iterate the stack to the first visible change
|
||||
await waitFor(() => {
|
||||
expect(API.getUndoStack().length).toBe(1);
|
||||
expect(API.getRedoStack().length).toBe(1);
|
||||
expect(API.getSnapshot()).toEqual([
|
||||
expect.objectContaining(rect1Props),
|
||||
expect.objectContaining({ ...rect2Props, isDeleted: false, x: 0 }),
|
||||
]);
|
||||
expect(h.elements).toEqual([
|
||||
expect.objectContaining(rect1Props),
|
||||
expect.objectContaining({ ...rect2Props, isDeleted: false, x: 0 }),
|
||||
]);
|
||||
});
|
||||
|
||||
// simulate force deleting the element remotely
|
||||
API.updateScene({
|
||||
elements: syncInvalidIndices([rect1]),
|
||||
captureUpdate: CaptureUpdateAction.NEVER,
|
||||
});
|
||||
|
||||
// snapshot was correctly updated and marked the element as deleted
|
||||
await waitFor(() => {
|
||||
expect(API.getUndoStack().length).toBe(1);
|
||||
expect(API.getRedoStack().length).toBe(1);
|
||||
expect(API.getSnapshot()).toEqual([
|
||||
expect.objectContaining(rect1Props),
|
||||
expect.objectContaining({ ...rect2Props, isDeleted: true, x: 0 }),
|
||||
]);
|
||||
expect(h.elements).toEqual([expect.objectContaining(rect1Props)]);
|
||||
});
|
||||
|
||||
act(() => h.app.actionManager.executeAction(redoAction));
|
||||
|
||||
// with explicit redo (as update) we again restored the element from the snapshot!
|
||||
await waitFor(() => {
|
||||
expect(API.getUndoStack().length).toBe(2);
|
||||
expect(API.getRedoStack().length).toBe(0);
|
||||
expect(API.getSnapshot()).toEqual([
|
||||
expect.objectContaining({ id: "A", isDeleted: false }),
|
||||
expect.objectContaining({ id: "B", isDeleted: true, x: 100 }),
|
||||
]);
|
||||
expect(h.history.isRedoStackEmpty).toBeTruthy();
|
||||
expect(h.elements).toEqual([
|
||||
expect.objectContaining({ id: "A", isDeleted: false }),
|
||||
expect.objectContaining({ id: "B", isDeleted: true, x: 100 }),
|
||||
]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import { THEME } from "@excalidraw/excalidraw";
|
||||
import { EVENT, CODES, KEYS } from "@excalidraw/common";
|
||||
import { useEffect, useLayoutEffect, useState } from "react";
|
||||
|
||||
import type { Theme } from "@excalidraw/element/types";
|
||||
|
||||
import { THEME } from "@excalidraw/excalidraw";
|
||||
import { EVENT } from "@excalidraw/excalidraw/constants";
|
||||
import type { Theme } from "@excalidraw/excalidraw/element/types";
|
||||
import { CODES, KEYS } from "@excalidraw/excalidraw/keys";
|
||||
import { STORAGE_KEYS } from "./app_constants";
|
||||
|
||||
const getDarkThemeMediaQuery = (): MediaQueryList | undefined =>
|
||||
|
||||
@@ -23,57 +23,29 @@ export default defineConfig(({ mode }) => {
|
||||
envDir: "../",
|
||||
resolve: {
|
||||
alias: [
|
||||
{
|
||||
find: /^@excalidraw\/common$/,
|
||||
replacement: path.resolve(
|
||||
__dirname,
|
||||
"../packages/common/src/index.ts",
|
||||
),
|
||||
},
|
||||
{
|
||||
find: /^@excalidraw\/common\/(.*?)/,
|
||||
replacement: path.resolve(__dirname, "../packages/common/src/$1"),
|
||||
},
|
||||
{
|
||||
find: /^@excalidraw\/element$/,
|
||||
replacement: path.resolve(
|
||||
__dirname,
|
||||
"../packages/element/src/index.ts",
|
||||
),
|
||||
},
|
||||
{
|
||||
find: /^@excalidraw\/element\/(.*?)/,
|
||||
replacement: path.resolve(__dirname, "../packages/element/src/$1"),
|
||||
},
|
||||
{
|
||||
find: /^@excalidraw\/excalidraw$/,
|
||||
replacement: path.resolve(
|
||||
__dirname,
|
||||
"../packages/excalidraw/index.tsx",
|
||||
),
|
||||
replacement: path.resolve(__dirname, "../packages/excalidraw/index.tsx"),
|
||||
},
|
||||
{
|
||||
find: /^@excalidraw\/excalidraw\/(.*?)/,
|
||||
replacement: path.resolve(__dirname, "../packages/excalidraw/$1"),
|
||||
},
|
||||
{
|
||||
find: /^@excalidraw\/math$/,
|
||||
replacement: path.resolve(__dirname, "../packages/math/src/index.ts"),
|
||||
},
|
||||
{
|
||||
find: /^@excalidraw\/math\/(.*?)/,
|
||||
replacement: path.resolve(__dirname, "../packages/math/src/$1"),
|
||||
},
|
||||
{
|
||||
find: /^@excalidraw\/utils$/,
|
||||
replacement: path.resolve(
|
||||
__dirname,
|
||||
"../packages/utils/src/index.ts",
|
||||
),
|
||||
replacement: path.resolve(__dirname, "../packages/utils/index.ts"),
|
||||
},
|
||||
{
|
||||
find: /^@excalidraw\/utils\/(.*?)/,
|
||||
replacement: path.resolve(__dirname, "../packages/utils/src/$1"),
|
||||
replacement: path.resolve(__dirname, "../packages/utils/$1"),
|
||||
},
|
||||
{
|
||||
find: /^@excalidraw\/math$/,
|
||||
replacement: path.resolve(__dirname, "../packages/math/index.ts"),
|
||||
},
|
||||
{
|
||||
find: /^@excalidraw\/math\/(.*?)/,
|
||||
replacement: path.resolve(__dirname, "../packages/math/$1"),
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -225,7 +197,7 @@ export default defineConfig(({ mode }) => {
|
||||
},
|
||||
],
|
||||
start_url: "/",
|
||||
id: "excalidraw",
|
||||
id:"excalidraw",
|
||||
display: "standalone",
|
||||
theme_color: "#121212",
|
||||
background_color: "#ffffff",
|
||||
|
||||
+12
-17
@@ -4,7 +4,9 @@
|
||||
"packageManager": "yarn@1.22.22",
|
||||
"workspaces": [
|
||||
"excalidraw-app",
|
||||
"packages/*",
|
||||
"packages/excalidraw",
|
||||
"packages/utils",
|
||||
"packages/math",
|
||||
"examples/*"
|
||||
],
|
||||
"devDependencies": {
|
||||
@@ -24,7 +26,6 @@
|
||||
"dotenv": "16.0.1",
|
||||
"eslint-config-prettier": "8.5.0",
|
||||
"eslint-config-react-app": "7.0.1",
|
||||
"eslint-plugin-import": "2.31.0",
|
||||
"eslint-plugin-prettier": "3.3.1",
|
||||
"http-server": "14.1.1",
|
||||
"husky": "7.0.4",
|
||||
@@ -33,8 +34,7 @@
|
||||
"pepjs": "0.5.3",
|
||||
"prettier": "2.6.2",
|
||||
"rewire": "6.0.0",
|
||||
"rimraf": "^5.0.0",
|
||||
"typescript": "5.9.3",
|
||||
"typescript": "4.9.4",
|
||||
"vite": "5.0.12",
|
||||
"vite-plugin-checker": "0.7.2",
|
||||
"vite-plugin-ejs": "1.7.0",
|
||||
@@ -44,7 +44,7 @@
|
||||
"vitest-canvas-mock": "0.3.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18.0.0"
|
||||
"node": "18.0.0 - 22.x.x"
|
||||
},
|
||||
"homepage": ".",
|
||||
"prettier": "@excalidraw/prettier-config",
|
||||
@@ -52,17 +52,13 @@
|
||||
"build-node": "node ./scripts/build-node.js",
|
||||
"build:app:docker": "yarn --cwd ./excalidraw-app build:app:docker",
|
||||
"build:app": "yarn --cwd ./excalidraw-app build:app",
|
||||
"build:common": "yarn --cwd ./packages/common build:esm",
|
||||
"build:element": "yarn --cwd ./packages/element build:esm",
|
||||
"build:excalidraw": "yarn --cwd ./packages/excalidraw build:esm",
|
||||
"build:math": "yarn --cwd ./packages/math build:esm",
|
||||
"build:packages": "yarn build:common && yarn build:math && yarn build:element && yarn build:excalidraw",
|
||||
"build:package": "yarn --cwd ./packages/excalidraw build:esm",
|
||||
"build:version": "yarn --cwd ./excalidraw-app build:version",
|
||||
"build": "yarn --cwd ./excalidraw-app build",
|
||||
"build:preview": "yarn --cwd ./excalidraw-app build:preview",
|
||||
"start": "yarn --cwd ./excalidraw-app start",
|
||||
"start:production": "yarn --cwd ./excalidraw-app start:production",
|
||||
"start:example": "yarn build:packages && yarn --cwd ./examples/with-script-in-browser start",
|
||||
"start:example": "yarn build:package && yarn --cwd ./examples/with-script-in-browser start",
|
||||
"test:all": "yarn test:typecheck && yarn test:code && yarn test:other && yarn test:app --watch=false",
|
||||
"test:app": "vitest",
|
||||
"test:code": "eslint --max-warnings=0 --ext .js,.ts,.tsx .",
|
||||
@@ -80,12 +76,11 @@
|
||||
"locales-coverage:description": "node scripts/locales-coverage-description.js",
|
||||
"prepare": "husky install",
|
||||
"prettier": "prettier \"**/*.{css,scss,json,md,html,yml}\" --ignore-path=.eslintignore",
|
||||
"release": "node scripts/release.js",
|
||||
"release:test": "node scripts/release.js --tag=test",
|
||||
"release:next": "node scripts/release.js --tag=next",
|
||||
"release:latest": "node scripts/release.js --tag=latest",
|
||||
"rm:build": "rimraf --glob excalidraw-app/build excalidraw-app/dist excalidraw-app/dev-dist packages/*/dist packages/*/build examples/*/build examples/*/dist",
|
||||
"rm:node_modules": "rimraf --glob node_modules excalidraw-app/node_modules packages/*/node_modules",
|
||||
"autorelease": "node scripts/autorelease.js",
|
||||
"prerelease:excalidraw": "node scripts/prerelease.js",
|
||||
"release:excalidraw": "node scripts/release.js",
|
||||
"rm:build": "rm -rf excalidraw-app/{build,dist,dev-dist} && rm -rf packages/*/{dist,build} && rm -rf examples/*/{build,dist}",
|
||||
"rm:node_modules": "rm -rf node_modules && rm -rf excalidraw-app/node_modules && rm -rf packages/*/node_modules",
|
||||
"clean-install": "yarn rm:node_modules && yarn install"
|
||||
},
|
||||
"resolutions": {
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
{
|
||||
"extends": ["../eslintrc.base.json"]
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
# @excalidraw/common
|
||||
|
||||
## Install
|
||||
|
||||
```bash
|
||||
npm install @excalidraw/common
|
||||
```
|
||||
|
||||
If you prefer Yarn over npm, use this command to install the Excalidraw utils package:
|
||||
|
||||
```bash
|
||||
yarn add @excalidraw/common
|
||||
```
|
||||
|
||||
With PNPM, similarly install the package with this command:
|
||||
|
||||
```bash
|
||||
pnpm add @excalidraw/common
|
||||
```
|
||||
Vendored
-3
@@ -1,3 +0,0 @@
|
||||
/// <reference types="vite/client" />
|
||||
import "@excalidraw/excalidraw/global";
|
||||
import "@excalidraw/excalidraw/css";
|
||||
@@ -1,59 +0,0 @@
|
||||
{
|
||||
"name": "@excalidraw/common",
|
||||
"version": "0.18.0",
|
||||
"type": "module",
|
||||
"types": "./dist/types/common/src/index.d.ts",
|
||||
"main": "./dist/prod/index.js",
|
||||
"module": "./dist/prod/index.js",
|
||||
"exports": {
|
||||
".": {
|
||||
"types": "./dist/types/common/src/index.d.ts",
|
||||
"development": "./dist/dev/index.js",
|
||||
"production": "./dist/prod/index.js",
|
||||
"default": "./dist/prod/index.js"
|
||||
},
|
||||
"./*": {
|
||||
"types": "./dist/types/common/src/*.d.ts",
|
||||
"development": "./dist/dev/index.js",
|
||||
"production": "./dist/prod/index.js",
|
||||
"default": "./dist/prod/index.js"
|
||||
}
|
||||
},
|
||||
"files": [
|
||||
"dist/*"
|
||||
],
|
||||
"description": "Excalidraw common functions, constants, etc.",
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
"license": "MIT",
|
||||
"keywords": [
|
||||
"excalidraw",
|
||||
"excalidraw-utils"
|
||||
],
|
||||
"browserslist": {
|
||||
"production": [
|
||||
">0.2%",
|
||||
"not dead",
|
||||
"not ie <= 11",
|
||||
"not op_mini all",
|
||||
"not safari < 12",
|
||||
"not kaios <= 2.5",
|
||||
"not edge < 79",
|
||||
"not chrome < 70",
|
||||
"not and_uc < 13",
|
||||
"not samsung < 10"
|
||||
],
|
||||
"development": [
|
||||
"last 1 chrome version",
|
||||
"last 1 firefox version",
|
||||
"last 1 safari version"
|
||||
]
|
||||
},
|
||||
"bugs": "https://github.com/excalidraw/excalidraw/issues",
|
||||
"repository": "https://github.com/excalidraw/excalidraw",
|
||||
"scripts": {
|
||||
"gen:types": "rimraf types && tsc",
|
||||
"build:esm": "rimraf dist && node ../../scripts/buildBase.js && yarn gen:types"
|
||||
}
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
/**
|
||||
* x and y position of top left corner, x and y position of bottom right corner
|
||||
*/
|
||||
export type Bounds = readonly [
|
||||
minX: number,
|
||||
minY: number,
|
||||
maxX: number,
|
||||
maxY: number,
|
||||
];
|
||||
|
||||
export const isBounds = (box: unknown): box is Bounds =>
|
||||
Array.isArray(box) &&
|
||||
box.length === 4 &&
|
||||
typeof box[0] === "number" &&
|
||||
typeof box[1] === "number" &&
|
||||
typeof box[2] === "number" &&
|
||||
typeof box[3] === "number";
|
||||
@@ -1,223 +0,0 @@
|
||||
export type StylesPanelMode = "compact" | "full" | "mobile";
|
||||
|
||||
export type EditorInterface = Readonly<{
|
||||
formFactor: "phone" | "tablet" | "desktop";
|
||||
desktopUIMode: "compact" | "full";
|
||||
userAgent: Readonly<{
|
||||
isMobileDevice: boolean;
|
||||
platform: "ios" | "android" | "other" | "unknown";
|
||||
}>;
|
||||
isTouchScreen: boolean;
|
||||
canFitSidebar: boolean;
|
||||
isLandscape: boolean;
|
||||
}>;
|
||||
|
||||
// storage key
|
||||
const DESKTOP_UI_MODE_STORAGE_KEY = "excalidraw.desktopUIMode";
|
||||
|
||||
// breakpoints
|
||||
// mobile: up to 699px
|
||||
export const MQ_MAX_MOBILE = 599;
|
||||
|
||||
export const MQ_MAX_WIDTH_LANDSCAPE = 1000;
|
||||
export const MQ_MAX_HEIGHT_LANDSCAPE = 500;
|
||||
|
||||
// tablets
|
||||
export const MQ_MIN_TABLET = MQ_MAX_MOBILE + 1; // lower bound (excludes phones)
|
||||
export const MQ_MAX_TABLET = 1400; // upper bound (excludes laptops/desktops)
|
||||
|
||||
// desktop/laptop
|
||||
export const MQ_MIN_WIDTH_DESKTOP = 1440;
|
||||
|
||||
// sidebar
|
||||
export const MQ_RIGHT_SIDEBAR_MIN_WIDTH = 1229;
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
// user agent detections
|
||||
export const isDarwin = /Mac|iPod|iPhone|iPad/.test(navigator.platform);
|
||||
export const isWindows = /^Win/.test(navigator.platform);
|
||||
export const isAndroid = /\b(android)\b/i.test(navigator.userAgent);
|
||||
export const isFirefox =
|
||||
typeof window !== "undefined" &&
|
||||
"netscape" in window &&
|
||||
navigator.userAgent.indexOf("rv:") > 1 &&
|
||||
navigator.userAgent.indexOf("Gecko") > 1;
|
||||
export const isChrome = navigator.userAgent.indexOf("Chrome") !== -1;
|
||||
export const isSafari =
|
||||
!isChrome && navigator.userAgent.indexOf("Safari") !== -1;
|
||||
export const isIOS =
|
||||
/iPad|iPhone/i.test(navigator.platform) ||
|
||||
// iPadOS 13+
|
||||
(navigator.userAgent.includes("Mac") && "ontouchend" in document);
|
||||
// keeping function so it can be mocked in test
|
||||
export const isBrave = () =>
|
||||
(navigator as any).brave?.isBrave?.name === "isBrave";
|
||||
|
||||
// export const isMobile =
|
||||
// isIOS ||
|
||||
// /android|webos|ipod|blackberry|iemobile|opera mini/i.test(
|
||||
// navigator.userAgent,
|
||||
// ) ||
|
||||
// /android|ios|ipod|blackberry|windows phone/i.test(navigator.platform);
|
||||
|
||||
// utilities
|
||||
export const isMobileBreakpoint = (width: number, height: number) => {
|
||||
return (
|
||||
width <= MQ_MAX_MOBILE ||
|
||||
(height < MQ_MAX_HEIGHT_LANDSCAPE && width < MQ_MAX_WIDTH_LANDSCAPE)
|
||||
);
|
||||
};
|
||||
|
||||
export const isTabletBreakpoint = (
|
||||
editorWidth: number,
|
||||
editorHeight: number,
|
||||
) => {
|
||||
const minSide = Math.min(editorWidth, editorHeight);
|
||||
const maxSide = Math.max(editorWidth, editorHeight);
|
||||
|
||||
return minSide >= MQ_MIN_TABLET && maxSide <= MQ_MAX_TABLET;
|
||||
};
|
||||
|
||||
const isMobileOrTablet = (): boolean => {
|
||||
const ua = navigator.userAgent || "";
|
||||
const platform = navigator.platform || "";
|
||||
const uaData = (navigator as any).userAgentData as
|
||||
| { mobile?: boolean; platform?: string }
|
||||
| undefined;
|
||||
|
||||
// --- 1) chromium: prefer ua client hints -------------------------------
|
||||
if (uaData) {
|
||||
const plat = (uaData.platform || "").toLowerCase();
|
||||
const isDesktopOS =
|
||||
plat === "windows" ||
|
||||
plat === "macos" ||
|
||||
plat === "linux" ||
|
||||
plat === "chrome os";
|
||||
if (uaData.mobile === true) {
|
||||
return true;
|
||||
}
|
||||
if (uaData.mobile === false && plat === "android") {
|
||||
const looksTouchTablet =
|
||||
matchMedia?.("(hover: none)").matches &&
|
||||
matchMedia?.("(pointer: coarse)").matches;
|
||||
return looksTouchTablet;
|
||||
}
|
||||
if (isDesktopOS) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// --- 2) ios (includes ipad) --------------------------------------------
|
||||
if (isIOS) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// --- 3) android legacy ua fallback -------------------------------------
|
||||
if (isAndroid) {
|
||||
const isAndroidPhone = /Mobile/i.test(ua);
|
||||
const isAndroidTablet = !isAndroidPhone;
|
||||
if (isAndroidPhone || isAndroidTablet) {
|
||||
const looksTouchTablet =
|
||||
matchMedia?.("(hover: none)").matches &&
|
||||
matchMedia?.("(pointer: coarse)").matches;
|
||||
return looksTouchTablet;
|
||||
}
|
||||
}
|
||||
|
||||
// --- 4) last resort desktop exclusion ----------------------------------
|
||||
const looksDesktopPlatform =
|
||||
/Win|Linux|CrOS|Mac/.test(platform) ||
|
||||
/Windows NT|X11|CrOS|Macintosh/.test(ua);
|
||||
if (looksDesktopPlatform) {
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
export const getFormFactor = (
|
||||
editorWidth: number,
|
||||
editorHeight: number,
|
||||
): EditorInterface["formFactor"] => {
|
||||
if (isMobileBreakpoint(editorWidth, editorHeight)) {
|
||||
return "phone";
|
||||
}
|
||||
|
||||
if (isTabletBreakpoint(editorWidth, editorHeight)) {
|
||||
return "tablet";
|
||||
}
|
||||
|
||||
return "desktop";
|
||||
};
|
||||
|
||||
export const deriveStylesPanelMode = (
|
||||
editorInterface: EditorInterface,
|
||||
): StylesPanelMode => {
|
||||
if (editorInterface.formFactor === "phone") {
|
||||
return "mobile";
|
||||
}
|
||||
|
||||
if (editorInterface.formFactor === "tablet") {
|
||||
return "compact";
|
||||
}
|
||||
|
||||
return editorInterface.desktopUIMode;
|
||||
};
|
||||
|
||||
export const createUserAgentDescriptor = (
|
||||
userAgentString: string,
|
||||
): EditorInterface["userAgent"] => {
|
||||
const normalizedUA = userAgentString ?? "";
|
||||
let platform: EditorInterface["userAgent"]["platform"] = "unknown";
|
||||
|
||||
if (isIOS) {
|
||||
platform = "ios";
|
||||
} else if (isAndroid) {
|
||||
platform = "android";
|
||||
} else if (normalizedUA) {
|
||||
platform = "other";
|
||||
}
|
||||
|
||||
return {
|
||||
isMobileDevice: isMobileOrTablet(),
|
||||
platform,
|
||||
} as const;
|
||||
};
|
||||
|
||||
export const loadDesktopUIModePreference = () => {
|
||||
if (typeof window === "undefined") {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
const stored = window.localStorage.getItem(DESKTOP_UI_MODE_STORAGE_KEY);
|
||||
if (stored === "compact" || stored === "full") {
|
||||
return stored as EditorInterface["desktopUIMode"];
|
||||
}
|
||||
} catch (error) {
|
||||
// ignore storage access issues (e.g., Safari private mode)
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
const persistDesktopUIMode = (mode: EditorInterface["desktopUIMode"]) => {
|
||||
if (typeof window === "undefined") {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
window.localStorage.setItem(DESKTOP_UI_MODE_STORAGE_KEY, mode);
|
||||
} catch (error) {
|
||||
// ignore storage access issues (e.g., Safari private mode)
|
||||
}
|
||||
};
|
||||
|
||||
export const setDesktopUIMode = (mode: EditorInterface["desktopUIMode"]) => {
|
||||
if (mode !== "compact" && mode !== "full") {
|
||||
return;
|
||||
}
|
||||
|
||||
persistDesktopUIMode(mode);
|
||||
|
||||
return mode;
|
||||
};
|
||||
@@ -1,15 +0,0 @@
|
||||
export * from "./binary-heap";
|
||||
export * from "./bounds";
|
||||
export * from "./colors";
|
||||
export * from "./constants";
|
||||
export * from "./font-metadata";
|
||||
export * from "./queue";
|
||||
export * from "./keys";
|
||||
export * from "./points";
|
||||
export * from "./promise-pool";
|
||||
export * from "./random";
|
||||
export * from "./url";
|
||||
export * from "./utils";
|
||||
export * from "./emitter";
|
||||
export * from "./visualdebug";
|
||||
export * from "./editorInterface";
|
||||
@@ -1,50 +0,0 @@
|
||||
import Pool from "es6-promise-pool";
|
||||
|
||||
// extending the missing types
|
||||
// relying on the [Index, T] to keep a correct order
|
||||
type TPromisePool<T, Index = number> = Pool<[Index, T][]> & {
|
||||
addEventListener: (
|
||||
type: "fulfilled",
|
||||
listener: (event: { data: { result: [Index, T] } }) => void,
|
||||
) => (event: { data: { result: [Index, T] } }) => void;
|
||||
removeEventListener: (
|
||||
type: "fulfilled",
|
||||
listener: (event: { data: { result: [Index, T] } }) => void,
|
||||
) => void;
|
||||
};
|
||||
|
||||
export class PromisePool<T> {
|
||||
private readonly pool: TPromisePool<T>;
|
||||
private readonly entries: Record<number, T> = {};
|
||||
|
||||
constructor(
|
||||
source: IterableIterator<Promise<void | readonly [number, T]>>,
|
||||
concurrency: number,
|
||||
) {
|
||||
this.pool = new Pool(
|
||||
source as unknown as () => void | PromiseLike<[number, T][]>,
|
||||
concurrency,
|
||||
) as TPromisePool<T>;
|
||||
}
|
||||
|
||||
public all() {
|
||||
const listener = (event: { data: { result: void | [number, T] } }) => {
|
||||
if (event.data.result) {
|
||||
// by default pool does not return the results, so we are gathering them manually
|
||||
// with the correct call order (represented by the index in the tuple)
|
||||
const [index, value] = event.data.result;
|
||||
this.entries[index] = value;
|
||||
}
|
||||
};
|
||||
|
||||
this.pool.addEventListener("fulfilled", listener);
|
||||
|
||||
return this.pool.start().then(() => {
|
||||
setTimeout(() => {
|
||||
this.pool.removeEventListener("fulfilled", listener);
|
||||
});
|
||||
|
||||
return Object.values(this.entries);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,82 +0,0 @@
|
||||
import {
|
||||
isTransparent,
|
||||
mapFind,
|
||||
reduceToCommonValue,
|
||||
} from "@excalidraw/common";
|
||||
|
||||
describe("@excalidraw/common/utils", () => {
|
||||
describe("isTransparent()", () => {
|
||||
it("should return true when color is rgb transparent", () => {
|
||||
expect(isTransparent("#ff00")).toEqual(true);
|
||||
expect(isTransparent("#fff00000")).toEqual(true);
|
||||
expect(isTransparent("transparent")).toEqual(true);
|
||||
});
|
||||
|
||||
it("should return false when color is not transparent", () => {
|
||||
expect(isTransparent("#ced4da")).toEqual(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("reduceToCommonValue()", () => {
|
||||
it("should return the common value when all values are the same", () => {
|
||||
expect(reduceToCommonValue([1, 1])).toEqual(1);
|
||||
expect(reduceToCommonValue([0, 0])).toEqual(0);
|
||||
expect(reduceToCommonValue(["a", "a"])).toEqual("a");
|
||||
expect(reduceToCommonValue(new Set([1]))).toEqual(1);
|
||||
expect(reduceToCommonValue([""])).toEqual("");
|
||||
expect(reduceToCommonValue([0])).toEqual(0);
|
||||
|
||||
const o = {};
|
||||
expect(reduceToCommonValue([o, o])).toEqual(o);
|
||||
|
||||
expect(
|
||||
reduceToCommonValue([{ a: 1 }, { a: 1, b: 2 }], (o) => o.a),
|
||||
).toEqual(1);
|
||||
expect(
|
||||
reduceToCommonValue(new Set([{ a: 1 }, { a: 1, b: 2 }]), (o) => o.a),
|
||||
).toEqual(1);
|
||||
});
|
||||
|
||||
it("should return `null` when values are different", () => {
|
||||
expect(reduceToCommonValue([1, 2, 3])).toEqual(null);
|
||||
expect(reduceToCommonValue(new Set([1, 2]))).toEqual(null);
|
||||
expect(reduceToCommonValue([{ a: 1 }, { a: 2 }], (o) => o.a)).toEqual(
|
||||
null,
|
||||
);
|
||||
});
|
||||
|
||||
it("should return `null` when some values are nullable", () => {
|
||||
expect(reduceToCommonValue([1, null, 1])).toEqual(null);
|
||||
expect(reduceToCommonValue([null, 1])).toEqual(null);
|
||||
expect(reduceToCommonValue([1, undefined])).toEqual(null);
|
||||
expect(reduceToCommonValue([undefined, 1])).toEqual(null);
|
||||
expect(reduceToCommonValue([null])).toEqual(null);
|
||||
expect(reduceToCommonValue([undefined])).toEqual(null);
|
||||
expect(reduceToCommonValue([])).toEqual(null);
|
||||
});
|
||||
});
|
||||
|
||||
describe("mapFind()", () => {
|
||||
it("should return the first mapped non-null element", () => {
|
||||
{
|
||||
let counter = 0;
|
||||
|
||||
const result = mapFind(["a", "b", "c"], (value) => {
|
||||
counter++;
|
||||
return value === "b" ? 42 : null;
|
||||
});
|
||||
expect(result).toEqual(42);
|
||||
expect(counter).toBe(2);
|
||||
}
|
||||
|
||||
expect(mapFind([1, 2], (value) => value * 0)).toBe(0);
|
||||
expect(mapFind([1, 2], () => false)).toBe(false);
|
||||
expect(mapFind([1, 2], () => "")).toBe("");
|
||||
});
|
||||
|
||||
it("should return undefined if no mapped element is found", () => {
|
||||
expect(mapFind([1, 2], () => undefined)).toBe(undefined);
|
||||
expect(mapFind([1, 2], () => null)).toBe(undefined);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,8 +0,0 @@
|
||||
{
|
||||
"extends": "../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "./dist/types"
|
||||
},
|
||||
"include": ["src/**/*", "global.d.ts"],
|
||||
"exclude": ["**/*.test.*", "tests", "types", "examples", "dist"]
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
{
|
||||
"extends": ["../eslintrc.base.json"]
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
# @excalidraw/element
|
||||
|
||||
## Install
|
||||
|
||||
```bash
|
||||
npm install @excalidraw/element
|
||||
```
|
||||
|
||||
If you prefer Yarn over npm, use this command to install the Excalidraw utils package:
|
||||
|
||||
```bash
|
||||
yarn add @excalidraw/element
|
||||
```
|
||||
|
||||
With PNPM, similarly install the package with this command:
|
||||
|
||||
```bash
|
||||
pnpm add @excalidraw/element
|
||||
```
|
||||
Vendored
-3
@@ -1,3 +0,0 @@
|
||||
/// <reference types="vite/client" />
|
||||
import "@excalidraw/excalidraw/global";
|
||||
import "@excalidraw/excalidraw/css";
|
||||
@@ -1,63 +0,0 @@
|
||||
{
|
||||
"name": "@excalidraw/element",
|
||||
"version": "0.18.0",
|
||||
"type": "module",
|
||||
"types": "./dist/types/element/src/index.d.ts",
|
||||
"main": "./dist/prod/index.js",
|
||||
"module": "./dist/prod/index.js",
|
||||
"exports": {
|
||||
".": {
|
||||
"types": "./dist/types/element/src/index.d.ts",
|
||||
"development": "./dist/dev/index.js",
|
||||
"production": "./dist/prod/index.js",
|
||||
"default": "./dist/prod/index.js"
|
||||
},
|
||||
"./*": {
|
||||
"types": "./dist/types/element/src/*.d.ts",
|
||||
"development": "./dist/dev/index.js",
|
||||
"production": "./dist/prod/index.js",
|
||||
"default": "./dist/prod/index.js"
|
||||
}
|
||||
},
|
||||
"files": [
|
||||
"dist/*"
|
||||
],
|
||||
"description": "Excalidraw elements-related logic",
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
"license": "MIT",
|
||||
"keywords": [
|
||||
"excalidraw",
|
||||
"excalidraw-utils"
|
||||
],
|
||||
"browserslist": {
|
||||
"production": [
|
||||
">0.2%",
|
||||
"not dead",
|
||||
"not ie <= 11",
|
||||
"not op_mini all",
|
||||
"not safari < 12",
|
||||
"not kaios <= 2.5",
|
||||
"not edge < 79",
|
||||
"not chrome < 70",
|
||||
"not and_uc < 13",
|
||||
"not samsung < 10"
|
||||
],
|
||||
"development": [
|
||||
"last 1 chrome version",
|
||||
"last 1 firefox version",
|
||||
"last 1 safari version"
|
||||
]
|
||||
},
|
||||
"bugs": "https://github.com/excalidraw/excalidraw/issues",
|
||||
"repository": "https://github.com/excalidraw/excalidraw",
|
||||
"scripts": {
|
||||
"gen:types": "rimraf types && tsc",
|
||||
"build:esm": "rimraf dist && node ../../scripts/buildBase.js && yarn gen:types"
|
||||
},
|
||||
"dependencies": {
|
||||
"@excalidraw/common": "0.18.0",
|
||||
"@excalidraw/math": "0.18.0"
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user