move to export dialog
This commit is contained in:
commit
90d68b3e0b
@ -1,10 +1,10 @@
|
|||||||
*
|
*
|
||||||
|
!.env
|
||||||
|
!.eslintrc.json
|
||||||
|
!.npmrc
|
||||||
|
!.prettierrc
|
||||||
|
!package.json
|
||||||
!public/
|
!public/
|
||||||
!src/
|
!src/
|
||||||
!.npmrc
|
|
||||||
!.eslintrc.json
|
|
||||||
!.prettierrc
|
|
||||||
!package-lock.json
|
|
||||||
!package.json
|
|
||||||
!tsconfig.json
|
!tsconfig.json
|
||||||
!.env
|
!yarn.lock
|
||||||
|
@ -4,3 +4,4 @@ package-lock.json
|
|||||||
.vscode/
|
.vscode/
|
||||||
firebase/
|
firebase/
|
||||||
dist/
|
dist/
|
||||||
|
public/workbox
|
||||||
|
@ -1,40 +1,6 @@
|
|||||||
{
|
{
|
||||||
"extends": ["prettier", "react-app"],
|
"extends": ["@excalidraw/eslint-config", "react-app"],
|
||||||
"plugins": ["prettier"],
|
|
||||||
"rules": {
|
"rules": {
|
||||||
"@typescript-eslint/no-unused-vars": "warn",
|
"import/no-anonymous-default-export": "off"
|
||||||
"curly": "warn",
|
|
||||||
"dot-notation": "warn",
|
|
||||||
"import/no-anonymous-default-export": "off",
|
|
||||||
"no-console": [
|
|
||||||
"warn",
|
|
||||||
{
|
|
||||||
"allow": ["warn", "error", "info"]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"no-else-return": "warn",
|
|
||||||
"no-lonely-if": "warn",
|
|
||||||
"no-restricted-syntax": [
|
|
||||||
"warn",
|
|
||||||
{
|
|
||||||
"message": "Use 't(...)' instead of literal text in JSX",
|
|
||||||
"selector": "JSXText[value=/\\w/]"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"no-unneeded-ternary": "warn",
|
|
||||||
"no-unused-expressions": "warn",
|
|
||||||
"no-useless-return": "warn",
|
|
||||||
"no-var": "warn",
|
|
||||||
"object-shorthand": "warn",
|
|
||||||
"one-var": ["warn", "never"],
|
|
||||||
"prefer-arrow-callback": "warn",
|
|
||||||
"prefer-const": [
|
|
||||||
"warn",
|
|
||||||
{
|
|
||||||
"destructuring": "all"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"prefer-template": "warn",
|
|
||||||
"prettier/prettier": "warn"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
6
.github/assets/crowdin.svg
vendored
Normal file
6
.github/assets/crowdin.svg
vendored
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
<svg height="50" viewBox="0 0 257 50" xmlns="http://www.w3.org/2000/svg" fill-rule="evenodd" clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="2">
|
||||||
|
<path fill="#fff" d="M-7.977-9.253h288.95v78.13H-7.977z" />
|
||||||
|
<path d="M67.626 32.315c-1.34 0-2.207 0-2.207-1.025 0-.236.079-.551.236-.946l4.02-8.907h12.929c1.34 0 2.128-.08 2.128.946 0 .315-.078.63-.236.946l-.788 1.734h5.439l1.104-2.444c.157-.394.157-.71.157-1.025 0-2.207-2.365-3.31-4.257-3.31H65.655l-5.754 12.691c-.158.394-.158.71-.158 1.025 0 2.365 1.97 3.547 4.73 3.547h20.26l1.26-3.232H67.627zm42.727-14.11H95.059l-6.937 17.342h5.518l5.519-14.032h8.435c1.34 0 2.05-.157 2.05.868 0 .315-.08.63-.237.946l-.789 1.734h5.518l1.104-2.444c.158-.394.158-.71.158-1.025 0-1.025-.552-1.892-1.734-2.522-.946-.473-2.208-.868-3.311-.868zm30.35 0h-21.285l-5.754 12.691c-.158.316-.158.63-.158 1.025 0 1.97 1.419 3.547 3.232 3.547h21.52l5.834-13.007c.158-.394.158-.71.158-1.024 0-2.05-1.734-3.233-3.547-3.233zm-6.701 14.19h-12.85c-1.34 0-1.97-.159-1.97-1.183 0-.316.079-.631.236-.946l4.178-8.908h12.929c1.26 0 1.891-.08 1.891.946 0 .315-.078.63-.236 1.025l-4.178 9.065zm13.953 3.152h28.695l7.41-17.264h-5.676l-6.149 14.032h-9.223l6.149-14.11h-5.676l-6.386 14.031h-6.306c-1.34 0-2.05-.157-2.05-1.182 0-.315.08-.63.237-.946l5.282-11.982h-5.519l-5.518 12.455c-1.103 3.39 2.207 4.966 4.73 4.966zm67.874-23.649l-5.913 1.577-1.97 4.73h-14.584c-3.548 0-6.7 1.576-8.278 4.73l-3.941 9.46c-.788 1.576.63 3.152 3.31 3.152h21.128l10.248-23.649zm-27.591 20.496c-1.183 0-1.735-.788-1.577-1.577l3.469-7.567c.788-1.813 2.68-1.892 4.414-1.892h11.825l-4.73 11.036h-13.401zm26.802 3.153l7.49-17.737-6.307 1.183-7.095 16.554h5.912zm8.435-19.944l1.656-3.705-6.228 1.261-1.577 3.705 6.15-1.26zm22.23 2.601h-20.417l-7.094 17.343h5.518l5.518-14.19h13.48c1.34 0 2.05-.078 2.05 1.026 0 .315-.08.63-.237.946l-5.518 12.297h5.518l5.834-13.007c.157-.315.157-.63.157-1.025 0-1.025-.552-1.892-1.734-2.522-.867-.473-1.892-.868-3.074-.868zm-192.82.868c-8.672-1.025-16.476.71-17.58 6.148 0 .237-.157 1.262-.157 1.42l1.419.157v2.207l-1.34-.157c.551 5.597 3.626 7.252 6.858 7.331h.236c1.42.079 2.917-.237 4.178-.788.08 0 .08-.08.08-.08v-.157c0-.079-.08-.079-.08-.157-.078 0-.078-.08-.157-.08-2.996.395-5.755-2.049-5.755-7.015 0-6.228 4.888-8.514 12.298-8.514.236.158.315-.237 0-.315zM36.803 30.344c.788 0 1.498.158 2.207.237.237 1.655 1.025 3.232 2.208 4.336-1.183-.158-2.208-.71-3.075-1.498a6.051 6.051 0 01-1.34-3.075zm2.68-5.439c0 .237-.157.552-.236.946h-1.025c-.552 0-1.025-.079-1.576-.158v-.157c.63-3.39 4.02-4.73 7.252-5.36a7.997 7.997 0 00-2.76 1.812c-.787.868-1.34 1.813-1.655 2.917z" fill="#2e3340" fill-rule="nonzero" />
|
||||||
|
<path d="M56.274 14.105c-6.543-1.813-34.055-4.02-34.055 11.273.946.158 1.577.315 2.05.394-.079 1.183 0 2.444 0 3.626l-2.444-.394c0 8.83 6.464 11.667 11.588 11.667.868 0 1.656-.078 2.523-.157 2.128-.237 4.178-.867 5.991-1.892.079 0 .079-.08.079-.08v-.157c0-.079-.079-.079-.079-.157-.079 0-.079-.08-.157-.08-4.336.868-10.17-.315-10.17-10.563 0-13.637 19.156-12.77 24.753-13.007.08 0 .08-.079.08-.079v-.157c0-.08 0-.08-.08-.158 0-.079 0-.079-.079-.079zM33.414 39.41a9.362 9.362 0 01-6.78-2.286c-1.892-1.656-3.074-3.942-3.31-6.385 1.655.236 3.704.394 5.438.473a9.43 9.43 0 001.577 4.808c.946 1.42 2.207 2.602 3.705 3.39h-.63zM28.92 24.984l-2.601-.237-2.602-.315c0-7.962 12.77-11.036 18.683-10.484-5.912 1.34-13.086 4.099-13.48 11.036z" fill="#2e3340" fill-rule="nonzero" />
|
||||||
|
<path d="M59.664 9.533c-7.962-2.68-17.027-4.02-25.462-3.941-12.22 0-27.67 3.626-28.064 16.081l3.31.788c-.393 1.577-.393 4.81-.393 4.81s-1.892-.553-2.917-.79c0 14.821 8.671 18.526 17.027 18.526 3.39 0 6.701-.552 9.854-1.734.08 0 .08-.08.08-.08v-.157c0-.079-.08-.079-.08-.157h-.157c-2.602 0-4.651.867-8.75-2.05-7.963-5.597-7.017-20.102 2.128-26.408 9.46-6.701 29.798-4.573 33.267-4.415h.157s.079 0 .079-.079v-.236l-.079-.158zm-36.42 34.292c-9.932 0-14.978-5.36-15.45-15.609 2.68.71 5.202 1.34 7.961 1.734-.157 4.02 1.262 7.962 4.02 11.037a12.488 12.488 0 005.046 2.916l-1.577-.078zM45.632 7.956c-12.06 0-26.014 1.42-28.773 14.584 0 0-7.41-1.182-9.066-1.576C9.843 4.409 38.38 5.67 49.89 7.956h-4.257z" fill="#2e3340" fill-rule="nonzero" />
|
||||||
|
</svg>
|
After Width: | Height: | Size: 4.1 KiB |
9
.github/assets/sentry.svg
vendored
Normal file
9
.github/assets/sentry.svg
vendored
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
<svg class="__sntry__ css-15xgryy e10nushx5" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 222 66" height="50" style="background-color: rgb(255, 255, 255);">
|
||||||
|
<defs>
|
||||||
|
<style type="text/css">
|
||||||
|
@media (prefers-color-scheme: dark) {svg.__sntry__ { background-color: #584674 !important; }path.__sntry__ { fill: #ffffff !important; }}
|
||||||
|
</style>
|
||||||
|
</defs>
|
||||||
|
<path d="M29,2.26a4.67,4.67,0,0,0-8,0L14.42,13.53A32.21,32.21,0,0,1,32.17,40.19H27.55A27.68,27.68,0,0,0,12.09,17.47L6,28a15.92,15.92,0,0,1,9.23,12.17H4.62A.76.76,0,0,1,4,39.06l2.94-5a10.74,10.74,0,0,0-3.36-1.9l-2.91,5a4.54,4.54,0,0,0,1.69,6.24A4.66,4.66,0,0,0,4.62,44H19.15a19.4,19.4,0,0,0-8-17.31l2.31-4A23.87,23.87,0,0,1,23.76,44H36.07a35.88,35.88,0,0,0-16.41-31.8l4.67-8a.77.77,0,0,1,1.05-.27c.53.29,20.29,34.77,20.66,35.17a.76.76,0,0,1-.68,1.13H40.6q.09,1.91,0,3.81h4.78A4.59,4.59,0,0,0,50,39.43a4.49,4.49,0,0,0-.62-2.28Z M124.32,28.28,109.56,9.22h-3.68V34.77h3.73V15.19l15.18,19.58h3.26V9.22h-3.73ZM87.15,23.54h13.23V20.22H87.14V12.53h14.93V9.21H83.34V34.77h18.92V31.45H87.14ZM71.59,20.3h0C66.44,19.06,65,18.08,65,15.7c0-2.14,1.89-3.59,4.71-3.59a12.06,12.06,0,0,1,7.07,2.55l2-2.83a14.1,14.1,0,0,0-9-3c-5.06,0-8.59,3-8.59,7.27,0,4.6,3,6.19,8.46,7.52C74.51,24.74,76,25.78,76,28.11s-2,3.77-5.09,3.77a12.34,12.34,0,0,1-8.3-3.26l-2.25,2.69a15.94,15.94,0,0,0,10.42,3.85c5.48,0,9-2.95,9-7.51C79.75,23.79,77.47,21.72,71.59,20.3ZM195.7,9.22l-7.69,12-7.64-12h-4.46L186,24.67V34.78h3.84V24.55L200,9.22Zm-64.63,3.46h8.37v22.1h3.84V12.68h8.37V9.22H131.08ZM169.41,24.8c3.86-1.07,6-3.77,6-7.63,0-4.91-3.59-8-9.38-8H154.67V34.76h3.8V25.58h6.45l6.48,9.2h4.44l-7-9.82Zm-10.95-2.5V12.6h7.17c3.74,0,5.88,1.77,5.88,4.84s-2.29,4.86-5.84,4.86Z" transform="translate(11, 11)" fill="#362d59" class="__sntry__">
|
||||||
|
</path>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 1.7 KiB |
3
.github/assets/vercel.svg
vendored
Normal file
3
.github/assets/vercel.svg
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
<svg height="50" viewBox="0 0 164 50" xmlns="http://www.w3.org/2000/svg" style="background:#fff" fill-rule="evenodd" clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="2">
|
||||||
|
<path d="M78.21 15.587c-5.672 0-9.762 3.864-9.762 9.661s4.604 9.66 10.276 9.66c3.427 0 6.448-1.416 8.319-3.805l-3.931-2.372c-1.038 1.186-2.615 1.879-4.388 1.879-2.461 0-4.552-1.342-5.328-3.489h14.397c.113-.601.18-1.223.18-1.879 0-5.79-4.09-9.655-9.763-9.655zm-4.86 7.783c.642-2.142 2.399-3.489 4.855-3.489 2.461 0 4.219 1.347 4.855 3.489h-9.71zm60.187-7.783c-5.673 0-9.763 3.864-9.763 9.661s4.604 9.66 10.276 9.66c3.427 0 6.449-1.416 8.319-3.805l-3.931-2.372c-1.038 1.186-2.615 1.879-4.388 1.879-2.461 0-4.552-1.342-5.328-3.489h14.397c.113-.601.18-1.223.18-1.879 0-5.79-4.09-9.655-9.762-9.655zm-4.856 7.783c.642-2.142 2.4-3.489 4.856-3.489 2.46 0 4.218 1.347 4.855 3.489h-9.711zm-20.054 1.878c0 3.22 2.015 5.367 5.139 5.367 2.116 0 3.704-1.003 4.52-2.64l3.947 2.378c-1.634 2.843-4.696 4.556-8.467 4.556-5.678 0-9.763-3.864-9.763-9.661s4.09-9.66 9.763-9.66c3.77 0 6.828 1.712 8.467 4.556l-3.946 2.377c-.817-1.637-2.405-2.64-4.521-2.64-3.12 0-5.139 2.147-5.139 5.367zm42.378-15.565v24.69h-4.624V9.682h4.624zM24.73 7l18.985 34.35H5.744L24.73 7zm47.465 2.683L57.956 35.446 43.72 9.683h5.338l8.9 16.102 8.898-16.102h5.339zm30.268 6.44v5.202a5.634 5.634 0 00-1.644-.263c-2.985 0-5.138 2.147-5.138 5.367v7.943h-4.624V16.124h4.624v4.938c0-2.727 3.036-4.938 6.782-4.938z" fill-rule="nonzero" />
|
||||||
|
</svg>
|
After Width: | Height: | Size: 1.4 KiB |
9
.github/dependabot.yml
vendored
9
.github/dependabot.yml
vendored
@ -1,36 +1,33 @@
|
|||||||
version: 2
|
version: 2
|
||||||
updates:
|
updates:
|
||||||
- package-ecosystem: npm
|
- package-ecosystem: npm
|
||||||
directory: "/"
|
directory: /
|
||||||
schedule:
|
schedule:
|
||||||
interval: weekly
|
interval: weekly
|
||||||
day: sunday
|
day: sunday
|
||||||
time: "01:00"
|
time: "01:00"
|
||||||
open-pull-requests-limit: 99
|
|
||||||
reviewers:
|
reviewers:
|
||||||
- lipis
|
- lipis
|
||||||
assignees:
|
assignees:
|
||||||
- lipis
|
- lipis
|
||||||
|
|
||||||
- package-ecosystem: npm
|
- package-ecosystem: npm
|
||||||
directory: "/src/packages/excalidraw/"
|
directory: /src/packages/excalidraw/
|
||||||
schedule:
|
schedule:
|
||||||
interval: weekly
|
interval: weekly
|
||||||
day: sunday
|
day: sunday
|
||||||
time: "01:00"
|
time: "01:00"
|
||||||
open-pull-requests-limit: 99
|
|
||||||
reviewers:
|
reviewers:
|
||||||
- ad1992
|
- ad1992
|
||||||
assignees:
|
assignees:
|
||||||
- ad1992
|
- ad1992
|
||||||
|
|
||||||
- package-ecosystem: npm
|
- package-ecosystem: npm
|
||||||
directory: "/src/packages/utils/"
|
directory: /src/packages/utils/
|
||||||
schedule:
|
schedule:
|
||||||
interval: weekly
|
interval: weekly
|
||||||
day: sunday
|
day: sunday
|
||||||
time: "01:00"
|
time: "01:00"
|
||||||
open-pull-requests-limit: 99
|
|
||||||
reviewers:
|
reviewers:
|
||||||
- ad1992
|
- ad1992
|
||||||
assignees:
|
assignees:
|
||||||
|
5
.github/workflows/build-docker.yml
vendored
5
.github/workflows/build-docker.yml
vendored
@ -6,9 +6,8 @@ on:
|
|||||||
- master
|
- master
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build-docker:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v1
|
- uses: actions/checkout@v2
|
||||||
- run: docker build -t excalidraw .
|
- run: docker build -t excalidraw .
|
||||||
|
20
.github/workflows/build-packages.yml
vendored
20
.github/workflows/build-packages.yml
vendored
@ -7,27 +7,23 @@ on:
|
|||||||
pull_request:
|
pull_request:
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
packages:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v1
|
- uses: actions/checkout@v2
|
||||||
|
|
||||||
- name: Setup Node.js 14.x
|
- name: Setup Node.js 14.x
|
||||||
uses: actions/setup-node@v1
|
uses: actions/setup-node@v2
|
||||||
with:
|
with:
|
||||||
node-version: 14.x
|
node-version: 14.x
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: |
|
run: |
|
||||||
npm ci
|
yarn --frozen-lockfile
|
||||||
npm ci --prefix src/packages/excalidraw
|
yarn --cwd src/packages/excalidraw
|
||||||
npm ci --prefix src/packages/utils
|
yarn --cwd src/packages/utils
|
||||||
|
|
||||||
- name: Build @excalidraw/excalidraw
|
- name: Build @excalidraw/excalidraw
|
||||||
run: |
|
run: |
|
||||||
npm run pack --prefix src/packages/excalidraw
|
yarn --cwd src/packages/excalidraw run pack
|
||||||
|
|
||||||
- name: Build @excalidraw/utils
|
- name: Build @excalidraw/utils
|
||||||
run: |
|
run: |
|
||||||
npm run pack --prefix src/packages/utils
|
yarn --cwd src/packages/utils run pack
|
||||||
|
1
.github/workflows/cancel.yml
vendored
1
.github/workflows/cancel.yml
vendored
@ -9,7 +9,6 @@ on:
|
|||||||
jobs:
|
jobs:
|
||||||
cancel:
|
cancel:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
timeout-minutes: 3
|
timeout-minutes: 3
|
||||||
steps:
|
steps:
|
||||||
- uses: styfle/cancel-workflow-action@0.6.0
|
- uses: styfle/cancel-workflow-action@0.6.0
|
||||||
|
12
.github/workflows/lint.yml
vendored
12
.github/workflows/lint.yml
vendored
@ -7,16 +7,16 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v1
|
- uses: actions/checkout@v2
|
||||||
|
|
||||||
- name: Setup Node.js 14.x
|
- name: Setup Node.js 14.x
|
||||||
uses: actions/setup-node@v1
|
uses: actions/setup-node@v2
|
||||||
with:
|
with:
|
||||||
node-version: 14.x
|
node-version: 14.x
|
||||||
|
|
||||||
- name: Install and lint
|
- name: Install and lint
|
||||||
run: |
|
run: |
|
||||||
npm ci
|
yarn --frozen-lockfile
|
||||||
npm run test:other
|
yarn test:other
|
||||||
npm run test:code
|
yarn test:code
|
||||||
npm run test:typecheck
|
yarn test:typecheck
|
||||||
|
6
.github/workflows/locales-coverage.yml
vendored
6
.github/workflows/locales-coverage.yml
vendored
@ -3,7 +3,7 @@ name: Build locales coverage
|
|||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- "l10n_master"
|
- l10n_master
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
locales:
|
locales:
|
||||||
@ -15,13 +15,13 @@ jobs:
|
|||||||
token: ${{ secrets.PUSH_TRANSLATIONS_COVERAGE_PAT }}
|
token: ${{ secrets.PUSH_TRANSLATIONS_COVERAGE_PAT }}
|
||||||
|
|
||||||
- name: Setup Node.js 14.x
|
- name: Setup Node.js 14.x
|
||||||
uses: actions/setup-node@v1
|
uses: actions/setup-node@v2
|
||||||
with:
|
with:
|
||||||
node-version: 14.x
|
node-version: 14.x
|
||||||
|
|
||||||
- name: Create report file
|
- name: Create report file
|
||||||
run: |
|
run: |
|
||||||
npm run locales-coverage
|
yarn locales-coverage
|
||||||
FILE_CHANGED=$(git diff src/locales/percentages.json)
|
FILE_CHANGED=$(git diff src/locales/percentages.json)
|
||||||
if [ ! -z "${FILE_CHANGED}" ]; then
|
if [ ! -z "${FILE_CHANGED}" ]; then
|
||||||
git config --global user.name 'Excalidraw Bot'
|
git config --global user.name 'Excalidraw Bot'
|
||||||
|
5
.github/workflows/semantic-pr-title.yml
vendored
5
.github/workflows/semantic-pr-title.yml
vendored
@ -1,4 +1,4 @@
|
|||||||
name: "Semantic PR title"
|
name: Semantic PR title
|
||||||
|
|
||||||
on:
|
on:
|
||||||
pull_request_target:
|
pull_request_target:
|
||||||
@ -8,9 +8,8 @@ on:
|
|||||||
- synchronize
|
- synchronize
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
main:
|
semantic:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: amannn/action-semantic-pull-request@v3.0.0
|
- uses: amannn/action-semantic-pull-request@v3.0.0
|
||||||
env:
|
env:
|
||||||
|
17
.github/workflows/sentry-production.yml
vendored
17
.github/workflows/sentry-production.yml
vendored
@ -1,4 +1,4 @@
|
|||||||
name: New Sentry Production Release
|
name: New Sentry production release
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
@ -6,28 +6,23 @@ on:
|
|||||||
- master
|
- master
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
release:
|
sentry:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v1.0.0
|
- uses: actions/checkout@v2
|
||||||
|
|
||||||
- name: Setup Node.js 14.x
|
- name: Setup Node.js 14.x
|
||||||
uses: actions/setup-node@v1
|
uses: actions/setup-node@v2
|
||||||
with:
|
with:
|
||||||
node-version: 14.x
|
node-version: 14.x
|
||||||
|
|
||||||
- name: Install and build
|
- name: Install and build
|
||||||
run: |
|
run: |
|
||||||
npm ci
|
yarn --frozen-lockfile
|
||||||
npm run build:app
|
yarn build:app
|
||||||
env:
|
env:
|
||||||
CI: true
|
CI: true
|
||||||
|
|
||||||
- name: Install Sentry
|
- name: Install Sentry
|
||||||
run: |
|
run: |
|
||||||
curl -sL https://sentry.io/get-cli/ | bash
|
curl -sL https://sentry.io/get-cli/ | bash
|
||||||
|
|
||||||
- name: Create new Sentry release
|
- name: Create new Sentry release
|
||||||
run: |
|
run: |
|
||||||
export SENTRY_RELEASE=$(sentry-cli releases propose-version)
|
export SENTRY_RELEASE=$(sentry-cli releases propose-version)
|
||||||
|
11
.github/workflows/test.yml
vendored
11
.github/workflows/test.yml
vendored
@ -5,16 +5,13 @@ on: pull_request
|
|||||||
jobs:
|
jobs:
|
||||||
test:
|
test:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v1
|
- uses: actions/checkout@v2
|
||||||
|
|
||||||
- name: Setup Node.js 14.x
|
- name: Setup Node.js 14.x
|
||||||
uses: actions/setup-node@v1
|
uses: actions/setup-node@v2
|
||||||
with:
|
with:
|
||||||
node-version: 14.x
|
node-version: 14.x
|
||||||
|
|
||||||
- name: Install and test
|
- name: Install and test
|
||||||
run: |
|
run: |
|
||||||
npm ci
|
yarn --frozen-lockfile
|
||||||
npm run test:app
|
yarn test:app
|
||||||
|
2
.gitignore
vendored
2
.gitignore
vendored
@ -16,7 +16,7 @@ firebase
|
|||||||
logs
|
logs
|
||||||
node_modules
|
node_modules
|
||||||
npm-debug.log*
|
npm-debug.log*
|
||||||
|
package-lock.json
|
||||||
static
|
static
|
||||||
yarn-debug.log*
|
yarn-debug.log*
|
||||||
yarn-error.log*
|
yarn-error.log*
|
||||||
yarn.lock
|
|
||||||
|
@ -1,4 +0,0 @@
|
|||||||
{
|
|
||||||
"proseWrap": "never",
|
|
||||||
"trailingComma": "all"
|
|
||||||
}
|
|
@ -5,7 +5,7 @@
|
|||||||
### Option 1 - Manual
|
### Option 1 - Manual
|
||||||
|
|
||||||
1. Fork and clone the repo
|
1. Fork and clone the repo
|
||||||
1. Run `npm install` to install dependencies
|
1. Run `yarn` to install dependencies
|
||||||
1. Create a branch for your PR with `git checkout -b your-branch-name`
|
1. Create a branch for your PR with `git checkout -b your-branch-name`
|
||||||
|
|
||||||
> To keep `master` branch pointing to remote repository and make pull requests from branches on your fork. To do this, run:
|
> To keep `master` branch pointing to remote repository and make pull requests from branches on your fork. To do this, run:
|
||||||
|
@ -2,13 +2,13 @@ FROM node:14-alpine AS build
|
|||||||
|
|
||||||
WORKDIR /opt/node_app
|
WORKDIR /opt/node_app
|
||||||
|
|
||||||
COPY package.json package-lock.json ./
|
COPY package.json yarn.lock ./
|
||||||
RUN npm i --no-optional
|
RUN yarn --ignore-optional
|
||||||
|
|
||||||
ARG NODE_ENV=production
|
ARG NODE_ENV=production
|
||||||
|
|
||||||
COPY . .
|
COPY . .
|
||||||
RUN npm run build:app:docker
|
RUN yarn build:app:docker
|
||||||
|
|
||||||
FROM nginx:1.17-alpine
|
FROM nginx:1.17-alpine
|
||||||
|
|
||||||
|
26
README.md
26
README.md
@ -28,6 +28,10 @@ If you like the project, you can become a sponsor at [Open Collective](https://o
|
|||||||
|
|
||||||
<a href="https://opencollective.com/excalidraw#category-CONTRIBUTE" target="_blank"><img src="https://opencollective.com/excalidraw/tiers/backers.svg?avatarHeight=32"/></a>
|
<a href="https://opencollective.com/excalidraw#category-CONTRIBUTE" target="_blank"><img src="https://opencollective.com/excalidraw/tiers/backers.svg?avatarHeight=32"/></a>
|
||||||
|
|
||||||
|
Last but not least, we're thankful to these companies for offering their services for free:
|
||||||
|
|
||||||
|
[](https://vercel.com) [](https://sentry.io) [](https://crowdin.com)
|
||||||
|
|
||||||
## Documentation
|
## Documentation
|
||||||
|
|
||||||
### Shortcuts
|
### Shortcuts
|
||||||
@ -86,6 +90,12 @@ Try out [`@excalidraw/excalidraw`](https://www.npmjs.com/package/@excalidraw/exc
|
|||||||
|
|
||||||
These instructions will get you a copy of the project up and running on your local machine for development and testing purposes.
|
These instructions will get you a copy of the project up and running on your local machine for development and testing purposes.
|
||||||
|
|
||||||
|
#### Requirements
|
||||||
|
|
||||||
|
- [Node.js](https://nodejs.org/en/)
|
||||||
|
- [Yarn](https://yarnpkg.com/getting-started/install)
|
||||||
|
- [Git](https://git-scm.com/downloads)
|
||||||
|
|
||||||
#### Clone the repo
|
#### Clone the repo
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
@ -94,14 +104,14 @@ git clone https://github.com/excalidraw/excalidraw.git
|
|||||||
|
|
||||||
#### Commands
|
#### Commands
|
||||||
|
|
||||||
| Command | Description |
|
| Command | Description |
|
||||||
| --------------------- | --------------------------------- |
|
| ------------------ | --------------------------------- |
|
||||||
| `npm install` | Install the dependencies |
|
| `yarn` | Install the dependencies |
|
||||||
| `npm start` | Run the project |
|
| `yarn start` | Run the project |
|
||||||
| `npm run fix` | Reformat all files with Prettier |
|
| `yarn fix` | Reformat all files with Prettier |
|
||||||
| `npm test` | Run tests |
|
| `yarn test` | Run tests |
|
||||||
| `npm run test:update` | Update test snapshots |
|
| `yarn test:update` | Update test snapshots |
|
||||||
| `npm run test:code` | Test for formatting with Prettier |
|
| `yarn test:code` | Test for formatting with Prettier |
|
||||||
|
|
||||||
#### Docker Compose
|
#### Docker Compose
|
||||||
|
|
||||||
|
@ -18,7 +18,7 @@ services:
|
|||||||
volumes:
|
volumes:
|
||||||
- ./:/opt/node_app/app:delegated
|
- ./:/opt/node_app/app:delegated
|
||||||
- ./package.json:/opt/node_app/package.json
|
- ./package.json:/opt/node_app/package.json
|
||||||
- ./package-lock.json:/opt/node_app/package-lock.json
|
- ./yarn.lock:/opt/node_app/yarn.lock
|
||||||
- notused:/opt/node_app/app/node_modules
|
- notused:/opt/node_app/app/node_modules
|
||||||
|
|
||||||
volumes:
|
volumes:
|
||||||
|
23611
package-lock.json
generated
23611
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
45
package.json
45
package.json
@ -19,21 +19,20 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@sentry/browser": "6.1.0",
|
"@sentry/browser": "6.2.2",
|
||||||
"@sentry/integrations": "6.1.0",
|
"@sentry/integrations": "6.2.1",
|
||||||
"@testing-library/jest-dom": "5.11.9",
|
"@testing-library/jest-dom": "5.11.9",
|
||||||
"@testing-library/react": "11.2.5",
|
"@testing-library/react": "11.2.5",
|
||||||
"@types/jest": "26.0.20",
|
"@types/jest": "26.0.20",
|
||||||
"@types/react": "17.0.2",
|
"@types/react": "17.0.2",
|
||||||
"@types/react-dom": "17.0.1",
|
"@types/react-dom": "17.0.1",
|
||||||
"@types/socket.io-client": "1.4.35",
|
"@types/socket.io-client": "1.4.36",
|
||||||
"browser-fs-access": "0.13.1",
|
"browser-fs-access": "0.14.2",
|
||||||
"clsx": "1.1.1",
|
"clsx": "1.1.1",
|
||||||
"firebase": "8.2.7",
|
"firebase": "8.2.10",
|
||||||
"i18next-browser-languagedetector": "6.0.1",
|
"i18next-browser-languagedetector": "6.0.1",
|
||||||
"lodash.throttle": "4.1.1",
|
"lodash.throttle": "4.1.1",
|
||||||
"nanoid": "3.1.20",
|
"nanoid": "3.1.21",
|
||||||
"node-sass": "4.14.1",
|
|
||||||
"open-color": "1.8.0",
|
"open-color": "1.8.0",
|
||||||
"pako": "1.0.11",
|
"pako": "1.0.11",
|
||||||
"png-chunk-text": "1.0.0",
|
"png-chunk-text": "1.0.0",
|
||||||
@ -43,18 +42,21 @@
|
|||||||
"pwacompat": "2.0.17",
|
"pwacompat": "2.0.17",
|
||||||
"react": "17.0.1",
|
"react": "17.0.1",
|
||||||
"react-dom": "17.0.1",
|
"react-dom": "17.0.1",
|
||||||
"react-scripts": "4.0.2",
|
"react-scripts": "4.0.3",
|
||||||
"roughjs": "4.3.1",
|
"roughjs": "4.3.1",
|
||||||
|
"sass": "1.32.8",
|
||||||
"socket.io-client": "2.3.1",
|
"socket.io-client": "2.3.1",
|
||||||
"typescript": "4.1.5"
|
"typescript": "4.2.3"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@excalidraw/eslint-config": "1.0.0",
|
||||||
|
"@excalidraw/prettier-config": "1.0.2",
|
||||||
"@types/lodash.throttle": "4.1.6",
|
"@types/lodash.throttle": "4.1.6",
|
||||||
"@types/pako": "1.0.1",
|
"@types/pako": "1.0.1",
|
||||||
"@types/resize-observer-browser": "0.1.5",
|
"@types/resize-observer-browser": "0.1.5",
|
||||||
"eslint-config-prettier": "7.2.0",
|
"eslint-config-prettier": "8.1.0",
|
||||||
"eslint-plugin-prettier": "3.3.1",
|
"eslint-plugin-prettier": "3.3.1",
|
||||||
"firebase-tools": "9.3.0",
|
"firebase-tools": "9.6.1",
|
||||||
"husky": "4.3.8",
|
"husky": "4.3.8",
|
||||||
"jest-canvas-mock": "2.3.1",
|
"jest-canvas-mock": "2.3.1",
|
||||||
"lint-staged": "10.5.4",
|
"lint-staged": "10.5.4",
|
||||||
@ -63,7 +65,7 @@
|
|||||||
"rewire": "5.0.0"
|
"rewire": "5.0.0"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=12.0.0"
|
"node": ">=14.0.0"
|
||||||
},
|
},
|
||||||
"homepage": ".",
|
"homepage": ".",
|
||||||
"husky": {
|
"husky": {
|
||||||
@ -78,28 +80,29 @@
|
|||||||
"resetMocks": false
|
"resetMocks": false
|
||||||
},
|
},
|
||||||
"name": "excalidraw",
|
"name": "excalidraw",
|
||||||
|
"prettier": "@excalidraw/prettier-config",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build-node": "node ./scripts/build-node.js",
|
"build-node": "node ./scripts/build-node.js",
|
||||||
"build:app:docker": "REACT_APP_DISABLE_SENTRY=true react-scripts build",
|
"build:app:docker": "REACT_APP_DISABLE_SENTRY=true react-scripts build",
|
||||||
"build:app": "REACT_APP_GIT_SHA=$VERCEL_GIT_COMMIT_SHA react-scripts build",
|
"build:app": "REACT_APP_GIT_SHA=$VERCEL_GIT_COMMIT_SHA react-scripts build",
|
||||||
"build:version": "node ./scripts/build-version.js",
|
"build:version": "node ./scripts/build-version.js",
|
||||||
"build": "npm run build:app && npm run build:version",
|
"build": "yarn build:app && yarn build:version",
|
||||||
"eject": "react-scripts eject",
|
"eject": "react-scripts eject",
|
||||||
"fix:code": "npm run test:code -- --fix",
|
"fix:code": "yarn test:code --fix",
|
||||||
"fix:other": "npm run prettier -- --write",
|
"fix:other": "yarn prettier --write",
|
||||||
"fix": "npm run fix:other && npm run fix:code",
|
"fix": "yarn fix:other && yarn fix:code",
|
||||||
"locales-coverage": "node scripts/build-locales-coverage.js",
|
"locales-coverage": "node scripts/build-locales-coverage.js",
|
||||||
"locales-coverage:description": "node scripts/locales-coverage-description.js",
|
"locales-coverage:description": "node scripts/locales-coverage-description.js",
|
||||||
"prettier": "prettier \"**/*.{css,scss,json,md,html,yml}\" --ignore-path=.eslintignore",
|
"prettier": "prettier \"**/*.{css,scss,json,md,html,yml}\" --ignore-path=.eslintignore",
|
||||||
"start": "react-scripts start",
|
"start": "react-scripts start",
|
||||||
"test:all": "npm run test:typecheck && npm run test:code && npm run test:other && npm run test:app -- --watchAll=false",
|
"test:all": "yarn test:typecheck && yarn test:code && yarn test:other && yarn test:app --watchAll=false",
|
||||||
"test:app": "react-scripts test --passWithNoTests",
|
"test:app": "react-scripts test --passWithNoTests",
|
||||||
"test:code": "eslint --max-warnings=0 --ignore-path .gitignore --ext .js,.ts,.tsx .",
|
"test:code": "eslint --max-warnings=0 --ext .js,.ts,.tsx .",
|
||||||
"test:debug": "react-scripts --inspect-brk test --runInBand --no-cache",
|
"test:debug": "react-scripts --inspect-brk test --runInBand --no-cache",
|
||||||
"test:other": "npm run prettier -- --list-different",
|
"test:other": "yarn prettier --list-different",
|
||||||
"test:typecheck": "tsc",
|
"test:typecheck": "tsc",
|
||||||
"test:update": "npm run test:app -- --updateSnapshot --watchAll=false",
|
"test:update": "yarn test:app --updateSnapshot --watchAll=false",
|
||||||
"test": "npm run test:app"
|
"test": "yarn test:app"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Binary file not shown.
BIN
public/Virgil.woff2
Normal file
BIN
public/Virgil.woff2
Normal file
Binary file not shown.
@ -1,7 +1,7 @@
|
|||||||
/* http://www.eaglefonts.com/fg-virgil-ttf-131249.htm */
|
/* http://www.eaglefonts.com/fg-virgil-ttf-131249.htm */
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: "Virgil";
|
font-family: "Virgil";
|
||||||
src: url("FG_Virgil.woff2");
|
src: url("Virgil.woff2");
|
||||||
font-display: swap;
|
font-display: swap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -60,7 +60,7 @@
|
|||||||
|
|
||||||
<link
|
<link
|
||||||
rel="preload"
|
rel="preload"
|
||||||
href="FG_Virgil.woff2"
|
href="Virgil.woff2"
|
||||||
as="font"
|
as="font"
|
||||||
type="font/woff2"
|
type="font/woff2"
|
||||||
crossorigin="anonymous"
|
crossorigin="anonymous"
|
||||||
@ -86,7 +86,9 @@
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<link rel="stylesheet" href="fonts.css" type="text/css" />
|
<link rel="stylesheet" href="fonts.css" type="text/css" />
|
||||||
|
<script>
|
||||||
|
window.EXCALIDRAW_ASSET_PATH = "/";
|
||||||
|
</script>
|
||||||
<% if (process.env.REACT_APP_GOOGLE_ANALYTICS_ID) { %>
|
<% if (process.env.REACT_APP_GOOGLE_ANALYTICS_ID) { %>
|
||||||
<script
|
<script
|
||||||
async
|
async
|
||||||
@ -110,8 +112,7 @@
|
|||||||
Roboto, Helvetica, Arial, sans-serif;
|
Roboto, Helvetica, Arial, sans-serif;
|
||||||
font-family: var(--ui-font);
|
font-family: var(--ui-font);
|
||||||
-webkit-text-size-adjust: 100%;
|
-webkit-text-size-adjust: 100%;
|
||||||
-webkit-user-select: none;
|
|
||||||
user-select: none;
|
|
||||||
width: 100vw;
|
width: 100vw;
|
||||||
height: 100vh;
|
height: 100vh;
|
||||||
}
|
}
|
||||||
|
@ -26,5 +26,18 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"capture_links": "new_client"
|
"capture_links": "new_client",
|
||||||
|
"share_target": {
|
||||||
|
"action": "/web-share-target",
|
||||||
|
"method": "POST",
|
||||||
|
"enctype": "multipart/form-data",
|
||||||
|
"params": {
|
||||||
|
"files": [
|
||||||
|
{
|
||||||
|
"name": "file",
|
||||||
|
"accept": ["application/vnd.excalidraw+json", "application/json", ".excalidraw"]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
2
public/workbox/workbox-background-sync.prod.js
Normal file
2
public/workbox/workbox-background-sync.prod.js
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
this.workbox=this.workbox||{},this.workbox.backgroundSync=function(t,e,s){"use strict";try{self["workbox:background-sync:4.3.1"]&&_()}catch(t){}const i=3,n="workbox-background-sync",a="requests",r="queueName";class c{constructor(t){this.t=t,this.s=new s.DBWrapper(n,i,{onupgradeneeded:this.i})}async pushEntry(t){delete t.id,t.queueName=this.t,await this.s.add(a,t)}async unshiftEntry(t){const[e]=await this.s.getAllMatching(a,{count:1});e?t.id=e.id-1:delete t.id,t.queueName=this.t,await this.s.add(a,t)}async popEntry(){return this.h({direction:"prev"})}async shiftEntry(){return this.h({direction:"next"})}async getAll(){return await this.s.getAllMatching(a,{index:r,query:IDBKeyRange.only(this.t)})}async deleteEntry(t){await this.s.delete(a,t)}async h({direction:t}){const[e]=await this.s.getAllMatching(a,{direction:t,index:r,query:IDBKeyRange.only(this.t),count:1});if(e)return await this.deleteEntry(e.id),e}i(t){const e=t.target.result;t.oldVersion>0&&t.oldVersion<i&&e.objectStoreNames.contains(a)&&e.deleteObjectStore(a),e.createObjectStore(a,{autoIncrement:!0,keyPath:"id"}).createIndex(r,r,{unique:!1})}}const h=["method","referrer","referrerPolicy","mode","credentials","cache","redirect","integrity","keepalive"];class o{static async fromRequest(t){const e={url:t.url,headers:{}};"GET"!==t.method&&(e.body=await t.clone().arrayBuffer());for(const[s,i]of t.headers.entries())e.headers[s]=i;for(const s of h)void 0!==t[s]&&(e[s]=t[s]);return new o(e)}constructor(t){"navigate"===t.mode&&(t.mode="same-origin"),this.o=t}toObject(){const t=Object.assign({},this.o);return t.headers=Object.assign({},this.o.headers),t.body&&(t.body=t.body.slice(0)),t}toRequest(){return new Request(this.o.url,this.o)}clone(){return new o(this.toObject())}}const u="workbox-background-sync",y=10080,w=new Set;class d{constructor(t,{onSync:s,maxRetentionTime:i}={}){if(w.has(t))throw new e.WorkboxError("duplicate-queue-name",{name:t});w.add(t),this.u=t,this.l=s||this.replayRequests,this.q=i||y,this.m=new c(this.u),this.p()}get name(){return this.u}async pushRequest(t){await this.g(t,"push")}async unshiftRequest(t){await this.g(t,"unshift")}async popRequest(){return this.R("pop")}async shiftRequest(){return this.R("shift")}async getAll(){const t=await this.m.getAll(),e=Date.now(),s=[];for(const i of t){const t=60*this.q*1e3;e-i.timestamp>t?await this.m.deleteEntry(i.id):s.push(f(i))}return s}async g({request:t,metadata:e,timestamp:s=Date.now()},i){const n={requestData:(await o.fromRequest(t.clone())).toObject(),timestamp:s};e&&(n.metadata=e),await this.m[`${i}Entry`](n),this.k?this.D=!0:await this.registerSync()}async R(t){const e=Date.now(),s=await this.m[`${t}Entry`]();if(s){const i=60*this.q*1e3;return e-s.timestamp>i?this.R(t):f(s)}}async replayRequests(){let t;for(;t=await this.shiftRequest();)try{await fetch(t.request.clone())}catch(s){throw await this.unshiftRequest(t),new e.WorkboxError("queue-replay-failed",{name:this.u})}}async registerSync(){if("sync"in registration)try{await registration.sync.register(`${u}:${this.u}`)}catch(t){}}p(){"sync"in registration?self.addEventListener("sync",t=>{if(t.tag===`${u}:${this.u}`){const e=async()=>{let e;this.k=!0;try{await this.l({queue:this})}catch(t){throw e=t}finally{!this.D||e&&!t.lastChance||await this.registerSync(),this.k=!1,this.D=!1}};t.waitUntil(e())}}):this.l({queue:this})}static get _(){return w}}const f=t=>{const e={request:new o(t.requestData).toRequest(),timestamp:t.timestamp};return t.metadata&&(e.metadata=t.metadata),e};return t.Queue=d,t.Plugin=class{constructor(...t){this.v=new d(...t),this.fetchDidFail=this.fetchDidFail.bind(this)}async fetchDidFail({request:t}){await this.v.pushRequest({request:t})}},t}({},workbox.core._private,workbox.core._private);
|
||||||
|
//# sourceMappingURL=workbox-background-sync.prod.js.map
|
2
public/workbox/workbox-broadcast-update.prod.js
Normal file
2
public/workbox/workbox-broadcast-update.prod.js
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
this.workbox=this.workbox||{},this.workbox.broadcastUpdate=function(e,t){"use strict";try{self["workbox:broadcast-update:4.3.1"]&&_()}catch(e){}const s=(e,t,s)=>{return!s.some(s=>e.headers.has(s)&&t.headers.has(s))||s.every(s=>{const n=e.headers.has(s)===t.headers.has(s),a=e.headers.get(s)===t.headers.get(s);return n&&a})},n="workbox",a=1e4,i=["content-length","etag","last-modified"],o=async({channel:e,cacheName:t,url:s})=>{const n={type:"CACHE_UPDATED",meta:"workbox-broadcast-update",payload:{cacheName:t,updatedURL:s}};if(e)e.postMessage(n);else{const e=await clients.matchAll({type:"window"});for(const t of e)t.postMessage(n)}};class c{constructor({headersToCheck:e,channelName:t,deferNoticationTimeout:s}={}){this.t=e||i,this.s=t||n,this.i=s||a,this.o()}notifyIfUpdated({oldResponse:e,newResponse:t,url:n,cacheName:a,event:i}){if(!s(e,t,this.t)){const e=(async()=>{i&&i.request&&"navigate"===i.request.mode&&await this.h(i),await this.l({channel:this.u(),cacheName:a,url:n})})();if(i)try{i.waitUntil(e)}catch(e){}return e}}async l(e){await o(e)}u(){return"BroadcastChannel"in self&&!this.p&&(this.p=new BroadcastChannel(this.s)),this.p}h(e){if(!this.m.has(e)){const s=new t.Deferred;this.m.set(e,s);const n=setTimeout(()=>{s.resolve()},this.i);s.promise.then(()=>clearTimeout(n))}return this.m.get(e).promise}o(){this.m=new Map,self.addEventListener("message",e=>{if("WINDOW_READY"===e.data.type&&"workbox-window"===e.data.meta&&this.m.size>0){for(const e of this.m.values())e.resolve();this.m.clear()}})}}return e.BroadcastCacheUpdate=c,e.Plugin=class{constructor(e){this.l=new c(e)}cacheDidUpdate({cacheName:e,oldResponse:t,newResponse:s,request:n,event:a}){t&&this.l.notifyIfUpdated({cacheName:e,oldResponse:t,newResponse:s,event:a,url:n.url})}},e.broadcastUpdate=o,e.responsesAreSame=s,e}({},workbox.core._private);
|
||||||
|
//# sourceMappingURL=workbox-broadcast-update.prod.js.map
|
2
public/workbox/workbox-cacheable-response.prod.js
Normal file
2
public/workbox/workbox-cacheable-response.prod.js
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
this.workbox=this.workbox||{},this.workbox.cacheableResponse=function(t){"use strict";try{self["workbox:cacheable-response:4.3.1"]&&_()}catch(t){}class s{constructor(t={}){this.t=t.statuses,this.s=t.headers}isResponseCacheable(t){let s=!0;return this.t&&(s=this.t.includes(t.status)),this.s&&s&&(s=Object.keys(this.s).some(s=>t.headers.get(s)===this.s[s])),s}}return t.CacheableResponse=s,t.Plugin=class{constructor(t){this.i=new s(t)}cacheWillUpdate({response:t}){return this.i.isResponseCacheable(t)?t:null}},t}({});
|
||||||
|
//# sourceMappingURL=workbox-cacheable-response.prod.js.map
|
2
public/workbox/workbox-core.prod.js
Normal file
2
public/workbox/workbox-core.prod.js
Normal file
File diff suppressed because one or more lines are too long
2
public/workbox/workbox-expiration.prod.js
Normal file
2
public/workbox/workbox-expiration.prod.js
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
this.workbox=this.workbox||{},this.workbox.expiration=function(t,e,s,i,a,n){"use strict";try{self["workbox:expiration:4.3.1"]&&_()}catch(t){}const h="workbox-expiration",c="cache-entries",r=t=>{const e=new URL(t,location);return e.hash="",e.href};class o{constructor(t){this.t=t,this.s=new e.DBWrapper(h,1,{onupgradeneeded:t=>this.i(t)})}i(t){const e=t.target.result.createObjectStore(c,{keyPath:"id"});e.createIndex("cacheName","cacheName",{unique:!1}),e.createIndex("timestamp","timestamp",{unique:!1}),s.deleteDatabase(this.t)}async setTimestamp(t,e){t=r(t),await this.s.put(c,{url:t,timestamp:e,cacheName:this.t,id:this.h(t)})}async getTimestamp(t){return(await this.s.get(c,this.h(t))).timestamp}async expireEntries(t,e){const s=await this.s.transaction(c,"readwrite",(s,i)=>{const a=s.objectStore(c),n=[];let h=0;a.index("timestamp").openCursor(null,"prev").onsuccess=(({target:s})=>{const a=s.result;if(a){const s=a.value;s.cacheName===this.t&&(t&&s.timestamp<t||e&&h>=e?n.push(a.value):h++),a.continue()}else i(n)})}),i=[];for(const t of s)await this.s.delete(c,t.id),i.push(t.url);return i}h(t){return this.t+"|"+r(t)}}class u{constructor(t,e={}){this.o=!1,this.u=!1,this.l=e.maxEntries,this.p=e.maxAgeSeconds,this.t=t,this.m=new o(t)}async expireEntries(){if(this.o)return void(this.u=!0);this.o=!0;const t=this.p?Date.now()-1e3*this.p:void 0,e=await this.m.expireEntries(t,this.l),s=await caches.open(this.t);for(const t of e)await s.delete(t);this.o=!1,this.u&&(this.u=!1,this.expireEntries())}async updateTimestamp(t){await this.m.setTimestamp(t,Date.now())}async isURLExpired(t){return await this.m.getTimestamp(t)<Date.now()-1e3*this.p}async delete(){this.u=!1,await this.m.expireEntries(1/0)}}return t.CacheExpiration=u,t.Plugin=class{constructor(t={}){this.D=t,this.p=t.maxAgeSeconds,this.g=new Map,t.purgeOnQuotaError&&n.registerQuotaErrorCallback(()=>this.deleteCacheAndMetadata())}k(t){if(t===a.cacheNames.getRuntimeName())throw new i.WorkboxError("expire-custom-caches-only");let e=this.g.get(t);return e||(e=new u(t,this.D),this.g.set(t,e)),e}cachedResponseWillBeUsed({event:t,request:e,cacheName:s,cachedResponse:i}){if(!i)return null;let a=this.N(i);const n=this.k(s);n.expireEntries();const h=n.updateTimestamp(e.url);if(t)try{t.waitUntil(h)}catch(t){}return a?i:null}N(t){if(!this.p)return!0;const e=this._(t);return null===e||e>=Date.now()-1e3*this.p}_(t){if(!t.headers.has("date"))return null;const e=t.headers.get("date"),s=new Date(e).getTime();return isNaN(s)?null:s}async cacheDidUpdate({cacheName:t,request:e}){const s=this.k(t);await s.updateTimestamp(e.url),await s.expireEntries()}async deleteCacheAndMetadata(){for(const[t,e]of this.g)await caches.delete(t),await e.delete();this.g=new Map}},t}({},workbox.core._private,workbox.core._private,workbox.core._private,workbox.core._private,workbox.core);
|
||||||
|
//# sourceMappingURL=workbox-expiration.prod.js.map
|
2
public/workbox/workbox-navigation-preload.prod.js
Normal file
2
public/workbox/workbox-navigation-preload.prod.js
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
this.workbox=this.workbox||{},this.workbox.navigationPreload=function(t){"use strict";try{self["workbox:navigation-preload:4.3.1"]&&_()}catch(t){}function e(){return Boolean(self.registration&&self.registration.navigationPreload)}return t.disable=function(){e()&&self.addEventListener("activate",t=>{t.waitUntil(self.registration.navigationPreload.disable().then(()=>{}))})},t.enable=function(t){e()&&self.addEventListener("activate",e=>{e.waitUntil(self.registration.navigationPreload.enable().then(()=>{t&&self.registration.navigationPreload.setHeaderValue(t)}))})},t.isSupported=e,t}({});
|
||||||
|
//# sourceMappingURL=workbox-navigation-preload.prod.js.map
|
2
public/workbox/workbox-offline-ga.prod.js
Normal file
2
public/workbox/workbox-offline-ga.prod.js
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
this.workbox=this.workbox||{},this.workbox.googleAnalytics=function(e,t,o,n,a,c,w){"use strict";try{self["workbox:google-analytics:4.3.1"]&&_()}catch(e){}const r=/^\/(\w+\/)?collect/,s=e=>async({queue:t})=>{let o;for(;o=await t.shiftRequest();){const{request:n,timestamp:a}=o,c=new URL(n.url);try{const w="POST"===n.method?new URLSearchParams(await n.clone().text()):c.searchParams,r=a-(Number(w.get("qt"))||0),s=Date.now()-r;if(w.set("qt",s),e.parameterOverrides)for(const t of Object.keys(e.parameterOverrides)){const o=e.parameterOverrides[t];w.set(t,o)}"function"==typeof e.hitFilter&&e.hitFilter.call(null,w),await fetch(new Request(c.origin+c.pathname,{body:w.toString(),method:"POST",mode:"cors",credentials:"omit",headers:{"Content-Type":"text/plain"}}))}catch(e){throw await t.unshiftRequest(o),e}}},i=e=>{const t=({url:e})=>"www.google-analytics.com"===e.hostname&&r.test(e.pathname),o=new w.NetworkOnly({plugins:[e]});return[new n.Route(t,o,"GET"),new n.Route(t,o,"POST")]},l=e=>{const t=new c.NetworkFirst({cacheName:e});return new n.Route(({url:e})=>"www.google-analytics.com"===e.hostname&&"/analytics.js"===e.pathname,t,"GET")},m=e=>{const t=new c.NetworkFirst({cacheName:e});return new n.Route(({url:e})=>"www.googletagmanager.com"===e.hostname&&"/gtag/js"===e.pathname,t,"GET")},u=e=>{const t=new c.NetworkFirst({cacheName:e});return new n.Route(({url:e})=>"www.googletagmanager.com"===e.hostname&&"/gtm.js"===e.pathname,t,"GET")};return e.initialize=((e={})=>{const n=o.cacheNames.getGoogleAnalyticsName(e.cacheName),c=new t.Plugin("workbox-google-analytics",{maxRetentionTime:2880,onSync:s(e)}),w=[u(n),l(n),m(n),...i(c)],r=new a.Router;for(const e of w)r.registerRoute(e);r.addFetchListener()}),e}({},workbox.backgroundSync,workbox.core._private,workbox.routing,workbox.routing,workbox.strategies,workbox.strategies);
|
||||||
|
//# sourceMappingURL=workbox-offline-ga.prod.js.map
|
2
public/workbox/workbox-precaching.prod.js
Normal file
2
public/workbox/workbox-precaching.prod.js
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
this.workbox=this.workbox||{},this.workbox.precaching=function(t,e,n,s,c){"use strict";try{self["workbox:precaching:4.3.1"]&&_()}catch(t){}const o=[],i={get:()=>o,add(t){o.push(...t)}};const a="__WB_REVISION__";function r(t){if(!t)throw new c.WorkboxError("add-to-cache-list-unexpected-type",{entry:t});if("string"==typeof t){const e=new URL(t,location);return{cacheKey:e.href,url:e.href}}const{revision:e,url:n}=t;if(!n)throw new c.WorkboxError("add-to-cache-list-unexpected-type",{entry:t});if(!e){const t=new URL(n,location);return{cacheKey:t.href,url:t.href}}const s=new URL(n,location),o=new URL(n,location);return o.searchParams.set(a,e),{cacheKey:o.href,url:s.href}}class l{constructor(t){this.t=e.cacheNames.getPrecacheName(t),this.s=new Map}addToCacheList(t){for(const e of t){const{cacheKey:t,url:n}=r(e);if(this.s.has(n)&&this.s.get(n)!==t)throw new c.WorkboxError("add-to-cache-list-conflicting-entries",{firstEntry:this.s.get(n),secondEntry:t});this.s.set(n,t)}}async install({event:t,plugins:e}={}){const n=[],s=[],c=await caches.open(this.t),o=await c.keys(),i=new Set(o.map(t=>t.url));for(const t of this.s.values())i.has(t)?s.push(t):n.push(t);const a=n.map(n=>this.o({event:t,plugins:e,url:n}));return await Promise.all(a),{updatedURLs:n,notUpdatedURLs:s}}async activate(){const t=await caches.open(this.t),e=await t.keys(),n=new Set(this.s.values()),s=[];for(const c of e)n.has(c.url)||(await t.delete(c),s.push(c.url));return{deletedURLs:s}}async o({url:t,event:e,plugins:o}){const i=new Request(t,{credentials:"same-origin"});let a,r=await s.fetchWrapper.fetch({event:e,plugins:o,request:i});for(const t of o||[])"cacheWillUpdate"in t&&(a=t.cacheWillUpdate.bind(t));if(!(a?a({event:e,request:i,response:r}):r.status<400))throw new c.WorkboxError("bad-precaching-response",{url:t,status:r.status});r.redirected&&(r=await async function(t){const e=t.clone(),n="body"in e?Promise.resolve(e.body):e.blob(),s=await n;return new Response(s,{headers:e.headers,status:e.status,statusText:e.statusText})}(r)),await n.cacheWrapper.put({event:e,plugins:o,request:i,response:r,cacheName:this.t,matchOptions:{ignoreSearch:!0}})}getURLsToCacheKeys(){return this.s}getCachedURLs(){return[...this.s.keys()]}getCacheKeyForURL(t){const e=new URL(t,location);return this.s.get(e.href)}}let u;const h=()=>(u||(u=new l),u);const d=(t,e)=>{const n=h().getURLsToCacheKeys();for(const s of function*(t,{ignoreURLParametersMatching:e,directoryIndex:n,cleanURLs:s,urlManipulation:c}={}){const o=new URL(t,location);o.hash="",yield o.href;const i=function(t,e){for(const n of[...t.searchParams.keys()])e.some(t=>t.test(n))&&t.searchParams.delete(n);return t}(o,e);if(yield i.href,n&&i.pathname.endsWith("/")){const t=new URL(i);t.pathname+=n,yield t.href}if(s){const t=new URL(i);t.pathname+=".html",yield t.href}if(c){const t=c({url:o});for(const e of t)yield e.href}}(t,e)){const t=n.get(s);if(t)return t}};let w=!1;const f=t=>{w||((({ignoreURLParametersMatching:t=[/^utm_/],directoryIndex:n="index.html",cleanURLs:s=!0,urlManipulation:c=null}={})=>{const o=e.cacheNames.getPrecacheName();addEventListener("fetch",e=>{const i=d(e.request.url,{cleanURLs:s,directoryIndex:n,ignoreURLParametersMatching:t,urlManipulation:c});if(!i)return;let a=caches.open(o).then(t=>t.match(i)).then(t=>t||fetch(i));e.respondWith(a)})})(t),w=!0)},y=t=>{const e=h(),n=i.get();t.waitUntil(e.install({event:t,plugins:n}).catch(t=>{throw t}))},p=t=>{const e=h(),n=i.get();t.waitUntil(e.activate({event:t,plugins:n}))},L=t=>{h().addToCacheList(t),t.length>0&&(addEventListener("install",y),addEventListener("activate",p))};return t.addPlugins=(t=>{i.add(t)}),t.addRoute=f,t.cleanupOutdatedCaches=(()=>{addEventListener("activate",t=>{const n=e.cacheNames.getPrecacheName();t.waitUntil((async(t,e="-precache-")=>{const n=(await caches.keys()).filter(n=>n.includes(e)&&n.includes(self.registration.scope)&&n!==t);return await Promise.all(n.map(t=>caches.delete(t))),n})(n).then(t=>{}))})}),t.getCacheKeyForURL=(t=>{return h().getCacheKeyForURL(t)}),t.precache=L,t.precacheAndRoute=((t,e)=>{L(t),f(e)}),t.PrecacheController=l,t}({},workbox.core._private,workbox.core._private,workbox.core._private,workbox.core._private);
|
||||||
|
//# sourceMappingURL=workbox-precaching.prod.js.map
|
2
public/workbox/workbox-range-requests.prod.js
Normal file
2
public/workbox/workbox-range-requests.prod.js
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
this.workbox=this.workbox||{},this.workbox.rangeRequests=function(e,n){"use strict";try{self["workbox:range-requests:4.3.1"]&&_()}catch(e){}async function t(e,t){try{if(206===t.status)return t;const s=e.headers.get("range");if(!s)throw new n.WorkboxError("no-range-header");const a=function(e){const t=e.trim().toLowerCase();if(!t.startsWith("bytes="))throw new n.WorkboxError("unit-must-be-bytes",{normalizedRangeHeader:t});if(t.includes(","))throw new n.WorkboxError("single-range-only",{normalizedRangeHeader:t});const s=/(\d*)-(\d*)/.exec(t);if(null===s||!s[1]&&!s[2])throw new n.WorkboxError("invalid-range-values",{normalizedRangeHeader:t});return{start:""===s[1]?null:Number(s[1]),end:""===s[2]?null:Number(s[2])}}(s),r=await t.blob(),i=function(e,t,s){const a=e.size;if(s>a||t<0)throw new n.WorkboxError("range-not-satisfiable",{size:a,end:s,start:t});let r,i;return null===t?(r=a-s,i=a):null===s?(r=t,i=a):(r=t,i=s+1),{start:r,end:i}}(r,a.start,a.end),o=r.slice(i.start,i.end),u=o.size,l=new Response(o,{status:206,statusText:"Partial Content",headers:t.headers});return l.headers.set("Content-Length",u),l.headers.set("Content-Range",`bytes ${i.start}-${i.end-1}/`+r.size),l}catch(e){return new Response("",{status:416,statusText:"Range Not Satisfiable"})}}return e.createPartialResponse=t,e.Plugin=class{async cachedResponseWillBeUsed({request:e,cachedResponse:n}){return n&&e.headers.has("range")?await t(e,n):n}},e}({},workbox.core._private);
|
||||||
|
//# sourceMappingURL=workbox-range-requests.prod.js.map
|
2
public/workbox/workbox-routing.prod.js
Normal file
2
public/workbox/workbox-routing.prod.js
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
this.workbox=this.workbox||{},this.workbox.routing=function(t,e,r){"use strict";try{self["workbox:routing:4.3.1"]&&_()}catch(t){}const s="GET",n=t=>t&&"object"==typeof t?t:{handle:t};class o{constructor(t,e,r){this.handler=n(e),this.match=t,this.method=r||s}}class i extends o{constructor(t,{whitelist:e=[/./],blacklist:r=[]}={}){super(t=>this.t(t),t),this.s=e,this.o=r}t({url:t,request:e}){if("navigate"!==e.mode)return!1;const r=t.pathname+t.search;for(const t of this.o)if(t.test(r))return!1;return!!this.s.some(t=>t.test(r))}}class u extends o{constructor(t,e,r){super(({url:e})=>{const r=t.exec(e.href);return r?e.origin!==location.origin&&0!==r.index?null:r.slice(1):null},e,r)}}class c{constructor(){this.i=new Map}get routes(){return this.i}addFetchListener(){self.addEventListener("fetch",t=>{const{request:e}=t,r=this.handleRequest({request:e,event:t});r&&t.respondWith(r)})}addCacheListener(){self.addEventListener("message",async t=>{if(t.data&&"CACHE_URLS"===t.data.type){const{payload:e}=t.data,r=Promise.all(e.urlsToCache.map(t=>{"string"==typeof t&&(t=[t]);const e=new Request(...t);return this.handleRequest({request:e})}));t.waitUntil(r),t.ports&&t.ports[0]&&(await r,t.ports[0].postMessage(!0))}})}handleRequest({request:t,event:e}){const r=new URL(t.url,location);if(!r.protocol.startsWith("http"))return;let s,{params:n,route:o}=this.findMatchingRoute({url:r,request:t,event:e}),i=o&&o.handler;if(!i&&this.u&&(i=this.u),i){try{s=i.handle({url:r,request:t,event:e,params:n})}catch(t){s=Promise.reject(t)}return s&&this.h&&(s=s.catch(t=>this.h.handle({url:r,event:e,err:t}))),s}}findMatchingRoute({url:t,request:e,event:r}){const s=this.i.get(e.method)||[];for(const n of s){let s,o=n.match({url:t,request:e,event:r});if(o)return Array.isArray(o)&&o.length>0?s=o:o.constructor===Object&&Object.keys(o).length>0&&(s=o),{route:n,params:s}}return{}}setDefaultHandler(t){this.u=n(t)}setCatchHandler(t){this.h=n(t)}registerRoute(t){this.i.has(t.method)||this.i.set(t.method,[]),this.i.get(t.method).push(t)}unregisterRoute(t){if(!this.i.has(t.method))throw new r.WorkboxError("unregister-route-but-not-found-with-method",{method:t.method});const e=this.i.get(t.method).indexOf(t);if(!(e>-1))throw new r.WorkboxError("unregister-route-route-not-registered");this.i.get(t.method).splice(e,1)}}let a;const h=()=>(a||((a=new c).addFetchListener(),a.addCacheListener()),a);return t.NavigationRoute=i,t.RegExpRoute=u,t.registerNavigationRoute=((t,r={})=>{const s=e.cacheNames.getPrecacheName(r.cacheName),n=new i(async()=>{try{const e=await caches.match(t,{cacheName:s});if(e)return e;throw new Error(`The cache ${s} did not have an entry for `+`${t}.`)}catch(e){return fetch(t)}},{whitelist:r.whitelist,blacklist:r.blacklist});return h().registerRoute(n),n}),t.registerRoute=((t,e,s="GET")=>{let n;if("string"==typeof t){const r=new URL(t,location);n=new o(({url:t})=>t.href===r.href,e,s)}else if(t instanceof RegExp)n=new u(t,e,s);else if("function"==typeof t)n=new o(t,e,s);else{if(!(t instanceof o))throw new r.WorkboxError("unsupported-route-type",{moduleName:"workbox-routing",funcName:"registerRoute",paramName:"capture"});n=t}return h().registerRoute(n),n}),t.Route=o,t.Router=c,t.setCatchHandler=(t=>{h().setCatchHandler(t)}),t.setDefaultHandler=(t=>{h().setDefaultHandler(t)}),t}({},workbox.core._private,workbox.core._private);
|
||||||
|
//# sourceMappingURL=workbox-routing.prod.js.map
|
2
public/workbox/workbox-strategies.prod.js
Normal file
2
public/workbox/workbox-strategies.prod.js
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
this.workbox=this.workbox||{},this.workbox.strategies=function(e,t,s,n,r){"use strict";try{self["workbox:strategies:4.3.1"]&&_()}catch(e){}class i{constructor(e={}){this.t=t.cacheNames.getRuntimeName(e.cacheName),this.s=e.plugins||[],this.i=e.fetchOptions||null,this.h=e.matchOptions||null}async handle({event:e,request:t}){return this.makeRequest({event:e,request:t||e.request})}async makeRequest({event:e,request:t}){"string"==typeof t&&(t=new Request(t));let n,i=await s.cacheWrapper.match({cacheName:this.t,request:t,event:e,matchOptions:this.h,plugins:this.s});if(!i)try{i=await this.u(t,e)}catch(e){n=e}if(!i)throw new r.WorkboxError("no-response",{url:t.url,error:n});return i}async u(e,t){const r=await n.fetchWrapper.fetch({request:e,event:t,fetchOptions:this.i,plugins:this.s}),i=r.clone(),h=s.cacheWrapper.put({cacheName:this.t,request:e,response:i,event:t,plugins:this.s});if(t)try{t.waitUntil(h)}catch(e){}return r}}class h{constructor(e={}){this.t=t.cacheNames.getRuntimeName(e.cacheName),this.s=e.plugins||[],this.h=e.matchOptions||null}async handle({event:e,request:t}){return this.makeRequest({event:e,request:t||e.request})}async makeRequest({event:e,request:t}){"string"==typeof t&&(t=new Request(t));const n=await s.cacheWrapper.match({cacheName:this.t,request:t,event:e,matchOptions:this.h,plugins:this.s});if(!n)throw new r.WorkboxError("no-response",{url:t.url});return n}}const u={cacheWillUpdate:({response:e})=>200===e.status||0===e.status?e:null};class a{constructor(e={}){if(this.t=t.cacheNames.getRuntimeName(e.cacheName),e.plugins){let t=e.plugins.some(e=>!!e.cacheWillUpdate);this.s=t?e.plugins:[u,...e.plugins]}else this.s=[u];this.o=e.networkTimeoutSeconds,this.i=e.fetchOptions||null,this.h=e.matchOptions||null}async handle({event:e,request:t}){return this.makeRequest({event:e,request:t||e.request})}async makeRequest({event:e,request:t}){const s=[];"string"==typeof t&&(t=new Request(t));const n=[];let i;if(this.o){const{id:r,promise:h}=this.l({request:t,event:e,logs:s});i=r,n.push(h)}const h=this.q({timeoutId:i,request:t,event:e,logs:s});n.push(h);let u=await Promise.race(n);if(u||(u=await h),!u)throw new r.WorkboxError("no-response",{url:t.url});return u}l({request:e,logs:t,event:s}){let n;return{promise:new Promise(t=>{n=setTimeout(async()=>{t(await this.p({request:e,event:s}))},1e3*this.o)}),id:n}}async q({timeoutId:e,request:t,logs:r,event:i}){let h,u;try{u=await n.fetchWrapper.fetch({request:t,event:i,fetchOptions:this.i,plugins:this.s})}catch(e){h=e}if(e&&clearTimeout(e),h||!u)u=await this.p({request:t,event:i});else{const e=u.clone(),n=s.cacheWrapper.put({cacheName:this.t,request:t,response:e,event:i,plugins:this.s});if(i)try{i.waitUntil(n)}catch(e){}}return u}p({event:e,request:t}){return s.cacheWrapper.match({cacheName:this.t,request:t,event:e,matchOptions:this.h,plugins:this.s})}}class c{constructor(e={}){this.t=t.cacheNames.getRuntimeName(e.cacheName),this.s=e.plugins||[],this.i=e.fetchOptions||null}async handle({event:e,request:t}){return this.makeRequest({event:e,request:t||e.request})}async makeRequest({event:e,request:t}){let s,i;"string"==typeof t&&(t=new Request(t));try{i=await n.fetchWrapper.fetch({request:t,event:e,fetchOptions:this.i,plugins:this.s})}catch(e){s=e}if(!i)throw new r.WorkboxError("no-response",{url:t.url,error:s});return i}}class o{constructor(e={}){if(this.t=t.cacheNames.getRuntimeName(e.cacheName),this.s=e.plugins||[],e.plugins){let t=e.plugins.some(e=>!!e.cacheWillUpdate);this.s=t?e.plugins:[u,...e.plugins]}else this.s=[u];this.i=e.fetchOptions||null,this.h=e.matchOptions||null}async handle({event:e,request:t}){return this.makeRequest({event:e,request:t||e.request})}async makeRequest({event:e,request:t}){"string"==typeof t&&(t=new Request(t));const n=this.u({request:t,event:e});let i,h=await s.cacheWrapper.match({cacheName:this.t,request:t,event:e,matchOptions:this.h,plugins:this.s});if(h){if(e)try{e.waitUntil(n)}catch(i){}}else try{h=await n}catch(e){i=e}if(!h)throw new r.WorkboxError("no-response",{url:t.url,error:i});return h}async u({request:e,event:t}){const r=await n.fetchWrapper.fetch({request:e,event:t,fetchOptions:this.i,plugins:this.s}),i=s.cacheWrapper.put({cacheName:this.t,request:e,response:r.clone(),event:t,plugins:this.s});if(t)try{t.waitUntil(i)}catch(e){}return r}}const l={cacheFirst:i,cacheOnly:h,networkFirst:a,networkOnly:c,staleWhileRevalidate:o},q=e=>{const t=l[e];return e=>new t(e)},w=q("cacheFirst"),p=q("cacheOnly"),v=q("networkFirst"),y=q("networkOnly"),m=q("staleWhileRevalidate");return e.CacheFirst=i,e.CacheOnly=h,e.NetworkFirst=a,e.NetworkOnly=c,e.StaleWhileRevalidate=o,e.cacheFirst=w,e.cacheOnly=p,e.networkFirst=v,e.networkOnly=y,e.staleWhileRevalidate=m,e}({},workbox.core._private,workbox.core._private,workbox.core._private,workbox.core._private);
|
||||||
|
//# sourceMappingURL=workbox-strategies.prod.js.map
|
2
public/workbox/workbox-streams.prod.js
Normal file
2
public/workbox/workbox-streams.prod.js
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
this.workbox=this.workbox||{},this.workbox.streams=function(e){"use strict";try{self["workbox:streams:4.3.1"]&&_()}catch(e){}function n(e){const n=e.map(e=>Promise.resolve(e).then(e=>(function(e){return e.body&&e.body.getReader?e.body.getReader():e.getReader?e.getReader():new Response(e).body.getReader()})(e)));let t,r;const s=new Promise((e,n)=>{t=e,r=n});let o=0;return{done:s,stream:new ReadableStream({pull(e){return n[o].then(e=>e.read()).then(r=>{if(r.done)return++o>=n.length?(e.close(),void t()):this.pull(e);e.enqueue(r.value)}).catch(e=>{throw r(e),e})},cancel(){t()}})}}function t(e={}){const n=new Headers(e);return n.has("content-type")||n.set("content-type","text/html"),n}function r(e,r){const{done:s,stream:o}=n(e),a=t(r);return{done:s,response:new Response(o,{headers:a})}}let s=void 0;function o(){if(void 0===s)try{new ReadableStream({start(){}}),s=!0}catch(e){s=!1}return s}return e.concatenate=n,e.concatenateToResponse=r,e.isSupported=o,e.strategy=function(e,n){return async({event:s,url:a,params:c})=>{if(o()){const{done:t,response:o}=r(e.map(e=>e({event:s,url:a,params:c})),n);return s.waitUntil(t),o}const i=await Promise.all(e.map(e=>e({event:s,url:a,params:c})).map(async e=>{const n=await e;return n instanceof Response?n.blob():n})),u=t(n);return new Response(new Blob(i),{headers:u})}},e}({});
|
||||||
|
//# sourceMappingURL=workbox-streams.prod.js.map
|
2
public/workbox/workbox-sw.js
Normal file
2
public/workbox/workbox-sw.js
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
!function(){"use strict";try{self["workbox:sw:4.3.1"]&&_()}catch(t){}const t="https://storage.googleapis.com/workbox-cdn/releases/4.3.1",e={backgroundSync:"background-sync",broadcastUpdate:"broadcast-update",cacheableResponse:"cacheable-response",core:"core",expiration:"expiration",googleAnalytics:"offline-ga",navigationPreload:"navigation-preload",precaching:"precaching",rangeRequests:"range-requests",routing:"routing",strategies:"strategies",streams:"streams"};self.workbox=new class{constructor(){return this.v={},this.t={debug:"localhost"===self.location.hostname,modulePathPrefix:null,modulePathCb:null},this.s=this.t.debug?"dev":"prod",this.o=!1,new Proxy(this,{get(t,s){if(t[s])return t[s];const o=e[s];return o&&t.loadModule(`workbox-${o}`),t[s]}})}setConfig(t={}){if(this.o)throw new Error("Config must be set before accessing workbox.* modules");Object.assign(this.t,t),this.s=this.t.debug?"dev":"prod"}loadModule(t){const e=this.i(t);try{importScripts(e),this.o=!0}catch(s){throw console.error(`Unable to import module '${t}' from '${e}'.`),s}}i(e){if(this.t.modulePathCb)return this.t.modulePathCb(e,this.t.debug);let s=[t];const o=`${e}.${this.s}.js`,r=this.t.modulePathPrefix;return r&&""===(s=r.split("/"))[s.length-1]&&s.splice(s.length-1,1),s.push(o),s.join("/")}}}();
|
||||||
|
//# sourceMappingURL=workbox-sw.js.map
|
2
public/workbox/workbox-window.prod.es5.mjs
Normal file
2
public/workbox/workbox-window.prod.es5.mjs
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
try{self["workbox:window:4.3.1"]&&_()}catch(n){}var n=function(n,t){return new Promise(function(i){var e=new MessageChannel;e.port1.onmessage=function(n){return i(n.data)},n.postMessage(t,[e.port2])})};function t(n,t){for(var i=0;i<t.length;i++){var e=t[i];e.enumerable=e.enumerable||!1,e.configurable=!0,"value"in e&&(e.writable=!0),Object.defineProperty(n,e.key,e)}}function i(n){if(void 0===n)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return n}try{self["workbox:core:4.3.1"]&&_()}catch(n){}var e=function(){var n=this;this.promise=new Promise(function(t,i){n.resolve=t,n.reject=i})},r=function(n,t){return new URL(n,location).href===new URL(t,location).href},o=function(n,t){Object.assign(this,t,{type:n})};function u(n){return function(){for(var t=[],i=0;i<arguments.length;i++)t[i]=arguments[i];try{return Promise.resolve(n.apply(this,t))}catch(n){return Promise.reject(n)}}}function a(n,t,i){return i?t?t(n):n:(n&&n.then||(n=Promise.resolve(n)),t?n.then(t):n)}function s(){}var c=function(c){var f,h;function v(n,t){var r;return void 0===t&&(t={}),(r=c.call(this)||this).t=n,r.i=t,r.o=0,r.u=new e,r.s=new e,r.h=new e,r.v=r.v.bind(i(i(r))),r.l=r.l.bind(i(i(r))),r.g=r.g.bind(i(i(r))),r.m=r.m.bind(i(i(r))),r}h=c,(f=v).prototype=Object.create(h.prototype),f.prototype.constructor=f,f.__proto__=h;var l,w,g,d=v.prototype;return d.register=u(function(n){var t,i,e=this,u=(void 0===n?{}:n).immediate,c=void 0!==u&&u;return t=function(){return e.p=Boolean(navigator.serviceWorker.controller),e.P=e.R(),a(e.k(),function(n){e.B=n,e.P&&(e.O=e.P,e.s.resolve(e.P),e.h.resolve(e.P),e.j(e.P),e.P.addEventListener("statechange",e.l,{once:!0}));var t=e.B.waiting;return t&&r(t.scriptURL,e.t)&&(e.O=t,Promise.resolve().then(function(){e.dispatchEvent(new o("waiting",{sw:t,wasWaitingBeforeRegister:!0}))})),e.O&&e.u.resolve(e.O),e.B.addEventListener("updatefound",e.g),navigator.serviceWorker.addEventListener("controllerchange",e.m,{once:!0}),"BroadcastChannel"in self&&(e.C=new BroadcastChannel("workbox"),e.C.addEventListener("message",e.v)),navigator.serviceWorker.addEventListener("message",e.v),e.B})},(i=function(){if(!c&&"complete"!==document.readyState)return function(n,t){if(!t)return n&&n.then?n.then(s):Promise.resolve()}(new Promise(function(n){return addEventListener("load",n)}))}())&&i.then?i.then(t):t(i)}),d.getSW=u(function(){return this.O||this.u.promise}),d.messageSW=u(function(t){return a(this.getSW(),function(i){return n(i,t)})}),d.R=function(){var n=navigator.serviceWorker.controller;if(n&&r(n.scriptURL,this.t))return n},d.k=u(function(){var n=this;return function(n,t){try{var i=n()}catch(n){return t(n)}return i&&i.then?i.then(void 0,t):i}(function(){return a(navigator.serviceWorker.register(n.t,n.i),function(t){return n.L=performance.now(),t})},function(n){throw n})}),d.j=function(t){n(t,{type:"WINDOW_READY",meta:"workbox-window"})},d.g=function(){var n=this.B.installing;this.o>0||!r(n.scriptURL,this.t)||performance.now()>this.L+6e4?(this.W=n,this.B.removeEventListener("updatefound",this.g)):(this.O=n,this.u.resolve(n)),++this.o,n.addEventListener("statechange",this.l)},d.l=function(n){var t=this,i=n.target,e=i.state,r=i===this.W,u=r?"external":"",a={sw:i,originalEvent:n};!r&&this.p&&(a.isUpdate=!0),this.dispatchEvent(new o(u+e,a)),"installed"===e?this._=setTimeout(function(){"installed"===e&&t.B.waiting===i&&t.dispatchEvent(new o(u+"waiting",a))},200):"activating"===e&&(clearTimeout(this._),r||this.s.resolve(i))},d.m=function(n){var t=this.O;t===navigator.serviceWorker.controller&&(this.dispatchEvent(new o("controlling",{sw:t,originalEvent:n})),this.h.resolve(t))},d.v=function(n){var t=n.data;this.dispatchEvent(new o("message",{data:t,originalEvent:n}))},l=v,(w=[{key:"active",get:function(){return this.s.promise}},{key:"controlling",get:function(){return this.h.promise}}])&&t(l.prototype,w),g&&t(l,g),v}(function(){function n(){this.D={}}var t=n.prototype;return t.addEventListener=function(n,t){this.T(n).add(t)},t.removeEventListener=function(n,t){this.T(n).delete(t)},t.dispatchEvent=function(n){n.target=this,this.T(n.type).forEach(function(t){return t(n)})},t.T=function(n){return this.D[n]=this.D[n]||new Set},n}());export{c as Workbox,n as messageSW};
|
||||||
|
//# sourceMappingURL=workbox-window.prod.es5.mjs.map
|
2
public/workbox/workbox-window.prod.mjs
Normal file
2
public/workbox/workbox-window.prod.mjs
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
try{self["workbox:window:4.3.1"]&&_()}catch(t){}const t=(t,s)=>new Promise(i=>{let e=new MessageChannel;e.port1.onmessage=(t=>i(t.data)),t.postMessage(s,[e.port2])});try{self["workbox:core:4.3.1"]&&_()}catch(t){}class s{constructor(){this.promise=new Promise((t,s)=>{this.resolve=t,this.reject=s})}}class i{constructor(){this.t={}}addEventListener(t,s){this.s(t).add(s)}removeEventListener(t,s){this.s(t).delete(s)}dispatchEvent(t){t.target=this,this.s(t.type).forEach(s=>s(t))}s(t){return this.t[t]=this.t[t]||new Set}}const e=(t,s)=>new URL(t,location).href===new URL(s,location).href;class n{constructor(t,s){Object.assign(this,s,{type:t})}}const h=200,a=6e4;class o extends i{constructor(t,i={}){super(),this.i=t,this.h=i,this.o=0,this.l=new s,this.g=new s,this.u=new s,this.m=this.m.bind(this),this.v=this.v.bind(this),this.p=this.p.bind(this),this._=this._.bind(this)}async register({immediate:t=!1}={}){t||"complete"===document.readyState||await new Promise(t=>addEventListener("load",t)),this.C=Boolean(navigator.serviceWorker.controller),this.W=this.L(),this.S=await this.B(),this.W&&(this.R=this.W,this.g.resolve(this.W),this.u.resolve(this.W),this.P(this.W),this.W.addEventListener("statechange",this.v,{once:!0}));const s=this.S.waiting;return s&&e(s.scriptURL,this.i)&&(this.R=s,Promise.resolve().then(()=>{this.dispatchEvent(new n("waiting",{sw:s,wasWaitingBeforeRegister:!0}))})),this.R&&this.l.resolve(this.R),this.S.addEventListener("updatefound",this.p),navigator.serviceWorker.addEventListener("controllerchange",this._,{once:!0}),"BroadcastChannel"in self&&(this.T=new BroadcastChannel("workbox"),this.T.addEventListener("message",this.m)),navigator.serviceWorker.addEventListener("message",this.m),this.S}get active(){return this.g.promise}get controlling(){return this.u.promise}async getSW(){return this.R||this.l.promise}async messageSW(s){const i=await this.getSW();return t(i,s)}L(){const t=navigator.serviceWorker.controller;if(t&&e(t.scriptURL,this.i))return t}async B(){try{const t=await navigator.serviceWorker.register(this.i,this.h);return this.U=performance.now(),t}catch(t){throw t}}P(s){t(s,{type:"WINDOW_READY",meta:"workbox-window"})}p(){const t=this.S.installing;this.o>0||!e(t.scriptURL,this.i)||performance.now()>this.U+a?(this.k=t,this.S.removeEventListener("updatefound",this.p)):(this.R=t,this.l.resolve(t)),++this.o,t.addEventListener("statechange",this.v)}v(t){const s=t.target,{state:i}=s,e=s===this.k,a=e?"external":"",o={sw:s,originalEvent:t};!e&&this.C&&(o.isUpdate=!0),this.dispatchEvent(new n(a+i,o)),"installed"===i?this.D=setTimeout(()=>{"installed"===i&&this.S.waiting===s&&this.dispatchEvent(new n(a+"waiting",o))},h):"activating"===i&&(clearTimeout(this.D),e||this.g.resolve(s))}_(t){const s=this.R;s===navigator.serviceWorker.controller&&(this.dispatchEvent(new n("controlling",{sw:s,originalEvent:t})),this.u.resolve(s))}m(t){const{data:s}=t;this.dispatchEvent(new n("message",{data:s,originalEvent:t}))}}export{o as Workbox,t as messageSW};
|
||||||
|
//# sourceMappingURL=workbox-window.prod.mjs.map
|
2
public/workbox/workbox-window.prod.umd.js
Normal file
2
public/workbox/workbox-window.prod.umd.js
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
!function(n,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports):"function"==typeof define&&define.amd?define(["exports"],t):t((n=n||self).workbox={})}(this,function(n){"use strict";try{self["workbox:window:4.3.1"]&&_()}catch(n){}var t=function(n,t){return new Promise(function(i){var e=new MessageChannel;e.port1.onmessage=function(n){return i(n.data)},n.postMessage(t,[e.port2])})};function i(n,t){for(var i=0;i<t.length;i++){var e=t[i];e.enumerable=e.enumerable||!1,e.configurable=!0,"value"in e&&(e.writable=!0),Object.defineProperty(n,e.key,e)}}function e(n){if(void 0===n)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return n}try{self["workbox:core:4.3.1"]&&_()}catch(n){}var r=function(){var n=this;this.promise=new Promise(function(t,i){n.resolve=t,n.reject=i})},o=function(n,t){return new URL(n,location).href===new URL(t,location).href},u=function(n,t){Object.assign(this,t,{type:n})};function s(n){return function(){for(var t=[],i=0;i<arguments.length;i++)t[i]=arguments[i];try{return Promise.resolve(n.apply(this,t))}catch(n){return Promise.reject(n)}}}function a(n,t,i){return i?t?t(n):n:(n&&n.then||(n=Promise.resolve(n)),t?n.then(t):n)}function c(){}var f=function(n){var f,h;function v(t,i){var o;return void 0===i&&(i={}),(o=n.call(this)||this).t=t,o.i=i,o.o=0,o.u=new r,o.s=new r,o.h=new r,o.v=o.v.bind(e(e(o))),o.l=o.l.bind(e(e(o))),o.g=o.g.bind(e(e(o))),o.m=o.m.bind(e(e(o))),o}h=n,(f=v).prototype=Object.create(h.prototype),f.prototype.constructor=f,f.__proto__=h;var l,w,d,g=v.prototype;return g.register=s(function(n){var t,i,e=this,r=(void 0===n?{}:n).immediate,s=void 0!==r&&r;return t=function(){return e.p=Boolean(navigator.serviceWorker.controller),e.P=e.j(),a(e.O(),function(n){e.R=n,e.P&&(e._=e.P,e.s.resolve(e.P),e.h.resolve(e.P),e.k(e.P),e.P.addEventListener("statechange",e.l,{once:!0}));var t=e.R.waiting;return t&&o(t.scriptURL,e.t)&&(e._=t,Promise.resolve().then(function(){e.dispatchEvent(new u("waiting",{sw:t,wasWaitingBeforeRegister:!0}))})),e._&&e.u.resolve(e._),e.R.addEventListener("updatefound",e.g),navigator.serviceWorker.addEventListener("controllerchange",e.m,{once:!0}),"BroadcastChannel"in self&&(e.B=new BroadcastChannel("workbox"),e.B.addEventListener("message",e.v)),navigator.serviceWorker.addEventListener("message",e.v),e.R})},(i=function(){if(!s&&"complete"!==document.readyState)return function(n,t){if(!t)return n&&n.then?n.then(c):Promise.resolve()}(new Promise(function(n){return addEventListener("load",n)}))}())&&i.then?i.then(t):t(i)}),g.getSW=s(function(){return this._||this.u.promise}),g.messageSW=s(function(n){return a(this.getSW(),function(i){return t(i,n)})}),g.j=function(){var n=navigator.serviceWorker.controller;if(n&&o(n.scriptURL,this.t))return n},g.O=s(function(){var n=this;return function(n,t){try{var i=n()}catch(n){return t(n)}return i&&i.then?i.then(void 0,t):i}(function(){return a(navigator.serviceWorker.register(n.t,n.i),function(t){return n.C=performance.now(),t})},function(n){throw n})}),g.k=function(n){t(n,{type:"WINDOW_READY",meta:"workbox-window"})},g.g=function(){var n=this.R.installing;this.o>0||!o(n.scriptURL,this.t)||performance.now()>this.C+6e4?(this.L=n,this.R.removeEventListener("updatefound",this.g)):(this._=n,this.u.resolve(n)),++this.o,n.addEventListener("statechange",this.l)},g.l=function(n){var t=this,i=n.target,e=i.state,r=i===this.L,o=r?"external":"",s={sw:i,originalEvent:n};!r&&this.p&&(s.isUpdate=!0),this.dispatchEvent(new u(o+e,s)),"installed"===e?this.W=setTimeout(function(){"installed"===e&&t.R.waiting===i&&t.dispatchEvent(new u(o+"waiting",s))},200):"activating"===e&&(clearTimeout(this.W),r||this.s.resolve(i))},g.m=function(n){var t=this._;t===navigator.serviceWorker.controller&&(this.dispatchEvent(new u("controlling",{sw:t,originalEvent:n})),this.h.resolve(t))},g.v=function(n){var t=n.data;this.dispatchEvent(new u("message",{data:t,originalEvent:n}))},l=v,(w=[{key:"active",get:function(){return this.s.promise}},{key:"controlling",get:function(){return this.h.promise}}])&&i(l.prototype,w),d&&i(l,d),v}(function(){function n(){this.D={}}var t=n.prototype;return t.addEventListener=function(n,t){this.M(n).add(t)},t.removeEventListener=function(n,t){this.M(n).delete(t)},t.dispatchEvent=function(n){n.target=this,this.M(n.type).forEach(function(t){return t(n)})},t.M=function(n){return this.D[n]=this.D[n]||new Set},n}());n.Workbox=f,n.messageSW=t,Object.defineProperty(n,"__esModule",{value:!0})});
|
||||||
|
//# sourceMappingURL=workbox-window.prod.umd.js.map
|
@ -5,7 +5,7 @@
|
|||||||
|
|
||||||
// In order to run:
|
// In order to run:
|
||||||
// npm install canvas # please do not check it in
|
// npm install canvas # please do not check it in
|
||||||
// npm run build-node
|
// yarn build-node
|
||||||
// node build/static/js/build-node.js
|
// node build/static/js/build-node.js
|
||||||
// open test.png
|
// open test.png
|
||||||
|
|
||||||
|
@ -24,6 +24,7 @@ const crowdinMap = {
|
|||||||
"nb-NO": "en-nb",
|
"nb-NO": "en-nb",
|
||||||
"nl-NL": "en-nl",
|
"nl-NL": "en-nl",
|
||||||
"nn-NO": "en-nnno",
|
"nn-NO": "en-nnno",
|
||||||
|
"oc-FR": "en-oc",
|
||||||
"pa-IN": "en-pain",
|
"pa-IN": "en-pain",
|
||||||
"pl-PL": "en-pl",
|
"pl-PL": "en-pl",
|
||||||
"pt-BR": "en-ptbr",
|
"pt-BR": "en-ptbr",
|
||||||
@ -60,6 +61,7 @@ const flags = {
|
|||||||
"nb-NO": "🇳🇴",
|
"nb-NO": "🇳🇴",
|
||||||
"nl-NL": "🇳🇱",
|
"nl-NL": "🇳🇱",
|
||||||
"nn-NO": "🇳🇴",
|
"nn-NO": "🇳🇴",
|
||||||
|
"oc-FR": "🏳",
|
||||||
"pa-IN": "🇮🇳",
|
"pa-IN": "🇮🇳",
|
||||||
"pl-PL": "🇵🇱",
|
"pl-PL": "🇵🇱",
|
||||||
"pt-BR": "🇧🇷",
|
"pt-BR": "🇧🇷",
|
||||||
@ -96,6 +98,7 @@ const languages = {
|
|||||||
"nb-NO": "Norsk bokmål",
|
"nb-NO": "Norsk bokmål",
|
||||||
"nl-NL": "Nederlands",
|
"nl-NL": "Nederlands",
|
||||||
"nn-NO": "Norsk nynorsk",
|
"nn-NO": "Norsk nynorsk",
|
||||||
|
"oc-FR": "Occitan",
|
||||||
"pa-IN": "ਪੰਜਾਬੀ",
|
"pa-IN": "ਪੰਜਾਬੀ",
|
||||||
"pl-PL": "Polski",
|
"pl-PL": "Polski",
|
||||||
"pt-BR": "Português Brasileiro",
|
"pt-BR": "Português Brasileiro",
|
||||||
|
@ -58,7 +58,7 @@ export const actionAlignTop = register({
|
|||||||
<ToolButton
|
<ToolButton
|
||||||
hidden={!enableActionGroup(elements, appState)}
|
hidden={!enableActionGroup(elements, appState)}
|
||||||
type="button"
|
type="button"
|
||||||
icon={<AlignTopIcon appearance={appState.appearance} />}
|
icon={<AlignTopIcon theme={appState.theme} />}
|
||||||
onClick={() => updateData(null)}
|
onClick={() => updateData(null)}
|
||||||
title={`${t("labels.alignTop")} — ${getShortcutKey(
|
title={`${t("labels.alignTop")} — ${getShortcutKey(
|
||||||
"CtrlOrCmd+Shift+Up",
|
"CtrlOrCmd+Shift+Up",
|
||||||
@ -87,7 +87,7 @@ export const actionAlignBottom = register({
|
|||||||
<ToolButton
|
<ToolButton
|
||||||
hidden={!enableActionGroup(elements, appState)}
|
hidden={!enableActionGroup(elements, appState)}
|
||||||
type="button"
|
type="button"
|
||||||
icon={<AlignBottomIcon appearance={appState.appearance} />}
|
icon={<AlignBottomIcon theme={appState.theme} />}
|
||||||
onClick={() => updateData(null)}
|
onClick={() => updateData(null)}
|
||||||
title={`${t("labels.alignBottom")} — ${getShortcutKey(
|
title={`${t("labels.alignBottom")} — ${getShortcutKey(
|
||||||
"CtrlOrCmd+Shift+Down",
|
"CtrlOrCmd+Shift+Down",
|
||||||
@ -116,7 +116,7 @@ export const actionAlignLeft = register({
|
|||||||
<ToolButton
|
<ToolButton
|
||||||
hidden={!enableActionGroup(elements, appState)}
|
hidden={!enableActionGroup(elements, appState)}
|
||||||
type="button"
|
type="button"
|
||||||
icon={<AlignLeftIcon appearance={appState.appearance} />}
|
icon={<AlignLeftIcon theme={appState.theme} />}
|
||||||
onClick={() => updateData(null)}
|
onClick={() => updateData(null)}
|
||||||
title={`${t("labels.alignLeft")} — ${getShortcutKey(
|
title={`${t("labels.alignLeft")} — ${getShortcutKey(
|
||||||
"CtrlOrCmd+Shift+Left",
|
"CtrlOrCmd+Shift+Left",
|
||||||
@ -145,7 +145,7 @@ export const actionAlignRight = register({
|
|||||||
<ToolButton
|
<ToolButton
|
||||||
hidden={!enableActionGroup(elements, appState)}
|
hidden={!enableActionGroup(elements, appState)}
|
||||||
type="button"
|
type="button"
|
||||||
icon={<AlignRightIcon appearance={appState.appearance} />}
|
icon={<AlignRightIcon theme={appState.theme} />}
|
||||||
onClick={() => updateData(null)}
|
onClick={() => updateData(null)}
|
||||||
title={`${t("labels.alignRight")} — ${getShortcutKey(
|
title={`${t("labels.alignRight")} — ${getShortcutKey(
|
||||||
"CtrlOrCmd+Shift+Right",
|
"CtrlOrCmd+Shift+Right",
|
||||||
@ -172,7 +172,7 @@ export const actionAlignVerticallyCentered = register({
|
|||||||
<ToolButton
|
<ToolButton
|
||||||
hidden={!enableActionGroup(elements, appState)}
|
hidden={!enableActionGroup(elements, appState)}
|
||||||
type="button"
|
type="button"
|
||||||
icon={<CenterVerticallyIcon appearance={appState.appearance} />}
|
icon={<CenterVerticallyIcon theme={appState.theme} />}
|
||||||
onClick={() => updateData(null)}
|
onClick={() => updateData(null)}
|
||||||
title={t("labels.centerVertically")}
|
title={t("labels.centerVertically")}
|
||||||
aria-label={t("labels.centerVertically")}
|
aria-label={t("labels.centerVertically")}
|
||||||
@ -197,7 +197,7 @@ export const actionAlignHorizontallyCentered = register({
|
|||||||
<ToolButton
|
<ToolButton
|
||||||
hidden={!enableActionGroup(elements, appState)}
|
hidden={!enableActionGroup(elements, appState)}
|
||||||
type="button"
|
type="button"
|
||||||
icon={<CenterHorizontallyIcon appearance={appState.appearance} />}
|
icon={<CenterHorizontallyIcon theme={appState.theme} />}
|
||||||
onClick={() => updateData(null)}
|
onClick={() => updateData(null)}
|
||||||
title={t("labels.centerHorizontally")}
|
title={t("labels.centerHorizontally")}
|
||||||
aria-label={t("labels.centerHorizontally")}
|
aria-label={t("labels.centerHorizontally")}
|
||||||
|
@ -48,7 +48,7 @@ export const actionClearCanvas = register({
|
|||||||
),
|
),
|
||||||
appState: {
|
appState: {
|
||||||
...getDefaultAppState(),
|
...getDefaultAppState(),
|
||||||
appearance: appState.appearance,
|
theme: appState.theme,
|
||||||
elementLocked: appState.elementLocked,
|
elementLocked: appState.elementLocked,
|
||||||
exportBackground: appState.exportBackground,
|
exportBackground: appState.exportBackground,
|
||||||
exportEmbedScene: appState.exportEmbedScene,
|
exportEmbedScene: appState.exportEmbedScene,
|
||||||
|
@ -17,7 +17,8 @@ export const actionCopy = register({
|
|||||||
};
|
};
|
||||||
},
|
},
|
||||||
contextItemLabel: "labels.copy",
|
contextItemLabel: "labels.copy",
|
||||||
keyTest: (event) => event[KEYS.CTRL_OR_CMD] && event.code === CODES.C,
|
// don't supply a shortcut since we handle this conditionally via onCopy event
|
||||||
|
keyTest: undefined,
|
||||||
});
|
});
|
||||||
|
|
||||||
export const actionCut = register({
|
export const actionCut = register({
|
||||||
@ -94,7 +95,14 @@ export const actionCopyAsPng = register({
|
|||||||
return {
|
return {
|
||||||
appState: {
|
appState: {
|
||||||
...appState,
|
...appState,
|
||||||
toastMessage: t("toast.copyToClipboardAsPng"),
|
toastMessage: t("toast.copyToClipboardAsPng", {
|
||||||
|
exportSelection: selectedElements.length
|
||||||
|
? t("toast.selection")
|
||||||
|
: t("toast.canvas"),
|
||||||
|
exportColorScheme: appState.exportWithDarkMode
|
||||||
|
? t("buttons.darkMode")
|
||||||
|
: t("buttons.lightMode"),
|
||||||
|
}),
|
||||||
},
|
},
|
||||||
commitToHistory: false,
|
commitToHistory: false,
|
||||||
};
|
};
|
||||||
|
@ -53,7 +53,7 @@ export const distributeHorizontally = register({
|
|||||||
<ToolButton
|
<ToolButton
|
||||||
hidden={!enableActionGroup(elements, appState)}
|
hidden={!enableActionGroup(elements, appState)}
|
||||||
type="button"
|
type="button"
|
||||||
icon={<DistributeHorizontallyIcon appearance={appState.appearance} />}
|
icon={<DistributeHorizontallyIcon theme={appState.theme} />}
|
||||||
onClick={() => updateData(null)}
|
onClick={() => updateData(null)}
|
||||||
title={`${t("labels.distributeHorizontally")} — ${getShortcutKey(
|
title={`${t("labels.distributeHorizontally")} — ${getShortcutKey(
|
||||||
"Alt+H",
|
"Alt+H",
|
||||||
@ -81,7 +81,7 @@ export const distributeVertically = register({
|
|||||||
<ToolButton
|
<ToolButton
|
||||||
hidden={!enableActionGroup(elements, appState)}
|
hidden={!enableActionGroup(elements, appState)}
|
||||||
type="button"
|
type="button"
|
||||||
icon={<DistributeVerticallyIcon appearance={appState.appearance} />}
|
icon={<DistributeVerticallyIcon theme={appState.theme} />}
|
||||||
onClick={() => updateData(null)}
|
onClick={() => updateData(null)}
|
||||||
title={`${t("labels.distributeVertically")} — ${getShortcutKey("Alt+V")}`}
|
title={`${t("labels.distributeVertically")} — ${getShortcutKey("Alt+V")}`}
|
||||||
aria-label={t("labels.distributeVertically")}
|
aria-label={t("labels.distributeVertically")}
|
||||||
|
@ -5,6 +5,7 @@ import { ProjectName } from "../components/ProjectName";
|
|||||||
import { ToolButton } from "../components/ToolButton";
|
import { ToolButton } from "../components/ToolButton";
|
||||||
import "../components/ToolIcon.scss";
|
import "../components/ToolIcon.scss";
|
||||||
import { Tooltip } from "../components/Tooltip";
|
import { Tooltip } from "../components/Tooltip";
|
||||||
|
import { DarkModeToggle, Appearence } from "../components/DarkModeToggle";
|
||||||
import { loadFromJSON, saveAsJSON } from "../data";
|
import { loadFromJSON, saveAsJSON } from "../data";
|
||||||
import { t } from "../i18n";
|
import { t } from "../i18n";
|
||||||
import useIsMobile from "../is-mobile";
|
import useIsMobile from "../is-mobile";
|
||||||
@ -17,11 +18,12 @@ export const actionChangeProjectName = register({
|
|||||||
trackEvent("change", "title");
|
trackEvent("change", "title");
|
||||||
return { appState: { ...appState, name: value }, commitToHistory: false };
|
return { appState: { ...appState, name: value }, commitToHistory: false };
|
||||||
},
|
},
|
||||||
PanelComponent: ({ appState, updateData }) => (
|
PanelComponent: ({ appState, updateData, appProps }) => (
|
||||||
<ProjectName
|
<ProjectName
|
||||||
label={t("labels.fileTitle")}
|
label={t("labels.fileTitle")}
|
||||||
value={appState.name || "Unnamed"}
|
value={appState.name || "Unnamed"}
|
||||||
onChange={(name: string) => updateData(name)}
|
onChange={(name: string) => updateData(name)}
|
||||||
|
isNameEditable={typeof appProps.name === "undefined"}
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
});
|
});
|
||||||
@ -204,3 +206,55 @@ export const actionLoadScene = register({
|
|||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const actionExportWithDarkMode = register({
|
||||||
|
name: "exportWithDarkMode",
|
||||||
|
perform: (_elements, appState, value) => {
|
||||||
|
return {
|
||||||
|
appState: { ...appState, exportWithDarkMode: value },
|
||||||
|
commitToHistory: false,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
PanelComponent: ({ appState, updateData }) => (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: "flex",
|
||||||
|
justifyContent: "flex-end",
|
||||||
|
marginTop: "-45px",
|
||||||
|
marginBottom: "10px",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<DarkModeToggle
|
||||||
|
value={appState.exportWithDarkMode ? "dark" : "light"}
|
||||||
|
onChange={(theme: Appearence) => {
|
||||||
|
updateData(theme === "dark");
|
||||||
|
}}
|
||||||
|
title={t("labels.toggleExportColorScheme")}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const actionToggleAutoSave = register({
|
||||||
|
name: "toggleAutoSave",
|
||||||
|
perform(elements, appState) {
|
||||||
|
trackEvent("toggle", "autoSave");
|
||||||
|
return {
|
||||||
|
appState: {
|
||||||
|
...appState,
|
||||||
|
autoSave: !appState.autoSave,
|
||||||
|
},
|
||||||
|
commitToHistory: false,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
PanelComponent: ({ appState, updateData }) => (
|
||||||
|
<label>
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
checked={appState.autoSave}
|
||||||
|
onChange={(event) => updateData(event.target.checked)}
|
||||||
|
/>{" "}
|
||||||
|
{t("labels.toggleAutoSave")}
|
||||||
|
</label>
|
||||||
|
),
|
||||||
|
});
|
||||||
|
@ -18,7 +18,7 @@ import { isBindingElement } from "../element/typeChecks";
|
|||||||
|
|
||||||
export const actionFinalize = register({
|
export const actionFinalize = register({
|
||||||
name: "finalize",
|
name: "finalize",
|
||||||
perform: (elements, appState) => {
|
perform: (elements, appState, _, { canvas }) => {
|
||||||
if (appState.editingLinearElement) {
|
if (appState.editingLinearElement) {
|
||||||
const {
|
const {
|
||||||
elementId,
|
elementId,
|
||||||
@ -126,7 +126,7 @@ export const actionFinalize = register({
|
|||||||
(!appState.elementLocked && appState.elementType !== "draw") ||
|
(!appState.elementLocked && appState.elementType !== "draw") ||
|
||||||
!multiPointElement
|
!multiPointElement
|
||||||
) {
|
) {
|
||||||
resetCursor();
|
resetCursor(canvas);
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
elements: newElements,
|
elements: newElements,
|
||||||
|
@ -134,7 +134,7 @@ export const actionGroup = register({
|
|||||||
<ToolButton
|
<ToolButton
|
||||||
hidden={!enableActionGroup(elements, appState)}
|
hidden={!enableActionGroup(elements, appState)}
|
||||||
type="button"
|
type="button"
|
||||||
icon={<GroupIcon appearance={appState.appearance} />}
|
icon={<GroupIcon theme={appState.theme} />}
|
||||||
onClick={() => updateData(null)}
|
onClick={() => updateData(null)}
|
||||||
title={`${t("labels.group")} — ${getShortcutKey("CtrlOrCmd+G")}`}
|
title={`${t("labels.group")} — ${getShortcutKey("CtrlOrCmd+G")}`}
|
||||||
aria-label={t("labels.group")}
|
aria-label={t("labels.group")}
|
||||||
@ -181,7 +181,7 @@ export const actionUngroup = register({
|
|||||||
<ToolButton
|
<ToolButton
|
||||||
type="button"
|
type="button"
|
||||||
hidden={getSelectedGroupIds(appState).length === 0}
|
hidden={getSelectedGroupIds(appState).length === 0}
|
||||||
icon={<UngroupIcon appearance={appState.appearance} />}
|
icon={<UngroupIcon theme={appState.theme} />}
|
||||||
onClick={() => updateData(null)}
|
onClick={() => updateData(null)}
|
||||||
title={`${t("labels.ungroup")} — ${getShortcutKey("CtrlOrCmd+Shift+G")}`}
|
title={`${t("labels.ungroup")} — ${getShortcutKey("CtrlOrCmd+Shift+G")}`}
|
||||||
aria-label={t("labels.ungroup")}
|
aria-label={t("labels.ungroup")}
|
||||||
|
@ -169,17 +169,17 @@ export const actionChangeFillStyle = register({
|
|||||||
{
|
{
|
||||||
value: "hachure",
|
value: "hachure",
|
||||||
text: t("labels.hachure"),
|
text: t("labels.hachure"),
|
||||||
icon: <FillHachureIcon appearance={appState.appearance} />,
|
icon: <FillHachureIcon theme={appState.theme} />,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
value: "cross-hatch",
|
value: "cross-hatch",
|
||||||
text: t("labels.crossHatch"),
|
text: t("labels.crossHatch"),
|
||||||
icon: <FillCrossHatchIcon appearance={appState.appearance} />,
|
icon: <FillCrossHatchIcon theme={appState.theme} />,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
value: "solid",
|
value: "solid",
|
||||||
text: t("labels.solid"),
|
text: t("labels.solid"),
|
||||||
icon: <FillSolidIcon appearance={appState.appearance} />,
|
icon: <FillSolidIcon theme={appState.theme} />,
|
||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
group="fill"
|
group="fill"
|
||||||
@ -219,32 +219,17 @@ export const actionChangeStrokeWidth = register({
|
|||||||
{
|
{
|
||||||
value: 1,
|
value: 1,
|
||||||
text: t("labels.thin"),
|
text: t("labels.thin"),
|
||||||
icon: (
|
icon: <StrokeWidthIcon theme={appState.theme} strokeWidth={2} />,
|
||||||
<StrokeWidthIcon
|
|
||||||
appearance={appState.appearance}
|
|
||||||
strokeWidth={2}
|
|
||||||
/>
|
|
||||||
),
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
value: 2,
|
value: 2,
|
||||||
text: t("labels.bold"),
|
text: t("labels.bold"),
|
||||||
icon: (
|
icon: <StrokeWidthIcon theme={appState.theme} strokeWidth={6} />,
|
||||||
<StrokeWidthIcon
|
|
||||||
appearance={appState.appearance}
|
|
||||||
strokeWidth={6}
|
|
||||||
/>
|
|
||||||
),
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
value: 4,
|
value: 4,
|
||||||
text: t("labels.extraBold"),
|
text: t("labels.extraBold"),
|
||||||
icon: (
|
icon: <StrokeWidthIcon theme={appState.theme} strokeWidth={10} />,
|
||||||
<StrokeWidthIcon
|
|
||||||
appearance={appState.appearance}
|
|
||||||
strokeWidth={10}
|
|
||||||
/>
|
|
||||||
),
|
|
||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
value={getFormValue(
|
value={getFormValue(
|
||||||
@ -282,17 +267,17 @@ export const actionChangeSloppiness = register({
|
|||||||
{
|
{
|
||||||
value: 0,
|
value: 0,
|
||||||
text: t("labels.architect"),
|
text: t("labels.architect"),
|
||||||
icon: <SloppinessArchitectIcon appearance={appState.appearance} />,
|
icon: <SloppinessArchitectIcon theme={appState.theme} />,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
value: 1,
|
value: 1,
|
||||||
text: t("labels.artist"),
|
text: t("labels.artist"),
|
||||||
icon: <SloppinessArtistIcon appearance={appState.appearance} />,
|
icon: <SloppinessArtistIcon theme={appState.theme} />,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
value: 2,
|
value: 2,
|
||||||
text: t("labels.cartoonist"),
|
text: t("labels.cartoonist"),
|
||||||
icon: <SloppinessCartoonistIcon appearance={appState.appearance} />,
|
icon: <SloppinessCartoonistIcon theme={appState.theme} />,
|
||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
value={getFormValue(
|
value={getFormValue(
|
||||||
@ -329,17 +314,17 @@ export const actionChangeStrokeStyle = register({
|
|||||||
{
|
{
|
||||||
value: "solid",
|
value: "solid",
|
||||||
text: t("labels.strokeStyle_solid"),
|
text: t("labels.strokeStyle_solid"),
|
||||||
icon: <StrokeStyleSolidIcon appearance={appState.appearance} />,
|
icon: <StrokeStyleSolidIcon theme={appState.theme} />,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
value: "dashed",
|
value: "dashed",
|
||||||
text: t("labels.strokeStyle_dashed"),
|
text: t("labels.strokeStyle_dashed"),
|
||||||
icon: <StrokeStyleDashedIcon appearance={appState.appearance} />,
|
icon: <StrokeStyleDashedIcon theme={appState.theme} />,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
value: "dotted",
|
value: "dotted",
|
||||||
text: t("labels.strokeStyle_dotted"),
|
text: t("labels.strokeStyle_dotted"),
|
||||||
icon: <StrokeStyleDottedIcon appearance={appState.appearance} />,
|
icon: <StrokeStyleDottedIcon theme={appState.theme} />,
|
||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
value={getFormValue(
|
value={getFormValue(
|
||||||
@ -580,12 +565,12 @@ export const actionChangeSharpness = register({
|
|||||||
{
|
{
|
||||||
value: "sharp",
|
value: "sharp",
|
||||||
text: t("labels.sharp"),
|
text: t("labels.sharp"),
|
||||||
icon: <EdgeSharpIcon appearance={appState.appearance} />,
|
icon: <EdgeSharpIcon theme={appState.theme} />,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
value: "round",
|
value: "round",
|
||||||
text: t("labels.round"),
|
text: t("labels.round"),
|
||||||
icon: <EdgeRoundIcon appearance={appState.appearance} />,
|
icon: <EdgeRoundIcon theme={appState.theme} />,
|
||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
value={getFormValue(
|
value={getFormValue(
|
||||||
@ -653,40 +638,27 @@ export const actionChangeArrowhead = register({
|
|||||||
{
|
{
|
||||||
value: null,
|
value: null,
|
||||||
text: t("labels.arrowhead_none"),
|
text: t("labels.arrowhead_none"),
|
||||||
icon: <ArrowheadNoneIcon appearance={appState.appearance} />,
|
icon: <ArrowheadNoneIcon theme={appState.theme} />,
|
||||||
keyBinding: "q",
|
keyBinding: "q",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
value: "arrow",
|
value: "arrow",
|
||||||
text: t("labels.arrowhead_arrow"),
|
text: t("labels.arrowhead_arrow"),
|
||||||
icon: (
|
icon: (
|
||||||
<ArrowheadArrowIcon
|
<ArrowheadArrowIcon theme={appState.theme} flip={!isRTL} />
|
||||||
appearance={appState.appearance}
|
|
||||||
flip={!isRTL}
|
|
||||||
/>
|
|
||||||
),
|
),
|
||||||
keyBinding: "w",
|
keyBinding: "w",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
value: "bar",
|
value: "bar",
|
||||||
text: t("labels.arrowhead_bar"),
|
text: t("labels.arrowhead_bar"),
|
||||||
icon: (
|
icon: <ArrowheadBarIcon theme={appState.theme} flip={!isRTL} />,
|
||||||
<ArrowheadBarIcon
|
|
||||||
appearance={appState.appearance}
|
|
||||||
flip={!isRTL}
|
|
||||||
/>
|
|
||||||
),
|
|
||||||
keyBinding: "e",
|
keyBinding: "e",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
value: "dot",
|
value: "dot",
|
||||||
text: t("labels.arrowhead_dot"),
|
text: t("labels.arrowhead_dot"),
|
||||||
icon: (
|
icon: <ArrowheadDotIcon theme={appState.theme} flip={!isRTL} />,
|
||||||
<ArrowheadDotIcon
|
|
||||||
appearance={appState.appearance}
|
|
||||||
flip={!isRTL}
|
|
||||||
/>
|
|
||||||
),
|
|
||||||
keyBinding: "r",
|
keyBinding: "r",
|
||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
@ -709,40 +681,27 @@ export const actionChangeArrowhead = register({
|
|||||||
value: null,
|
value: null,
|
||||||
text: t("labels.arrowhead_none"),
|
text: t("labels.arrowhead_none"),
|
||||||
keyBinding: "q",
|
keyBinding: "q",
|
||||||
icon: <ArrowheadNoneIcon appearance={appState.appearance} />,
|
icon: <ArrowheadNoneIcon theme={appState.theme} />,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
value: "arrow",
|
value: "arrow",
|
||||||
text: t("labels.arrowhead_arrow"),
|
text: t("labels.arrowhead_arrow"),
|
||||||
keyBinding: "w",
|
keyBinding: "w",
|
||||||
icon: (
|
icon: (
|
||||||
<ArrowheadArrowIcon
|
<ArrowheadArrowIcon theme={appState.theme} flip={isRTL} />
|
||||||
appearance={appState.appearance}
|
|
||||||
flip={isRTL}
|
|
||||||
/>
|
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
value: "bar",
|
value: "bar",
|
||||||
text: t("labels.arrowhead_bar"),
|
text: t("labels.arrowhead_bar"),
|
||||||
keyBinding: "e",
|
keyBinding: "e",
|
||||||
icon: (
|
icon: <ArrowheadBarIcon theme={appState.theme} flip={isRTL} />,
|
||||||
<ArrowheadBarIcon
|
|
||||||
appearance={appState.appearance}
|
|
||||||
flip={isRTL}
|
|
||||||
/>
|
|
||||||
),
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
value: "dot",
|
value: "dot",
|
||||||
text: t("labels.arrowhead_dot"),
|
text: t("labels.arrowhead_dot"),
|
||||||
keyBinding: "r",
|
keyBinding: "r",
|
||||||
icon: (
|
icon: <ArrowheadDotIcon theme={appState.theme} flip={isRTL} />,
|
||||||
<ArrowheadDotIcon
|
|
||||||
appearance={appState.appearance}
|
|
||||||
flip={isRTL}
|
|
||||||
/>
|
|
||||||
),
|
|
||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
value={getFormValue<Arrowhead | null>(
|
value={getFormValue<Arrowhead | null>(
|
||||||
|
@ -1,20 +0,0 @@
|
|||||||
import { register } from "./register";
|
|
||||||
import { AppState } from "../types";
|
|
||||||
import { trackEvent } from "../analytics";
|
|
||||||
|
|
||||||
export const actionToggleAutoSave = register({
|
|
||||||
name: "toggleAutoSave",
|
|
||||||
perform(elements, appState) {
|
|
||||||
trackEvent("toggle", "autoSave");
|
|
||||||
return {
|
|
||||||
appState: {
|
|
||||||
...appState,
|
|
||||||
autoSave: !appState.autoSave,
|
|
||||||
},
|
|
||||||
commitToHistory: false,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
checked: (appState: AppState) => appState.autoSave,
|
|
||||||
contextItemLabel: "labels.toggleAutoSave",
|
|
||||||
keyTest: (event) => false,
|
|
||||||
});
|
|
@ -38,7 +38,7 @@ export const actionSendBackward = register({
|
|||||||
onClick={() => updateData(null)}
|
onClick={() => updateData(null)}
|
||||||
title={`${t("labels.sendBackward")} — ${getShortcutKey("CtrlOrCmd+[")}`}
|
title={`${t("labels.sendBackward")} — ${getShortcutKey("CtrlOrCmd+[")}`}
|
||||||
>
|
>
|
||||||
<SendBackwardIcon appearance={appState.appearance} />
|
<SendBackwardIcon theme={appState.theme} />
|
||||||
</button>
|
</button>
|
||||||
),
|
),
|
||||||
});
|
});
|
||||||
@ -65,7 +65,7 @@ export const actionBringForward = register({
|
|||||||
onClick={() => updateData(null)}
|
onClick={() => updateData(null)}
|
||||||
title={`${t("labels.bringForward")} — ${getShortcutKey("CtrlOrCmd+]")}`}
|
title={`${t("labels.bringForward")} — ${getShortcutKey("CtrlOrCmd+]")}`}
|
||||||
>
|
>
|
||||||
<BringForwardIcon appearance={appState.appearance} />
|
<BringForwardIcon theme={appState.theme} />
|
||||||
</button>
|
</button>
|
||||||
),
|
),
|
||||||
});
|
});
|
||||||
@ -99,7 +99,7 @@ export const actionSendToBack = register({
|
|||||||
: getShortcutKey("CtrlOrCmd+Shift+[")
|
: getShortcutKey("CtrlOrCmd+Shift+[")
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
<SendToBackIcon appearance={appState.appearance} />
|
<SendToBackIcon theme={appState.theme} />
|
||||||
</button>
|
</button>
|
||||||
),
|
),
|
||||||
});
|
});
|
||||||
@ -133,7 +133,7 @@ export const actionBringToFront = register({
|
|||||||
: getShortcutKey("CtrlOrCmd+Shift+]")
|
: getShortcutKey("CtrlOrCmd+Shift+]")
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
<BringToFrontIcon appearance={appState.appearance} />
|
<BringToFrontIcon theme={appState.theme} />
|
||||||
</button>
|
</button>
|
||||||
),
|
),
|
||||||
});
|
});
|
||||||
|
@ -33,6 +33,7 @@ export { actionFinalize } from "./actionFinalize";
|
|||||||
export {
|
export {
|
||||||
actionChangeProjectName,
|
actionChangeProjectName,
|
||||||
actionChangeExportBackground,
|
actionChangeExportBackground,
|
||||||
|
actionToggleAutoSave,
|
||||||
actionSaveScene,
|
actionSaveScene,
|
||||||
actionSaveAsScene,
|
actionSaveAsScene,
|
||||||
actionLoadScene,
|
actionLoadScene,
|
||||||
@ -74,7 +75,6 @@ export {
|
|||||||
} from "./actionClipboard";
|
} from "./actionClipboard";
|
||||||
|
|
||||||
export { actionToggleGridMode } from "./actionToggleGridMode";
|
export { actionToggleGridMode } from "./actionToggleGridMode";
|
||||||
export { actionToggleAutoSave } from "./actionToggleAutoSave";
|
|
||||||
export { actionToggleZenMode } from "./actionToggleZenMode";
|
export { actionToggleZenMode } from "./actionToggleZenMode";
|
||||||
|
|
||||||
export { actionToggleStats } from "./actionToggleStats";
|
export { actionToggleStats } from "./actionToggleStats";
|
||||||
|
@ -122,6 +122,7 @@ export class ActionManager implements ActionsManagerInterface {
|
|||||||
appState={this.getAppState()}
|
appState={this.getAppState()}
|
||||||
updateData={updateData}
|
updateData={updateData}
|
||||||
id={id}
|
id={id}
|
||||||
|
appProps={this.app.props}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { ExcalidrawElement } from "../element/types";
|
import { ExcalidrawElement } from "../element/types";
|
||||||
import { AppState } from "../types";
|
import { AppState, ExcalidrawProps } from "../types";
|
||||||
|
|
||||||
/** if false, the action should be prevented */
|
/** if false, the action should be prevented */
|
||||||
export type ActionResult =
|
export type ActionResult =
|
||||||
@ -86,7 +86,8 @@ export type ActionName =
|
|||||||
| "alignHorizontallyCentered"
|
| "alignHorizontallyCentered"
|
||||||
| "distributeHorizontally"
|
| "distributeHorizontally"
|
||||||
| "distributeVertically"
|
| "distributeVertically"
|
||||||
| "viewMode";
|
| "viewMode"
|
||||||
|
| "exportWithDarkMode";
|
||||||
|
|
||||||
export interface Action {
|
export interface Action {
|
||||||
name: ActionName;
|
name: ActionName;
|
||||||
@ -94,6 +95,7 @@ export interface Action {
|
|||||||
elements: readonly ExcalidrawElement[];
|
elements: readonly ExcalidrawElement[];
|
||||||
appState: AppState;
|
appState: AppState;
|
||||||
updateData: (formData?: any) => void;
|
updateData: (formData?: any) => void;
|
||||||
|
appProps: ExcalidrawProps;
|
||||||
id?: string;
|
id?: string;
|
||||||
}>;
|
}>;
|
||||||
perform: ActionFn;
|
perform: ActionFn;
|
||||||
|
@ -14,7 +14,7 @@ export const getDefaultAppState = (): Omit<
|
|||||||
> => {
|
> => {
|
||||||
return {
|
return {
|
||||||
autoSave: false,
|
autoSave: false,
|
||||||
appearance: "light",
|
theme: "light",
|
||||||
collaborators: new Map(),
|
collaborators: new Map(),
|
||||||
currentChartType: "bar",
|
currentChartType: "bar",
|
||||||
currentItemBackgroundColor: "transparent",
|
currentItemBackgroundColor: "transparent",
|
||||||
@ -41,6 +41,7 @@ export const getDefaultAppState = (): Omit<
|
|||||||
errorMessage: null,
|
errorMessage: null,
|
||||||
exportBackground: true,
|
exportBackground: true,
|
||||||
exportEmbedScene: false,
|
exportEmbedScene: false,
|
||||||
|
exportWithDarkMode: false,
|
||||||
fileHandle: null,
|
fileHandle: null,
|
||||||
gridSize: null,
|
gridSize: null,
|
||||||
height: window.innerHeight,
|
height: window.innerHeight,
|
||||||
@ -93,7 +94,7 @@ const APP_STATE_STORAGE_CONF = (<
|
|||||||
config: { [K in keyof T]: K extends keyof AppState ? T[K] : never },
|
config: { [K in keyof T]: K extends keyof AppState ? T[K] : never },
|
||||||
) => config)({
|
) => config)({
|
||||||
autoSave: { browser: true, export: false },
|
autoSave: { browser: true, export: false },
|
||||||
appearance: { browser: true, export: false },
|
theme: { browser: true, export: false },
|
||||||
collaborators: { browser: false, export: false },
|
collaborators: { browser: false, export: false },
|
||||||
currentChartType: { browser: true, export: false },
|
currentChartType: { browser: true, export: false },
|
||||||
currentItemBackgroundColor: { browser: true, export: false },
|
currentItemBackgroundColor: { browser: true, export: false },
|
||||||
@ -120,6 +121,7 @@ const APP_STATE_STORAGE_CONF = (<
|
|||||||
errorMessage: { browser: false, export: false },
|
errorMessage: { browser: false, export: false },
|
||||||
exportBackground: { browser: true, export: false },
|
exportBackground: { browser: true, export: false },
|
||||||
exportEmbedScene: { browser: true, export: false },
|
exportEmbedScene: { browser: true, export: false },
|
||||||
|
exportWithDarkMode: { browser: true, export: false },
|
||||||
fileHandle: { browser: false, export: false },
|
fileHandle: { browser: false, export: false },
|
||||||
gridSize: { browser: true, export: true },
|
gridSize: { browser: true, export: true },
|
||||||
height: { browser: false, export: false },
|
height: { browser: false, export: false },
|
||||||
|
@ -151,10 +151,12 @@ const LIBRARY_ICON = (
|
|||||||
);
|
);
|
||||||
|
|
||||||
export const ShapesSwitcher = ({
|
export const ShapesSwitcher = ({
|
||||||
|
canvas,
|
||||||
elementType,
|
elementType,
|
||||||
setAppState,
|
setAppState,
|
||||||
isLibraryOpen,
|
isLibraryOpen,
|
||||||
}: {
|
}: {
|
||||||
|
canvas: HTMLCanvasElement | null;
|
||||||
elementType: ExcalidrawElement["type"];
|
elementType: ExcalidrawElement["type"];
|
||||||
setAppState: React.Component<any, AppState>["setState"];
|
setAppState: React.Component<any, AppState>["setState"];
|
||||||
isLibraryOpen: boolean;
|
isLibraryOpen: boolean;
|
||||||
@ -185,7 +187,7 @@ export const ShapesSwitcher = ({
|
|||||||
multiElement: null,
|
multiElement: null,
|
||||||
selectedElementIds: {},
|
selectedElementIds: {},
|
||||||
});
|
});
|
||||||
setCursorForShape(value);
|
setCursorForShape(canvas, value);
|
||||||
setAppState({});
|
setAppState({});
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
@ -21,7 +21,6 @@ import {
|
|||||||
actionSelectAll,
|
actionSelectAll,
|
||||||
actionSendBackward,
|
actionSendBackward,
|
||||||
actionSendToBack,
|
actionSendToBack,
|
||||||
actionToggleAutoSave,
|
|
||||||
actionToggleGridMode,
|
actionToggleGridMode,
|
||||||
actionToggleStats,
|
actionToggleStats,
|
||||||
actionToggleZenMode,
|
actionToggleZenMode,
|
||||||
@ -174,6 +173,7 @@ import {
|
|||||||
ResolvablePromise,
|
ResolvablePromise,
|
||||||
resolvablePromise,
|
resolvablePromise,
|
||||||
sceneCoordsToViewportCoords,
|
sceneCoordsToViewportCoords,
|
||||||
|
setCursor,
|
||||||
setCursorForShape,
|
setCursorForShape,
|
||||||
tupleToCoors,
|
tupleToCoors,
|
||||||
viewportCoordsToSceneCoords,
|
viewportCoordsToSceneCoords,
|
||||||
@ -272,9 +272,10 @@ export type ExcalidrawImperativeAPI = {
|
|||||||
history: {
|
history: {
|
||||||
clear: InstanceType<typeof App>["resetHistory"];
|
clear: InstanceType<typeof App>["resetHistory"];
|
||||||
};
|
};
|
||||||
setScrollToCenter: InstanceType<typeof App>["setScrollToCenter"];
|
setScrollToContent: InstanceType<typeof App>["setScrollToContent"];
|
||||||
getSceneElements: InstanceType<typeof App>["getSceneElements"];
|
getSceneElements: InstanceType<typeof App>["getSceneElements"];
|
||||||
getAppState: () => InstanceType<typeof App>["state"];
|
getAppState: () => InstanceType<typeof App>["state"];
|
||||||
|
setCanvasOffsets: InstanceType<typeof App>["setCanvasOffsets"];
|
||||||
readyPromise: ResolvablePromise<ExcalidrawImperativeAPI>;
|
readyPromise: ResolvablePromise<ExcalidrawImperativeAPI>;
|
||||||
ready: true;
|
ready: true;
|
||||||
};
|
};
|
||||||
@ -298,22 +299,24 @@ class App extends React.Component<ExcalidrawProps, AppState> {
|
|||||||
const {
|
const {
|
||||||
width = window.innerWidth,
|
width = window.innerWidth,
|
||||||
height = window.innerHeight,
|
height = window.innerHeight,
|
||||||
offsetLeft,
|
|
||||||
offsetTop,
|
|
||||||
excalidrawRef,
|
excalidrawRef,
|
||||||
viewModeEnabled = false,
|
viewModeEnabled = false,
|
||||||
zenModeEnabled = false,
|
zenModeEnabled = false,
|
||||||
gridModeEnabled = false,
|
gridModeEnabled = false,
|
||||||
|
theme = defaultAppState.theme,
|
||||||
|
name = defaultAppState.name,
|
||||||
} = props;
|
} = props;
|
||||||
this.state = {
|
this.state = {
|
||||||
...defaultAppState,
|
...defaultAppState,
|
||||||
|
theme,
|
||||||
isLoading: true,
|
isLoading: true,
|
||||||
width,
|
width,
|
||||||
height,
|
height,
|
||||||
...this.getCanvasOffsets({ offsetLeft, offsetTop }),
|
...this.getCanvasOffsets(),
|
||||||
viewModeEnabled,
|
viewModeEnabled,
|
||||||
zenModeEnabled,
|
zenModeEnabled,
|
||||||
gridSize: gridModeEnabled ? GRID_SIZE : null,
|
gridSize: gridModeEnabled ? GRID_SIZE : null,
|
||||||
|
name,
|
||||||
};
|
};
|
||||||
if (excalidrawRef) {
|
if (excalidrawRef) {
|
||||||
const readyPromise =
|
const readyPromise =
|
||||||
@ -329,9 +332,10 @@ class App extends React.Component<ExcalidrawProps, AppState> {
|
|||||||
history: {
|
history: {
|
||||||
clear: this.resetHistory,
|
clear: this.resetHistory,
|
||||||
},
|
},
|
||||||
setScrollToCenter: this.setScrollToCenter,
|
setScrollToContent: this.setScrollToContent,
|
||||||
getSceneElements: this.getSceneElements,
|
getSceneElements: this.getSceneElements,
|
||||||
getAppState: () => this.state,
|
getAppState: () => this.state,
|
||||||
|
setCanvasOffsets: this.setCanvasOffsets,
|
||||||
} as const;
|
} as const;
|
||||||
if (typeof excalidrawRef === "function") {
|
if (typeof excalidrawRef === "function") {
|
||||||
excalidrawRef(api);
|
excalidrawRef(api);
|
||||||
@ -415,8 +419,6 @@ class App extends React.Component<ExcalidrawProps, AppState> {
|
|||||||
zenModeEnabled,
|
zenModeEnabled,
|
||||||
width: canvasDOMWidth,
|
width: canvasDOMWidth,
|
||||||
height: canvasDOMHeight,
|
height: canvasDOMHeight,
|
||||||
offsetTop,
|
|
||||||
offsetLeft,
|
|
||||||
viewModeEnabled,
|
viewModeEnabled,
|
||||||
} = this.state;
|
} = this.state;
|
||||||
|
|
||||||
@ -434,8 +436,6 @@ class App extends React.Component<ExcalidrawProps, AppState> {
|
|||||||
style={{
|
style={{
|
||||||
width: canvasDOMWidth,
|
width: canvasDOMWidth,
|
||||||
height: canvasDOMHeight,
|
height: canvasDOMHeight,
|
||||||
top: offsetTop,
|
|
||||||
left: offsetLeft,
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<LayerUI
|
<LayerUI
|
||||||
@ -463,6 +463,8 @@ class App extends React.Component<ExcalidrawProps, AppState> {
|
|||||||
showExitZenModeBtn={
|
showExitZenModeBtn={
|
||||||
typeof this.props?.zenModeEnabled === "undefined" && zenModeEnabled
|
typeof this.props?.zenModeEnabled === "undefined" && zenModeEnabled
|
||||||
}
|
}
|
||||||
|
showThemeBtn={typeof this.props?.theme === "undefined"}
|
||||||
|
libraryReturnUrl={this.props.libraryReturnUrl}
|
||||||
/>
|
/>
|
||||||
<div className="excalidraw-textEditorContainer" />
|
<div className="excalidraw-textEditorContainer" />
|
||||||
{this.state.showStats && (
|
{this.state.showStats && (
|
||||||
@ -523,6 +525,8 @@ class App extends React.Component<ExcalidrawProps, AppState> {
|
|||||||
let viewModeEnabled = actionResult?.appState?.viewModeEnabled || false;
|
let viewModeEnabled = actionResult?.appState?.viewModeEnabled || false;
|
||||||
let zenModeEnabled = actionResult?.appState?.zenModeEnabled || false;
|
let zenModeEnabled = actionResult?.appState?.zenModeEnabled || false;
|
||||||
let gridSize = actionResult?.appState?.gridSize || null;
|
let gridSize = actionResult?.appState?.gridSize || null;
|
||||||
|
let theme = actionResult?.appState?.theme || "light";
|
||||||
|
let name = actionResult?.appState?.name || this.state.name;
|
||||||
|
|
||||||
if (typeof this.props.viewModeEnabled !== "undefined") {
|
if (typeof this.props.viewModeEnabled !== "undefined") {
|
||||||
viewModeEnabled = this.props.viewModeEnabled;
|
viewModeEnabled = this.props.viewModeEnabled;
|
||||||
@ -536,19 +540,33 @@ class App extends React.Component<ExcalidrawProps, AppState> {
|
|||||||
gridSize = this.props.gridModeEnabled ? GRID_SIZE : null;
|
gridSize = this.props.gridModeEnabled ? GRID_SIZE : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (typeof this.props.theme !== "undefined") {
|
||||||
|
theme = this.props.theme;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof this.props.name !== "undefined") {
|
||||||
|
name = this.props.name;
|
||||||
|
}
|
||||||
|
|
||||||
this.setState(
|
this.setState(
|
||||||
(state) => ({
|
(state) => {
|
||||||
...actionResult.appState,
|
// using Object.assign instead of spread to fool TS 4.2.2+ into
|
||||||
editingElement:
|
// regarding the resulting type as not containing undefined
|
||||||
editingElement || actionResult.appState?.editingElement || null,
|
// (which the following expression will never contain)
|
||||||
width: state.width,
|
return Object.assign(actionResult.appState || {}, {
|
||||||
height: state.height,
|
editingElement:
|
||||||
offsetTop: state.offsetTop,
|
editingElement || actionResult.appState?.editingElement || null,
|
||||||
offsetLeft: state.offsetLeft,
|
width: state.width,
|
||||||
viewModeEnabled,
|
height: state.height,
|
||||||
zenModeEnabled,
|
offsetTop: state.offsetTop,
|
||||||
gridSize,
|
offsetLeft: state.offsetLeft,
|
||||||
}),
|
viewModeEnabled,
|
||||||
|
zenModeEnabled,
|
||||||
|
gridSize,
|
||||||
|
theme,
|
||||||
|
name,
|
||||||
|
});
|
||||||
|
},
|
||||||
() => {
|
() => {
|
||||||
if (actionResult.syncHistory) {
|
if (actionResult.syncHistory) {
|
||||||
history.setCurrentState(
|
history.setCurrentState(
|
||||||
@ -589,7 +607,7 @@ class App extends React.Component<ExcalidrawProps, AppState> {
|
|||||||
private importLibraryFromUrl = async (url: string) => {
|
private importLibraryFromUrl = async (url: string) => {
|
||||||
window.history.replaceState({}, APP_NAME, window.location.origin);
|
window.history.replaceState({}, APP_NAME, window.location.origin);
|
||||||
try {
|
try {
|
||||||
const request = await fetch(url);
|
const request = await fetch(decodeURIComponent(url));
|
||||||
const blob = await request.blob();
|
const blob = await request.blob();
|
||||||
const json = JSON.parse(await blob.text());
|
const json = JSON.parse(await blob.text());
|
||||||
if (!isValidLibrary(json)) {
|
if (!isValidLibrary(json)) {
|
||||||
@ -625,7 +643,7 @@ class App extends React.Component<ExcalidrawProps, AppState> {
|
|||||||
this.setState((state) => ({
|
this.setState((state) => ({
|
||||||
...getDefaultAppState(),
|
...getDefaultAppState(),
|
||||||
isLoading: opts?.resetLoadingState ? false : state.isLoading,
|
isLoading: opts?.resetLoadingState ? false : state.isLoading,
|
||||||
appearance: this.state.appearance,
|
theme: this.state.theme,
|
||||||
}));
|
}));
|
||||||
this.resetHistory();
|
this.resetHistory();
|
||||||
},
|
},
|
||||||
@ -674,19 +692,24 @@ class App extends React.Component<ExcalidrawProps, AppState> {
|
|||||||
|
|
||||||
scene.appState = {
|
scene.appState = {
|
||||||
...scene.appState,
|
...scene.appState,
|
||||||
...calculateScrollCenter(
|
|
||||||
scene.elements,
|
|
||||||
{
|
|
||||||
...scene.appState,
|
|
||||||
width: this.state.width,
|
|
||||||
height: this.state.height,
|
|
||||||
offsetTop: this.state.offsetTop,
|
|
||||||
offsetLeft: this.state.offsetLeft,
|
|
||||||
},
|
|
||||||
null,
|
|
||||||
),
|
|
||||||
isLoading: false,
|
isLoading: false,
|
||||||
};
|
};
|
||||||
|
if (initialData?.scrollToContent) {
|
||||||
|
scene.appState = {
|
||||||
|
...scene.appState,
|
||||||
|
...calculateScrollCenter(
|
||||||
|
scene.elements,
|
||||||
|
{
|
||||||
|
...scene.appState,
|
||||||
|
width: this.state.width,
|
||||||
|
height: this.state.height,
|
||||||
|
offsetTop: this.state.offsetTop,
|
||||||
|
offsetLeft: this.state.offsetLeft,
|
||||||
|
},
|
||||||
|
null,
|
||||||
|
),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
this.resetHistory();
|
this.resetHistory();
|
||||||
this.syncActionResult({
|
this.syncActionResult({
|
||||||
@ -732,14 +755,13 @@ class App extends React.Component<ExcalidrawProps, AppState> {
|
|||||||
this.scene.addCallback(this.onSceneUpdated);
|
this.scene.addCallback(this.onSceneUpdated);
|
||||||
this.addEventListeners();
|
this.addEventListeners();
|
||||||
|
|
||||||
// optim to avoid extra render on init
|
const searchParams = new URLSearchParams(window.location.search.slice(1));
|
||||||
if (
|
|
||||||
typeof this.props.offsetLeft === "number" &&
|
if (searchParams.has("web-share-target")) {
|
||||||
typeof this.props.offsetTop === "number"
|
// Obtain a file that was shared via the Web Share Target API.
|
||||||
) {
|
this.restoreFileFromShare();
|
||||||
this.initializeScene();
|
|
||||||
} else {
|
} else {
|
||||||
this.setState(this.getCanvasOffsets(this.props), () => {
|
this.setState(this.getCanvasOffsets(), () => {
|
||||||
this.initializeScene();
|
this.initializeScene();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -844,16 +866,12 @@ class App extends React.Component<ExcalidrawProps, AppState> {
|
|||||||
|
|
||||||
if (
|
if (
|
||||||
prevProps.width !== this.props.width ||
|
prevProps.width !== this.props.width ||
|
||||||
prevProps.height !== this.props.height ||
|
prevProps.height !== this.props.height
|
||||||
(typeof this.props.offsetLeft === "number" &&
|
|
||||||
prevProps.offsetLeft !== this.props.offsetLeft) ||
|
|
||||||
(typeof this.props.offsetTop === "number" &&
|
|
||||||
prevProps.offsetTop !== this.props.offsetTop)
|
|
||||||
) {
|
) {
|
||||||
this.setState({
|
this.setState({
|
||||||
width: this.props.width ?? window.innerWidth,
|
width: this.props.width ?? window.innerWidth,
|
||||||
height: this.props.height ?? window.innerHeight,
|
height: this.props.height ?? window.innerHeight,
|
||||||
...this.getCanvasOffsets(this.props),
|
...this.getCanvasOffsets(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -872,16 +890,27 @@ class App extends React.Component<ExcalidrawProps, AppState> {
|
|||||||
this.setState({ zenModeEnabled: !!this.props.zenModeEnabled });
|
this.setState({ zenModeEnabled: !!this.props.zenModeEnabled });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (prevProps.theme !== this.props.theme && this.props.theme) {
|
||||||
|
this.setState({ theme: this.props.theme });
|
||||||
|
}
|
||||||
|
|
||||||
if (prevProps.gridModeEnabled !== this.props.gridModeEnabled) {
|
if (prevProps.gridModeEnabled !== this.props.gridModeEnabled) {
|
||||||
this.setState({
|
this.setState({
|
||||||
gridSize: this.props.gridModeEnabled ? GRID_SIZE : null,
|
gridSize: this.props.gridModeEnabled ? GRID_SIZE : null,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this.props.name && prevProps.name !== this.props.name) {
|
||||||
|
this.setState({
|
||||||
|
name: this.props.name,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
document
|
document
|
||||||
.querySelector(".excalidraw")
|
.querySelector(".excalidraw")
|
||||||
?.classList.toggle("Appearance_dark", this.state.appearance === "dark");
|
?.classList.toggle("theme--dark", this.state.theme === "dark");
|
||||||
|
|
||||||
if (this.state.autoSave) {
|
if (this.state.autoSave && this.state.fileHandle) {
|
||||||
this.saveLocalSceneDebounced(
|
this.saveLocalSceneDebounced(
|
||||||
this.scene.getElementsIncludingDeleted(),
|
this.scene.getElementsIncludingDeleted(),
|
||||||
this.state,
|
this.state,
|
||||||
@ -981,6 +1010,7 @@ class App extends React.Component<ExcalidrawProps, AppState> {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
renderOptimizations: true,
|
renderOptimizations: true,
|
||||||
|
renderScrollbars: !isMobile(),
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
if (scrollBars) {
|
if (scrollBars) {
|
||||||
@ -1010,7 +1040,13 @@ class App extends React.Component<ExcalidrawProps, AppState> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private onScroll = debounce(() => {
|
private onScroll = debounce(() => {
|
||||||
this.setState({ ...this.getCanvasOffsets() });
|
const { offsetTop, offsetLeft } = this.getCanvasOffsets();
|
||||||
|
this.setState((state) => {
|
||||||
|
if (state.offsetLeft === offsetLeft && state.offsetTop === offsetTop) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return { offsetTop, offsetLeft };
|
||||||
|
});
|
||||||
}, SCROLL_TIMEOUT);
|
}, SCROLL_TIMEOUT);
|
||||||
|
|
||||||
private saveLocalSceneDebounced = debounce(
|
private saveLocalSceneDebounced = debounce(
|
||||||
@ -1031,6 +1067,21 @@ class App extends React.Component<ExcalidrawProps, AppState> {
|
|||||||
});
|
});
|
||||||
|
|
||||||
private onCopy = withBatchedUpdates((event: ClipboardEvent) => {
|
private onCopy = withBatchedUpdates((event: ClipboardEvent) => {
|
||||||
|
const activeSelection = document.getSelection();
|
||||||
|
// if there's a selected text is outside the component, prevent our copy
|
||||||
|
// action
|
||||||
|
if (
|
||||||
|
activeSelection?.anchorNode &&
|
||||||
|
// it can happen that certain interactions will create a selection
|
||||||
|
// outside (or potentially inside) the component without actually
|
||||||
|
// selecting anything (i.e. the selection range is collapsed). Copying
|
||||||
|
// in such case wouldn't copy anything to the clipboard anyway, so prevent
|
||||||
|
// our copy handler only if the selection isn't collapsed
|
||||||
|
!activeSelection.isCollapsed &&
|
||||||
|
!this.excalidrawContainerRef.current!.contains(activeSelection.anchorNode)
|
||||||
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (isWritableElement(event.target)) {
|
if (isWritableElement(event.target)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -1082,7 +1133,6 @@ class App extends React.Component<ExcalidrawProps, AppState> {
|
|||||||
};
|
};
|
||||||
|
|
||||||
private onTapEnd = (event: TouchEvent) => {
|
private onTapEnd = (event: TouchEvent) => {
|
||||||
event.preventDefault();
|
|
||||||
if (event.touches.length > 0) {
|
if (event.touches.length > 0) {
|
||||||
this.setState({
|
this.setState({
|
||||||
previousSelectedElementIds: {},
|
previousSelectedElementIds: {},
|
||||||
@ -1259,7 +1309,7 @@ class App extends React.Component<ExcalidrawProps, AppState> {
|
|||||||
this.actionManager.executeAction(actionToggleStats);
|
this.actionManager.executeAction(actionToggleStats);
|
||||||
};
|
};
|
||||||
|
|
||||||
setScrollToCenter = (remoteElements: readonly ExcalidrawElement[]) => {
|
setScrollToContent = (remoteElements: readonly ExcalidrawElement[]) => {
|
||||||
this.setState({
|
this.setState({
|
||||||
...calculateScrollCenter(
|
...calculateScrollCenter(
|
||||||
getNonDeletedElements(remoteElements),
|
getNonDeletedElements(remoteElements),
|
||||||
@ -1273,6 +1323,22 @@ class App extends React.Component<ExcalidrawProps, AppState> {
|
|||||||
this.setState({ toastMessage: null });
|
this.setState({ toastMessage: null });
|
||||||
};
|
};
|
||||||
|
|
||||||
|
restoreFileFromShare = async () => {
|
||||||
|
try {
|
||||||
|
const webShareTargetCache = await caches.open("web-share-target");
|
||||||
|
|
||||||
|
const file = await webShareTargetCache.match("shared-file");
|
||||||
|
if (file) {
|
||||||
|
const blob = await file.blob();
|
||||||
|
this.loadFileToCanvas(blob);
|
||||||
|
await webShareTargetCache.delete("shared-file");
|
||||||
|
window.history.replaceState(null, APP_NAME, window.location.pathname);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
this.setState({ errorMessage: error.message });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
public updateScene = withBatchedUpdates((sceneData: SceneData) => {
|
public updateScene = withBatchedUpdates((sceneData: SceneData) => {
|
||||||
if (sceneData.commitToHistory) {
|
if (sceneData.commitToHistory) {
|
||||||
history.resumeRecording();
|
history.resumeRecording();
|
||||||
@ -1354,7 +1420,7 @@ class App extends React.Component<ExcalidrawProps, AppState> {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (event[KEYS.CTRL_OR_CMD]) {
|
if (event[KEYS.CTRL_OR_CMD] && this.state.isBindingEnabled) {
|
||||||
this.setState({ isBindingEnabled: false });
|
this.setState({ isBindingEnabled: false });
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1450,16 +1516,16 @@ class App extends React.Component<ExcalidrawProps, AppState> {
|
|||||||
}
|
}
|
||||||
if (event.key === KEYS.SPACE && gesture.pointers.size === 0) {
|
if (event.key === KEYS.SPACE && gesture.pointers.size === 0) {
|
||||||
isHoldingSpace = true;
|
isHoldingSpace = true;
|
||||||
document.documentElement.style.cursor = CURSOR_TYPE.GRABBING;
|
setCursor(this.canvas, CURSOR_TYPE.GRABBING);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
private onKeyUp = withBatchedUpdates((event: KeyboardEvent) => {
|
private onKeyUp = withBatchedUpdates((event: KeyboardEvent) => {
|
||||||
if (event.key === KEYS.SPACE) {
|
if (event.key === KEYS.SPACE) {
|
||||||
if (this.state.elementType === "selection") {
|
if (this.state.elementType === "selection") {
|
||||||
resetCursor();
|
resetCursor(this.canvas);
|
||||||
} else {
|
} else {
|
||||||
setCursorForShape(this.state.elementType);
|
setCursorForShape(this.canvas, this.state.elementType);
|
||||||
this.setState({
|
this.setState({
|
||||||
selectedElementIds: {},
|
selectedElementIds: {},
|
||||||
selectedGroupIds: {},
|
selectedGroupIds: {},
|
||||||
@ -1485,7 +1551,7 @@ class App extends React.Component<ExcalidrawProps, AppState> {
|
|||||||
|
|
||||||
private selectShapeTool(elementType: AppState["elementType"]) {
|
private selectShapeTool(elementType: AppState["elementType"]) {
|
||||||
if (!isHoldingSpace) {
|
if (!isHoldingSpace) {
|
||||||
setCursorForShape(elementType);
|
setCursorForShape(this.canvas, elementType);
|
||||||
}
|
}
|
||||||
if (isToolIcon(document.activeElement)) {
|
if (isToolIcon(document.activeElement)) {
|
||||||
document.activeElement.blur();
|
document.activeElement.blur();
|
||||||
@ -1581,7 +1647,10 @@ class App extends React.Component<ExcalidrawProps, AppState> {
|
|||||||
},
|
},
|
||||||
this.state,
|
this.state,
|
||||||
);
|
);
|
||||||
return [viewportX, viewportY];
|
return [
|
||||||
|
viewportX - this.state.offsetLeft,
|
||||||
|
viewportY - this.state.offsetTop,
|
||||||
|
];
|
||||||
},
|
},
|
||||||
onChange: withBatchedUpdates((text) => {
|
onChange: withBatchedUpdates((text) => {
|
||||||
updateElement(text);
|
updateElement(text);
|
||||||
@ -1589,10 +1658,12 @@ class App extends React.Component<ExcalidrawProps, AppState> {
|
|||||||
updateBoundElements(element);
|
updateBoundElements(element);
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
onSubmit: withBatchedUpdates((text) => {
|
onSubmit: withBatchedUpdates(({ text, viaKeyboard }) => {
|
||||||
const isDeleted = !text.trim();
|
const isDeleted = !text.trim();
|
||||||
updateElement(text, isDeleted);
|
updateElement(text, isDeleted);
|
||||||
if (!isDeleted) {
|
// select the created text element only if submitting via keyboard
|
||||||
|
// (when submitting via click it should act as signal to deselect)
|
||||||
|
if (!isDeleted && viaKeyboard) {
|
||||||
this.setState((prevState) => ({
|
this.setState((prevState) => ({
|
||||||
selectedElementIds: {
|
selectedElementIds: {
|
||||||
...prevState.selectedElementIds,
|
...prevState.selectedElementIds,
|
||||||
@ -1611,7 +1682,7 @@ class App extends React.Component<ExcalidrawProps, AppState> {
|
|||||||
editingElement: null,
|
editingElement: null,
|
||||||
});
|
});
|
||||||
if (this.state.elementLocked) {
|
if (this.state.elementLocked) {
|
||||||
setCursorForShape(this.state.elementType);
|
setCursorForShape(this.canvas, this.state.elementType);
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
element,
|
element,
|
||||||
@ -1792,7 +1863,7 @@ class App extends React.Component<ExcalidrawProps, AppState> {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
resetCursor();
|
resetCursor(this.canvas);
|
||||||
|
|
||||||
const { x: sceneX, y: sceneY } = viewportCoordsToSceneCoords(
|
const { x: sceneX, y: sceneY } = viewportCoordsToSceneCoords(
|
||||||
event,
|
event,
|
||||||
@ -1824,7 +1895,7 @@ class App extends React.Component<ExcalidrawProps, AppState> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
resetCursor();
|
resetCursor(this.canvas);
|
||||||
|
|
||||||
if (!event[KEYS.CTRL_OR_CMD]) {
|
if (!event[KEYS.CTRL_OR_CMD]) {
|
||||||
this.startTextEditing({
|
this.startTextEditing({
|
||||||
@ -1890,9 +1961,9 @@ class App extends React.Component<ExcalidrawProps, AppState> {
|
|||||||
const isOverScrollBar = isPointerOverScrollBars.isOverEither;
|
const isOverScrollBar = isPointerOverScrollBars.isOverEither;
|
||||||
if (!this.state.draggingElement && !this.state.multiElement) {
|
if (!this.state.draggingElement && !this.state.multiElement) {
|
||||||
if (isOverScrollBar) {
|
if (isOverScrollBar) {
|
||||||
resetCursor();
|
resetCursor(this.canvas);
|
||||||
} else {
|
} else {
|
||||||
setCursorForShape(this.state.elementType);
|
setCursorForShape(this.canvas, this.state.elementType);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1943,7 +2014,7 @@ class App extends React.Component<ExcalidrawProps, AppState> {
|
|||||||
const { points, lastCommittedPoint } = multiElement;
|
const { points, lastCommittedPoint } = multiElement;
|
||||||
const lastPoint = points[points.length - 1];
|
const lastPoint = points[points.length - 1];
|
||||||
|
|
||||||
setCursorForShape(this.state.elementType);
|
setCursorForShape(this.canvas, this.state.elementType);
|
||||||
|
|
||||||
if (lastPoint === lastCommittedPoint) {
|
if (lastPoint === lastCommittedPoint) {
|
||||||
// if we haven't yet created a temp point and we're beyond commit-zone
|
// if we haven't yet created a temp point and we're beyond commit-zone
|
||||||
@ -1960,7 +2031,7 @@ class App extends React.Component<ExcalidrawProps, AppState> {
|
|||||||
points: [...points, [scenePointerX - rx, scenePointerY - ry]],
|
points: [...points, [scenePointerX - rx, scenePointerY - ry]],
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
document.documentElement.style.cursor = CURSOR_TYPE.POINTER;
|
setCursor(this.canvas, CURSOR_TYPE.POINTER);
|
||||||
// in this branch, we're inside the commit zone, and no uncommitted
|
// in this branch, we're inside the commit zone, and no uncommitted
|
||||||
// point exists. Thus do nothing (don't add/remove points).
|
// point exists. Thus do nothing (don't add/remove points).
|
||||||
}
|
}
|
||||||
@ -1974,13 +2045,13 @@ class App extends React.Component<ExcalidrawProps, AppState> {
|
|||||||
lastCommittedPoint[1],
|
lastCommittedPoint[1],
|
||||||
) < LINE_CONFIRM_THRESHOLD
|
) < LINE_CONFIRM_THRESHOLD
|
||||||
) {
|
) {
|
||||||
document.documentElement.style.cursor = CURSOR_TYPE.POINTER;
|
setCursor(this.canvas, CURSOR_TYPE.POINTER);
|
||||||
mutateElement(multiElement, {
|
mutateElement(multiElement, {
|
||||||
points: points.slice(0, -1),
|
points: points.slice(0, -1),
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
if (isPathALoop(points, this.state.zoom.value)) {
|
if (isPathALoop(points, this.state.zoom.value)) {
|
||||||
document.documentElement.style.cursor = CURSOR_TYPE.POINTER;
|
setCursor(this.canvas, CURSOR_TYPE.POINTER);
|
||||||
}
|
}
|
||||||
// update last uncommitted point
|
// update last uncommitted point
|
||||||
mutateElement(multiElement, {
|
mutateElement(multiElement, {
|
||||||
@ -2023,8 +2094,9 @@ class App extends React.Component<ExcalidrawProps, AppState> {
|
|||||||
elementWithTransformHandleType &&
|
elementWithTransformHandleType &&
|
||||||
elementWithTransformHandleType.transformHandleType
|
elementWithTransformHandleType.transformHandleType
|
||||||
) {
|
) {
|
||||||
document.documentElement.style.cursor = getCursorForResizingElement(
|
setCursor(
|
||||||
elementWithTransformHandleType,
|
this.canvas,
|
||||||
|
getCursorForResizingElement(elementWithTransformHandleType),
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -2037,9 +2109,12 @@ class App extends React.Component<ExcalidrawProps, AppState> {
|
|||||||
event.pointerType,
|
event.pointerType,
|
||||||
);
|
);
|
||||||
if (transformHandleType) {
|
if (transformHandleType) {
|
||||||
document.documentElement.style.cursor = getCursorForResizingElement({
|
setCursor(
|
||||||
transformHandleType,
|
this.canvas,
|
||||||
});
|
getCursorForResizingElement({
|
||||||
|
transformHandleType,
|
||||||
|
}),
|
||||||
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -2049,11 +2124,12 @@ class App extends React.Component<ExcalidrawProps, AppState> {
|
|||||||
scenePointer.y,
|
scenePointer.y,
|
||||||
);
|
);
|
||||||
if (this.state.elementType === "text") {
|
if (this.state.elementType === "text") {
|
||||||
document.documentElement.style.cursor = isTextElement(hitElement)
|
setCursor(
|
||||||
? CURSOR_TYPE.TEXT
|
this.canvas,
|
||||||
: CURSOR_TYPE.CROSSHAIR;
|
isTextElement(hitElement) ? CURSOR_TYPE.TEXT : CURSOR_TYPE.CROSSHAIR,
|
||||||
|
);
|
||||||
} else if (isOverScrollBar) {
|
} else if (isOverScrollBar) {
|
||||||
document.documentElement.style.cursor = CURSOR_TYPE.AUTO;
|
setCursor(this.canvas, CURSOR_TYPE.AUTO);
|
||||||
} else if (
|
} else if (
|
||||||
hitElement ||
|
hitElement ||
|
||||||
this.isHittingCommonBoundingBoxOfSelectedElements(
|
this.isHittingCommonBoundingBoxOfSelectedElements(
|
||||||
@ -2061,9 +2137,9 @@ class App extends React.Component<ExcalidrawProps, AppState> {
|
|||||||
selectedElements,
|
selectedElements,
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
document.documentElement.style.cursor = CURSOR_TYPE.MOVE;
|
setCursor(this.canvas, CURSOR_TYPE.MOVE);
|
||||||
} else {
|
} else {
|
||||||
document.documentElement.style.cursor = CURSOR_TYPE.AUTO;
|
setCursor(this.canvas, CURSOR_TYPE.AUTO);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -2077,6 +2153,14 @@ class App extends React.Component<ExcalidrawProps, AppState> {
|
|||||||
) => {
|
) => {
|
||||||
event.persist();
|
event.persist();
|
||||||
|
|
||||||
|
// remove any active selection when we start to interact with canvas
|
||||||
|
// (mainly, we care about removing selection outside the component which
|
||||||
|
// would prevent our copy handling otherwise)
|
||||||
|
const selection = document.getSelection();
|
||||||
|
if (selection?.anchorNode) {
|
||||||
|
selection.removeAllRanges();
|
||||||
|
}
|
||||||
|
|
||||||
this.maybeOpenContextMenuAfterPointerDownOnTouchDevices(event);
|
this.maybeOpenContextMenuAfterPointerDownOnTouchDevices(event);
|
||||||
this.maybeCleanupAfterMissingPointerUp(event);
|
this.maybeCleanupAfterMissingPointerUp(event);
|
||||||
|
|
||||||
@ -2104,15 +2188,6 @@ class App extends React.Component<ExcalidrawProps, AppState> {
|
|||||||
|
|
||||||
this.updateGestureOnPointerDown(event);
|
this.updateGestureOnPointerDown(event);
|
||||||
|
|
||||||
// fixes pointermove causing selection of UI texts #32
|
|
||||||
event.preventDefault();
|
|
||||||
// Preventing the event above disables default behavior
|
|
||||||
// of defocusing potentially focused element, which is what we
|
|
||||||
// want when clicking inside the canvas.
|
|
||||||
if (document.activeElement instanceof HTMLElement) {
|
|
||||||
document.activeElement.blur();
|
|
||||||
}
|
|
||||||
|
|
||||||
// don't select while panning
|
// don't select while panning
|
||||||
if (gesture.pointers.size > 1) {
|
if (gesture.pointers.size > 1) {
|
||||||
return;
|
return;
|
||||||
@ -2236,7 +2311,7 @@ class App extends React.Component<ExcalidrawProps, AppState> {
|
|||||||
let nextPastePrevented = false;
|
let nextPastePrevented = false;
|
||||||
const isLinux = /Linux/.test(window.navigator.platform);
|
const isLinux = /Linux/.test(window.navigator.platform);
|
||||||
|
|
||||||
document.documentElement.style.cursor = CURSOR_TYPE.GRABBING;
|
setCursor(this.canvas, CURSOR_TYPE.GRABBING);
|
||||||
let { clientX: lastX, clientY: lastY } = event;
|
let { clientX: lastX, clientY: lastY } = event;
|
||||||
const onPointerMove = withBatchedUpdates((event: PointerEvent) => {
|
const onPointerMove = withBatchedUpdates((event: PointerEvent) => {
|
||||||
const deltaX = lastX - event.clientX;
|
const deltaX = lastX - event.clientX;
|
||||||
@ -2288,7 +2363,7 @@ class App extends React.Component<ExcalidrawProps, AppState> {
|
|||||||
lastPointerUp = null;
|
lastPointerUp = null;
|
||||||
isPanning = false;
|
isPanning = false;
|
||||||
if (!isHoldingSpace) {
|
if (!isHoldingSpace) {
|
||||||
setCursorForShape(this.state.elementType);
|
setCursorForShape(this.canvas, this.state.elementType);
|
||||||
}
|
}
|
||||||
this.setState({
|
this.setState({
|
||||||
cursorButton: "up",
|
cursorButton: "up",
|
||||||
@ -2404,7 +2479,7 @@ class App extends React.Component<ExcalidrawProps, AppState> {
|
|||||||
|
|
||||||
const onPointerUp = withBatchedUpdates(() => {
|
const onPointerUp = withBatchedUpdates(() => {
|
||||||
isDraggingScrollBar = false;
|
isDraggingScrollBar = false;
|
||||||
setCursorForShape(this.state.elementType);
|
setCursorForShape(this.canvas, this.state.elementType);
|
||||||
lastPointerUp = null;
|
lastPointerUp = null;
|
||||||
this.setState({
|
this.setState({
|
||||||
cursorButton: "up",
|
cursorButton: "up",
|
||||||
@ -2467,9 +2542,12 @@ class App extends React.Component<ExcalidrawProps, AppState> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (pointerDownState.resize.handleType) {
|
if (pointerDownState.resize.handleType) {
|
||||||
document.documentElement.style.cursor = getCursorForResizingElement({
|
setCursor(
|
||||||
transformHandleType: pointerDownState.resize.handleType,
|
this.canvas,
|
||||||
});
|
getCursorForResizingElement({
|
||||||
|
transformHandleType: pointerDownState.resize.handleType,
|
||||||
|
}),
|
||||||
|
);
|
||||||
pointerDownState.resize.isResizing = true;
|
pointerDownState.resize.isResizing = true;
|
||||||
pointerDownState.resize.offset = tupleToCoors(
|
pointerDownState.resize.offset = tupleToCoors(
|
||||||
getResizeOffsetXY(
|
getResizeOffsetXY(
|
||||||
@ -2634,7 +2712,7 @@ class App extends React.Component<ExcalidrawProps, AppState> {
|
|||||||
insertAtParentCenter: !event.altKey,
|
insertAtParentCenter: !event.altKey,
|
||||||
});
|
});
|
||||||
|
|
||||||
resetCursor();
|
resetCursor(this.canvas);
|
||||||
if (!this.state.elementLocked) {
|
if (!this.state.elementLocked) {
|
||||||
this.setState({
|
this.setState({
|
||||||
elementType: "selection",
|
elementType: "selection",
|
||||||
@ -2691,7 +2769,7 @@ class App extends React.Component<ExcalidrawProps, AppState> {
|
|||||||
mutateElement(multiElement, {
|
mutateElement(multiElement, {
|
||||||
lastCommittedPoint: multiElement.points[multiElement.points.length - 1],
|
lastCommittedPoint: multiElement.points[multiElement.points.length - 1],
|
||||||
});
|
});
|
||||||
document.documentElement.style.cursor = CURSOR_TYPE.POINTER;
|
setCursor(this.canvas, CURSOR_TYPE.POINTER);
|
||||||
} else {
|
} else {
|
||||||
const [gridX, gridY] = getGridPoint(
|
const [gridX, gridY] = getGridPoint(
|
||||||
pointerDownState.origin.x,
|
pointerDownState.origin.x,
|
||||||
@ -3226,7 +3304,7 @@ class App extends React.Component<ExcalidrawProps, AppState> {
|
|||||||
}
|
}
|
||||||
this.setState({ suggestedBindings: [], startBoundElement: null });
|
this.setState({ suggestedBindings: [], startBoundElement: null });
|
||||||
if (!elementLocked && elementType !== "draw") {
|
if (!elementLocked && elementType !== "draw") {
|
||||||
resetCursor();
|
resetCursor(this.canvas);
|
||||||
this.setState((prevState) => ({
|
this.setState((prevState) => ({
|
||||||
draggingElement: null,
|
draggingElement: null,
|
||||||
elementType: "selection",
|
elementType: "selection",
|
||||||
@ -3397,7 +3475,7 @@ class App extends React.Component<ExcalidrawProps, AppState> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!elementLocked && elementType !== "draw") {
|
if (!elementLocked && elementType !== "draw") {
|
||||||
resetCursor();
|
resetCursor(this.canvas);
|
||||||
this.setState({
|
this.setState({
|
||||||
draggingElement: null,
|
draggingElement: null,
|
||||||
suggestedBindings: [],
|
suggestedBindings: [],
|
||||||
@ -3555,26 +3633,12 @@ class App extends React.Component<ExcalidrawProps, AppState> {
|
|||||||
// This will only work as of Chrome 86,
|
// This will only work as of Chrome 86,
|
||||||
// but can be safely ignored on older releases.
|
// but can be safely ignored on older releases.
|
||||||
const item = event.dataTransfer.items[0];
|
const item = event.dataTransfer.items[0];
|
||||||
// TODO: Make this part of `AppState`.
|
|
||||||
(file as any).handle = await (item as any).getAsFileSystemHandle();
|
(file as any).handle = await (item as any).getAsFileSystemHandle();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.warn(error.name, error.message);
|
console.warn(error.name, error.message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
loadFromBlob(file, this.state)
|
this.loadFileToCanvas(file);
|
||||||
.then(({ elements, appState }) =>
|
|
||||||
this.syncActionResult({
|
|
||||||
elements,
|
|
||||||
appState: {
|
|
||||||
...(appState || this.state),
|
|
||||||
isLoading: false,
|
|
||||||
},
|
|
||||||
commitToHistory: true,
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
.catch((error) => {
|
|
||||||
this.setState({ isLoading: false, errorMessage: error.message });
|
|
||||||
});
|
|
||||||
} else if (
|
} else if (
|
||||||
file?.type === MIME_TYPES.excalidrawlib ||
|
file?.type === MIME_TYPES.excalidrawlib ||
|
||||||
file?.name.endsWith(".excalidrawlib")
|
file?.name.endsWith(".excalidrawlib")
|
||||||
@ -3594,6 +3658,23 @@ class App extends React.Component<ExcalidrawProps, AppState> {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
loadFileToCanvas = (file: Blob) => {
|
||||||
|
loadFromBlob(file, this.state)
|
||||||
|
.then(({ elements, appState }) =>
|
||||||
|
this.syncActionResult({
|
||||||
|
elements,
|
||||||
|
appState: {
|
||||||
|
...(appState || this.state),
|
||||||
|
isLoading: false,
|
||||||
|
},
|
||||||
|
commitToHistory: true,
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.catch((error) => {
|
||||||
|
this.setState({ isLoading: false, errorMessage: error.message });
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
private handleCanvasContextMenu = (
|
private handleCanvasContextMenu = (
|
||||||
event: React.PointerEvent<HTMLCanvasElement>,
|
event: React.PointerEvent<HTMLCanvasElement>,
|
||||||
) => {
|
) => {
|
||||||
@ -3732,7 +3813,6 @@ class App extends React.Component<ExcalidrawProps, AppState> {
|
|||||||
typeof this.props.zenModeEnabled === "undefined" && actionToggleZenMode,
|
typeof this.props.zenModeEnabled === "undefined" && actionToggleZenMode,
|
||||||
typeof this.props.viewModeEnabled === "undefined" &&
|
typeof this.props.viewModeEnabled === "undefined" &&
|
||||||
actionToggleViewMode,
|
actionToggleViewMode,
|
||||||
actionToggleAutoSave,
|
|
||||||
actionToggleStats,
|
actionToggleStats,
|
||||||
];
|
];
|
||||||
|
|
||||||
@ -3779,7 +3859,6 @@ class App extends React.Component<ExcalidrawProps, AppState> {
|
|||||||
actionToggleZenMode,
|
actionToggleZenMode,
|
||||||
typeof this.props.viewModeEnabled === "undefined" &&
|
typeof this.props.viewModeEnabled === "undefined" &&
|
||||||
actionToggleViewMode,
|
actionToggleViewMode,
|
||||||
actionToggleAutoSave,
|
|
||||||
separator,
|
separator,
|
||||||
actionToggleStats,
|
actionToggleStats,
|
||||||
],
|
],
|
||||||
@ -3977,33 +4056,22 @@ class App extends React.Component<ExcalidrawProps, AppState> {
|
|||||||
}
|
}
|
||||||
}, 300);
|
}, 300);
|
||||||
|
|
||||||
private getCanvasOffsets(offsets?: {
|
public setCanvasOffsets = () => {
|
||||||
offsetLeft?: number;
|
this.setState({ ...this.getCanvasOffsets() });
|
||||||
offsetTop?: number;
|
};
|
||||||
}): Pick<AppState, "offsetTop" | "offsetLeft"> {
|
|
||||||
if (
|
private getCanvasOffsets(): Pick<AppState, "offsetTop" | "offsetLeft"> {
|
||||||
typeof offsets?.offsetLeft === "number" &&
|
|
||||||
typeof offsets?.offsetTop === "number"
|
|
||||||
) {
|
|
||||||
return {
|
|
||||||
offsetLeft: offsets.offsetLeft,
|
|
||||||
offsetTop: offsets.offsetTop,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
if (this.excalidrawContainerRef?.current?.parentElement) {
|
if (this.excalidrawContainerRef?.current?.parentElement) {
|
||||||
const parentElement = this.excalidrawContainerRef.current.parentElement;
|
const parentElement = this.excalidrawContainerRef.current.parentElement;
|
||||||
const { left, top } = parentElement.getBoundingClientRect();
|
const { left, top } = parentElement.getBoundingClientRect();
|
||||||
return {
|
return {
|
||||||
offsetLeft:
|
offsetLeft: left,
|
||||||
typeof offsets?.offsetLeft === "number" ? offsets.offsetLeft : left,
|
offsetTop: top,
|
||||||
offsetTop:
|
|
||||||
typeof offsets?.offsetTop === "number" ? offsets.offsetTop : top,
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
offsetLeft:
|
offsetLeft: 0,
|
||||||
typeof offsets?.offsetLeft === "number" ? offsets.offsetLeft : 0,
|
offsetTop: 0,
|
||||||
offsetTop: typeof offsets?.offsetTop === "number" ? offsets.offsetTop : 0,
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -7,20 +7,24 @@ export const BackgroundPickerAndDarkModeToggle = ({
|
|||||||
appState,
|
appState,
|
||||||
setAppState,
|
setAppState,
|
||||||
actionManager,
|
actionManager,
|
||||||
|
showThemeBtn,
|
||||||
}: {
|
}: {
|
||||||
actionManager: ActionManager;
|
actionManager: ActionManager;
|
||||||
appState: AppState;
|
appState: AppState;
|
||||||
setAppState: React.Component<any, AppState>["setState"];
|
setAppState: React.Component<any, AppState>["setState"];
|
||||||
|
showThemeBtn: boolean;
|
||||||
}) => (
|
}) => (
|
||||||
<div style={{ display: "flex" }}>
|
<div style={{ display: "flex" }}>
|
||||||
{actionManager.renderAction("changeViewBackgroundColor")}
|
{actionManager.renderAction("changeViewBackgroundColor")}
|
||||||
<div style={{ marginInlineStart: "0.25rem" }}>
|
{showThemeBtn && (
|
||||||
<DarkModeToggle
|
<div style={{ marginInlineStart: "0.25rem" }}>
|
||||||
value={appState.appearance}
|
<DarkModeToggle
|
||||||
onChange={(appearance) => {
|
value={appState.theme}
|
||||||
setAppState({ appearance });
|
onChange={(theme) => {
|
||||||
}}
|
setAppState({ theme });
|
||||||
/>
|
}}
|
||||||
</div>
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
.excalidraw {
|
.excalidraw {
|
||||||
.CollabButton.is-collaborating {
|
.CollabButton.is-collaborating {
|
||||||
background-color: var(--button-special-active-background-color);
|
background-color: var(--button-special-active-bg-color);
|
||||||
|
|
||||||
.ToolIcon__icon svg,
|
.ToolIcon__icon svg,
|
||||||
.ToolIcon__label {
|
.ToolIcon__label {
|
||||||
|
@ -2,9 +2,9 @@
|
|||||||
|
|
||||||
.excalidraw {
|
.excalidraw {
|
||||||
.color-picker {
|
.color-picker {
|
||||||
background: var(--popup-background-color);
|
background: var(--popup-bg-color);
|
||||||
border: 0px solid transparentize($oc-white, 0.75);
|
border: 0 solid transparentize($oc-white, 0.75);
|
||||||
box-shadow: transparentize($oc-black, 0.75) 0px 1px 4px;
|
box-shadow: transparentize($oc-black, 0.75) 0 1px 4px;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
|
|
||||||
@ -24,11 +24,11 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.color-picker-triangle {
|
.color-picker-triangle {
|
||||||
width: 0px;
|
width: 0;
|
||||||
height: 0px;
|
height: 0;
|
||||||
border-style: solid;
|
border-style: solid;
|
||||||
border-width: 0px 9px 10px;
|
border-width: 0 9px 10px;
|
||||||
border-color: transparent transparent var(--popup-background-color);
|
border-color: transparent transparent var(--popup-bg-color);
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: -10px;
|
top: -10px;
|
||||||
|
|
||||||
@ -73,7 +73,7 @@
|
|||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
border: 1px solid #ddd;
|
border: 1px solid #ddd;
|
||||||
background-color: currentColor !important;
|
background-color: currentColor !important;
|
||||||
filter: var(--appearance-filter);
|
filter: var(--theme-filter);
|
||||||
|
|
||||||
&:focus {
|
&:focus {
|
||||||
/* TODO: only show the border when the color is too light to see as a shadow */
|
/* TODO: only show the border when the color is too light to see as a shadow */
|
||||||
@ -84,12 +84,12 @@
|
|||||||
|
|
||||||
.color-picker-transparent {
|
.color-picker-transparent {
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
box-shadow: transparentize($oc-black, 0.9) 0px 0px 0px 1px inset;
|
box-shadow: transparentize($oc-black, 0.9) 0 0 0 1px inset;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0px;
|
top: 0;
|
||||||
right: 0px;
|
right: 0;
|
||||||
bottom: 0px;
|
bottom: 0;
|
||||||
left: 0px;
|
left: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.color-picker-transparent,
|
.color-picker-transparent,
|
||||||
@ -104,11 +104,11 @@
|
|||||||
width: 1.875rem;
|
width: 1.875rem;
|
||||||
|
|
||||||
:root[dir="ltr"] & {
|
:root[dir="ltr"] & {
|
||||||
border-radius: 4px 0px 0px 4px;
|
border-radius: 4px 0 0 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
:root[dir="rtl"] & {
|
:root[dir="rtl"] & {
|
||||||
border-radius: 0px 4px 4px 0px;
|
border-radius: 0 4px 4px 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
color: var(--input-label-color);
|
color: var(--input-label-color);
|
||||||
@ -144,7 +144,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.color-input-container:focus-within .color-picker-hash::after {
|
.color-input-container:focus-within .color-picker-hash::after {
|
||||||
background: var(--input-background-color);
|
background: var(--input-bg-color);
|
||||||
|
|
||||||
:root[dir="ltr"] & {
|
:root[dir="ltr"] & {
|
||||||
right: -2px;
|
right: -2px;
|
||||||
@ -163,19 +163,19 @@
|
|||||||
width: 12ch; /* length of `transparent` + 1 */
|
width: 12ch; /* length of `transparent` + 1 */
|
||||||
margin: 0;
|
margin: 0;
|
||||||
font-size: 1rem;
|
font-size: 1rem;
|
||||||
background-color: var(--input-background-color);
|
background-color: var(--input-bg-color);
|
||||||
color: var(--text-color-primary);
|
color: var(--text-primary-color);
|
||||||
border: 0px;
|
border: 0;
|
||||||
outline: none;
|
outline: none;
|
||||||
height: 1.75em;
|
height: 1.75em;
|
||||||
box-shadow: var(--input-border-color) 0px 0px 0px 1px inset;
|
box-shadow: var(--input-border-color) 0 0 0 1px inset;
|
||||||
|
|
||||||
:root[dir="ltr"] & {
|
:root[dir="ltr"] & {
|
||||||
border-radius: 0px 4px 4px 0px;
|
border-radius: 0 4px 4px 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
:root[dir="rtl"] & {
|
:root[dir="rtl"] & {
|
||||||
border-radius: 4px 0px 0px 4px;
|
border-radius: 4px 0 0 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
float: left;
|
float: left;
|
||||||
@ -192,7 +192,7 @@
|
|||||||
position: relative;
|
position: relative;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
background-color: transparent !important;
|
background-color: transparent !important;
|
||||||
filter: var(--appearance-filter);
|
filter: var(--theme-filter);
|
||||||
|
|
||||||
&:after {
|
&:after {
|
||||||
content: "";
|
content: "";
|
||||||
@ -228,7 +228,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.color-picker-type-elementBackground .color-picker-keybinding {
|
.color-picker-type-elementBackground .color-picker-keybinding {
|
||||||
color: #fff;
|
color: $oc-white;
|
||||||
}
|
}
|
||||||
|
|
||||||
.color-picker-swatch[aria-label="transparent"] .color-picker-keybinding {
|
.color-picker-swatch[aria-label="transparent"] .color-picker-keybinding {
|
||||||
@ -239,12 +239,12 @@
|
|||||||
color: #d4d4d4;
|
color: #d4d4d4;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.Appearance_dark {
|
&.theme--dark {
|
||||||
.color-picker-type-elementBackground .color-picker-keybinding {
|
.color-picker-type-elementBackground .color-picker-keybinding {
|
||||||
color: #000;
|
color: $oc-black;
|
||||||
}
|
}
|
||||||
.color-picker-swatch[aria-label="transparent"] .color-picker-keybinding {
|
.color-picker-swatch[aria-label="transparent"] .color-picker-keybinding {
|
||||||
color: #000;
|
color: $oc-black;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,13 +4,13 @@
|
|||||||
.context-menu {
|
.context-menu {
|
||||||
position: relative;
|
position: relative;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
box-shadow: 0px 3px 10px transparentize($oc-black, 0.8);
|
box-shadow: 0 3px 10px transparentize($oc-black, 0.8);
|
||||||
padding: 0;
|
padding: 0;
|
||||||
list-style: none;
|
list-style: none;
|
||||||
user-select: none;
|
user-select: none;
|
||||||
margin: -0.25rem 0 0 0.125rem;
|
margin: -0.25rem 0 0 0.125rem;
|
||||||
padding: 0.5rem 0;
|
padding: 0.5rem 0;
|
||||||
background-color: var(--popup-secondary-background-color);
|
background-color: var(--popup-secondary-bg-color);
|
||||||
border: 1px solid var(--button-gray-3);
|
border: 1px solid var(--button-gray-3);
|
||||||
cursor: default;
|
cursor: default;
|
||||||
}
|
}
|
||||||
@ -61,12 +61,12 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.context-menu-option:hover {
|
.context-menu-option:hover {
|
||||||
color: var(--popup-background-color);
|
color: var(--popup-bg-color);
|
||||||
background-color: var(--select-highlight-color);
|
background-color: var(--select-highlight-color);
|
||||||
|
|
||||||
&.dangerous {
|
&.dangerous {
|
||||||
.context-menu-option__label {
|
.context-menu-option__label {
|
||||||
color: var(--popup-background-color);
|
color: var(--popup-bg-color);
|
||||||
}
|
}
|
||||||
background-color: $oc-red-6;
|
background-color: $oc-red-6;
|
||||||
}
|
}
|
||||||
|
@ -34,11 +34,11 @@ const ContextMenu = ({
|
|||||||
}: ContextMenuProps) => {
|
}: ContextMenuProps) => {
|
||||||
const isDarkTheme = !!document
|
const isDarkTheme = !!document
|
||||||
.querySelector(".excalidraw")
|
.querySelector(".excalidraw")
|
||||||
?.classList.contains("Appearance_dark");
|
?.classList.contains("theme--dark");
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={clsx("excalidraw", {
|
className={clsx("excalidraw", {
|
||||||
"Appearance_dark Appearance_dark-background-none": isDarkTheme,
|
"theme--dark theme--dark-background-none": isDarkTheme,
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
<Popover
|
<Popover
|
||||||
|
@ -10,13 +10,19 @@ export type Appearence = "light" | "dark";
|
|||||||
export const DarkModeToggle = (props: {
|
export const DarkModeToggle = (props: {
|
||||||
value: Appearence;
|
value: Appearence;
|
||||||
onChange: (value: Appearence) => void;
|
onChange: (value: Appearence) => void;
|
||||||
|
title?: string;
|
||||||
}) => {
|
}) => {
|
||||||
|
const title = props.title
|
||||||
|
? props.title
|
||||||
|
: props.value === "dark"
|
||||||
|
? t("buttons.lightMode")
|
||||||
|
: t("buttons.darkMode");
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<label
|
<label
|
||||||
className={`ToolIcon ToolIcon_type_floating ToolIcon_size_M`}
|
className="ToolIcon ToolIcon_type_floating ToolIcon_size_M"
|
||||||
title={
|
data-testid="toggle-dark-mode"
|
||||||
props.value === "dark" ? t("buttons.lightMode") : t("buttons.darkMode")
|
title={title}
|
||||||
}
|
|
||||||
>
|
>
|
||||||
<input
|
<input
|
||||||
className="ToolIcon_type_checkbox ToolIcon_toggle_opaque"
|
className="ToolIcon_type_checkbox ToolIcon_toggle_opaque"
|
||||||
@ -25,11 +31,7 @@ export const DarkModeToggle = (props: {
|
|||||||
props.onChange(event.target.checked ? "dark" : "light")
|
props.onChange(event.target.checked ? "dark" : "light")
|
||||||
}
|
}
|
||||||
checked={props.value === "dark"}
|
checked={props.value === "dark"}
|
||||||
aria-label={
|
aria-label={title}
|
||||||
props.value === "dark"
|
|
||||||
? t("buttons.lightMode")
|
|
||||||
: t("buttons.darkMode")
|
|
||||||
}
|
|
||||||
/>
|
/>
|
||||||
<div className="ToolIcon__icon">
|
<div className="ToolIcon__icon">
|
||||||
{props.value === "light" ? ICONS.MOON : ICONS.SUN}
|
{props.value === "light" ? ICONS.MOON : ICONS.SUN}
|
||||||
|
@ -45,7 +45,7 @@
|
|||||||
position: sticky;
|
position: sticky;
|
||||||
top: 0;
|
top: 0;
|
||||||
padding: calc(var(--space-factor) * 2);
|
padding: calc(var(--space-factor) * 2);
|
||||||
background: var(--bg-color-island);
|
background: var(--island-bg-color);
|
||||||
font-size: 1.25em;
|
font-size: 1.25em;
|
||||||
|
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
|
@ -16,7 +16,7 @@
|
|||||||
max-height: 25rem;
|
max-height: 25rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.Appearance_dark .ExportDialog__preview canvas {
|
&.theme--dark .ExportDialog__preview canvas {
|
||||||
filter: none;
|
filter: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -34,6 +34,17 @@
|
|||||||
|
|
||||||
.TextInput {
|
.TextInput {
|
||||||
height: calc(1rem - 3px);
|
height: calc(1rem - 3px);
|
||||||
|
width: 200px;
|
||||||
|
overflow: hidden;
|
||||||
|
text-align: center;
|
||||||
|
|
||||||
|
&--readonly {
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
&:hover {
|
||||||
|
background: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -19,6 +19,9 @@ import { ToolButton } from "./ToolButton";
|
|||||||
const scales = [1, 2, 3];
|
const scales = [1, 2, 3];
|
||||||
const defaultScale = scales.includes(devicePixelRatio) ? devicePixelRatio : 1;
|
const defaultScale = scales.includes(devicePixelRatio) ? devicePixelRatio : 1;
|
||||||
|
|
||||||
|
const supportsContextFilters =
|
||||||
|
"filter" in document.createElement("canvas").getContext("2d")!;
|
||||||
|
|
||||||
export const ErrorCanvasPreview = () => {
|
export const ErrorCanvasPreview = () => {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
@ -128,6 +131,8 @@ const ExportModal = ({
|
|||||||
return (
|
return (
|
||||||
<div className="ExportDialog">
|
<div className="ExportDialog">
|
||||||
<div className="ExportDialog__preview" ref={previewRef} />
|
<div className="ExportDialog__preview" ref={previewRef} />
|
||||||
|
{supportsContextFilters &&
|
||||||
|
actionManager.renderAction("exportWithDarkMode")}
|
||||||
<Stack.Col gap={2} align="center">
|
<Stack.Col gap={2} align="center">
|
||||||
<div className="ExportDialog__actions">
|
<div className="ExportDialog__actions">
|
||||||
<Stack.Row gap={2}>
|
<Stack.Row gap={2}>
|
||||||
@ -198,6 +203,7 @@ const ExportModal = ({
|
|||||||
</Stack.Row>
|
</Stack.Row>
|
||||||
</div>
|
</div>
|
||||||
{actionManager.renderAction("changeExportBackground")}
|
{actionManager.renderAction("changeExportBackground")}
|
||||||
|
{actionManager.renderAction("toggleAutoSave")}
|
||||||
{someElementIsSelected && (
|
{someElementIsSelected && (
|
||||||
<div>
|
<div>
|
||||||
<label>
|
<label>
|
||||||
@ -252,6 +258,7 @@ export const ExportDialog = ({
|
|||||||
onClick={() => {
|
onClick={() => {
|
||||||
setModalIsShown(true);
|
setModalIsShown(true);
|
||||||
}}
|
}}
|
||||||
|
data-testid="export-button"
|
||||||
icon={exportFile}
|
icon={exportFile}
|
||||||
type="button"
|
type="button"
|
||||||
aria-label={t("buttons.export")}
|
aria-label={t("buttons.export")}
|
||||||
|
@ -3,7 +3,7 @@ import React from "react";
|
|||||||
|
|
||||||
// https://github.com/tholman/github-corners
|
// https://github.com/tholman/github-corners
|
||||||
export const GitHubCorner = React.memo(
|
export const GitHubCorner = React.memo(
|
||||||
({ appearance }: { appearance: "light" | "dark" }) => (
|
({ theme }: { theme: "light" | "dark" }) => (
|
||||||
<svg
|
<svg
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
width="40"
|
width="40"
|
||||||
@ -19,18 +19,18 @@ export const GitHubCorner = React.memo(
|
|||||||
>
|
>
|
||||||
<path
|
<path
|
||||||
d="M0 0l115 115h15l12 27 108 108V0z"
|
d="M0 0l115 115h15l12 27 108 108V0z"
|
||||||
fill={appearance === "light" ? oc.gray[6] : oc.gray[8]}
|
fill={theme === "light" ? oc.gray[6] : oc.gray[8]}
|
||||||
/>
|
/>
|
||||||
<path
|
<path
|
||||||
className="octo-arm"
|
className="octo-arm"
|
||||||
d="M128 109c-15-9-9-19-9-19 3-7 2-11 2-11-1-7 3-2 3-2 4 5 2 11 2 11-3 10 5 15 9 16"
|
d="M128 109c-15-9-9-19-9-19 3-7 2-11 2-11-1-7 3-2 3-2 4 5 2 11 2 11-3 10 5 15 9 16"
|
||||||
style={{ transformOrigin: "130px 106px" }}
|
style={{ transformOrigin: "130px 106px" }}
|
||||||
fill={appearance === "light" ? oc.white : oc.black}
|
fill={theme === "light" ? oc.white : oc.black}
|
||||||
/>
|
/>
|
||||||
<path
|
<path
|
||||||
className="octo-body"
|
className="octo-body"
|
||||||
d="M115 115s4 2 5 0l14-14c3-2 6-3 8-3-8-11-15-24 2-41 5-5 10-7 16-7 1-2 3-7 12-11 0 0 5 3 7 16 4 2 8 5 12 9s7 8 9 12c14 3 17 7 17 7-4 8-9 11-11 11 0 6-2 11-7 16-16 16-30 10-41 2 0 3-1 7-5 11l-12 11c-1 1 1 5 1 5z"
|
d="M115 115s4 2 5 0l14-14c3-2 6-3 8-3-8-11-15-24 2-41 5-5 10-7 16-7 1-2 3-7 12-11 0 0 5 3 7 16 4 2 8 5 12 9s7 8 9 12c14 3 17 7 17 7-4 8-9 11-11 11 0 6-2 11-7 16-16 16-30 10-41 2 0 3-1 7-5 11l-12 11c-1 1 1 5 1 5z"
|
||||||
fill={appearance === "light" ? oc.white : oc.black}
|
fill={theme === "light" ? oc.white : oc.black}
|
||||||
/>
|
/>
|
||||||
</a>
|
</a>
|
||||||
</svg>
|
</svg>
|
||||||
|
@ -26,7 +26,7 @@ $wide-viewport-width: 1000px;
|
|||||||
|
|
||||||
> span {
|
> span {
|
||||||
padding: 0.2rem 0.4rem;
|
padding: 0.2rem 0.4rem;
|
||||||
background-color: var(--overlay-background-color);
|
background-color: var(--overlay-bg-color);
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -8,9 +8,9 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.picker {
|
.picker {
|
||||||
background: var(--popup-background-color);
|
background: var(--popup-bg-color);
|
||||||
border: 0px solid transparentize($oc-white, 0.75);
|
border: 0 solid transparentize($oc-white, 0.75);
|
||||||
box-shadow: transparentize($oc-black, 0.75) 0px 1px 4px;
|
box-shadow: transparentize($oc-black, 0.75) 0 1px 4px;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
}
|
}
|
||||||
@ -56,8 +56,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.picker-triangle {
|
.picker-triangle {
|
||||||
width: 0px;
|
width: 0;
|
||||||
height: 0px;
|
height: 0;
|
||||||
position: relative;
|
position: relative;
|
||||||
top: -10px;
|
top: -10px;
|
||||||
:root[dir="ltr"] & {
|
:root[dir="ltr"] & {
|
||||||
@ -73,7 +73,7 @@
|
|||||||
content: "";
|
content: "";
|
||||||
position: absolute;
|
position: absolute;
|
||||||
border-style: solid;
|
border-style: solid;
|
||||||
border-width: 0px 9px 10px;
|
border-width: 0 9px 10px;
|
||||||
border-color: transparent transparent transparentize($oc-black, 0.9);
|
border-color: transparent transparent transparentize($oc-black, 0.9);
|
||||||
top: -1px;
|
top: -1px;
|
||||||
}
|
}
|
||||||
@ -82,8 +82,8 @@
|
|||||||
content: "";
|
content: "";
|
||||||
position: absolute;
|
position: absolute;
|
||||||
border-style: solid;
|
border-style: solid;
|
||||||
border-width: 0px 9px 10px;
|
border-width: 0 9px 10px;
|
||||||
border-color: transparent transparent var(--popup-background-color);
|
border-color: transparent transparent var(--popup-bg-color);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -121,7 +121,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.picker-type-elementBackground .picker-keybinding {
|
.picker-type-elementBackground .picker-keybinding {
|
||||||
color: #fff;
|
color: $oc-white;
|
||||||
}
|
}
|
||||||
|
|
||||||
.picker-swatch[aria-label="transparent"] .picker-keybinding {
|
.picker-swatch[aria-label="transparent"] .picker-keybinding {
|
||||||
@ -132,12 +132,12 @@
|
|||||||
color: #d4d4d4;
|
color: #d4d4d4;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.Appearance_dark {
|
&.theme--dark {
|
||||||
.picker-type-elementBackground .picker-keybinding {
|
.picker-type-elementBackground .picker-keybinding {
|
||||||
color: #000;
|
color: $oc-black;
|
||||||
}
|
}
|
||||||
.picker-swatch[aria-label="transparent"] .picker-keybinding {
|
.picker-swatch[aria-label="transparent"] .picker-keybinding {
|
||||||
color: #000;
|
color: $oc-black;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
.excalidraw {
|
.excalidraw {
|
||||||
.Island {
|
.Island {
|
||||||
--padding: 0;
|
--padding: 0;
|
||||||
background-color: var(--bg-color-island);
|
background-color: var(--island-bg-color);
|
||||||
backdrop-filter: saturate(100%) blur(10px);
|
backdrop-filter: saturate(100%) blur(10px);
|
||||||
box-shadow: var(--shadow-island);
|
box-shadow: var(--shadow-island);
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
|
@ -73,7 +73,7 @@
|
|||||||
&__footer {
|
&__footer {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
z-index: 100;
|
z-index: 100;
|
||||||
bottom: 0px;
|
bottom: 0;
|
||||||
|
|
||||||
:root[dir="ltr"] & {
|
:root[dir="ltr"] & {
|
||||||
right: 0;
|
right: 0;
|
||||||
@ -94,7 +94,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
:root[dir="ltr"] &.transition-right {
|
:root[dir="ltr"] &.transition-right {
|
||||||
transform: translate(999px, 0px);
|
transform: translate(999px, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
:root[dir="rtl"] &.transition-left {
|
:root[dir="rtl"] &.transition-left {
|
||||||
|
@ -17,7 +17,7 @@ import { Language, t } from "../i18n";
|
|||||||
import useIsMobile from "../is-mobile";
|
import useIsMobile from "../is-mobile";
|
||||||
import { calculateScrollCenter, getSelectedElements } from "../scene";
|
import { calculateScrollCenter, getSelectedElements } from "../scene";
|
||||||
import { ExportType } from "../scene/types";
|
import { ExportType } from "../scene/types";
|
||||||
import { AppState, LibraryItem, LibraryItems } from "../types";
|
import { AppState, ExcalidrawProps, LibraryItem, LibraryItems } from "../types";
|
||||||
import { muteFSAbortError } from "../utils";
|
import { muteFSAbortError } from "../utils";
|
||||||
import { SelectedShapeActions, ShapesSwitcher, ZoomActions } from "./Actions";
|
import { SelectedShapeActions, ShapesSwitcher, ZoomActions } from "./Actions";
|
||||||
import { BackgroundPickerAndDarkModeToggle } from "./BackgroundPickerAndDarkModeToggle";
|
import { BackgroundPickerAndDarkModeToggle } from "./BackgroundPickerAndDarkModeToggle";
|
||||||
@ -53,6 +53,7 @@ interface LayerUIProps {
|
|||||||
onInsertElements: (elements: readonly NonDeletedExcalidrawElement[]) => void;
|
onInsertElements: (elements: readonly NonDeletedExcalidrawElement[]) => void;
|
||||||
zenModeEnabled: boolean;
|
zenModeEnabled: boolean;
|
||||||
showExitZenModeBtn: boolean;
|
showExitZenModeBtn: boolean;
|
||||||
|
showThemeBtn: boolean;
|
||||||
toggleZenMode: () => void;
|
toggleZenMode: () => void;
|
||||||
langCode: Language["code"];
|
langCode: Language["code"];
|
||||||
isCollaborating: boolean;
|
isCollaborating: boolean;
|
||||||
@ -63,6 +64,7 @@ interface LayerUIProps {
|
|||||||
) => void;
|
) => void;
|
||||||
renderCustomFooter?: (isMobile: boolean) => JSX.Element;
|
renderCustomFooter?: (isMobile: boolean) => JSX.Element;
|
||||||
viewModeEnabled: boolean;
|
viewModeEnabled: boolean;
|
||||||
|
libraryReturnUrl: ExcalidrawProps["libraryReturnUrl"];
|
||||||
}
|
}
|
||||||
|
|
||||||
const useOnClickOutside = (
|
const useOnClickOutside = (
|
||||||
@ -101,6 +103,7 @@ const LibraryMenuItems = ({
|
|||||||
pendingElements,
|
pendingElements,
|
||||||
setAppState,
|
setAppState,
|
||||||
setLibraryItems,
|
setLibraryItems,
|
||||||
|
libraryReturnUrl,
|
||||||
}: {
|
}: {
|
||||||
library: LibraryItems;
|
library: LibraryItems;
|
||||||
pendingElements: LibraryItem;
|
pendingElements: LibraryItem;
|
||||||
@ -109,6 +112,7 @@ const LibraryMenuItems = ({
|
|||||||
onAddToLibrary: (elements: LibraryItem) => void;
|
onAddToLibrary: (elements: LibraryItem) => void;
|
||||||
setAppState: React.Component<any, AppState>["setState"];
|
setAppState: React.Component<any, AppState>["setState"];
|
||||||
setLibraryItems: (library: LibraryItems) => void;
|
setLibraryItems: (library: LibraryItems) => void;
|
||||||
|
libraryReturnUrl: ExcalidrawProps["libraryReturnUrl"];
|
||||||
}) => {
|
}) => {
|
||||||
const isMobile = useIsMobile();
|
const isMobile = useIsMobile();
|
||||||
const numCells = library.length + (pendingElements.length > 0 ? 1 : 0);
|
const numCells = library.length + (pendingElements.length > 0 ? 1 : 0);
|
||||||
@ -117,6 +121,8 @@ const LibraryMenuItems = ({
|
|||||||
const rows = [];
|
const rows = [];
|
||||||
let addedPendingElements = false;
|
let addedPendingElements = false;
|
||||||
|
|
||||||
|
const referrer = libraryReturnUrl || window.location.origin;
|
||||||
|
|
||||||
rows.push(
|
rows.push(
|
||||||
<div className="layer-ui__library-header">
|
<div className="layer-ui__library-header">
|
||||||
<ToolButton
|
<ToolButton
|
||||||
@ -166,7 +172,10 @@ const LibraryMenuItems = ({
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<a href="https://libraries.excalidraw.com" target="_excalidraw_libraries">
|
<a
|
||||||
|
href={`https://libraries.excalidraw.com?referrer=${referrer}`}
|
||||||
|
target="_excalidraw_libraries"
|
||||||
|
>
|
||||||
{t("labels.libraries")}
|
{t("labels.libraries")}
|
||||||
</a>
|
</a>
|
||||||
</div>,
|
</div>,
|
||||||
@ -219,12 +228,14 @@ const LibraryMenu = ({
|
|||||||
pendingElements,
|
pendingElements,
|
||||||
onAddToLibrary,
|
onAddToLibrary,
|
||||||
setAppState,
|
setAppState,
|
||||||
|
libraryReturnUrl,
|
||||||
}: {
|
}: {
|
||||||
pendingElements: LibraryItem;
|
pendingElements: LibraryItem;
|
||||||
onClickOutside: (event: MouseEvent) => void;
|
onClickOutside: (event: MouseEvent) => void;
|
||||||
onInsertShape: (elements: LibraryItem) => void;
|
onInsertShape: (elements: LibraryItem) => void;
|
||||||
onAddToLibrary: () => void;
|
onAddToLibrary: () => void;
|
||||||
setAppState: React.Component<any, AppState>["setState"];
|
setAppState: React.Component<any, AppState>["setState"];
|
||||||
|
libraryReturnUrl: ExcalidrawProps["libraryReturnUrl"];
|
||||||
}) => {
|
}) => {
|
||||||
const ref = useRef<HTMLDivElement | null>(null);
|
const ref = useRef<HTMLDivElement | null>(null);
|
||||||
useOnClickOutside(ref, (event) => {
|
useOnClickOutside(ref, (event) => {
|
||||||
@ -297,6 +308,7 @@ const LibraryMenu = ({
|
|||||||
pendingElements={pendingElements}
|
pendingElements={pendingElements}
|
||||||
setAppState={setAppState}
|
setAppState={setAppState}
|
||||||
setLibraryItems={setLibraryItems}
|
setLibraryItems={setLibraryItems}
|
||||||
|
libraryReturnUrl={libraryReturnUrl}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</Island>
|
</Island>
|
||||||
@ -314,11 +326,13 @@ const LayerUI = ({
|
|||||||
onInsertElements,
|
onInsertElements,
|
||||||
zenModeEnabled,
|
zenModeEnabled,
|
||||||
showExitZenModeBtn,
|
showExitZenModeBtn,
|
||||||
|
showThemeBtn,
|
||||||
toggleZenMode,
|
toggleZenMode,
|
||||||
isCollaborating,
|
isCollaborating,
|
||||||
onExportToBackend,
|
onExportToBackend,
|
||||||
renderCustomFooter,
|
renderCustomFooter,
|
||||||
viewModeEnabled,
|
viewModeEnabled,
|
||||||
|
libraryReturnUrl,
|
||||||
}: LayerUIProps) => {
|
}: LayerUIProps) => {
|
||||||
const isMobile = useIsMobile();
|
const isMobile = useIsMobile();
|
||||||
|
|
||||||
@ -429,6 +443,7 @@ const LayerUI = ({
|
|||||||
actionManager={actionManager}
|
actionManager={actionManager}
|
||||||
appState={appState}
|
appState={appState}
|
||||||
setAppState={setAppState}
|
setAppState={setAppState}
|
||||||
|
showThemeBtn={showThemeBtn}
|
||||||
/>
|
/>
|
||||||
</Stack.Col>
|
</Stack.Col>
|
||||||
</Island>
|
</Island>
|
||||||
@ -482,6 +497,7 @@ const LayerUI = ({
|
|||||||
onInsertShape={onInsertElements}
|
onInsertShape={onInsertElements}
|
||||||
onAddToLibrary={deselectItems}
|
onAddToLibrary={deselectItems}
|
||||||
setAppState={setAppState}
|
setAppState={setAppState}
|
||||||
|
libraryReturnUrl={libraryReturnUrl}
|
||||||
/>
|
/>
|
||||||
) : null;
|
) : null;
|
||||||
|
|
||||||
@ -516,6 +532,7 @@ const LayerUI = ({
|
|||||||
{heading}
|
{heading}
|
||||||
<Stack.Row gap={1}>
|
<Stack.Row gap={1}>
|
||||||
<ShapesSwitcher
|
<ShapesSwitcher
|
||||||
|
canvas={canvas}
|
||||||
elementType={appState.elementType}
|
elementType={appState.elementType}
|
||||||
setAppState={setAppState}
|
setAppState={setAppState}
|
||||||
isLibraryOpen={appState.isLibraryOpen}
|
isLibraryOpen={appState.isLibraryOpen}
|
||||||
@ -589,7 +606,7 @@ const LayerUI = ({
|
|||||||
},
|
},
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<GitHubCorner appearance={appState.appearance} />
|
<GitHubCorner theme={appState.theme} />
|
||||||
</aside>
|
</aside>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@ -657,6 +674,7 @@ const LayerUI = ({
|
|||||||
isCollaborating={isCollaborating}
|
isCollaborating={isCollaborating}
|
||||||
renderCustomFooter={renderCustomFooter}
|
renderCustomFooter={renderCustomFooter}
|
||||||
viewModeEnabled={viewModeEnabled}
|
viewModeEnabled={viewModeEnabled}
|
||||||
|
showThemeBtn={showThemeBtn}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
@ -703,6 +721,7 @@ const areEqual = (prev: LayerUIProps, next: LayerUIProps) => {
|
|||||||
|
|
||||||
const keys = Object.keys(prevAppState) as (keyof Partial<AppState>)[];
|
const keys = Object.keys(prevAppState) as (keyof Partial<AppState>)[];
|
||||||
return (
|
return (
|
||||||
|
prev.renderCustomFooter === next.renderCustomFooter &&
|
||||||
prev.langCode === next.langCode &&
|
prev.langCode === next.langCode &&
|
||||||
prev.elements === next.elements &&
|
prev.elements === next.elements &&
|
||||||
keys.every((key) => prevAppState[key] === nextAppState[key])
|
keys.every((key) => prevAppState[key] === nextAppState[key])
|
||||||
|
@ -16,7 +16,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.library-unit__dragger > svg {
|
.library-unit__dragger > svg {
|
||||||
filter: var(--appearance-filter);
|
filter: var(--theme-filter);
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
max-height: 100%;
|
max-height: 100%;
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
|
@ -30,6 +30,7 @@ type MobileMenuProps = {
|
|||||||
isCollaborating: boolean;
|
isCollaborating: boolean;
|
||||||
renderCustomFooter?: (isMobile: boolean) => JSX.Element;
|
renderCustomFooter?: (isMobile: boolean) => JSX.Element;
|
||||||
viewModeEnabled: boolean;
|
viewModeEnabled: boolean;
|
||||||
|
showThemeBtn: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const MobileMenu = ({
|
export const MobileMenu = ({
|
||||||
@ -45,6 +46,7 @@ export const MobileMenu = ({
|
|||||||
isCollaborating,
|
isCollaborating,
|
||||||
renderCustomFooter,
|
renderCustomFooter,
|
||||||
viewModeEnabled,
|
viewModeEnabled,
|
||||||
|
showThemeBtn,
|
||||||
}: MobileMenuProps) => {
|
}: MobileMenuProps) => {
|
||||||
const renderToolbar = () => {
|
const renderToolbar = () => {
|
||||||
return (
|
return (
|
||||||
@ -57,6 +59,7 @@ export const MobileMenu = ({
|
|||||||
{heading}
|
{heading}
|
||||||
<Stack.Row gap={1}>
|
<Stack.Row gap={1}>
|
||||||
<ShapesSwitcher
|
<ShapesSwitcher
|
||||||
|
canvas={canvas}
|
||||||
elementType={appState.elementType}
|
elementType={appState.elementType}
|
||||||
setAppState={setAppState}
|
setAppState={setAppState}
|
||||||
isLibraryOpen={appState.isLibraryOpen}
|
isLibraryOpen={appState.isLibraryOpen}
|
||||||
@ -129,6 +132,7 @@ export const MobileMenu = ({
|
|||||||
actionManager={actionManager}
|
actionManager={actionManager}
|
||||||
appState={appState}
|
appState={appState}
|
||||||
setAppState={setAppState}
|
setAppState={setAppState}
|
||||||
|
showThemeBtn={showThemeBtn}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
</>
|
</>
|
||||||
|
@ -44,12 +44,13 @@
|
|||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
|
|
||||||
// for modals, reset blurry bg
|
// for modals, reset blurry bg
|
||||||
background: var(--bg-color-island);
|
background: var(--island-bg-color);
|
||||||
backdrop-filter: none;
|
backdrop-filter: none;
|
||||||
|
|
||||||
border: 1px solid var(--dialog-border);
|
border: 1px solid var(--dialog-border-color);
|
||||||
box-shadow: 0 2px 10px transparentize($oc-black, 0.75);
|
box-shadow: 0 2px 10px transparentize($oc-black, 0.75);
|
||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
|
||||||
@media #{$is-mobile-query} {
|
@media #{$is-mobile-query} {
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
|
@ -51,14 +51,14 @@ const useBodyRoot = () => {
|
|||||||
useLayoutEffect(() => {
|
useLayoutEffect(() => {
|
||||||
const isDarkTheme = !!document
|
const isDarkTheme = !!document
|
||||||
.querySelector(".excalidraw")
|
.querySelector(".excalidraw")
|
||||||
?.classList.contains("Appearance_dark");
|
?.classList.contains("theme--dark");
|
||||||
const div = document.createElement("div");
|
const div = document.createElement("div");
|
||||||
|
|
||||||
div.classList.add("excalidraw", "excalidraw-modal-container");
|
div.classList.add("excalidraw", "excalidraw-modal-container");
|
||||||
|
|
||||||
if (isDarkTheme) {
|
if (isDarkTheme) {
|
||||||
div.classList.add("Appearance_dark");
|
div.classList.add("theme--dark");
|
||||||
div.classList.add("Appearance_dark-background-none");
|
div.classList.add("theme--dark-background-none");
|
||||||
}
|
}
|
||||||
document.body.appendChild(div);
|
document.body.appendChild(div);
|
||||||
|
|
||||||
|
@ -7,6 +7,7 @@ type Props = {
|
|||||||
value: string;
|
value: string;
|
||||||
onChange: (value: string) => void;
|
onChange: (value: string) => void;
|
||||||
label: string;
|
label: string;
|
||||||
|
isNameEditable: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export class ProjectName extends Component<Props> {
|
export class ProjectName extends Component<Props> {
|
||||||
@ -43,7 +44,7 @@ export class ProjectName extends Component<Props> {
|
|||||||
};
|
};
|
||||||
|
|
||||||
public render() {
|
public render() {
|
||||||
return (
|
return this.props.isNameEditable ? (
|
||||||
<span
|
<span
|
||||||
suppressContentEditableWarning
|
suppressContentEditableWarning
|
||||||
ref={this.makeEditable}
|
ref={this.makeEditable}
|
||||||
@ -57,6 +58,13 @@ export class ProjectName extends Component<Props> {
|
|||||||
>
|
>
|
||||||
{this.props.value}
|
{this.props.value}
|
||||||
</span>
|
</span>
|
||||||
|
) : (
|
||||||
|
<span
|
||||||
|
className="TextInput TextInput--readonly"
|
||||||
|
aria-label={this.props.label}
|
||||||
|
>
|
||||||
|
{this.props.value}
|
||||||
|
</span>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,51 +1,53 @@
|
|||||||
@import "../css/variables.module";
|
@import "../css/variables.module";
|
||||||
|
|
||||||
.Stats {
|
.excalidraw {
|
||||||
position: absolute;
|
.Stats {
|
||||||
top: 64px;
|
position: absolute;
|
||||||
right: 12px;
|
top: 64px;
|
||||||
font-size: 12px;
|
right: 12px;
|
||||||
z-index: 999;
|
font-size: 12px;
|
||||||
|
z-index: 999;
|
||||||
|
|
||||||
h3 {
|
h3 {
|
||||||
margin: 0 24px 8px 0;
|
margin: 0 24px 8px 0;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
.close {
|
.close {
|
||||||
float: right;
|
float: right;
|
||||||
height: 16px;
|
height: 16px;
|
||||||
width: 16px;
|
width: 16px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
svg {
|
svg {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
table {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
th {
|
||||||
|
border-bottom: 1px solid var(--input-border-color);
|
||||||
|
padding: 4px;
|
||||||
|
}
|
||||||
|
tr {
|
||||||
|
td:nth-child(2) {
|
||||||
|
min-width: 24px;
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
table {
|
:root[dir="rtl"] & {
|
||||||
width: 100%;
|
left: 12px;
|
||||||
th {
|
right: initial;
|
||||||
border-bottom: 1px solid var(--input-border-color);
|
|
||||||
padding: 4px;
|
h3 {
|
||||||
}
|
margin: 0 0 8px 24px;
|
||||||
tr {
|
}
|
||||||
td:nth-child(2) {
|
.close {
|
||||||
min-width: 24px;
|
float: left;
|
||||||
text-align: right;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
:root[dir="rtl"] & {
|
|
||||||
left: 12px;
|
|
||||||
right: initial;
|
|
||||||
|
|
||||||
h3 {
|
|
||||||
margin: 0 0 8px 24px;
|
|
||||||
}
|
|
||||||
.close {
|
|
||||||
float: left;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -2,18 +2,18 @@
|
|||||||
|
|
||||||
.excalidraw {
|
.excalidraw {
|
||||||
.TextInput {
|
.TextInput {
|
||||||
color: var(--text-color-primary);
|
color: var(--text-primary-color);
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
border: 1.5px solid var(--button-gray-1);
|
border: 1.5px solid var(--button-gray-1);
|
||||||
line-height: 1;
|
line-height: 1;
|
||||||
padding: 0.75rem;
|
padding: 0.75rem;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
border-radius: var(--space-factor);
|
border-radius: var(--space-factor);
|
||||||
background-color: var(--input-background-color);
|
background-color: var(--input-bg-color);
|
||||||
|
|
||||||
&:not(:focus) {
|
&:not(:focus) {
|
||||||
&:hover {
|
&:hover {
|
||||||
background-color: var(--input-hover-background-color);
|
background-color: var(--input-hover-bg-color);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -19,6 +19,7 @@
|
|||||||
|
|
||||||
.Toast__message {
|
.Toast__message {
|
||||||
color: var(--popup-text-color);
|
color: var(--popup-text-color);
|
||||||
|
white-space: pre-wrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes fade-in {
|
@keyframes fade-in {
|
||||||
|
@ -58,6 +58,7 @@ export const ToolButton = React.forwardRef((props: ToolButtonProps, ref) => {
|
|||||||
"ToolIcon--selected": props.selected,
|
"ToolIcon--selected": props.selected,
|
||||||
},
|
},
|
||||||
)}
|
)}
|
||||||
|
data-testid={props["data-testid"]}
|
||||||
hidden={props.hidden}
|
hidden={props.hidden}
|
||||||
title={props.title}
|
title={props.title}
|
||||||
aria-label={props["aria-label"]}
|
aria-label={props["aria-label"]}
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import * as Sentry from "@sentry/browser";
|
import * as Sentry from "@sentry/browser";
|
||||||
import { resetCursor } from "../utils";
|
|
||||||
import { t } from "../i18n";
|
import { t } from "../i18n";
|
||||||
|
|
||||||
interface TopErrorBoundaryState {
|
interface TopErrorBoundaryState {
|
||||||
@ -24,7 +23,6 @@ export class TopErrorBoundary extends React.Component<
|
|||||||
}
|
}
|
||||||
|
|
||||||
componentDidCatch(error: Error, errorInfo: any) {
|
componentDidCatch(error: Error, errorInfo: any) {
|
||||||
resetCursor();
|
|
||||||
const _localStorage: any = {};
|
const _localStorage: any = {};
|
||||||
for (const [key, value] of Object.entries({ ...localStorage })) {
|
for (const [key, value] of Object.entries({ ...localStorage })) {
|
||||||
try {
|
try {
|
||||||
|
@ -11,12 +11,12 @@ import React from "react";
|
|||||||
import oc from "open-color";
|
import oc from "open-color";
|
||||||
import clsx from "clsx";
|
import clsx from "clsx";
|
||||||
|
|
||||||
const activeElementColor = (appearance: "light" | "dark") =>
|
const activeElementColor = (theme: "light" | "dark") =>
|
||||||
appearance === "light" ? oc.orange[4] : oc.orange[9];
|
theme === "light" ? oc.orange[4] : oc.orange[9];
|
||||||
const iconFillColor = (appearance: "light" | "dark") =>
|
const iconFillColor = (theme: "light" | "dark") =>
|
||||||
appearance === "light" ? oc.black : oc.gray[4];
|
theme === "light" ? oc.black : oc.gray[4];
|
||||||
const handlerColor = (appearance: "light" | "dark") =>
|
const handlerColor = (theme: "light" | "dark") =>
|
||||||
appearance === "light" ? oc.white : "#1e1e1e";
|
theme === "light" ? oc.white : "#1e1e1e";
|
||||||
|
|
||||||
type Opts = {
|
type Opts = {
|
||||||
width?: number;
|
width?: number;
|
||||||
@ -113,6 +113,16 @@ export const questionCircle = createIcon(
|
|||||||
{ mirror: true },
|
{ mirror: true },
|
||||||
);
|
);
|
||||||
|
|
||||||
|
export const share = createIcon(
|
||||||
|
"M18 16.08c-.76 0-1.44.3-1.96.77L8.91 12.7c.05-.23.09-.46.09-.7s-.04-.47-.09-.7l7.05-4.11c.54.5 1.25.81 2.04.81 1.66 0 3-1.34 3-3s-1.34-3-3-3-3 1.34-3 3c0 .24.04.47.09.7L8.04 9.81C7.5 9.31 6.79 9 6 9c-1.66 0-3 1.34-3 3s1.34 3 3 3c.79 0 1.5-.31 2.04-.81l7.12 4.16c-.05.21-.08.43-.08.65 0 1.61 1.31 2.92 2.92 2.92 1.61 0 2.92-1.31 2.92-2.92s-1.31-2.92-2.92-2.92z",
|
||||||
|
{ width: 24, height: 24 },
|
||||||
|
);
|
||||||
|
|
||||||
|
export const shareIOS = createIcon(
|
||||||
|
"M16 5l-1.42 1.42-1.59-1.59V16h-1.98V4.83L9.42 6.42 8 5l4-4 4 4zm4 5v11c0 1.1-.9 2-2 2H6c-1.11 0-2-.9-2-2V10c0-1.11.89-2 2-2h3v2H6v11h12V10h-3V8h3c1.1 0 2 .89 2 2z",
|
||||||
|
{ width: 24, height: 24 },
|
||||||
|
);
|
||||||
|
|
||||||
// Icon imported form Storybook
|
// Icon imported form Storybook
|
||||||
// Storybook is licensed under MIT https://github.com/storybookjs/storybook/blob/next/LICENSE
|
// Storybook is licensed under MIT https://github.com/storybookjs/storybook/blob/next/LICENSE
|
||||||
export const resetZoom = createIcon(
|
export const resetZoom = createIcon(
|
||||||
@ -126,19 +136,19 @@ export const resetZoom = createIcon(
|
|||||||
);
|
);
|
||||||
|
|
||||||
export const BringForwardIcon = React.memo(
|
export const BringForwardIcon = React.memo(
|
||||||
({ appearance }: { appearance: "light" | "dark" }) =>
|
({ theme }: { theme: "light" | "dark" }) =>
|
||||||
createIcon(
|
createIcon(
|
||||||
<>
|
<>
|
||||||
<path
|
<path
|
||||||
d="M22 9.556C22 8.696 21.303 8 20.444 8H16v8H8v4.444C8 21.304 8.697 22 9.556 22h10.888c.86 0 1.556-.697 1.556-1.556V9.556z"
|
d="M22 9.556C22 8.696 21.303 8 20.444 8H16v8H8v4.444C8 21.304 8.697 22 9.556 22h10.888c.86 0 1.556-.697 1.556-1.556V9.556z"
|
||||||
fill={iconFillColor(appearance)}
|
fill={iconFillColor(theme)}
|
||||||
stroke={iconFillColor(appearance)}
|
stroke={iconFillColor(theme)}
|
||||||
strokeWidth="2"
|
strokeWidth="2"
|
||||||
/>
|
/>
|
||||||
<path
|
<path
|
||||||
d="M16 3.556C16 2.696 15.303 2 14.444 2H3.556C2.696 2 2 2.697 2 3.556v10.888C2 15.304 2.697 16 3.556 16h10.888c.86 0 1.556-.697 1.556-1.556V3.556z"
|
d="M16 3.556C16 2.696 15.303 2 14.444 2H3.556C2.696 2 2 2.697 2 3.556v10.888C2 15.304 2.697 16 3.556 16h10.888c.86 0 1.556-.697 1.556-1.556V3.556z"
|
||||||
fill={activeElementColor(appearance)}
|
fill={activeElementColor(theme)}
|
||||||
stroke={activeElementColor(appearance)}
|
stroke={activeElementColor(theme)}
|
||||||
strokeWidth="2"
|
strokeWidth="2"
|
||||||
/>
|
/>
|
||||||
</>,
|
</>,
|
||||||
@ -147,19 +157,19 @@ export const BringForwardIcon = React.memo(
|
|||||||
);
|
);
|
||||||
|
|
||||||
export const SendBackwardIcon = React.memo(
|
export const SendBackwardIcon = React.memo(
|
||||||
({ appearance }: { appearance: "light" | "dark" }) =>
|
({ theme }: { theme: "light" | "dark" }) =>
|
||||||
createIcon(
|
createIcon(
|
||||||
<>
|
<>
|
||||||
<path
|
<path
|
||||||
d="M16 3.556C16 2.696 15.303 2 14.444 2H3.556C2.696 2 2 2.697 2 3.556v10.888C2 15.304 2.697 16 3.556 16h10.888c.86 0 1.556-.697 1.556-1.556V3.556z"
|
d="M16 3.556C16 2.696 15.303 2 14.444 2H3.556C2.696 2 2 2.697 2 3.556v10.888C2 15.304 2.697 16 3.556 16h10.888c.86 0 1.556-.697 1.556-1.556V3.556z"
|
||||||
fill={activeElementColor(appearance)}
|
fill={activeElementColor(theme)}
|
||||||
stroke={activeElementColor(appearance)}
|
stroke={activeElementColor(theme)}
|
||||||
strokeWidth="2"
|
strokeWidth="2"
|
||||||
/>
|
/>
|
||||||
<path
|
<path
|
||||||
d="M22 9.556C22 8.696 21.303 8 20.444 8H9.556C8.696 8 8 8.697 8 9.556v10.888C8 21.304 8.697 22 9.556 22h10.888c.86 0 1.556-.697 1.556-1.556V9.556z"
|
d="M22 9.556C22 8.696 21.303 8 20.444 8H9.556C8.696 8 8 8.697 8 9.556v10.888C8 21.304 8.697 22 9.556 22h10.888c.86 0 1.556-.697 1.556-1.556V9.556z"
|
||||||
fill={iconFillColor(appearance)}
|
fill={iconFillColor(theme)}
|
||||||
stroke={iconFillColor(appearance)}
|
stroke={iconFillColor(theme)}
|
||||||
strokeWidth="2"
|
strokeWidth="2"
|
||||||
/>
|
/>
|
||||||
</>,
|
</>,
|
||||||
@ -168,19 +178,19 @@ export const SendBackwardIcon = React.memo(
|
|||||||
);
|
);
|
||||||
|
|
||||||
export const BringToFrontIcon = React.memo(
|
export const BringToFrontIcon = React.memo(
|
||||||
({ appearance }: { appearance: "light" | "dark" }) =>
|
({ theme }: { theme: "light" | "dark" }) =>
|
||||||
createIcon(
|
createIcon(
|
||||||
<>
|
<>
|
||||||
<path
|
<path
|
||||||
d="M13 21a1 1 0 001 1h7a1 1 0 001-1v-7a1 1 0 00-1-1h-3v5h-5v3zM11 3a1 1 0 00-1-1H3a1 1 0 00-1 1v7a1 1 0 001 1h3V6h5V3z"
|
d="M13 21a1 1 0 001 1h7a1 1 0 001-1v-7a1 1 0 00-1-1h-3v5h-5v3zM11 3a1 1 0 00-1-1H3a1 1 0 00-1 1v7a1 1 0 001 1h3V6h5V3z"
|
||||||
fill={iconFillColor(appearance)}
|
fill={iconFillColor(theme)}
|
||||||
stroke={iconFillColor(appearance)}
|
stroke={iconFillColor(theme)}
|
||||||
strokeWidth="2"
|
strokeWidth="2"
|
||||||
/>
|
/>
|
||||||
<path
|
<path
|
||||||
d="M18 7.333C18 6.597 17.403 6 16.667 6H7.333C6.597 6 6 6.597 6 7.333v9.334C6 17.403 6.597 18 7.333 18h9.334c.736 0 1.333-.597 1.333-1.333V7.333z"
|
d="M18 7.333C18 6.597 17.403 6 16.667 6H7.333C6.597 6 6 6.597 6 7.333v9.334C6 17.403 6.597 18 7.333 18h9.334c.736 0 1.333-.597 1.333-1.333V7.333z"
|
||||||
fill={activeElementColor(appearance)}
|
fill={activeElementColor(theme)}
|
||||||
stroke={activeElementColor(appearance)}
|
stroke={activeElementColor(theme)}
|
||||||
strokeWidth="2"
|
strokeWidth="2"
|
||||||
/>
|
/>
|
||||||
</>,
|
</>,
|
||||||
@ -189,20 +199,20 @@ export const BringToFrontIcon = React.memo(
|
|||||||
);
|
);
|
||||||
|
|
||||||
export const SendToBackIcon = React.memo(
|
export const SendToBackIcon = React.memo(
|
||||||
({ appearance }: { appearance: "light" | "dark" }) =>
|
({ theme }: { theme: "light" | "dark" }) =>
|
||||||
createIcon(
|
createIcon(
|
||||||
<>
|
<>
|
||||||
<path
|
<path
|
||||||
d="M18 7.333C18 6.597 17.403 6 16.667 6H7.333C6.597 6 6 6.597 6 7.333v9.334C6 17.403 6.597 18 7.333 18h9.334c.736 0 1.333-.597 1.333-1.333V7.333z"
|
d="M18 7.333C18 6.597 17.403 6 16.667 6H7.333C6.597 6 6 6.597 6 7.333v9.334C6 17.403 6.597 18 7.333 18h9.334c.736 0 1.333-.597 1.333-1.333V7.333z"
|
||||||
fill={activeElementColor(appearance)}
|
fill={activeElementColor(theme)}
|
||||||
stroke={activeElementColor(appearance)}
|
stroke={activeElementColor(theme)}
|
||||||
strokeLinejoin="round"
|
strokeLinejoin="round"
|
||||||
strokeWidth="2"
|
strokeWidth="2"
|
||||||
/>
|
/>
|
||||||
<path
|
<path
|
||||||
d="M11 3a1 1 0 00-1-1H3a1 1 0 00-1 1v7a1 1 0 001 1h8V3zM22 14a1 1 0 00-1-1h-7a1 1 0 00-1 1v7a1 1 0 001 1h8v-8z"
|
d="M11 3a1 1 0 00-1-1H3a1 1 0 00-1 1v7a1 1 0 001 1h8V3zM22 14a1 1 0 00-1-1h-7a1 1 0 00-1 1v7a1 1 0 001 1h8v-8z"
|
||||||
fill={iconFillColor(appearance)}
|
fill={iconFillColor(theme)}
|
||||||
stroke={iconFillColor(appearance)}
|
stroke={iconFillColor(theme)}
|
||||||
strokeLinejoin="round"
|
strokeLinejoin="round"
|
||||||
strokeWidth="2"
|
strokeWidth="2"
|
||||||
/>
|
/>
|
||||||
@ -218,20 +228,20 @@ export const SendToBackIcon = React.memo(
|
|||||||
// that would make them lie about their function.
|
// that would make them lie about their function.
|
||||||
//
|
//
|
||||||
export const AlignTopIcon = React.memo(
|
export const AlignTopIcon = React.memo(
|
||||||
({ appearance }: { appearance: "light" | "dark" }) =>
|
({ theme }: { theme: "light" | "dark" }) =>
|
||||||
createIcon(
|
createIcon(
|
||||||
<>
|
<>
|
||||||
<path
|
<path
|
||||||
d="M 2,5 H 22"
|
d="M 2,5 H 22"
|
||||||
fill={iconFillColor(appearance)}
|
fill={iconFillColor(theme)}
|
||||||
stroke={iconFillColor(appearance)}
|
stroke={iconFillColor(theme)}
|
||||||
strokeWidth="2"
|
strokeWidth="2"
|
||||||
strokeLinecap="round"
|
strokeLinecap="round"
|
||||||
/>
|
/>
|
||||||
<path
|
<path
|
||||||
d="M 6,7 C 5.446,7 5,7.446 5,8 v 9.999992 c 0,0.554 0.446,1 1,1 h 3.0000001 c 0.554,0 0.9999999,-0.446 0.9999999,-1 V 8 C 10,7.446 9.5540001,7 9.0000001,7 Z m 9,0 c -0.554,0 -1,0.446 -1,1 v 5.999992 c 0,0.554 0.446,1 1,1 h 3 c 0.554,0 1,-0.446 1,-1 V 8 C 19,7.446 18.554,7 18,7 Z"
|
d="M 6,7 C 5.446,7 5,7.446 5,8 v 9.999992 c 0,0.554 0.446,1 1,1 h 3.0000001 c 0.554,0 0.9999999,-0.446 0.9999999,-1 V 8 C 10,7.446 9.5540001,7 9.0000001,7 Z m 9,0 c -0.554,0 -1,0.446 -1,1 v 5.999992 c 0,0.554 0.446,1 1,1 h 3 c 0.554,0 1,-0.446 1,-1 V 8 C 19,7.446 18.554,7 18,7 Z"
|
||||||
fill={activeElementColor(appearance)}
|
fill={activeElementColor(theme)}
|
||||||
stroke={activeElementColor(appearance)}
|
stroke={activeElementColor(theme)}
|
||||||
strokeWidth="2"
|
strokeWidth="2"
|
||||||
/>
|
/>
|
||||||
</>,
|
</>,
|
||||||
@ -240,20 +250,20 @@ export const AlignTopIcon = React.memo(
|
|||||||
);
|
);
|
||||||
|
|
||||||
export const AlignBottomIcon = React.memo(
|
export const AlignBottomIcon = React.memo(
|
||||||
({ appearance }: { appearance: "light" | "dark" }) =>
|
({ theme }: { theme: "light" | "dark" }) =>
|
||||||
createIcon(
|
createIcon(
|
||||||
<>
|
<>
|
||||||
<path
|
<path
|
||||||
d="M 2,19 H 22"
|
d="M 2,19 H 22"
|
||||||
fill={iconFillColor(appearance)}
|
fill={iconFillColor(theme)}
|
||||||
stroke={iconFillColor(appearance)}
|
stroke={iconFillColor(theme)}
|
||||||
strokeWidth="2"
|
strokeWidth="2"
|
||||||
strokeLinecap="round"
|
strokeLinecap="round"
|
||||||
/>
|
/>
|
||||||
<path
|
<path
|
||||||
d="m 6,16.999992 c -0.554,0 -1,-0.446 -1,-1 V 6 C 5,5.446 5.446,5 6,5 H 9.0000001 C 9.5540001,5 10,5.446 10,6 v 9.999992 c 0,0.554 -0.4459999,1 -0.9999999,1 z m 9,0 c -0.554,0 -1,-0.446 -1,-1 V 10 c 0,-0.554 0.446,-1 1,-1 h 3 c 0.554,0 1,0.446 1,1 v 5.999992 c 0,0.554 -0.446,1 -1,1 z"
|
d="m 6,16.999992 c -0.554,0 -1,-0.446 -1,-1 V 6 C 5,5.446 5.446,5 6,5 H 9.0000001 C 9.5540001,5 10,5.446 10,6 v 9.999992 c 0,0.554 -0.4459999,1 -0.9999999,1 z m 9,0 c -0.554,0 -1,-0.446 -1,-1 V 10 c 0,-0.554 0.446,-1 1,-1 h 3 c 0.554,0 1,0.446 1,1 v 5.999992 c 0,0.554 -0.446,1 -1,1 z"
|
||||||
fill={activeElementColor(appearance)}
|
fill={activeElementColor(theme)}
|
||||||
stroke={activeElementColor(appearance)}
|
stroke={activeElementColor(theme)}
|
||||||
strokeWidth="2"
|
strokeWidth="2"
|
||||||
/>
|
/>
|
||||||
</>,
|
</>,
|
||||||
@ -262,20 +272,20 @@ export const AlignBottomIcon = React.memo(
|
|||||||
);
|
);
|
||||||
|
|
||||||
export const AlignLeftIcon = React.memo(
|
export const AlignLeftIcon = React.memo(
|
||||||
({ appearance }: { appearance: "light" | "dark" }) =>
|
({ theme }: { theme: "light" | "dark" }) =>
|
||||||
createIcon(
|
createIcon(
|
||||||
<>
|
<>
|
||||||
<path
|
<path
|
||||||
d="M 5,2 V 22"
|
d="M 5,2 V 22"
|
||||||
fill={iconFillColor(appearance)}
|
fill={iconFillColor(theme)}
|
||||||
stroke={iconFillColor(appearance)}
|
stroke={iconFillColor(theme)}
|
||||||
strokeWidth="2"
|
strokeWidth="2"
|
||||||
strokeLinecap="round"
|
strokeLinecap="round"
|
||||||
/>
|
/>
|
||||||
<path
|
<path
|
||||||
d="m 7.000004,5.999996 c 0,-0.554 0.446,-1 1,-1 h 9.999992 c 0.554,0 1,0.446 1,1 v 3.0000001 c 0,0.554 -0.446,0.9999999 -1,0.9999999 H 8.000004 c -0.554,0 -1,-0.4459999 -1,-0.9999999 z m 0,9 c 0,-0.554 0.446,-1 1,-1 h 5.999992 c 0.554,0 1,0.446 1,1 v 3 c 0,0.554 -0.446,1 -1,1 H 8.000004 c -0.554,0 -1,-0.446 -1,-1 z"
|
d="m 7.000004,5.999996 c 0,-0.554 0.446,-1 1,-1 h 9.999992 c 0.554,0 1,0.446 1,1 v 3.0000001 c 0,0.554 -0.446,0.9999999 -1,0.9999999 H 8.000004 c -0.554,0 -1,-0.4459999 -1,-0.9999999 z m 0,9 c 0,-0.554 0.446,-1 1,-1 h 5.999992 c 0.554,0 1,0.446 1,1 v 3 c 0,0.554 -0.446,1 -1,1 H 8.000004 c -0.554,0 -1,-0.446 -1,-1 z"
|
||||||
fill={activeElementColor(appearance)}
|
fill={activeElementColor(theme)}
|
||||||
stroke={activeElementColor(appearance)}
|
stroke={activeElementColor(theme)}
|
||||||
strokeWidth="2"
|
strokeWidth="2"
|
||||||
/>
|
/>
|
||||||
</>,
|
</>,
|
||||||
@ -284,20 +294,20 @@ export const AlignLeftIcon = React.memo(
|
|||||||
);
|
);
|
||||||
|
|
||||||
export const AlignRightIcon = React.memo(
|
export const AlignRightIcon = React.memo(
|
||||||
({ appearance }: { appearance: "light" | "dark" }) =>
|
({ theme }: { theme: "light" | "dark" }) =>
|
||||||
createIcon(
|
createIcon(
|
||||||
<>
|
<>
|
||||||
<path
|
<path
|
||||||
d="M 19,2 V 22"
|
d="M 19,2 V 22"
|
||||||
fill={iconFillColor(appearance)}
|
fill={iconFillColor(theme)}
|
||||||
stroke={iconFillColor(appearance)}
|
stroke={iconFillColor(theme)}
|
||||||
strokeWidth="2"
|
strokeWidth="2"
|
||||||
strokeLinecap="round"
|
strokeLinecap="round"
|
||||||
/>
|
/>
|
||||||
<path
|
<path
|
||||||
d="m 16.999996,5.999996 c 0,-0.554 -0.446,-1 -1,-1 H 6.000004 c -0.554,0 -1,0.446 -1,1 v 3.0000001 c 0,0.554 0.446,0.9999999 1,0.9999999 h 9.999992 c 0.554,0 1,-0.4459999 1,-0.9999999 z m 0,9 c 0,-0.554 -0.446,-1 -1,-1 h -5.999992 c -0.554,0 -1,0.446 -1,1 v 3 c 0,0.554 0.446,1 1,1 h 5.999992 c 0.554,0 1,-0.446 1,-1 z"
|
d="m 16.999996,5.999996 c 0,-0.554 -0.446,-1 -1,-1 H 6.000004 c -0.554,0 -1,0.446 -1,1 v 3.0000001 c 0,0.554 0.446,0.9999999 1,0.9999999 h 9.999992 c 0.554,0 1,-0.4459999 1,-0.9999999 z m 0,9 c 0,-0.554 -0.446,-1 -1,-1 h -5.999992 c -0.554,0 -1,0.446 -1,1 v 3 c 0,0.554 0.446,1 1,1 h 5.999992 c 0.554,0 1,-0.446 1,-1 z"
|
||||||
fill={activeElementColor(appearance)}
|
fill={activeElementColor(theme)}
|
||||||
stroke={activeElementColor(appearance)}
|
stroke={activeElementColor(theme)}
|
||||||
strokeWidth="2"
|
strokeWidth="2"
|
||||||
/>
|
/>
|
||||||
</>,
|
</>,
|
||||||
@ -306,20 +316,20 @@ export const AlignRightIcon = React.memo(
|
|||||||
);
|
);
|
||||||
|
|
||||||
export const DistributeHorizontallyIcon = React.memo(
|
export const DistributeHorizontallyIcon = React.memo(
|
||||||
({ appearance }: { appearance: "light" | "dark" }) =>
|
({ theme }: { theme: "light" | "dark" }) =>
|
||||||
createIcon(
|
createIcon(
|
||||||
<>
|
<>
|
||||||
<path d="M5 5V19Z" fill="black" />
|
<path d="M5 5V19Z" fill="black" />
|
||||||
<path
|
<path
|
||||||
d="M19 5V19M5 5V19"
|
d="M19 5V19M5 5V19"
|
||||||
stroke={iconFillColor(appearance)}
|
stroke={iconFillColor(theme)}
|
||||||
strokeWidth="2"
|
strokeWidth="2"
|
||||||
strokeLinecap="round"
|
strokeLinecap="round"
|
||||||
/>
|
/>
|
||||||
<path
|
<path
|
||||||
d="M15 9C15.554 9 16 9.446 16 10V14C16 14.554 15.554 15 15 15H9C8.446 15 8 14.554 8 14V10C8 9.446 8.446 9 9 9H15Z"
|
d="M15 9C15.554 9 16 9.446 16 10V14C16 14.554 15.554 15 15 15H9C8.446 15 8 14.554 8 14V10C8 9.446 8.446 9 9 9H15Z"
|
||||||
fill={activeElementColor(appearance)}
|
fill={activeElementColor(theme)}
|
||||||
stroke={activeElementColor(appearance)}
|
stroke={activeElementColor(theme)}
|
||||||
strokeWidth="2"
|
strokeWidth="2"
|
||||||
/>
|
/>
|
||||||
</>,
|
</>,
|
||||||
@ -336,20 +346,20 @@ export const DistributeHorizontallyIcon = React.memo(
|
|||||||
></svg>;
|
></svg>;
|
||||||
|
|
||||||
export const DistributeVerticallyIcon = React.memo(
|
export const DistributeVerticallyIcon = React.memo(
|
||||||
({ appearance }: { appearance: "light" | "dark" }) =>
|
({ theme }: { theme: "light" | "dark" }) =>
|
||||||
createIcon(
|
createIcon(
|
||||||
<>
|
<>
|
||||||
<path
|
<path
|
||||||
d="M5 5L19 5M5 19H19"
|
d="M5 5L19 5M5 19H19"
|
||||||
fill={iconFillColor(appearance)}
|
fill={iconFillColor(theme)}
|
||||||
stroke={iconFillColor(appearance)}
|
stroke={iconFillColor(theme)}
|
||||||
strokeWidth="2"
|
strokeWidth="2"
|
||||||
strokeLinecap="round"
|
strokeLinecap="round"
|
||||||
/>
|
/>
|
||||||
<path
|
<path
|
||||||
d="M15 9C15.554 9 16 9.446 16 10V14C16 14.554 15.554 15 15 15H9C8.446 15 8 14.554 8 14V10C8 9.446 8.446 9 9 9H15Z"
|
d="M15 9C15.554 9 16 9.446 16 10V14C16 14.554 15.554 15 15 15H9C8.446 15 8 14.554 8 14V10C8 9.446 8.446 9 9 9H15Z"
|
||||||
fill={activeElementColor(appearance)}
|
fill={activeElementColor(theme)}
|
||||||
stroke={activeElementColor(appearance)}
|
stroke={activeElementColor(theme)}
|
||||||
strokeWidth="2"
|
strokeWidth="2"
|
||||||
/>
|
/>
|
||||||
</>,
|
</>,
|
||||||
@ -358,19 +368,19 @@ export const DistributeVerticallyIcon = React.memo(
|
|||||||
);
|
);
|
||||||
|
|
||||||
export const CenterVerticallyIcon = React.memo(
|
export const CenterVerticallyIcon = React.memo(
|
||||||
({ appearance }: { appearance: "light" | "dark" }) =>
|
({ theme }: { theme: "light" | "dark" }) =>
|
||||||
createIcon(
|
createIcon(
|
||||||
<>
|
<>
|
||||||
<path
|
<path
|
||||||
d="m 5.000004,16.999996 c 0,0.554 0.446,1 1,1 h 3 c 0.554,0 1,-0.446 1,-1 v -10 c 0,-0.554 -0.446,-1 -1,-1 h -3 c -0.554,0 -1,0.446 -1,1 z m 9,-2 c 0,0.554 0.446,1 1,1 h 3 c 0.554,0 1,-0.446 1,-1 v -6 c 0,-0.554 -0.446,-1 -1,-1 h -3 c -0.554,0 -1,0.446 -1,1 z"
|
d="m 5.000004,16.999996 c 0,0.554 0.446,1 1,1 h 3 c 0.554,0 1,-0.446 1,-1 v -10 c 0,-0.554 -0.446,-1 -1,-1 h -3 c -0.554,0 -1,0.446 -1,1 z m 9,-2 c 0,0.554 0.446,1 1,1 h 3 c 0.554,0 1,-0.446 1,-1 v -6 c 0,-0.554 -0.446,-1 -1,-1 h -3 c -0.554,0 -1,0.446 -1,1 z"
|
||||||
fill={activeElementColor(appearance)}
|
fill={activeElementColor(theme)}
|
||||||
stroke={activeElementColor(appearance)}
|
stroke={activeElementColor(theme)}
|
||||||
strokeWidth="2"
|
strokeWidth="2"
|
||||||
/>
|
/>
|
||||||
<path
|
<path
|
||||||
d="M 2,12 H 22"
|
d="M 2,12 H 22"
|
||||||
fill={iconFillColor(appearance)}
|
fill={iconFillColor(theme)}
|
||||||
stroke={iconFillColor(appearance)}
|
stroke={iconFillColor(theme)}
|
||||||
strokeWidth="2"
|
strokeWidth="2"
|
||||||
strokeDasharray="1, 2.8"
|
strokeDasharray="1, 2.8"
|
||||||
strokeLinecap="round"
|
strokeLinecap="round"
|
||||||
@ -381,19 +391,19 @@ export const CenterVerticallyIcon = React.memo(
|
|||||||
);
|
);
|
||||||
|
|
||||||
export const CenterHorizontallyIcon = React.memo(
|
export const CenterHorizontallyIcon = React.memo(
|
||||||
({ appearance }: { appearance: "light" | "dark" }) =>
|
({ theme }: { theme: "light" | "dark" }) =>
|
||||||
createIcon(
|
createIcon(
|
||||||
<>
|
<>
|
||||||
<path
|
<path
|
||||||
d="M 7 5 C 6.446 5 6 5.446 6 6 L 6 9 C 6 9.554 6.446 10 7 10 L 17 10 C 17.554 10 18 9.554 18 9 L 18 6 C 18 5.446 17.554 5 17 5 L 7 5 z M 9 14 C 8.446 14 8 14.446 8 15 L 8 18 C 8 18.554 8.446 19 9 19 L 15 19 C 15.554 19 16 18.554 16 18 L 16 15 C 16 14.446 15.554 14 15 14 L 9 14 z "
|
d="M 7 5 C 6.446 5 6 5.446 6 6 L 6 9 C 6 9.554 6.446 10 7 10 L 17 10 C 17.554 10 18 9.554 18 9 L 18 6 C 18 5.446 17.554 5 17 5 L 7 5 z M 9 14 C 8.446 14 8 14.446 8 15 L 8 18 C 8 18.554 8.446 19 9 19 L 15 19 C 15.554 19 16 18.554 16 18 L 16 15 C 16 14.446 15.554 14 15 14 L 9 14 z "
|
||||||
fill={activeElementColor(appearance)}
|
fill={activeElementColor(theme)}
|
||||||
stroke={activeElementColor(appearance)}
|
stroke={activeElementColor(theme)}
|
||||||
strokeWidth="2"
|
strokeWidth="2"
|
||||||
/>
|
/>
|
||||||
<path
|
<path
|
||||||
d="M 12,2 V 22"
|
d="M 12,2 V 22"
|
||||||
fill={iconFillColor(appearance)}
|
fill={iconFillColor(theme)}
|
||||||
stroke={iconFillColor(appearance)}
|
stroke={iconFillColor(theme)}
|
||||||
strokeWidth="2"
|
strokeWidth="2"
|
||||||
strokeDasharray="1, 2.8"
|
strokeDasharray="1, 2.8"
|
||||||
strokeLinecap="round"
|
strokeLinecap="round"
|
||||||
@ -438,77 +448,76 @@ export const shield = createIcon(
|
|||||||
{ width: 24 },
|
{ width: 24 },
|
||||||
);
|
);
|
||||||
|
|
||||||
export const GroupIcon = React.memo(
|
export const GroupIcon = React.memo(({ theme }: { theme: "light" | "dark" }) =>
|
||||||
({ appearance }: { appearance: "light" | "dark" }) =>
|
createIcon(
|
||||||
createIcon(
|
<>
|
||||||
<>
|
<path d="M25 26H111V111H25" fill={iconFillColor(theme)} />
|
||||||
<path d="M25 26H111V111H25" fill={iconFillColor(appearance)} />
|
<path
|
||||||
<path
|
d="M25 111C25 80.2068 25 49.4135 25 26M25 26C48.6174 26 72.2348 26 111 26H25ZM25 26C53.3671 26 81.7343 26 111 26H25ZM111 26C111 52.303 111 78.606 111 111V26ZM111 26C111 51.2947 111 76.5893 111 111V26ZM111 111C87.0792 111 63.1585 111 25 111H111ZM111 111C87.4646 111 63.9293 111 25 111H111ZM25 111C25 81.1514 25 51.3028 25 26V111Z"
|
||||||
d="M25 111C25 80.2068 25 49.4135 25 26M25 26C48.6174 26 72.2348 26 111 26H25ZM25 26C53.3671 26 81.7343 26 111 26H25ZM111 26C111 52.303 111 78.606 111 111V26ZM111 26C111 51.2947 111 76.5893 111 111V26ZM111 111C87.0792 111 63.1585 111 25 111H111ZM111 111C87.4646 111 63.9293 111 25 111H111ZM25 111C25 81.1514 25 51.3028 25 26V111Z"
|
stroke={iconFillColor(theme)}
|
||||||
stroke={iconFillColor(appearance)}
|
strokeWidth="2"
|
||||||
strokeWidth="2"
|
/>
|
||||||
/>
|
<path d="M100 100H160V160H100" fill={iconFillColor(theme)} />
|
||||||
<path d="M100 100H160V160H100" fill={iconFillColor(appearance)} />
|
<path
|
||||||
<path
|
d="M100 160C100 144.106 100 128.211 100 100M100 100C117.706 100 135.412 100 160 100H100ZM100 100C114.214 100 128.428 100 160 100H100ZM160 100C160 120.184 160 140.369 160 160V100ZM160 100C160 113.219 160 126.437 160 160V100ZM160 160C145.534 160 131.068 160 100 160H160ZM160 160C143.467 160 126.934 160 100 160H160ZM100 160C100 143.661 100 127.321 100 100V160Z"
|
||||||
d="M100 160C100 144.106 100 128.211 100 100M100 100C117.706 100 135.412 100 160 100H100ZM100 100C114.214 100 128.428 100 160 100H100ZM160 100C160 120.184 160 140.369 160 160V100ZM160 100C160 113.219 160 126.437 160 160V100ZM160 160C145.534 160 131.068 160 100 160H160ZM160 160C143.467 160 126.934 160 100 160H160ZM100 160C100 143.661 100 127.321 100 100V160Z"
|
stroke={iconFillColor(theme)}
|
||||||
stroke={iconFillColor(appearance)}
|
strokeWidth="2"
|
||||||
strokeWidth="2"
|
/>
|
||||||
/>
|
<rect
|
||||||
<rect
|
x="2.5"
|
||||||
x="2.5"
|
y="2.5"
|
||||||
y="2.5"
|
width="30"
|
||||||
width="30"
|
height="30"
|
||||||
height="30"
|
fill={handlerColor(theme)}
|
||||||
fill={handlerColor(appearance)}
|
stroke={iconFillColor(theme)}
|
||||||
stroke={iconFillColor(appearance)}
|
strokeWidth="6"
|
||||||
strokeWidth="6"
|
/>
|
||||||
/>
|
<rect
|
||||||
<rect
|
x="2.5"
|
||||||
x="2.5"
|
y="149.5"
|
||||||
y="149.5"
|
width="30"
|
||||||
width="30"
|
height="30"
|
||||||
height="30"
|
fill={handlerColor(theme)}
|
||||||
fill={handlerColor(appearance)}
|
stroke={iconFillColor(theme)}
|
||||||
stroke={iconFillColor(appearance)}
|
strokeWidth="6"
|
||||||
strokeWidth="6"
|
/>
|
||||||
/>
|
<rect
|
||||||
<rect
|
x="147.5"
|
||||||
x="147.5"
|
y="149.5"
|
||||||
y="149.5"
|
width="30"
|
||||||
width="30"
|
height="30"
|
||||||
height="30"
|
fill={handlerColor(theme)}
|
||||||
fill={handlerColor(appearance)}
|
stroke={iconFillColor(theme)}
|
||||||
stroke={iconFillColor(appearance)}
|
strokeWidth="6"
|
||||||
strokeWidth="6"
|
/>
|
||||||
/>
|
<rect
|
||||||
<rect
|
x="147.5"
|
||||||
x="147.5"
|
y="2.5"
|
||||||
y="2.5"
|
width="30"
|
||||||
width="30"
|
height="30"
|
||||||
height="30"
|
fill={handlerColor(theme)}
|
||||||
fill={handlerColor(appearance)}
|
stroke={iconFillColor(theme)}
|
||||||
stroke={iconFillColor(appearance)}
|
strokeWidth="6"
|
||||||
strokeWidth="6"
|
/>
|
||||||
/>
|
</>,
|
||||||
</>,
|
{ width: 182, height: 182, mirror: true },
|
||||||
{ width: 182, height: 182, mirror: true },
|
),
|
||||||
),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
export const UngroupIcon = React.memo(
|
export const UngroupIcon = React.memo(
|
||||||
({ appearance }: { appearance: "light" | "dark" }) =>
|
({ theme }: { theme: "light" | "dark" }) =>
|
||||||
createIcon(
|
createIcon(
|
||||||
<>
|
<>
|
||||||
<path d="M25 26H111V111H25" fill={iconFillColor(appearance)} />
|
<path d="M25 26H111V111H25" fill={iconFillColor(theme)} />
|
||||||
<path
|
<path
|
||||||
d="M25 111C25 80.2068 25 49.4135 25 26M25 26C48.6174 26 72.2348 26 111 26H25ZM25 26C53.3671 26 81.7343 26 111 26H25ZM111 26C111 52.303 111 78.606 111 111V26ZM111 26C111 51.2947 111 76.5893 111 111V26ZM111 111C87.0792 111 63.1585 111 25 111H111ZM111 111C87.4646 111 63.9293 111 25 111H111ZM25 111C25 81.1514 25 51.3028 25 26V111Z"
|
d="M25 111C25 80.2068 25 49.4135 25 26M25 26C48.6174 26 72.2348 26 111 26H25ZM25 26C53.3671 26 81.7343 26 111 26H25ZM111 26C111 52.303 111 78.606 111 111V26ZM111 26C111 51.2947 111 76.5893 111 111V26ZM111 111C87.0792 111 63.1585 111 25 111H111ZM111 111C87.4646 111 63.9293 111 25 111H111ZM25 111C25 81.1514 25 51.3028 25 26V111Z"
|
||||||
stroke={iconFillColor(appearance)}
|
stroke={iconFillColor(theme)}
|
||||||
strokeWidth="2"
|
strokeWidth="2"
|
||||||
/>
|
/>
|
||||||
<path d="M100 100H160V160H100" fill={iconFillColor(appearance)} />
|
<path d="M100 100H160V160H100" fill={iconFillColor(theme)} />
|
||||||
<path
|
<path
|
||||||
d="M100 160C100 144.106 100 128.211 100 100M100 100C117.706 100 135.412 100 160 100H100ZM100 100C114.214 100 128.428 100 160 100H100ZM160 100C160 120.184 160 140.369 160 160V100ZM160 100C160 113.219 160 126.437 160 160V100ZM160 160C145.534 160 131.068 160 100 160H160ZM160 160C143.467 160 126.934 160 100 160H160ZM100 160C100 143.661 100 127.321 100 100V160Z"
|
d="M100 160C100 144.106 100 128.211 100 100M100 100C117.706 100 135.412 100 160 100H100ZM100 100C114.214 100 128.428 100 160 100H100ZM160 100C160 120.184 160 140.369 160 160V100ZM160 100C160 113.219 160 126.437 160 160V100ZM160 160C145.534 160 131.068 160 100 160H160ZM160 160C143.467 160 126.934 160 100 160H160ZM100 160C100 143.661 100 127.321 100 100V160Z"
|
||||||
stroke={iconFillColor(appearance)}
|
stroke={iconFillColor(theme)}
|
||||||
strokeWidth="2"
|
strokeWidth="2"
|
||||||
/>
|
/>
|
||||||
<rect
|
<rect
|
||||||
@ -516,8 +525,8 @@ export const UngroupIcon = React.memo(
|
|||||||
y="2.5"
|
y="2.5"
|
||||||
width="30"
|
width="30"
|
||||||
height="30"
|
height="30"
|
||||||
fill={handlerColor(appearance)}
|
fill={handlerColor(theme)}
|
||||||
stroke={iconFillColor(appearance)}
|
stroke={iconFillColor(theme)}
|
||||||
strokeWidth="6"
|
strokeWidth="6"
|
||||||
/>
|
/>
|
||||||
<rect
|
<rect
|
||||||
@ -525,8 +534,8 @@ export const UngroupIcon = React.memo(
|
|||||||
y="149.5"
|
y="149.5"
|
||||||
width="30"
|
width="30"
|
||||||
height="30"
|
height="30"
|
||||||
fill={handlerColor(appearance)}
|
fill={handlerColor(theme)}
|
||||||
stroke={iconFillColor(appearance)}
|
stroke={iconFillColor(theme)}
|
||||||
strokeWidth="6"
|
strokeWidth="6"
|
||||||
/>
|
/>
|
||||||
<rect
|
<rect
|
||||||
@ -534,8 +543,8 @@ export const UngroupIcon = React.memo(
|
|||||||
y="149.5"
|
y="149.5"
|
||||||
width="30"
|
width="30"
|
||||||
height="30"
|
height="30"
|
||||||
fill={handlerColor(appearance)}
|
fill={handlerColor(theme)}
|
||||||
stroke={iconFillColor(appearance)}
|
stroke={iconFillColor(theme)}
|
||||||
strokeWidth="6"
|
strokeWidth="6"
|
||||||
/>
|
/>
|
||||||
<rect
|
<rect
|
||||||
@ -543,8 +552,8 @@ export const UngroupIcon = React.memo(
|
|||||||
y="78.5"
|
y="78.5"
|
||||||
width="30"
|
width="30"
|
||||||
height="30"
|
height="30"
|
||||||
fill={handlerColor(appearance)}
|
fill={handlerColor(theme)}
|
||||||
stroke={iconFillColor(appearance)}
|
stroke={iconFillColor(theme)}
|
||||||
strokeWidth="6"
|
strokeWidth="6"
|
||||||
/>
|
/>
|
||||||
<rect
|
<rect
|
||||||
@ -552,8 +561,8 @@ export const UngroupIcon = React.memo(
|
|||||||
y="2.5"
|
y="2.5"
|
||||||
width="30"
|
width="30"
|
||||||
height="30"
|
height="30"
|
||||||
fill={handlerColor(appearance)}
|
fill={handlerColor(theme)}
|
||||||
stroke={iconFillColor(appearance)}
|
stroke={iconFillColor(theme)}
|
||||||
strokeWidth="6"
|
strokeWidth="6"
|
||||||
/>
|
/>
|
||||||
<rect
|
<rect
|
||||||
@ -561,8 +570,8 @@ export const UngroupIcon = React.memo(
|
|||||||
y="102.5"
|
y="102.5"
|
||||||
width="30"
|
width="30"
|
||||||
height="30"
|
height="30"
|
||||||
fill={handlerColor(appearance)}
|
fill={handlerColor(theme)}
|
||||||
stroke={iconFillColor(appearance)}
|
stroke={iconFillColor(theme)}
|
||||||
strokeWidth="6"
|
strokeWidth="6"
|
||||||
/>
|
/>
|
||||||
</>,
|
</>,
|
||||||
@ -571,22 +580,22 @@ export const UngroupIcon = React.memo(
|
|||||||
);
|
);
|
||||||
|
|
||||||
export const FillHachureIcon = React.memo(
|
export const FillHachureIcon = React.memo(
|
||||||
({ appearance }: { appearance: "light" | "dark" }) =>
|
({ theme }: { theme: "light" | "dark" }) =>
|
||||||
createIcon(
|
createIcon(
|
||||||
<path
|
<path
|
||||||
fillRule="evenodd"
|
fillRule="evenodd"
|
||||||
clipRule="evenodd"
|
clipRule="evenodd"
|
||||||
d="M20.101 16H28.0934L36 8.95989V4H33.5779L20.101 16ZM30.5704 4L17.0935 16H9.10101L22.5779 4H30.5704ZM19.5704 4L6.09349 16H4V10.7475L11.5779 4H19.5704ZM8.57036 4H4V8.06952L8.57036 4ZM36 11.6378L31.101 16H36V11.6378ZM2 2V18H38V2H2Z"
|
d="M20.101 16H28.0934L36 8.95989V4H33.5779L20.101 16ZM30.5704 4L17.0935 16H9.10101L22.5779 4H30.5704ZM19.5704 4L6.09349 16H4V10.7475L11.5779 4H19.5704ZM8.57036 4H4V8.06952L8.57036 4ZM36 11.6378L31.101 16H36V11.6378ZM2 2V18H38V2H2Z"
|
||||||
fill={iconFillColor(appearance)}
|
fill={iconFillColor(theme)}
|
||||||
/>,
|
/>,
|
||||||
{ width: 40, height: 20 },
|
{ width: 40, height: 20 },
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
export const FillCrossHatchIcon = React.memo(
|
export const FillCrossHatchIcon = React.memo(
|
||||||
({ appearance }: { appearance: "light" | "dark" }) =>
|
({ theme }: { theme: "light" | "dark" }) =>
|
||||||
createIcon(
|
createIcon(
|
||||||
<g fill={iconFillColor(appearance)} fillRule="evenodd" clipRule="evenodd">
|
<g fill={iconFillColor(theme)} fillRule="evenodd" clipRule="evenodd">
|
||||||
<path d="M20.101 16H28.0934L36 8.95989V4H33.5779L20.101 16ZM30.5704 4L17.0935 16H9.10101L22.5779 4H30.5704ZM19.5704 4L6.09349 16H4V10.7475L11.5779 4H19.5704ZM8.57036 4H4V8.06952L8.57036 4ZM36 11.6378L31.101 16H36V11.6378ZM2 2V18H38V2H2Z" />
|
<path d="M20.101 16H28.0934L36 8.95989V4H33.5779L20.101 16ZM30.5704 4L17.0935 16H9.10101L22.5779 4H30.5704ZM19.5704 4L6.09349 16H4V10.7475L11.5779 4H19.5704ZM8.57036 4H4V8.06952L8.57036 4ZM36 11.6378L31.101 16H36V11.6378ZM2 2V18H38V2H2Z" />
|
||||||
<path d="M14.0001 18L3.00006 4.00002L4.5727 2.76438L15.5727 16.7644L14.0001 18ZM25.0001 18L14.0001 4.00002L15.5727 2.76438L26.5727 16.7644L25.0001 18ZM36.0001 18L25.0001 4.00002L26.5727 2.76438L37.5727 16.7644L36.0001 18Z" />
|
<path d="M14.0001 18L3.00006 4.00002L4.5727 2.76438L15.5727 16.7644L14.0001 18ZM25.0001 18L14.0001 4.00002L15.5727 2.76438L26.5727 16.7644L25.0001 18ZM36.0001 18L25.0001 4.00002L26.5727 2.76438L37.5727 16.7644L36.0001 18Z" />
|
||||||
</g>,
|
</g>,
|
||||||
@ -595,25 +604,19 @@ export const FillCrossHatchIcon = React.memo(
|
|||||||
);
|
);
|
||||||
|
|
||||||
export const FillSolidIcon = React.memo(
|
export const FillSolidIcon = React.memo(
|
||||||
({ appearance }: { appearance: "light" | "dark" }) =>
|
({ theme }: { theme: "light" | "dark" }) =>
|
||||||
createIcon(<path d="M2 2H38V18H2V2Z" fill={iconFillColor(appearance)} />, {
|
createIcon(<path d="M2 2H38V18H2V2Z" fill={iconFillColor(theme)} />, {
|
||||||
width: 40,
|
width: 40,
|
||||||
height: 20,
|
height: 20,
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
export const StrokeWidthIcon = React.memo(
|
export const StrokeWidthIcon = React.memo(
|
||||||
({
|
({ theme, strokeWidth }: { theme: "light" | "dark"; strokeWidth: number }) =>
|
||||||
appearance,
|
|
||||||
strokeWidth,
|
|
||||||
}: {
|
|
||||||
appearance: "light" | "dark";
|
|
||||||
strokeWidth: number;
|
|
||||||
}) =>
|
|
||||||
createIcon(
|
createIcon(
|
||||||
<path
|
<path
|
||||||
d="M6 10H34"
|
d="M6 10H34"
|
||||||
stroke={iconFillColor(appearance)}
|
stroke={iconFillColor(theme)}
|
||||||
strokeWidth={strokeWidth}
|
strokeWidth={strokeWidth}
|
||||||
fill="none"
|
fill="none"
|
||||||
/>,
|
/>,
|
||||||
@ -622,11 +625,11 @@ export const StrokeWidthIcon = React.memo(
|
|||||||
);
|
);
|
||||||
|
|
||||||
export const StrokeStyleSolidIcon = React.memo(
|
export const StrokeStyleSolidIcon = React.memo(
|
||||||
({ appearance }: { appearance: "light" | "dark" }) =>
|
({ theme }: { theme: "light" | "dark" }) =>
|
||||||
createIcon(
|
createIcon(
|
||||||
<path
|
<path
|
||||||
d="M6 10H34"
|
d="M6 10H34"
|
||||||
stroke={iconFillColor(appearance)}
|
stroke={iconFillColor(theme)}
|
||||||
strokeWidth={2}
|
strokeWidth={2}
|
||||||
fill="none"
|
fill="none"
|
||||||
/>,
|
/>,
|
||||||
@ -638,11 +641,11 @@ export const StrokeStyleSolidIcon = React.memo(
|
|||||||
);
|
);
|
||||||
|
|
||||||
export const StrokeStyleDashedIcon = React.memo(
|
export const StrokeStyleDashedIcon = React.memo(
|
||||||
({ appearance }: { appearance: "light" | "dark" }) =>
|
({ theme }: { theme: "light" | "dark" }) =>
|
||||||
createIcon(
|
createIcon(
|
||||||
<path
|
<path
|
||||||
d="M6 10H34"
|
d="M6 10H34"
|
||||||
stroke={iconFillColor(appearance)}
|
stroke={iconFillColor(theme)}
|
||||||
strokeWidth={2.5}
|
strokeWidth={2.5}
|
||||||
strokeDasharray={"10, 8"}
|
strokeDasharray={"10, 8"}
|
||||||
fill="none"
|
fill="none"
|
||||||
@ -652,11 +655,11 @@ export const StrokeStyleDashedIcon = React.memo(
|
|||||||
);
|
);
|
||||||
|
|
||||||
export const StrokeStyleDottedIcon = React.memo(
|
export const StrokeStyleDottedIcon = React.memo(
|
||||||
({ appearance }: { appearance: "light" | "dark" }) =>
|
({ theme }: { theme: "light" | "dark" }) =>
|
||||||
createIcon(
|
createIcon(
|
||||||
<path
|
<path
|
||||||
d="M6 10H34"
|
d="M6 10H34"
|
||||||
stroke={iconFillColor(appearance)}
|
stroke={iconFillColor(theme)}
|
||||||
strokeWidth={2.5}
|
strokeWidth={2.5}
|
||||||
strokeDasharray={"4, 4"}
|
strokeDasharray={"4, 4"}
|
||||||
fill="none"
|
fill="none"
|
||||||
@ -666,11 +669,11 @@ export const StrokeStyleDottedIcon = React.memo(
|
|||||||
);
|
);
|
||||||
|
|
||||||
export const SloppinessArchitectIcon = React.memo(
|
export const SloppinessArchitectIcon = React.memo(
|
||||||
({ appearance }: { appearance: "light" | "dark" }) =>
|
({ theme }: { theme: "light" | "dark" }) =>
|
||||||
createIcon(
|
createIcon(
|
||||||
<path
|
<path
|
||||||
d="M3.00098 16.1691C6.28774 13.9744 19.6399 2.8905 22.7215 3.00082C25.8041 3.11113 19.1158 15.5488 21.4962 16.8309C23.8757 18.1131 34.4155 11.7148 37.0001 10.6919"
|
d="M3.00098 16.1691C6.28774 13.9744 19.6399 2.8905 22.7215 3.00082C25.8041 3.11113 19.1158 15.5488 21.4962 16.8309C23.8757 18.1131 34.4155 11.7148 37.0001 10.6919"
|
||||||
stroke={iconFillColor(appearance)}
|
stroke={iconFillColor(theme)}
|
||||||
strokeWidth={2}
|
strokeWidth={2}
|
||||||
fill="none"
|
fill="none"
|
||||||
/>,
|
/>,
|
||||||
@ -679,11 +682,11 @@ export const SloppinessArchitectIcon = React.memo(
|
|||||||
);
|
);
|
||||||
|
|
||||||
export const SloppinessArtistIcon = React.memo(
|
export const SloppinessArtistIcon = React.memo(
|
||||||
({ appearance }: { appearance: "light" | "dark" }) =>
|
({ theme }: { theme: "light" | "dark" }) =>
|
||||||
createIcon(
|
createIcon(
|
||||||
<path
|
<path
|
||||||
d="M3 17C6.68158 14.8752 16.1296 9.09849 22.0648 6.54922C28 3.99995 22.2896 13.3209 25 14C27.7104 14.6791 36.3757 9.6471 36.3757 9.6471M6.40706 15C13 11.1918 20.0468 1.51045 23.0234 3.0052C26 4.49995 20.457 12.8659 22.7285 16.4329C25 20 36.3757 13 36.3757 13"
|
d="M3 17C6.68158 14.8752 16.1296 9.09849 22.0648 6.54922C28 3.99995 22.2896 13.3209 25 14C27.7104 14.6791 36.3757 9.6471 36.3757 9.6471M6.40706 15C13 11.1918 20.0468 1.51045 23.0234 3.0052C26 4.49995 20.457 12.8659 22.7285 16.4329C25 20 36.3757 13 36.3757 13"
|
||||||
stroke={iconFillColor(appearance)}
|
stroke={iconFillColor(theme)}
|
||||||
strokeWidth={2}
|
strokeWidth={2}
|
||||||
fill="none"
|
fill="none"
|
||||||
/>,
|
/>,
|
||||||
@ -692,11 +695,11 @@ export const SloppinessArtistIcon = React.memo(
|
|||||||
);
|
);
|
||||||
|
|
||||||
export const SloppinessCartoonistIcon = React.memo(
|
export const SloppinessCartoonistIcon = React.memo(
|
||||||
({ appearance }: { appearance: "light" | "dark" }) =>
|
({ theme }: { theme: "light" | "dark" }) =>
|
||||||
createIcon(
|
createIcon(
|
||||||
<path
|
<path
|
||||||
d="M3 15.6468C6.93692 13.5378 22.5544 2.81528 26.6206 3.00242C30.6877 3.18956 25.6708 15.3346 27.4009 16.7705C29.1309 18.2055 35.4001 12.4762 37 11.6177M3.97143 10.4917C6.61158 9.24563 16.3706 2.61886 19.8104 3.01724C23.2522 3.41472 22.0773 12.2013 24.6181 12.8783C27.1598 13.5536 33.3179 8.04068 35.0571 7.07244"
|
d="M3 15.6468C6.93692 13.5378 22.5544 2.81528 26.6206 3.00242C30.6877 3.18956 25.6708 15.3346 27.4009 16.7705C29.1309 18.2055 35.4001 12.4762 37 11.6177M3.97143 10.4917C6.61158 9.24563 16.3706 2.61886 19.8104 3.01724C23.2522 3.41472 22.0773 12.2013 24.6181 12.8783C27.1598 13.5536 33.3179 8.04068 35.0571 7.07244"
|
||||||
stroke={iconFillColor(appearance)}
|
stroke={iconFillColor(theme)}
|
||||||
strokeWidth={2}
|
strokeWidth={2}
|
||||||
fill="none"
|
fill="none"
|
||||||
/>,
|
/>,
|
||||||
@ -705,11 +708,11 @@ export const SloppinessCartoonistIcon = React.memo(
|
|||||||
);
|
);
|
||||||
|
|
||||||
export const EdgeSharpIcon = React.memo(
|
export const EdgeSharpIcon = React.memo(
|
||||||
({ appearance }: { appearance: "light" | "dark" }) =>
|
({ theme }: { theme: "light" | "dark" }) =>
|
||||||
createIcon(
|
createIcon(
|
||||||
<path
|
<path
|
||||||
d="M10 17L10 5L35 5"
|
d="M10 17L10 5L35 5"
|
||||||
stroke={iconFillColor(appearance)}
|
stroke={iconFillColor(theme)}
|
||||||
strokeWidth={2}
|
strokeWidth={2}
|
||||||
fill="none"
|
fill="none"
|
||||||
/>,
|
/>,
|
||||||
@ -718,11 +721,11 @@ export const EdgeSharpIcon = React.memo(
|
|||||||
);
|
);
|
||||||
|
|
||||||
export const EdgeRoundIcon = React.memo(
|
export const EdgeRoundIcon = React.memo(
|
||||||
({ appearance }: { appearance: "light" | "dark" }) =>
|
({ theme }: { theme: "light" | "dark" }) =>
|
||||||
createIcon(
|
createIcon(
|
||||||
<path
|
<path
|
||||||
d="M10 17V15C10 8 13 5 21 5L33.5 5"
|
d="M10 17V15C10 8 13 5 21 5L33.5 5"
|
||||||
stroke={iconFillColor(appearance)}
|
stroke={iconFillColor(theme)}
|
||||||
strokeWidth={2}
|
strokeWidth={2}
|
||||||
fill="none"
|
fill="none"
|
||||||
/>,
|
/>,
|
||||||
@ -731,11 +734,11 @@ export const EdgeRoundIcon = React.memo(
|
|||||||
);
|
);
|
||||||
|
|
||||||
export const ArrowheadNoneIcon = React.memo(
|
export const ArrowheadNoneIcon = React.memo(
|
||||||
({ appearance }: { appearance: "light" | "dark" }) =>
|
({ theme }: { theme: "light" | "dark" }) =>
|
||||||
createIcon(
|
createIcon(
|
||||||
<path
|
<path
|
||||||
d="M6 10H34"
|
d="M6 10H34"
|
||||||
stroke={iconFillColor(appearance)}
|
stroke={iconFillColor(theme)}
|
||||||
strokeWidth={2}
|
strokeWidth={2}
|
||||||
fill="none"
|
fill="none"
|
||||||
/>,
|
/>,
|
||||||
@ -747,17 +750,11 @@ export const ArrowheadNoneIcon = React.memo(
|
|||||||
);
|
);
|
||||||
|
|
||||||
export const ArrowheadArrowIcon = React.memo(
|
export const ArrowheadArrowIcon = React.memo(
|
||||||
({
|
({ theme, flip = false }: { theme: "light" | "dark"; flip?: boolean }) =>
|
||||||
appearance,
|
|
||||||
flip = false,
|
|
||||||
}: {
|
|
||||||
appearance: "light" | "dark";
|
|
||||||
flip?: boolean;
|
|
||||||
}) =>
|
|
||||||
createIcon(
|
createIcon(
|
||||||
<g
|
<g
|
||||||
transform={flip ? "translate(40, 0) scale(-1, 1)" : ""}
|
transform={flip ? "translate(40, 0) scale(-1, 1)" : ""}
|
||||||
stroke={iconFillColor(appearance)}
|
stroke={iconFillColor(theme)}
|
||||||
strokeWidth={2}
|
strokeWidth={2}
|
||||||
fill="none"
|
fill="none"
|
||||||
>
|
>
|
||||||
@ -769,17 +766,11 @@ export const ArrowheadArrowIcon = React.memo(
|
|||||||
);
|
);
|
||||||
|
|
||||||
export const ArrowheadDotIcon = React.memo(
|
export const ArrowheadDotIcon = React.memo(
|
||||||
({
|
({ theme, flip = false }: { theme: "light" | "dark"; flip?: boolean }) =>
|
||||||
appearance,
|
|
||||||
flip = false,
|
|
||||||
}: {
|
|
||||||
appearance: "light" | "dark";
|
|
||||||
flip?: boolean;
|
|
||||||
}) =>
|
|
||||||
createIcon(
|
createIcon(
|
||||||
<g
|
<g
|
||||||
stroke={iconFillColor(appearance)}
|
stroke={iconFillColor(theme)}
|
||||||
fill={iconFillColor(appearance)}
|
fill={iconFillColor(theme)}
|
||||||
transform={flip ? "translate(40, 0) scale(-1, 1)" : ""}
|
transform={flip ? "translate(40, 0) scale(-1, 1)" : ""}
|
||||||
>
|
>
|
||||||
<path d="M32 10L6 10" strokeWidth={2} />
|
<path d="M32 10L6 10" strokeWidth={2} />
|
||||||
@ -790,18 +781,12 @@ export const ArrowheadDotIcon = React.memo(
|
|||||||
);
|
);
|
||||||
|
|
||||||
export const ArrowheadBarIcon = React.memo(
|
export const ArrowheadBarIcon = React.memo(
|
||||||
({
|
({ theme, flip = false }: { theme: "light" | "dark"; flip?: boolean }) =>
|
||||||
appearance,
|
|
||||||
flip = false,
|
|
||||||
}: {
|
|
||||||
appearance: "light" | "dark";
|
|
||||||
flip?: boolean;
|
|
||||||
}) =>
|
|
||||||
createIcon(
|
createIcon(
|
||||||
<g transform={flip ? "translate(40, 0) scale(-1, 1)" : ""}>
|
<g transform={flip ? "translate(40, 0) scale(-1, 1)" : ""}>
|
||||||
<path
|
<path
|
||||||
d="M34 10H5.99996M34 10L34 5M34 10L34 15"
|
d="M34 10H5.99996M34 10L34 5M34 10L34 15"
|
||||||
stroke={iconFillColor(appearance)}
|
stroke={iconFillColor(theme)}
|
||||||
strokeWidth={2}
|
strokeWidth={2}
|
||||||
fill="none"
|
fill="none"
|
||||||
/>
|
/>
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import { FontFamily } from "./element/types";
|
import { FontFamily } from "./element/types";
|
||||||
|
import cssVariables from "./css/variables.module.scss";
|
||||||
|
|
||||||
export const APP_NAME = "Excalidraw";
|
export const APP_NAME = "Excalidraw";
|
||||||
|
|
||||||
@ -93,8 +94,8 @@ export const TOUCH_CTX_MENU_TIMEOUT = 500;
|
|||||||
export const TITLE_TIMEOUT = 10000;
|
export const TITLE_TIMEOUT = 10000;
|
||||||
export const TOAST_TIMEOUT = 5000;
|
export const TOAST_TIMEOUT = 5000;
|
||||||
export const VERSION_TIMEOUT = 30000;
|
export const VERSION_TIMEOUT = 30000;
|
||||||
export const SCROLL_TIMEOUT = 500;
|
|
||||||
export const AUTO_SAVE_TIMEOUT = 500;
|
export const AUTO_SAVE_TIMEOUT = 500;
|
||||||
|
export const SCROLL_TIMEOUT = 100;
|
||||||
|
|
||||||
export const ZOOM_STEP = 0.1;
|
export const ZOOM_STEP = 0.1;
|
||||||
|
|
||||||
@ -108,3 +109,5 @@ export const MODES = {
|
|||||||
ZEN: "zenMode",
|
ZEN: "zenMode",
|
||||||
GRID: "gridMode",
|
GRID: "gridMode",
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const THEME_FILTER = cssVariables.themeFilter;
|
||||||
|
@ -8,13 +8,22 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.excalidraw {
|
.excalidraw {
|
||||||
color: var(--text-color-primary);
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
color: var(--text-primary-color);
|
||||||
display: flex;
|
display: flex;
|
||||||
top: 0;
|
top: 0;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
right: 0;
|
right: 0;
|
||||||
|
|
||||||
|
// serves 2 purposes:
|
||||||
|
// 1. prevent selecting text outside the component when double-clicking or
|
||||||
|
// dragging inside it (e.g. on canvas)
|
||||||
|
// 2. prevent selecting UI, both from the inside, and from outside the
|
||||||
|
// component (e.g. if you select text in a sidebar)
|
||||||
|
user-select: none;
|
||||||
|
|
||||||
a {
|
a {
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
@ -27,7 +36,6 @@
|
|||||||
|
|
||||||
canvas {
|
canvas {
|
||||||
touch-action: none;
|
touch-action: none;
|
||||||
user-select: none;
|
|
||||||
|
|
||||||
// following props improve blurriness at certain devicePixelRatios.
|
// following props improve blurriness at certain devicePixelRatios.
|
||||||
// AFAIK it doesn't affect export (in fact, export seems sharp either way).
|
// AFAIK it doesn't affect export (in fact, export seems sharp either way).
|
||||||
@ -39,21 +47,21 @@
|
|||||||
z-index: var(--zIndex-canvas);
|
z-index: var(--zIndex-canvas);
|
||||||
}
|
}
|
||||||
|
|
||||||
&.Appearance_dark {
|
&.theme--dark {
|
||||||
// The percentage is inspired by
|
// The percentage is inspired by
|
||||||
// https://material.io/design/color/dark-theme.html#properties, which
|
// https://material.io/design/color/dark-theme.html#properties, which
|
||||||
// recommends surface color of #121212, 93% yields #111111 for #FFF
|
// recommends surface color of #121212, 93% yields #111111 for #FFF
|
||||||
|
|
||||||
canvas {
|
canvas {
|
||||||
filter: var(--appearance-filter);
|
filter: var(--theme-filter);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.FixedSideContainer {
|
.FixedSideContainer {
|
||||||
padding-top: var(--sat, 0px);
|
padding-top: var(--sat, 0);
|
||||||
padding-right: var(--sar, 0px);
|
padding-right: var(--sar, 0);
|
||||||
padding-bottom: var(--sab, 0px);
|
padding-bottom: var(--sab, 0);
|
||||||
padding-left: var(--sal, 0px);
|
padding-left: var(--sal, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
.panelRow {
|
.panelRow {
|
||||||
@ -71,7 +79,7 @@
|
|||||||
margin-top: 0.333rem;
|
margin-top: 0.333rem;
|
||||||
margin-bottom: 0.333rem;
|
margin-bottom: 0.333rem;
|
||||||
font-size: 0.75rem;
|
font-size: 0.75rem;
|
||||||
color: var(--text-color-primary);
|
color: var(--text-primary-color);
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
@ -237,10 +245,10 @@
|
|||||||
left: 0;
|
left: 0;
|
||||||
right: 0;
|
right: 0;
|
||||||
--bar-padding: calc(4 * var(--space-factor));
|
--bar-padding: calc(4 * var(--space-factor));
|
||||||
padding-top: #{"max(var(--bar-padding), var(--sat, 0px))"};
|
padding-top: #{"max(var(--bar-padding), var(--sat,0))"};
|
||||||
padding-right: var(--sar, 0px);
|
padding-right: var(--sar, 0);
|
||||||
padding-bottom: var(--sab, 0px);
|
padding-bottom: var(--sab, 0);
|
||||||
padding-left: var(--sal, 0px);
|
padding-left: var(--sal, 0);
|
||||||
z-index: 4;
|
z-index: 4;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: flex-end;
|
align-items: flex-end;
|
||||||
@ -257,7 +265,7 @@
|
|||||||
pointer-events: initial;
|
pointer-events: initial;
|
||||||
|
|
||||||
.panelColumn {
|
.panelColumn {
|
||||||
padding: 8px 8px 0px 8px;
|
padding: 8px 8px 0 8px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -460,7 +468,7 @@
|
|||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
.scroll-back-to-content {
|
.scroll-back-to-content {
|
||||||
bottom: calc(80px + var(--sab, 0px));
|
bottom: calc(80px + var(--sab, 0));
|
||||||
z-index: -1;
|
z-index: -1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,75 +1,74 @@
|
|||||||
@import "open-color/open-color.scss";
|
@import "open-color/open-color.scss";
|
||||||
|
@import "./variables.module.scss";
|
||||||
|
|
||||||
:root {
|
.excalidraw {
|
||||||
--bg-color-island: rgba(255, 255, 255, 0.9);
|
--theme-filter: none;
|
||||||
--popup-background-color: #{$oc-white};
|
--button-destructive-bg-color: #{$oc-red-1};
|
||||||
--space-factor: 0.25rem;
|
--button-destructive-color: #{$oc-red-9};
|
||||||
--button-gray-1: #{$oc-gray-2};
|
--button-gray-1: #{$oc-gray-2};
|
||||||
--button-gray-2: #{$oc-gray-4};
|
--button-gray-2: #{$oc-gray-4};
|
||||||
--button-gray-3: #{$oc-gray-5};
|
--button-gray-3: #{$oc-gray-5};
|
||||||
--input-border-color: #{$oc-gray-3};
|
--button-special-active-bg-color: #{$oc-green-0};
|
||||||
--input-background-color: #{$oc-white};
|
--dialog-border-color: #{$oc-gray-6};
|
||||||
--input-hover-background-color: #{$oc-gray-1};
|
--dropdown-icon: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" width="292.4" height="292.4" viewBox="0 0 292 292"><path d="M287 197L159 69c-4-3-8-5-13-5s-9 2-13 5L5 197c-3 4-5 8-5 13s2 9 5 13c4 4 8 5 13 5h256c5 0 9-1 13-5s5-8 5-13-1-9-5-13z"/></svg>');
|
||||||
--input-label-color: #{$oc-gray-7};
|
--focus-highlight-color: #{$oc-blue-2};
|
||||||
--icon-fill-color: #{$oc-black};
|
--icon-fill-color: #{$oc-black};
|
||||||
--icon-green-fill-color: #{$oc-green-9};
|
--icon-green-fill-color: #{$oc-green-9};
|
||||||
|
--input-bg-color: #{$oc-white};
|
||||||
|
--input-border-color: #{$oc-gray-3};
|
||||||
|
--input-hover-bg-color: #{$oc-gray-1};
|
||||||
|
--input-label-color: #{$oc-gray-7};
|
||||||
|
--island-bg-color: rgba(255, 255, 255, 0.9);
|
||||||
--keybinding-color: #{$oc-gray-5};
|
--keybinding-color: #{$oc-gray-5};
|
||||||
--sat: env(safe-area-inset-top);
|
--link-color: #{$oc-blue-7};
|
||||||
|
--overlay-bg-color: #{transparentize($oc-white, 0.12)};
|
||||||
|
--popup-bg-color: #{$oc-white};
|
||||||
|
--popup-secondary-bg-color: #{$oc-gray-1};
|
||||||
|
--popup-text-color: #{$oc-black};
|
||||||
|
--popup-text-inverted-color: #{$oc-white};
|
||||||
--sab: env(safe-area-inset-bottom);
|
--sab: env(safe-area-inset-bottom);
|
||||||
--sal: env(safe-area-inset-left);
|
--sal: env(safe-area-inset-left);
|
||||||
--sar: env(safe-area-inset-right);
|
--sar: env(safe-area-inset-right);
|
||||||
--text-color-primary: #{$oc-gray-8};
|
--sat: env(safe-area-inset-top);
|
||||||
--shadow-island: 0 1px 5px #{transparentize($oc-black, 0.85)};
|
|
||||||
--overlay-background-color: #{transparentize($oc-white, 0.12)};
|
|
||||||
--dropdown-icon: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" width="292.4" height="292.4" viewBox="0 0 292 292"><path d="M287 197L159 69c-4-3-8-5-13-5s-9 2-13 5L5 197c-3 4-5 8-5 13s2 9 5 13c4 4 8 5 13 5h256c5 0 9-1 13-5s5-8 5-13-1-9-5-13z"/></svg>');
|
|
||||||
--focus-highlight-color: #{$oc-blue-2};
|
|
||||||
--select-highlight-color: #{$oc-blue-5};
|
--select-highlight-color: #{$oc-blue-5};
|
||||||
--appearance-filter: none;
|
--shadow-island: 0 1px 5px #{transparentize($oc-black, 0.85)};
|
||||||
--button-special-active-background-color: #{$oc-green-0};
|
--space-factor: 0.25rem;
|
||||||
--button-destructive-color: #{$oc-red-9};
|
--text-primary-color: #{$oc-gray-8};
|
||||||
--button-destructive-background-color: #{$oc-red-1};
|
|
||||||
--popup-secondary-background-color: #{$oc-gray-1};
|
|
||||||
--popup-text-color: #{$oc-black};
|
|
||||||
--popup-text-inverted-color: #{$oc-white};
|
|
||||||
--dialog-border: #{$oc-gray-6};
|
|
||||||
--link-color: #{$oc-blue-7};
|
|
||||||
}
|
|
||||||
|
|
||||||
.excalidraw {
|
&.theme--dark {
|
||||||
&.Appearance_dark {
|
background: $oc-black;
|
||||||
background: #000;
|
|
||||||
|
|
||||||
&.Appearance_dark-background-none {
|
&.theme--dark-background-none {
|
||||||
background: none;
|
background: none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&.Appearance_dark {
|
&.theme--dark {
|
||||||
--text-color-primary: #{$oc-gray-4};
|
--theme-filter: #{$theme-filter};
|
||||||
--bg-color-island: #1e1e1e;
|
--button-destructive-bg-color: #5a0000;
|
||||||
--popup-background-color: #2c2c2c;
|
--button-destructive-color: #{$oc-red-3};
|
||||||
--button-gray-1: #363636;
|
--button-gray-1: #363636;
|
||||||
--button-gray-2: #272727;
|
--button-gray-2: #272727;
|
||||||
--button-gray-3: #222;
|
--button-gray-3: #222;
|
||||||
--input-border-color: #2e2e2e;
|
--button-special-active-bg-color: #204624;
|
||||||
--input-background-color: #121212;
|
--dialog-border-color: #{$oc-gray-9};
|
||||||
--input-hover-background-color: #181818;
|
|
||||||
--input-label-color: #{$oc-gray-2};
|
|
||||||
--icon-fill-color: #{$oc-gray-4};
|
|
||||||
--icon-green-fill-color: #{$oc-green-4};
|
|
||||||
--keybinding-color: #{$oc-gray-6};
|
|
||||||
--shadow-island: 0 1px 5px #{transparentize($oc-black, 0.7)};
|
|
||||||
--overlay-background-color: rgba(30, 30, 30, 0.88);
|
|
||||||
--dropdown-icon: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" width="292.4" height="292.4" viewBox="0 0 292 292"><path fill="%23ced4da" d="M287 197L159 69c-4-3-8-5-13-5s-9 2-13 5L5 197c-3 4-5 8-5 13s2 9 5 13c4 4 8 5 13 5h256c5 0 9-1 13-5s5-8 5-13-1-9-5-13z"/></svg>');
|
--dropdown-icon: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" width="292.4" height="292.4" viewBox="0 0 292 292"><path fill="%23ced4da" d="M287 197L159 69c-4-3-8-5-13-5s-9 2-13 5L5 197c-3 4-5 8-5 13s2 9 5 13c4 4 8 5 13 5h256c5 0 9-1 13-5s5-8 5-13-1-9-5-13z"/></svg>');
|
||||||
--focus-highlight-color: #{$oc-blue-6};
|
--focus-highlight-color: #{$oc-blue-6};
|
||||||
--select-highlight-color: #{$oc-blue-4};
|
--icon-fill-color: #{$oc-gray-4};
|
||||||
--appearance-filter: invert(93%) hue-rotate(180deg);
|
--icon-green-fill-color: #{$oc-green-4};
|
||||||
--button-special-active-background-color: #204624;
|
--input-bg-color: #121212;
|
||||||
--button-destructive-color: #{$oc-red-3};
|
--input-border-color: #2e2e2e;
|
||||||
--button-destructive-background-color: #5a0000;
|
--input-hover-bg-color: #181818;
|
||||||
--popup-secondary-background-color: #222;
|
--input-label-color: #{$oc-gray-2};
|
||||||
|
--island-bg-color: #1e1e1e;
|
||||||
|
--keybinding-color: #{$oc-gray-6};
|
||||||
|
--overlay-bg-color: #{transparentize($oc-gray-8, 0.88)};
|
||||||
|
--popup-bg-color: #2c2c2c;
|
||||||
|
--popup-secondary-bg-color: #222;
|
||||||
--popup-text-color: #{$oc-gray-4};
|
--popup-text-color: #{$oc-gray-4};
|
||||||
--popup-text-inverted-color: #2c2c2c;
|
--popup-text-inverted-color: #2c2c2c;
|
||||||
--dialog-border: #{$oc-gray-9};
|
--select-highlight-color: #{$oc-blue-4};
|
||||||
|
--shadow-island: 0 1px 5px #{transparentize($oc-black, 0.7)};
|
||||||
|
--text-primary-color: #{$oc-gray-4};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,9 @@
|
|||||||
|
|
||||||
// keep up to date with is-mobile.tsx
|
// keep up to date with is-mobile.tsx
|
||||||
$is-mobile-query: "(max-width: 600px), (max-height: 500px) and (max-width: 1000px)";
|
$is-mobile-query: "(max-width: 600px), (max-height: 500px) and (max-width: 1000px)";
|
||||||
|
$theme-filter: "invert(93%) hue-rotate(180deg)";
|
||||||
|
|
||||||
:export {
|
:export {
|
||||||
isMobileQuery: unquote($is-mobile-query);
|
isMobileQuery: unquote($is-mobile-query);
|
||||||
|
themeFilter: unquote($theme-filter);
|
||||||
}
|
}
|
||||||
|
@ -5,8 +5,9 @@ import { CanvasError } from "../errors";
|
|||||||
import { t } from "../i18n";
|
import { t } from "../i18n";
|
||||||
import { calculateScrollCenter } from "../scene";
|
import { calculateScrollCenter } from "../scene";
|
||||||
import { AppState } from "../types";
|
import { AppState } from "../types";
|
||||||
|
import { isValidExcalidrawData } from "./json";
|
||||||
import { restore } from "./restore";
|
import { restore } from "./restore";
|
||||||
import { ImportedDataState, LibraryData } from "./types";
|
import { LibraryData } from "./types";
|
||||||
|
|
||||||
const parseFileContents = async (blob: Blob | File) => {
|
const parseFileContents = async (blob: Blob | File) => {
|
||||||
let contents: string;
|
let contents: string;
|
||||||
@ -85,15 +86,15 @@ export const loadFromBlob = async (
|
|||||||
) => {
|
) => {
|
||||||
const contents = await parseFileContents(blob);
|
const contents = await parseFileContents(blob);
|
||||||
try {
|
try {
|
||||||
const data: ImportedDataState = JSON.parse(contents);
|
const data = JSON.parse(contents);
|
||||||
if (data.type !== "excalidraw") {
|
if (!isValidExcalidrawData(data)) {
|
||||||
throw new Error(t("alerts.couldNotLoadInvalidFile"));
|
throw new Error(t("alerts.couldNotLoadInvalidFile"));
|
||||||
}
|
}
|
||||||
const result = restore(
|
const result = restore(
|
||||||
{
|
{
|
||||||
elements: clearElementsForExport(data.elements || []),
|
elements: clearElementsForExport(data.elements || []),
|
||||||
appState: {
|
appState: {
|
||||||
appearance: localAppState?.appearance,
|
theme: localAppState?.theme,
|
||||||
fileHandle:
|
fileHandle:
|
||||||
blob.handle &&
|
blob.handle &&
|
||||||
["application/json", MIME_TYPES.excalidraw].includes(
|
["application/json", MIME_TYPES.excalidraw].includes(
|
||||||
|
@ -41,6 +41,7 @@ export const exportCanvas = async (
|
|||||||
if (type === "svg" || type === "clipboard-svg") {
|
if (type === "svg" || type === "clipboard-svg") {
|
||||||
const tempSvg = exportToSvg(elements, {
|
const tempSvg = exportToSvg(elements, {
|
||||||
exportBackground,
|
exportBackground,
|
||||||
|
exportWithDarkMode: appState.exportWithDarkMode,
|
||||||
viewBackgroundColor,
|
viewBackgroundColor,
|
||||||
exportPadding,
|
exportPadding,
|
||||||
scale,
|
scale,
|
||||||
|
@ -6,6 +6,7 @@ import { ExcalidrawElement } from "../element/types";
|
|||||||
import { AppState } from "../types";
|
import { AppState } from "../types";
|
||||||
import { loadFromBlob } from "./blob";
|
import { loadFromBlob } from "./blob";
|
||||||
import { Library } from "./library";
|
import { Library } from "./library";
|
||||||
|
import { ImportedDataState } from "./types";
|
||||||
|
|
||||||
export const serializeAsJSON = (
|
export const serializeAsJSON = (
|
||||||
elements: readonly ExcalidrawElement[],
|
elements: readonly ExcalidrawElement[],
|
||||||
@ -29,13 +30,13 @@ export const saveAsJSON = async (
|
|||||||
) => {
|
) => {
|
||||||
const serialized = serializeAsJSON(elements, appState);
|
const serialized = serializeAsJSON(elements, appState);
|
||||||
const blob = new Blob([serialized], {
|
const blob = new Blob([serialized], {
|
||||||
type: "application/json",
|
type: MIME_TYPES.excalidraw,
|
||||||
});
|
});
|
||||||
|
|
||||||
const fileHandle = await fileSave(
|
const fileHandle = await fileSave(
|
||||||
blob,
|
blob,
|
||||||
{
|
{
|
||||||
fileName: appState.name,
|
fileName: `${appState.name}.excalidraw`,
|
||||||
description: "Excalidraw file",
|
description: "Excalidraw file",
|
||||||
extensions: [".excalidraw"],
|
extensions: [".excalidraw"],
|
||||||
},
|
},
|
||||||
@ -47,12 +48,34 @@ export const saveAsJSON = async (
|
|||||||
export const loadFromJSON = async (localAppState: AppState) => {
|
export const loadFromJSON = async (localAppState: AppState) => {
|
||||||
const blob = await fileOpen({
|
const blob = await fileOpen({
|
||||||
description: "Excalidraw files",
|
description: "Excalidraw files",
|
||||||
|
// ToDo: Be over-permissive until https://bugs.webkit.org/show_bug.cgi?id=34442
|
||||||
|
// gets resolved. Else, iOS users cannot open `.excalidraw` files.
|
||||||
|
/*
|
||||||
extensions: [".json", ".excalidraw", ".png", ".svg"],
|
extensions: [".json", ".excalidraw", ".png", ".svg"],
|
||||||
mimeTypes: ["application/json", "image/png", "image/svg+xml"],
|
mimeTypes: [
|
||||||
|
MIME_TYPES.excalidraw,
|
||||||
|
"application/json",
|
||||||
|
"image/png",
|
||||||
|
"image/svg+xml",
|
||||||
|
],
|
||||||
|
*/
|
||||||
});
|
});
|
||||||
return loadFromBlob(blob, localAppState);
|
return loadFromBlob(blob, localAppState);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const isValidExcalidrawData = (data?: {
|
||||||
|
type?: any;
|
||||||
|
elements?: any;
|
||||||
|
appState?: any;
|
||||||
|
}): data is ImportedDataState => {
|
||||||
|
return (
|
||||||
|
data?.type === "excalidraw" &&
|
||||||
|
(!data.elements ||
|
||||||
|
(Array.isArray(data.elements) &&
|
||||||
|
(!data.appState || typeof data.appState === "object")))
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
export const isValidLibrary = (json: any) => {
|
export const isValidLibrary = (json: any) => {
|
||||||
return (
|
return (
|
||||||
typeof json === "object" &&
|
typeof json === "object" &&
|
||||||
@ -87,8 +110,11 @@ export const saveLibraryAsJSON = async () => {
|
|||||||
export const importLibraryFromJSON = async () => {
|
export const importLibraryFromJSON = async () => {
|
||||||
const blob = await fileOpen({
|
const blob = await fileOpen({
|
||||||
description: "Excalidraw library files",
|
description: "Excalidraw library files",
|
||||||
|
// ToDo: Be over-permissive until https://bugs.webkit.org/show_bug.cgi?id=34442
|
||||||
|
// gets resolved. Else, iOS users cannot open `.excalidraw` files.
|
||||||
|
/*
|
||||||
extensions: [".json", ".excalidrawlib"],
|
extensions: [".json", ".excalidrawlib"],
|
||||||
mimeTypes: ["application/json"],
|
*/
|
||||||
});
|
});
|
||||||
Library.importLibrary(blob);
|
Library.importLibrary(blob);
|
||||||
};
|
};
|
||||||
|
@ -141,7 +141,7 @@ export const restoreElements = (
|
|||||||
}, [] as ExcalidrawElement[]);
|
}, [] as ExcalidrawElement[]);
|
||||||
};
|
};
|
||||||
|
|
||||||
const restoreAppState = (
|
export const restoreAppState = (
|
||||||
appState: ImportedDataState["appState"],
|
appState: ImportedDataState["appState"],
|
||||||
localAppState: Partial<AppState> | null,
|
localAppState: Partial<AppState> | null,
|
||||||
): AppState => {
|
): AppState => {
|
||||||
|
@ -15,6 +15,7 @@ export interface ImportedDataState {
|
|||||||
source?: string;
|
source?: string;
|
||||||
elements?: DataState["elements"] | null;
|
elements?: DataState["elements"] | null;
|
||||||
appState?: Partial<DataState["appState"]> | null;
|
appState?: Partial<DataState["appState"]> | null;
|
||||||
|
scrollToContent?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface LibraryData {
|
export interface LibraryData {
|
||||||
|
@ -182,9 +182,9 @@ const bindLinearElement = (
|
|||||||
} as PointBinding,
|
} as PointBinding,
|
||||||
});
|
});
|
||||||
mutateElement(hoveredElement, {
|
mutateElement(hoveredElement, {
|
||||||
boundElementIds: [
|
boundElementIds: Array.from(
|
||||||
...new Set([...(hoveredElement.boundElementIds ?? []), linearElement.id]),
|
new Set([...(hoveredElement.boundElementIds ?? []), linearElement.id]),
|
||||||
],
|
),
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -87,9 +87,30 @@ export const mutateElement = <TElement extends Mutable<ExcalidrawElement>>(
|
|||||||
export const newElementWith = <TElement extends ExcalidrawElement>(
|
export const newElementWith = <TElement extends ExcalidrawElement>(
|
||||||
element: TElement,
|
element: TElement,
|
||||||
updates: ElementUpdate<TElement>,
|
updates: ElementUpdate<TElement>,
|
||||||
): TElement => ({
|
): TElement => {
|
||||||
...element,
|
let didChange = false;
|
||||||
...updates,
|
for (const key in updates) {
|
||||||
version: element.version + 1,
|
const value = (updates as any)[key];
|
||||||
versionNonce: randomInteger(),
|
if (typeof value !== "undefined") {
|
||||||
});
|
if (
|
||||||
|
(element as any)[key] === value &&
|
||||||
|
// if object, always update in case its deep prop was mutated
|
||||||
|
(typeof value !== "object" || value === null || key === "groupIds")
|
||||||
|
) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
didChange = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!didChange) {
|
||||||
|
return element;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
...element,
|
||||||
|
...updates,
|
||||||
|
version: element.version + 1,
|
||||||
|
versionNonce: randomInteger(),
|
||||||
|
};
|
||||||
|
};
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user