Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 09daff487a | |||
| 17330c4c03 | |||
| 2a4ad6fc41 | |||
| ec6999554a |
@@ -0,0 +1,12 @@
|
||||
# http://EditorConfig.org
|
||||
|
||||
# top-level EditorConfig file
|
||||
root = true
|
||||
|
||||
[*]
|
||||
charset = utf-8
|
||||
end_of_line = lf
|
||||
indent_size = 2
|
||||
indent_style = space
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
||||
@@ -6,7 +6,7 @@ on:
|
||||
- master
|
||||
|
||||
jobs:
|
||||
build:
|
||||
build-docker:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
|
||||
@@ -13,10 +13,10 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
|
||||
- name: Setup Node.js 14.x
|
||||
- name: Setup Node.js 12.x
|
||||
uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: 14.x
|
||||
node-version: 12.x
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
name: Cancel previous runs
|
||||
|
||||
on: push
|
||||
|
||||
name: Cancel
|
||||
on: [push]
|
||||
jobs:
|
||||
cancel:
|
||||
name: "Cancel Previous Runs"
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
timeout-minutes: 3
|
||||
steps:
|
||||
- uses: styfle/cancel-workflow-action@0.6.0
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
name: Lint
|
||||
|
||||
on: push
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
lint:
|
||||
@@ -9,10 +13,10 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
|
||||
- name: Setup Node.js 14.x
|
||||
- name: Setup Node.js 12.x
|
||||
uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: 14.x
|
||||
node-version: 12.x
|
||||
|
||||
- name: Install and lint
|
||||
run: |
|
||||
@@ -20,3 +24,5 @@ jobs:
|
||||
npm run test:other
|
||||
npm run test:code
|
||||
npm run test:typecheck
|
||||
env:
|
||||
CI: true
|
||||
|
||||
@@ -14,18 +14,18 @@ jobs:
|
||||
with:
|
||||
token: ${{ secrets.PUSH_TRANSLATIONS_COVERAGE_PAT }}
|
||||
|
||||
- name: Setup Node.js 14.x
|
||||
- name: Setup Node.js 12.x
|
||||
uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: 14.x
|
||||
node-version: 12.x
|
||||
|
||||
- name: Create report file
|
||||
run: |
|
||||
npm run locales-coverage
|
||||
FILE_CHANGED=$(git diff src/locales/percentages.json)
|
||||
if [ ! -z "${FILE_CHANGED}" ]; then
|
||||
git config --global user.name 'Excalidraw Bot'
|
||||
git config --global user.email 'bot@excalidraw.com'
|
||||
git config --global user.name 'Kostas Bariotis'
|
||||
git config --global user.email 'konmpar@gmail.com'
|
||||
git add src/locales/percentages.json
|
||||
git commit -am "Auto commit: Calculate translation coverage"
|
||||
git push
|
||||
|
||||
@@ -10,7 +10,6 @@ on:
|
||||
jobs:
|
||||
main:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: amannn/action-semantic-pull-request@v3.0.0
|
||||
env:
|
||||
|
||||
@@ -8,14 +8,13 @@ on:
|
||||
jobs:
|
||||
release:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v1.0.0
|
||||
|
||||
- name: Setup Node.js 14.x
|
||||
- name: Setup Node.js 12.x
|
||||
uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: 14.x
|
||||
node-version: 12.x
|
||||
|
||||
- name: Install and build
|
||||
run: |
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
name: Tests
|
||||
|
||||
on: push
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
test:
|
||||
@@ -9,12 +13,14 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
|
||||
- name: Setup Node.js 14.x
|
||||
- name: Setup Node.js 12.x
|
||||
uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: 14.x
|
||||
node-version: 12.x
|
||||
|
||||
- name: Install and test
|
||||
run: |
|
||||
npm ci
|
||||
npm run test:app
|
||||
env:
|
||||
CI: true
|
||||
|
||||
Generated
+128
-73
@@ -4,9 +4,9 @@
|
||||
"lockfileVersion": 1,
|
||||
"dependencies": {
|
||||
"@apidevtools/json-schema-ref-parser": {
|
||||
"version": "9.0.7",
|
||||
"resolved": "https://registry.npmjs.org/@apidevtools/json-schema-ref-parser/-/json-schema-ref-parser-9.0.7.tgz",
|
||||
"integrity": "sha512-QdwOGF1+eeyFh+17v2Tz626WX0nucd1iKOm6JUTUvCZdbolblCOOQCxGrQPY0f7jEhn36PiAWqZnsC2r5vmUWg==",
|
||||
"version": "9.0.6",
|
||||
"resolved": "https://registry.npmjs.org/@apidevtools/json-schema-ref-parser/-/json-schema-ref-parser-9.0.6.tgz",
|
||||
"integrity": "sha512-M3YgsLjI0lZxvrpeGVk9Ap032W6TPQkH6pRAZz81Ac3WUNF79VQooAFnp8umjvVzUmD93NkogxEwbSce7qMsUg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@jsdevtools/ono": "^7.1.3",
|
||||
@@ -1308,9 +1308,9 @@
|
||||
"integrity": "sha512-Jj2xW+8+8XPfWGkv9HPv/uR+Qrmq37NPYT352wf7MvE9LrstpLVmFg3LqG6MCRr5miLAom5sen2gZ+iOhVDeRA=="
|
||||
},
|
||||
"@firebase/app": {
|
||||
"version": "0.6.14",
|
||||
"resolved": "https://registry.npmjs.org/@firebase/app/-/app-0.6.14.tgz",
|
||||
"integrity": "sha512-ZQKuiJ+fzr4tULgWoXbW+AZVTGsejOkSrlQ+zx78WiGKIubpFJLklnP3S0oYr/1nHzr4vaKuM4G8IL1Wv/+MpQ==",
|
||||
"version": "0.6.13",
|
||||
"resolved": "https://registry.npmjs.org/@firebase/app/-/app-0.6.13.tgz",
|
||||
"integrity": "sha512-xGrJETzvCb89VYbGSHFHCW7O/y067HRxT7MGehUE1xMxdPVBDNayHnxEuKwzfGvXAjVmajXBKFlKxaCWpgSjCQ==",
|
||||
"requires": {
|
||||
"@firebase/app-types": "0.6.1",
|
||||
"@firebase/component": "0.1.21",
|
||||
@@ -1334,9 +1334,9 @@
|
||||
"integrity": "sha512-L/ZnJRAq7F++utfuoTKX4CLBG5YR7tFO3PLzG1/oXXKEezJ0kRL3CMRoueBEmTCzVb/6SIs2Qlaw++uDgi5Xyg=="
|
||||
},
|
||||
"@firebase/auth": {
|
||||
"version": "0.16.2",
|
||||
"resolved": "https://registry.npmjs.org/@firebase/auth/-/auth-0.16.2.tgz",
|
||||
"integrity": "sha512-68TlDL0yh3kF8PiCzI8m8RWd/bf/xCLUsdz1NZ2Dwea0sp6e2WAhu0sem1GfhwuEwL+Ns4jCdX7qbe/OQlkVEA==",
|
||||
"version": "0.16.1",
|
||||
"resolved": "https://registry.npmjs.org/@firebase/auth/-/auth-0.16.1.tgz",
|
||||
"integrity": "sha512-7juD7D/kaxNti/xa5G+ZGJJs+bdJUWOW0MlNBtXwiG+TjMh69EDmwJnQmmc9h/32QVvXt1qo1OGWOoMMpF/2Gg==",
|
||||
"requires": {
|
||||
"@firebase/auth-types": "0.10.1"
|
||||
}
|
||||
@@ -1368,9 +1368,9 @@
|
||||
}
|
||||
},
|
||||
"@firebase/database": {
|
||||
"version": "0.9.1",
|
||||
"resolved": "https://registry.npmjs.org/@firebase/database/-/database-0.9.1.tgz",
|
||||
"integrity": "sha512-JdxgNvniSZiAx+lrdAQxkCZOTv+UfdmhRm9JA4RTs4XOpvwzmRtJTAIGBn+9CWXUAkWkjt5CYHLmYysD7NGj6g==",
|
||||
"version": "0.8.3",
|
||||
"resolved": "https://registry.npmjs.org/@firebase/database/-/database-0.8.3.tgz",
|
||||
"integrity": "sha512-i29rr3kcPltIkA8La9M1lgsSxx9bfu5lCQ0T+tbJptZ3UpqpcL1NzCcZa24cJjiLgq3HQNPyLvUvCtcPSFDlRg==",
|
||||
"requires": {
|
||||
"@firebase/auth-interop-types": "0.1.5",
|
||||
"@firebase/component": "0.1.21",
|
||||
@@ -1405,9 +1405,9 @@
|
||||
}
|
||||
},
|
||||
"@firebase/firestore": {
|
||||
"version": "2.1.4",
|
||||
"resolved": "https://registry.npmjs.org/@firebase/firestore/-/firestore-2.1.4.tgz",
|
||||
"integrity": "sha512-chSOvJyVoS7HmH7YOyqQP66wMwmsYNo2nPbFkrmQM/fRGXntNxXD1Greu1uts2hNyNeDLNrFHW5y7PlE3LAbwQ==",
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@firebase/firestore/-/firestore-2.1.2.tgz",
|
||||
"integrity": "sha512-8yUdBLLr6UhE+IjPR+fxLBD0bDnEqF9GalohfURZeLQPaL3b+LtqqGCLvvXC4MKT0lJAHOV8J9LA6rHj8vI0/Q==",
|
||||
"requires": {
|
||||
"@firebase/component": "0.1.21",
|
||||
"@firebase/firestore-types": "2.1.0",
|
||||
@@ -2663,70 +2663,125 @@
|
||||
}
|
||||
},
|
||||
"@sentry/browser": {
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-6.0.1.tgz",
|
||||
"integrity": "sha512-iP8Bqxj4Ye8CXA4ja77buPZfXsKiZYUgHFzBQxVMihTHA8ZZLgBMPLQI6uFfHuJJW+1/yLzOf8BhvF2zknAebg==",
|
||||
"version": "5.30.0",
|
||||
"resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-5.30.0.tgz",
|
||||
"integrity": "sha512-rOb58ZNVJWh1VuMuBG1mL9r54nZqKeaIlwSlvzJfc89vyfd7n6tQ1UXMN383QBz/MS5H5z44Hy5eE+7pCrYAfw==",
|
||||
"requires": {
|
||||
"@sentry/core": "6.0.1",
|
||||
"@sentry/types": "6.0.1",
|
||||
"@sentry/utils": "6.0.1",
|
||||
"@sentry/core": "5.30.0",
|
||||
"@sentry/types": "5.30.0",
|
||||
"@sentry/utils": "5.30.0",
|
||||
"tslib": "^1.9.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"@sentry/types": {
|
||||
"version": "5.30.0",
|
||||
"resolved": "https://registry.npmjs.org/@sentry/types/-/types-5.30.0.tgz",
|
||||
"integrity": "sha512-R8xOqlSTZ+htqrfteCWU5Nk0CDN5ApUTvrlvBuiH1DyP6czDZ4ktbZB0hAgBlVcK0U+qpD3ag3Tqqpa5Q67rPw=="
|
||||
},
|
||||
"@sentry/utils": {
|
||||
"version": "5.30.0",
|
||||
"resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-5.30.0.tgz",
|
||||
"integrity": "sha512-zaYmoH0NWWtvnJjC9/CBseXMtKHm/tm40sz3YfJRxeQjyzRqNQPgivpd9R/oDJCYj999mzdW382p/qi2ypjLww==",
|
||||
"requires": {
|
||||
"@sentry/types": "5.30.0",
|
||||
"tslib": "^1.9.3"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"@sentry/core": {
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@sentry/core/-/core-6.0.1.tgz",
|
||||
"integrity": "sha512-EoxgodyClasI8PA4GyU8Cp88W3R5ebpiLsE7fCcBcOU0DOBRkO8GAZ5IzfCDtYDJ50c9npivum5Oyj2wf8CXYw==",
|
||||
"version": "5.30.0",
|
||||
"resolved": "https://registry.npmjs.org/@sentry/core/-/core-5.30.0.tgz",
|
||||
"integrity": "sha512-TmfrII8w1PQZSZgPpUESqjB+jC6MvZJZdLtE/0hZ+SrnKhW3x5WlYLvTXZpcWePYBku7rl2wn1RZu6uT0qCTeg==",
|
||||
"requires": {
|
||||
"@sentry/hub": "6.0.1",
|
||||
"@sentry/minimal": "6.0.1",
|
||||
"@sentry/types": "6.0.1",
|
||||
"@sentry/utils": "6.0.1",
|
||||
"@sentry/hub": "5.30.0",
|
||||
"@sentry/minimal": "5.30.0",
|
||||
"@sentry/types": "5.30.0",
|
||||
"@sentry/utils": "5.30.0",
|
||||
"tslib": "^1.9.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"@sentry/types": {
|
||||
"version": "5.30.0",
|
||||
"resolved": "https://registry.npmjs.org/@sentry/types/-/types-5.30.0.tgz",
|
||||
"integrity": "sha512-R8xOqlSTZ+htqrfteCWU5Nk0CDN5ApUTvrlvBuiH1DyP6czDZ4ktbZB0hAgBlVcK0U+qpD3ag3Tqqpa5Q67rPw=="
|
||||
},
|
||||
"@sentry/utils": {
|
||||
"version": "5.30.0",
|
||||
"resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-5.30.0.tgz",
|
||||
"integrity": "sha512-zaYmoH0NWWtvnJjC9/CBseXMtKHm/tm40sz3YfJRxeQjyzRqNQPgivpd9R/oDJCYj999mzdW382p/qi2ypjLww==",
|
||||
"requires": {
|
||||
"@sentry/types": "5.30.0",
|
||||
"tslib": "^1.9.3"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"@sentry/hub": {
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@sentry/hub/-/hub-6.0.1.tgz",
|
||||
"integrity": "sha512-pGckNdhKcr7qYVXgSgA/QVGArATcmQu54YFAR5xTnkWVHpAwNmh0fc4CJCc4JBwS/LXSU1Y0nYiLQduVfnv8Cg==",
|
||||
"version": "5.30.0",
|
||||
"resolved": "https://registry.npmjs.org/@sentry/hub/-/hub-5.30.0.tgz",
|
||||
"integrity": "sha512-2tYrGnzb1gKz2EkMDQcfLrDTvmGcQPuWxLnJKXJvYTQDGLlEvi2tWz1VIHjunmOvJrB5aIQLhm+dcMRwFZDCqQ==",
|
||||
"requires": {
|
||||
"@sentry/types": "6.0.1",
|
||||
"@sentry/utils": "6.0.1",
|
||||
"@sentry/types": "5.30.0",
|
||||
"@sentry/utils": "5.30.0",
|
||||
"tslib": "^1.9.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"@sentry/types": {
|
||||
"version": "5.30.0",
|
||||
"resolved": "https://registry.npmjs.org/@sentry/types/-/types-5.30.0.tgz",
|
||||
"integrity": "sha512-R8xOqlSTZ+htqrfteCWU5Nk0CDN5ApUTvrlvBuiH1DyP6czDZ4ktbZB0hAgBlVcK0U+qpD3ag3Tqqpa5Q67rPw=="
|
||||
},
|
||||
"@sentry/utils": {
|
||||
"version": "5.30.0",
|
||||
"resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-5.30.0.tgz",
|
||||
"integrity": "sha512-zaYmoH0NWWtvnJjC9/CBseXMtKHm/tm40sz3YfJRxeQjyzRqNQPgivpd9R/oDJCYj999mzdW382p/qi2ypjLww==",
|
||||
"requires": {
|
||||
"@sentry/types": "5.30.0",
|
||||
"tslib": "^1.9.3"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"@sentry/integrations": {
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@sentry/integrations/-/integrations-6.0.1.tgz",
|
||||
"integrity": "sha512-5HGwKW0otSVXSLAJ9ezqlux4AYdeX6ElzQgpm6roWEBXEWf/5OyD0n+M3+yHq4NdQXk2kkfL/0DCyNdy8zZX2Q==",
|
||||
"version": "5.30.0",
|
||||
"resolved": "https://registry.npmjs.org/@sentry/integrations/-/integrations-5.30.0.tgz",
|
||||
"integrity": "sha512-Fqh4ALLoQWdd+1ih0iBduANWFyNmFWMxwvBu3V/wLDRi8OcquI0lEzWai1InzTJTiNhRHPnhuU++l/vkO0OCww==",
|
||||
"requires": {
|
||||
"@sentry/types": "6.0.1",
|
||||
"@sentry/utils": "6.0.1",
|
||||
"@sentry/types": "5.30.0",
|
||||
"@sentry/utils": "5.30.0",
|
||||
"localforage": "1.8.1",
|
||||
"tslib": "^1.9.3"
|
||||
}
|
||||
},
|
||||
"@sentry/minimal": {
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@sentry/minimal/-/minimal-6.0.1.tgz",
|
||||
"integrity": "sha512-TQ/M5A+OsxtQJ8dzHwrclxKXpJNdQeM1PUoYhff4BvsOXJScvZb7+Yn0OUEQXEc9pSMNt62tnQy4ct80iAMTHw==",
|
||||
"version": "5.30.0",
|
||||
"resolved": "https://registry.npmjs.org/@sentry/minimal/-/minimal-5.30.0.tgz",
|
||||
"integrity": "sha512-BwWb/owZKtkDX+Sc4zCSTNcvZUq7YcH3uAVlmh/gtR9rmUvbzAA3ewLuB3myi4wWRAMEtny6+J/FN/x+2wn9Xw==",
|
||||
"requires": {
|
||||
"@sentry/hub": "6.0.1",
|
||||
"@sentry/types": "6.0.1",
|
||||
"@sentry/hub": "5.30.0",
|
||||
"@sentry/types": "5.30.0",
|
||||
"tslib": "^1.9.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"@sentry/types": {
|
||||
"version": "5.30.0",
|
||||
"resolved": "https://registry.npmjs.org/@sentry/types/-/types-5.30.0.tgz",
|
||||
"integrity": "sha512-R8xOqlSTZ+htqrfteCWU5Nk0CDN5ApUTvrlvBuiH1DyP6czDZ4ktbZB0hAgBlVcK0U+qpD3ag3Tqqpa5Q67rPw=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"@sentry/types": {
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@sentry/types/-/types-6.0.1.tgz",
|
||||
"integrity": "sha512-cEoe19vtam75Tf6eWmaobfbeV8XwBdr5FJoSVTomzcSsEiP2FHGOEhlE7kVBigzeH5Lri0aibiW6BDi1hIqHdg=="
|
||||
"version": "5.30.0",
|
||||
"resolved": "https://registry.npmjs.org/@sentry/types/-/types-5.30.0.tgz",
|
||||
"integrity": "sha512-R8xOqlSTZ+htqrfteCWU5Nk0CDN5ApUTvrlvBuiH1DyP6czDZ4ktbZB0hAgBlVcK0U+qpD3ag3Tqqpa5Q67rPw=="
|
||||
},
|
||||
"@sentry/utils": {
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-6.0.1.tgz",
|
||||
"integrity": "sha512-bjGuBYnG6fulZ8mLhPGBxttNu96DCN6d7Glw2sfLf4aurn1kjJ/58hP2c8dH0OqWO5e+rGYTsZ5Dr5kqVKNGTg==",
|
||||
"version": "5.30.0",
|
||||
"resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-5.30.0.tgz",
|
||||
"integrity": "sha512-zaYmoH0NWWtvnJjC9/CBseXMtKHm/tm40sz3YfJRxeQjyzRqNQPgivpd9R/oDJCYj999mzdW382p/qi2ypjLww==",
|
||||
"requires": {
|
||||
"@sentry/types": "6.0.1",
|
||||
"@sentry/types": "5.30.0",
|
||||
"tslib": "^1.9.3"
|
||||
}
|
||||
},
|
||||
@@ -5100,10 +5155,10 @@
|
||||
"resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz",
|
||||
"integrity": "sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8="
|
||||
},
|
||||
"browser-fs-access": {
|
||||
"version": "0.13.0",
|
||||
"resolved": "https://registry.npmjs.org/browser-fs-access/-/browser-fs-access-0.13.0.tgz",
|
||||
"integrity": "sha512-qP8zFVhRQThxYgBXdlFHbzIrWb1us0G5kL2ZL0vW4BO5llKE4qBAcQsQrw4KN+6vjw8sKeWaGWJtzijfRT4N0Q=="
|
||||
"browser-nativefs": {
|
||||
"version": "0.12.0",
|
||||
"resolved": "https://registry.npmjs.org/browser-nativefs/-/browser-nativefs-0.12.0.tgz",
|
||||
"integrity": "sha512-ZCHJcQI6bBm9YjB+6wMT1nWg+/mnWnz7r3gJ8sx7RjgLtWROFq+BuD12cAncD6y45MIbUqFM8eMKXoHXOxSFxA=="
|
||||
},
|
||||
"browser-process-hrtime": {
|
||||
"version": "1.0.0",
|
||||
@@ -7932,9 +7987,9 @@
|
||||
}
|
||||
},
|
||||
"eslint-config-prettier": {
|
||||
"version": "7.2.0",
|
||||
"resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-7.2.0.tgz",
|
||||
"integrity": "sha512-rV4Qu0C3nfJKPOAhFujFxB7RMP+URFyQqqOZW9DMRD7ZDTFyjaIlETU3xzHELt++4ugC0+Jm084HQYkkJe+Ivg==",
|
||||
"version": "7.1.0",
|
||||
"resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-7.1.0.tgz",
|
||||
"integrity": "sha512-9sm5/PxaFG7qNJvJzTROMM1Bk1ozXVTKI0buKOyb0Bsr1hrwi0H/TzxF/COtf1uxikIK8SwhX7K6zg78jAzbeA==",
|
||||
"dev": true
|
||||
},
|
||||
"eslint-config-react-app": {
|
||||
@@ -9012,16 +9067,16 @@
|
||||
}
|
||||
},
|
||||
"firebase": {
|
||||
"version": "8.2.5",
|
||||
"resolved": "https://registry.npmjs.org/firebase/-/firebase-8.2.5.tgz",
|
||||
"integrity": "sha512-x9KUJR8PvqLUNzNKWHjAnO7rJVgK546G0F+vjlJTNl+J/8oFTdWh8X4PvYda0z0XM68A2Y9xPGf3blz5qHCn0A==",
|
||||
"version": "8.2.3",
|
||||
"resolved": "https://registry.npmjs.org/firebase/-/firebase-8.2.3.tgz",
|
||||
"integrity": "sha512-WdbcGSiLxiW/kGZT+EyqD9z3Md7kR35+k9qMjDn/twiIrm6Hh7Qi/Z69cqxhKW6+4uK5ghXIF28CjK67OyD9Qw==",
|
||||
"requires": {
|
||||
"@firebase/analytics": "0.6.2",
|
||||
"@firebase/app": "0.6.14",
|
||||
"@firebase/app": "0.6.13",
|
||||
"@firebase/app-types": "0.6.1",
|
||||
"@firebase/auth": "0.16.2",
|
||||
"@firebase/database": "0.9.1",
|
||||
"@firebase/firestore": "2.1.4",
|
||||
"@firebase/auth": "0.16.1",
|
||||
"@firebase/database": "0.8.3",
|
||||
"@firebase/firestore": "2.1.2",
|
||||
"@firebase/functions": "0.6.1",
|
||||
"@firebase/installations": "0.4.19",
|
||||
"@firebase/messaging": "0.7.3",
|
||||
@@ -9033,9 +9088,9 @@
|
||||
}
|
||||
},
|
||||
"firebase-tools": {
|
||||
"version": "9.2.2",
|
||||
"resolved": "https://registry.npmjs.org/firebase-tools/-/firebase-tools-9.2.2.tgz",
|
||||
"integrity": "sha512-AFjf7S9NjEM+u8ZByJEKASxRG1g+LLg/A0CrzA3V91P92MN+8cyrCigEs7mCdtFknLaShrCgzROyo/OEwd4xdA==",
|
||||
"version": "9.2.1",
|
||||
"resolved": "https://registry.npmjs.org/firebase-tools/-/firebase-tools-9.2.1.tgz",
|
||||
"integrity": "sha512-sD4wfB5hs/8IKXV6AJOmkpvXf/St7gVc9QeW4Qz21PG7CkirgRf6FqcYkPKtBcro4wfj48dihnYx/IO1+XPTGg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@google-cloud/pubsub": "^2.7.0",
|
||||
@@ -11090,9 +11145,9 @@
|
||||
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
|
||||
},
|
||||
"ini": {
|
||||
"version": "1.3.8",
|
||||
"resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz",
|
||||
"integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew=="
|
||||
"version": "1.3.5",
|
||||
"resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz",
|
||||
"integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw=="
|
||||
},
|
||||
"inquirer": {
|
||||
"version": "7.0.4",
|
||||
@@ -11507,9 +11562,9 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"ip-regex": {
|
||||
"version": "4.3.0",
|
||||
"resolved": "https://registry.npmjs.org/ip-regex/-/ip-regex-4.3.0.tgz",
|
||||
"integrity": "sha512-B9ZWJxHHOHUhUjCPrMpLD4xEq35bUTClHM1S6CBU5ixQnkZmwipwgc96vAd7AAGM9TGHvJR+Uss+/Ak6UphK+Q==",
|
||||
"version": "4.2.0",
|
||||
"resolved": "https://registry.npmjs.org/ip-regex/-/ip-regex-4.2.0.tgz",
|
||||
"integrity": "sha512-n5cDDeTWWRwK1EBoWwRti+8nP4NbytBBY0pldmnIkq6Z55KNFmWofh4rl9dPZpj+U/nVq7gweR3ylrvMt4YZ5A==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
|
||||
+19
-19
@@ -1,10 +1,5 @@
|
||||
{
|
||||
"browserslist": {
|
||||
"development": [
|
||||
"last 1 chrome version",
|
||||
"last 1 firefox version",
|
||||
"last 1 safari version"
|
||||
],
|
||||
"production": [
|
||||
">0.2%",
|
||||
"not dead",
|
||||
@@ -16,20 +11,25 @@
|
||||
"not chrome < 70",
|
||||
"not and_uc < 13",
|
||||
"not samsung < 10"
|
||||
],
|
||||
"development": [
|
||||
"last 1 chrome version",
|
||||
"last 1 firefox version",
|
||||
"last 1 safari version"
|
||||
]
|
||||
},
|
||||
"dependencies": {
|
||||
"@sentry/browser": "6.0.1",
|
||||
"@sentry/integrations": "6.0.1",
|
||||
"@sentry/browser": "5.30.0",
|
||||
"@sentry/integrations": "5.30.0",
|
||||
"@testing-library/jest-dom": "5.11.9",
|
||||
"@testing-library/react": "11.2.3",
|
||||
"@types/jest": "26.0.20",
|
||||
"@types/react": "17.0.0",
|
||||
"@types/react-dom": "17.0.0",
|
||||
"@types/socket.io-client": "1.4.35",
|
||||
"browser-fs-access": "0.13.0",
|
||||
"browser-nativefs": "0.12.0",
|
||||
"clsx": "1.1.1",
|
||||
"firebase": "8.2.5",
|
||||
"firebase": "8.2.3",
|
||||
"i18next-browser-languagedetector": "6.0.1",
|
||||
"lodash.throttle": "4.1.1",
|
||||
"nanoid": "3.1.20",
|
||||
@@ -51,9 +51,9 @@
|
||||
"devDependencies": {
|
||||
"@types/lodash.throttle": "4.1.6",
|
||||
"@types/pako": "1.0.1",
|
||||
"eslint-config-prettier": "7.2.0",
|
||||
"eslint-config-prettier": "7.1.0",
|
||||
"eslint-plugin-prettier": "3.3.1",
|
||||
"firebase-tools": "9.2.2",
|
||||
"firebase-tools": "9.2.1",
|
||||
"husky": "4.3.8",
|
||||
"jest-canvas-mock": "2.3.0",
|
||||
"lint-staged": "10.5.3",
|
||||
@@ -71,34 +71,34 @@
|
||||
}
|
||||
},
|
||||
"jest": {
|
||||
"resetMocks": false,
|
||||
"transformIgnorePatterns": [
|
||||
"node_modules/(?!(roughjs|points-on-curve|path-data-parser|points-on-path|browser-fs-access)/)"
|
||||
]
|
||||
"node_modules/(?!(roughjs|points-on-curve|path-data-parser|points-on-path|browser-nativefs)/)"
|
||||
],
|
||||
"resetMocks": false
|
||||
},
|
||||
"name": "excalidraw",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"build": "npm run build:app && npm run build:version",
|
||||
"build-node": "node ./scripts/build-node.js",
|
||||
"build:app": "REACT_APP_GIT_SHA=$VERCEL_GIT_COMMIT_SHA 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:version": "node ./scripts/build-version.js",
|
||||
"build": "npm run build:app && npm run build:version",
|
||||
"eject": "react-scripts eject",
|
||||
"fix": "npm run fix:other && npm run fix:code",
|
||||
"fix:code": "npm run test:code -- --fix",
|
||||
"fix:other": "npm run prettier -- --write",
|
||||
"fix": "npm run fix:other && npm run fix:code",
|
||||
"locales-coverage": "node scripts/build-locales-coverage.js",
|
||||
"locales-coverage:description": "node scripts/locales-coverage-description.js",
|
||||
"prettier": "prettier \"**/*.{css,scss,json,md,html,yml}\" --ignore-path=.eslintignore",
|
||||
"start": "react-scripts start",
|
||||
"test": "npm run test:app",
|
||||
"test:all": "npm run test:typecheck && npm run test:code && npm run test:other && npm run test:app -- --watchAll=false",
|
||||
"test:app": "react-scripts test --passWithNoTests",
|
||||
"test:code": "eslint --max-warnings=0 --ignore-path .gitignore --ext .js,.ts,.tsx .",
|
||||
"test:debug": "react-scripts --inspect-brk test --runInBand --no-cache",
|
||||
"test:other": "npm run prettier -- --list-different",
|
||||
"test:typecheck": "tsc",
|
||||
"test:update": "npm run test:app -- --updateSnapshot --watchAll=false"
|
||||
"test:update": "npm run test:app -- --updateSnapshot --watchAll=false",
|
||||
"test": "npm run test:app"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,5 +17,6 @@ export const actionAddToLibrary = register({
|
||||
});
|
||||
return false;
|
||||
},
|
||||
contextMenuOrder: 6,
|
||||
contextItemLabel: "labels.addToLibrary",
|
||||
});
|
||||
|
||||
@@ -3,7 +3,6 @@ import { getDefaultAppState } from "../appState";
|
||||
import { ColorPicker } from "../components/ColorPicker";
|
||||
import { resetZoom, trash, zoomIn, zoomOut } from "../components/icons";
|
||||
import { ToolButton } from "../components/ToolButton";
|
||||
import { ZOOM_STEP } from "../constants";
|
||||
import { getCommonBounds, getNonDeletedElements } from "../element";
|
||||
import { newElementWith } from "../element/mutateElement";
|
||||
import { ExcalidrawElement } from "../element/types";
|
||||
@@ -76,6 +75,8 @@ export const actionClearCanvas = register({
|
||||
),
|
||||
});
|
||||
|
||||
const ZOOM_STEP = 0.1;
|
||||
|
||||
export const actionZoomIn = register({
|
||||
name: "zoomIn",
|
||||
perform: (_elements, appState) => {
|
||||
|
||||
@@ -1,114 +0,0 @@
|
||||
import { CODES, KEYS } from "../keys";
|
||||
import { register } from "./register";
|
||||
import { copyToClipboard } from "../clipboard";
|
||||
import { actionDeleteSelected } from "./actionDeleteSelected";
|
||||
import { getSelectedElements } from "../scene/selection";
|
||||
import { exportCanvas } from "../data/index";
|
||||
import { getNonDeletedElements } from "../element";
|
||||
import { t } from "../i18n";
|
||||
|
||||
export const actionCopy = register({
|
||||
name: "copy",
|
||||
perform: (elements, appState) => {
|
||||
copyToClipboard(getNonDeletedElements(elements), appState);
|
||||
|
||||
return {
|
||||
commitToHistory: false,
|
||||
};
|
||||
},
|
||||
contextItemLabel: "labels.copy",
|
||||
keyTest: (event) => event[KEYS.CTRL_OR_CMD] && event.code === CODES.C,
|
||||
});
|
||||
|
||||
export const actionCut = register({
|
||||
name: "cut",
|
||||
perform: (elements, appState, data, app) => {
|
||||
actionCopy.perform(elements, appState, data, app);
|
||||
return actionDeleteSelected.perform(elements, appState, data, app);
|
||||
},
|
||||
contextItemLabel: "labels.cut",
|
||||
keyTest: (event) => event[KEYS.CTRL_OR_CMD] && event.code === CODES.X,
|
||||
});
|
||||
|
||||
export const actionCopyAsSvg = register({
|
||||
name: "copyAsSvg",
|
||||
perform: async (elements, appState, _data, app) => {
|
||||
if (!app.canvas) {
|
||||
return {
|
||||
commitToHistory: false,
|
||||
};
|
||||
}
|
||||
const selectedElements = getSelectedElements(
|
||||
getNonDeletedElements(elements),
|
||||
appState,
|
||||
);
|
||||
try {
|
||||
await exportCanvas(
|
||||
"clipboard-svg",
|
||||
selectedElements.length
|
||||
? selectedElements
|
||||
: getNonDeletedElements(elements),
|
||||
appState,
|
||||
app.canvas,
|
||||
appState,
|
||||
);
|
||||
return {
|
||||
commitToHistory: false,
|
||||
};
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
return {
|
||||
appState: {
|
||||
...appState,
|
||||
errorMessage: error.message,
|
||||
},
|
||||
commitToHistory: false,
|
||||
};
|
||||
}
|
||||
},
|
||||
contextItemLabel: "labels.copyAsSvg",
|
||||
});
|
||||
|
||||
export const actionCopyAsPng = register({
|
||||
name: "copyAsPng",
|
||||
perform: async (elements, appState, _data, app) => {
|
||||
if (!app.canvas) {
|
||||
return {
|
||||
commitToHistory: false,
|
||||
};
|
||||
}
|
||||
const selectedElements = getSelectedElements(
|
||||
getNonDeletedElements(elements),
|
||||
appState,
|
||||
);
|
||||
try {
|
||||
await exportCanvas(
|
||||
"clipboard",
|
||||
selectedElements.length
|
||||
? selectedElements
|
||||
: getNonDeletedElements(elements),
|
||||
appState,
|
||||
app.canvas,
|
||||
appState,
|
||||
);
|
||||
return {
|
||||
appState: {
|
||||
...appState,
|
||||
toastMessage: t("toast.copyToClipboardAsPng"),
|
||||
},
|
||||
commitToHistory: false,
|
||||
};
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
return {
|
||||
appState: {
|
||||
...appState,
|
||||
errorMessage: error.message,
|
||||
},
|
||||
commitToHistory: false,
|
||||
};
|
||||
}
|
||||
},
|
||||
contextItemLabel: "labels.copyAsPng",
|
||||
keyTest: (event) => event.code === CODES.C && event.altKey && event.shiftKey,
|
||||
});
|
||||
@@ -136,6 +136,7 @@ export const actionDeleteSelected = register({
|
||||
};
|
||||
},
|
||||
contextItemLabel: "labels.delete",
|
||||
contextMenuOrder: 999999,
|
||||
keyTest: (event) => event.key === KEYS.BACKSPACE || event.key === KEYS.DELETE,
|
||||
PanelComponent: ({ elements, appState, updateData }) => (
|
||||
<ToolButton
|
||||
|
||||
@@ -125,6 +125,7 @@ export const actionGroup = register({
|
||||
commitToHistory: true,
|
||||
};
|
||||
},
|
||||
contextMenuOrder: 4,
|
||||
contextItemLabel: "labels.group",
|
||||
contextItemPredicate: (elements, appState) =>
|
||||
enableActionGroup(elements, appState),
|
||||
@@ -173,6 +174,7 @@ export const actionUngroup = register({
|
||||
},
|
||||
keyTest: (event) =>
|
||||
event.shiftKey && event[KEYS.CTRL_OR_CMD] && event.code === CODES.G,
|
||||
contextMenuOrder: 5,
|
||||
contextItemLabel: "labels.ungroup",
|
||||
contextItemPredicate: (elements, appState) =>
|
||||
getSelectedGroupIds(appState).length > 0,
|
||||
|
||||
@@ -6,7 +6,7 @@ import { t } from "../i18n";
|
||||
import { SceneHistory, HistoryEntry } from "../history";
|
||||
import { ExcalidrawElement } from "../element/types";
|
||||
import { AppState } from "../types";
|
||||
import { isWindows, KEYS } from "../keys";
|
||||
import { KEYS } from "../keys";
|
||||
import { getElementMap } from "../element";
|
||||
import { newElementWith } from "../element/mutateElement";
|
||||
import { fixBindingsAfterDeletion } from "../element/binding";
|
||||
@@ -59,16 +59,16 @@ const writeData = (
|
||||
return { commitToHistory };
|
||||
};
|
||||
|
||||
const testUndo = (shift: boolean) => (event: KeyboardEvent) =>
|
||||
event[KEYS.CTRL_OR_CMD] && /z/i.test(event.key) && event.shiftKey === shift;
|
||||
|
||||
type ActionCreator = (history: SceneHistory) => Action;
|
||||
|
||||
export const createUndoAction: ActionCreator = (history) => ({
|
||||
name: "undo",
|
||||
perform: (elements, appState) =>
|
||||
writeData(elements, appState, () => history.undoOnce()),
|
||||
keyTest: (event) =>
|
||||
event[KEYS.CTRL_OR_CMD] &&
|
||||
event.key.toLowerCase() === KEYS.Z &&
|
||||
!event.shiftKey,
|
||||
keyTest: testUndo(false),
|
||||
PanelComponent: ({ updateData }) => (
|
||||
<ToolButton
|
||||
type="button"
|
||||
@@ -84,11 +84,7 @@ export const createRedoAction: ActionCreator = (history) => ({
|
||||
name: "redo",
|
||||
perform: (elements, appState) =>
|
||||
writeData(elements, appState, () => history.redoOnce()),
|
||||
keyTest: (event) =>
|
||||
(event[KEYS.CTRL_OR_CMD] &&
|
||||
event.shiftKey &&
|
||||
event.key.toLowerCase() === KEYS.Z) ||
|
||||
(isWindows && event.ctrlKey && !event.shiftKey && event.key === KEYS.Y),
|
||||
keyTest: testUndo(true),
|
||||
PanelComponent: ({ updateData }) => (
|
||||
<ToolButton
|
||||
type="button"
|
||||
|
||||
@@ -74,7 +74,7 @@ export const actionShortcuts = register({
|
||||
return {
|
||||
appState: {
|
||||
...appState,
|
||||
showHelpDialog: !appState.showHelpDialog,
|
||||
showHelpDialog: true,
|
||||
},
|
||||
commitToHistory: false,
|
||||
};
|
||||
|
||||
@@ -34,6 +34,7 @@ export const actionCopyStyles = register({
|
||||
contextItemLabel: "labels.copyStyles",
|
||||
keyTest: (event) =>
|
||||
event[KEYS.CTRL_OR_CMD] && event.altKey && event.code === CODES.C,
|
||||
contextMenuOrder: 0,
|
||||
});
|
||||
|
||||
export const actionPasteStyles = register({
|
||||
@@ -73,4 +74,5 @@ export const actionPasteStyles = register({
|
||||
contextItemLabel: "labels.pasteStyles",
|
||||
keyTest: (event) =>
|
||||
event[KEYS.CTRL_OR_CMD] && event.altKey && event.code === CODES.V,
|
||||
contextMenuOrder: 1,
|
||||
});
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
import { CODES, KEYS } from "../keys";
|
||||
import { register } from "./register";
|
||||
import { GRID_SIZE } from "../constants";
|
||||
import { AppState } from "../types";
|
||||
|
||||
export const actionToggleGridMode = register({
|
||||
name: "gridMode",
|
||||
perform(elements, appState) {
|
||||
return {
|
||||
appState: {
|
||||
...appState,
|
||||
gridSize: this.checked!(appState) ? null : GRID_SIZE,
|
||||
},
|
||||
commitToHistory: false,
|
||||
};
|
||||
},
|
||||
checked: (appState: AppState) => appState.gridSize !== null,
|
||||
contextItemLabel: "labels.gridMode",
|
||||
keyTest: (event) => event[KEYS.CTRL_OR_CMD] && event.code === CODES.QUOTE,
|
||||
});
|
||||
@@ -1,16 +0,0 @@
|
||||
import { register } from "./register";
|
||||
|
||||
export const actionToggleStats = register({
|
||||
name: "stats",
|
||||
perform(elements, appState) {
|
||||
return {
|
||||
appState: {
|
||||
...appState,
|
||||
showStats: !this.checked!(appState),
|
||||
},
|
||||
commitToHistory: false,
|
||||
};
|
||||
},
|
||||
checked: (appState) => appState.showStats,
|
||||
contextItemLabel: "stats.title",
|
||||
});
|
||||
@@ -1,19 +0,0 @@
|
||||
import { CODES, KEYS } from "../keys";
|
||||
import { register } from "./register";
|
||||
|
||||
export const actionToggleZenMode = register({
|
||||
name: "zenMode",
|
||||
perform(elements, appState) {
|
||||
return {
|
||||
appState: {
|
||||
...appState,
|
||||
zenModeEnabled: !this.checked!(appState),
|
||||
},
|
||||
commitToHistory: false,
|
||||
};
|
||||
},
|
||||
checked: (appState) => appState.zenModeEnabled,
|
||||
contextItemLabel: "buttons.zenMode",
|
||||
keyTest: (event) =>
|
||||
!event[KEYS.CTRL_OR_CMD] && event.altKey && event.code === CODES.Z,
|
||||
});
|
||||
@@ -65,15 +65,3 @@ export {
|
||||
distributeHorizontally,
|
||||
distributeVertically,
|
||||
} from "./actionDistribute";
|
||||
|
||||
export {
|
||||
actionCopy,
|
||||
actionCut,
|
||||
actionCopyAsPng,
|
||||
actionCopyAsSvg,
|
||||
} from "./actionClipboard";
|
||||
|
||||
export { actionToggleGridMode } from "./actionToggleGridMode";
|
||||
export { actionToggleZenMode } from "./actionToggleZenMode";
|
||||
|
||||
export { actionToggleStats } from "./actionToggleStats";
|
||||
|
||||
+37
-10
@@ -3,15 +3,14 @@ import {
|
||||
Action,
|
||||
ActionsManagerInterface,
|
||||
UpdaterFn,
|
||||
ActionFilterFn,
|
||||
ActionName,
|
||||
ActionResult,
|
||||
} from "./types";
|
||||
import { ExcalidrawElement } from "../element/types";
|
||||
import { AppState } from "../types";
|
||||
|
||||
// This is the <App> component, but for now we don't care about anything but its
|
||||
// `canvas` state.
|
||||
type App = { canvas: HTMLCanvasElement | null };
|
||||
import { t } from "../i18n";
|
||||
import { ShortcutName } from "./shortcuts";
|
||||
|
||||
export class ActionManager implements ActionsManagerInterface {
|
||||
actions = {} as ActionsManagerInterface["actions"];
|
||||
@@ -19,14 +18,13 @@ export class ActionManager implements ActionsManagerInterface {
|
||||
updater: (actionResult: ActionResult | Promise<ActionResult>) => void;
|
||||
|
||||
getAppState: () => Readonly<AppState>;
|
||||
|
||||
getElementsIncludingDeleted: () => readonly ExcalidrawElement[];
|
||||
app: App;
|
||||
|
||||
constructor(
|
||||
updater: UpdaterFn,
|
||||
getAppState: () => AppState,
|
||||
getElementsIncludingDeleted: () => readonly ExcalidrawElement[],
|
||||
app: App,
|
||||
) {
|
||||
this.updater = (actionResult) => {
|
||||
if (actionResult && "then" in actionResult) {
|
||||
@@ -39,7 +37,6 @@ export class ActionManager implements ActionsManagerInterface {
|
||||
};
|
||||
this.getAppState = getAppState;
|
||||
this.getElementsIncludingDeleted = getElementsIncludingDeleted;
|
||||
this.app = app;
|
||||
}
|
||||
|
||||
registerAction(action: Action) {
|
||||
@@ -73,7 +70,6 @@ export class ActionManager implements ActionsManagerInterface {
|
||||
this.getElementsIncludingDeleted(),
|
||||
this.getAppState(),
|
||||
null,
|
||||
this.app,
|
||||
),
|
||||
);
|
||||
return true;
|
||||
@@ -85,11 +81,43 @@ export class ActionManager implements ActionsManagerInterface {
|
||||
this.getElementsIncludingDeleted(),
|
||||
this.getAppState(),
|
||||
null,
|
||||
this.app,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
getContextMenuItems(actionFilter: ActionFilterFn = (action) => action) {
|
||||
return Object.values(this.actions)
|
||||
.filter(actionFilter)
|
||||
.filter((action) => "contextItemLabel" in action)
|
||||
.filter((action) =>
|
||||
action.contextItemPredicate
|
||||
? action.contextItemPredicate(
|
||||
this.getElementsIncludingDeleted(),
|
||||
this.getAppState(),
|
||||
)
|
||||
: true,
|
||||
)
|
||||
.sort(
|
||||
(a, b) =>
|
||||
(a.contextMenuOrder !== undefined ? a.contextMenuOrder : 999) -
|
||||
(b.contextMenuOrder !== undefined ? b.contextMenuOrder : 999),
|
||||
)
|
||||
.map((action) => ({
|
||||
// take last bit of the label "labels.<shortcutName>"
|
||||
shortcutName: action.contextItemLabel?.split(".").pop() as ShortcutName,
|
||||
label: action.contextItemLabel ? t(action.contextItemLabel) : "",
|
||||
action: () => {
|
||||
this.updater(
|
||||
action.perform(
|
||||
this.getElementsIncludingDeleted(),
|
||||
this.getAppState(),
|
||||
null,
|
||||
),
|
||||
);
|
||||
},
|
||||
}));
|
||||
}
|
||||
|
||||
// Id is an attribute that we can use to pass in data like keys.
|
||||
// This is needed for dynamically generated action components
|
||||
// like the user list. We can use this key to extract more
|
||||
@@ -104,7 +132,6 @@ export class ActionManager implements ActionsManagerInterface {
|
||||
this.getElementsIncludingDeleted(),
|
||||
this.getAppState(),
|
||||
formState,
|
||||
this.app,
|
||||
),
|
||||
);
|
||||
};
|
||||
|
||||
@@ -9,7 +9,7 @@ export type ShortcutName =
|
||||
| "copyStyles"
|
||||
| "pasteStyles"
|
||||
| "selectAll"
|
||||
| "deleteSelectedElements"
|
||||
| "delete"
|
||||
| "duplicateSelection"
|
||||
| "sendBackward"
|
||||
| "bringForward"
|
||||
@@ -31,7 +31,7 @@ const shortcutMap: Record<ShortcutName, string[]> = {
|
||||
copyStyles: [getShortcutKey("CtrlOrCmd+Alt+C")],
|
||||
pasteStyles: [getShortcutKey("CtrlOrCmd+Alt+V")],
|
||||
selectAll: [getShortcutKey("CtrlOrCmd+A")],
|
||||
deleteSelectedElements: [getShortcutKey("Del")],
|
||||
delete: [getShortcutKey("Del")],
|
||||
duplicateSelection: [
|
||||
getShortcutKey("CtrlOrCmd+D"),
|
||||
getShortcutKey(`Alt+${t("helpDialog.drag")}`),
|
||||
|
||||
+4
-10
@@ -16,18 +16,12 @@ type ActionFn = (
|
||||
elements: readonly ExcalidrawElement[],
|
||||
appState: Readonly<AppState>,
|
||||
formData: any,
|
||||
app: { canvas: HTMLCanvasElement | null },
|
||||
) => ActionResult | Promise<ActionResult>;
|
||||
|
||||
export type UpdaterFn = (res: ActionResult) => void;
|
||||
export type ActionFilterFn = (action: Action) => void;
|
||||
|
||||
export type ActionName =
|
||||
| "copy"
|
||||
| "cut"
|
||||
| "paste"
|
||||
| "copyAsPng"
|
||||
| "copyAsSvg"
|
||||
| "sendBackward"
|
||||
| "bringForward"
|
||||
| "sendToBack"
|
||||
@@ -35,9 +29,6 @@ export type ActionName =
|
||||
| "copyStyles"
|
||||
| "selectAll"
|
||||
| "pasteStyles"
|
||||
| "gridMode"
|
||||
| "zenMode"
|
||||
| "stats"
|
||||
| "changeStrokeColor"
|
||||
| "changeBackgroundColor"
|
||||
| "changeFillStyle"
|
||||
@@ -102,16 +93,19 @@ export interface Action {
|
||||
elements: readonly ExcalidrawElement[],
|
||||
) => boolean;
|
||||
contextItemLabel?: string;
|
||||
contextMenuOrder?: number;
|
||||
contextItemPredicate?: (
|
||||
elements: readonly ExcalidrawElement[],
|
||||
appState: AppState,
|
||||
) => boolean;
|
||||
checked?: (appState: Readonly<AppState>) => boolean;
|
||||
}
|
||||
|
||||
export interface ActionsManagerInterface {
|
||||
actions: Record<ActionName, Action>;
|
||||
registerAction: (action: Action) => void;
|
||||
handleKeyDown: (event: KeyboardEvent) => boolean;
|
||||
getContextMenuItems: (
|
||||
actionFilter: ActionFilterFn,
|
||||
) => { label: string; action: () => void }[];
|
||||
renderAction: (name: ActionName) => React.ReactElement | null;
|
||||
}
|
||||
|
||||
+3
-3
@@ -5,7 +5,7 @@ import {
|
||||
DEFAULT_TEXT_ALIGN,
|
||||
} from "./constants";
|
||||
import { t } from "./i18n";
|
||||
import { AppState, NormalizedZoomValue } from "./types";
|
||||
import { AppState, FlooredNumber, NormalizedZoomValue } from "./types";
|
||||
import { getDateTime } from "./utils";
|
||||
|
||||
export const getDefaultAppState = (): Omit<
|
||||
@@ -56,8 +56,8 @@ export const getDefaultAppState = (): Omit<
|
||||
previousSelectedElementIds: {},
|
||||
resizingElement: null,
|
||||
scrolledOutside: false,
|
||||
scrollX: 0,
|
||||
scrollY: 0,
|
||||
scrollX: 0 as FlooredNumber,
|
||||
scrollY: 0 as FlooredNumber,
|
||||
selectedElementIds: {},
|
||||
selectedGroupIds: {},
|
||||
selectionElement: null,
|
||||
|
||||
+147
-121
@@ -3,28 +3,7 @@ import React from "react";
|
||||
import { RoughCanvas } from "roughjs/bin/canvas";
|
||||
import rough from "roughjs/bin/rough";
|
||||
import "../actions";
|
||||
import {
|
||||
actionAddToLibrary,
|
||||
actionBringForward,
|
||||
actionBringToFront,
|
||||
actionCopy,
|
||||
actionCopyAsPng,
|
||||
actionCopyAsSvg,
|
||||
actionCopyStyles,
|
||||
actionCut,
|
||||
actionDeleteSelected,
|
||||
actionDuplicateSelection,
|
||||
actionFinalize,
|
||||
actionGroup,
|
||||
actionPasteStyles,
|
||||
actionSelectAll,
|
||||
actionSendBackward,
|
||||
actionSendToBack,
|
||||
actionToggleGridMode,
|
||||
actionToggleStats,
|
||||
actionToggleZenMode,
|
||||
actionUngroup,
|
||||
} from "../actions";
|
||||
import { actionDeleteSelected, actionFinalize } from "../actions";
|
||||
import { createRedoAction, createUndoAction } from "../actions/actionHistory";
|
||||
import { ActionManager } from "../actions/manager";
|
||||
import { actions } from "../actions/register";
|
||||
@@ -39,6 +18,7 @@ import {
|
||||
} from "../clipboard";
|
||||
import {
|
||||
APP_NAME,
|
||||
CANVAS_ONLY_ACTIONS,
|
||||
CURSOR_TYPE,
|
||||
DEFAULT_VERTICAL_ALIGN,
|
||||
DRAGGING_THRESHOLD,
|
||||
@@ -46,15 +26,15 @@ import {
|
||||
ELEMENT_TRANSLATE_AMOUNT,
|
||||
ENV,
|
||||
EVENT,
|
||||
GRID_SIZE,
|
||||
LINE_CONFIRM_THRESHOLD,
|
||||
MIME_TYPES,
|
||||
POINTER_BUTTON,
|
||||
TAP_TWICE_TIMEOUT,
|
||||
TEXT_TO_CENTER_SNAP_THRESHOLD,
|
||||
TOUCH_CTX_MENU_TIMEOUT,
|
||||
ZOOM_STEP,
|
||||
} from "../constants";
|
||||
import { loadFromBlob } from "../data";
|
||||
import { exportCanvas, loadFromBlob } from "../data";
|
||||
import { isValidLibrary } from "../data/json";
|
||||
import { Library } from "../data/library";
|
||||
import { restore } from "../data/restore";
|
||||
@@ -147,6 +127,7 @@ import {
|
||||
getSelectedElements,
|
||||
isOverScrollBars,
|
||||
isSomeElementSelected,
|
||||
normalizeScroll,
|
||||
} from "../scene";
|
||||
import Scene from "../scene/Scene";
|
||||
import { SceneState, ScrollBars } from "../scene/types";
|
||||
@@ -174,7 +155,6 @@ import {
|
||||
viewportCoordsToSceneCoords,
|
||||
withBatchedUpdates,
|
||||
} from "../utils";
|
||||
import { isMobile } from "../is-mobile";
|
||||
import ContextMenu from "./ContextMenu";
|
||||
import LayerUI from "./LayerUI";
|
||||
import { Stats } from "./Stats";
|
||||
@@ -268,7 +248,6 @@ export type ExcalidrawImperativeAPI = {
|
||||
};
|
||||
setScrollToCenter: InstanceType<typeof App>["setScrollToCenter"];
|
||||
getSceneElements: InstanceType<typeof App>["getSceneElements"];
|
||||
getAppState: () => InstanceType<typeof App>["state"];
|
||||
readyPromise: ResolvablePromise<ExcalidrawImperativeAPI>;
|
||||
ready: true;
|
||||
};
|
||||
@@ -319,7 +298,6 @@ class App extends React.Component<ExcalidrawProps, AppState> {
|
||||
},
|
||||
setScrollToCenter: this.setScrollToCenter,
|
||||
getSceneElements: this.getSceneElements,
|
||||
getAppState: () => this.state,
|
||||
} as const;
|
||||
if (typeof excalidrawRef === "function") {
|
||||
excalidrawRef(api);
|
||||
@@ -334,7 +312,6 @@ class App extends React.Component<ExcalidrawProps, AppState> {
|
||||
this.syncActionResult,
|
||||
() => this.state,
|
||||
() => this.scene.getElementsIncludingDeleted(),
|
||||
this,
|
||||
);
|
||||
this.actionManager.registerAll(actions);
|
||||
|
||||
@@ -929,6 +906,44 @@ class App extends React.Component<ExcalidrawProps, AppState> {
|
||||
copyToClipboard(this.scene.getElements(), this.state);
|
||||
};
|
||||
|
||||
private copyToClipboardAsPng = async () => {
|
||||
const elements = this.scene.getElements();
|
||||
|
||||
const selectedElements = getSelectedElements(elements, this.state);
|
||||
try {
|
||||
await exportCanvas(
|
||||
"clipboard",
|
||||
selectedElements.length ? selectedElements : elements,
|
||||
this.state,
|
||||
this.canvas!,
|
||||
this.state,
|
||||
);
|
||||
this.setState({ toastMessage: t("toast.copyToClipboardAsPng") });
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
this.setState({ errorMessage: error.message });
|
||||
}
|
||||
};
|
||||
|
||||
private copyToClipboardAsSvg = async () => {
|
||||
const selectedElements = getSelectedElements(
|
||||
this.scene.getElements(),
|
||||
this.state,
|
||||
);
|
||||
try {
|
||||
await exportCanvas(
|
||||
"clipboard-svg",
|
||||
selectedElements.length ? selectedElements : this.scene.getElements(),
|
||||
this.state,
|
||||
this.canvas!,
|
||||
this.state,
|
||||
);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
this.setState({ errorMessage: error.message });
|
||||
}
|
||||
};
|
||||
|
||||
private static resetTapTwice() {
|
||||
didTapTwice = false;
|
||||
}
|
||||
@@ -1131,18 +1146,24 @@ class App extends React.Component<ExcalidrawProps, AppState> {
|
||||
};
|
||||
|
||||
toggleZenMode = () => {
|
||||
this.actionManager.executeAction(actionToggleZenMode);
|
||||
this.setState({
|
||||
zenModeEnabled: !this.state.zenModeEnabled,
|
||||
});
|
||||
};
|
||||
|
||||
toggleGridMode = () => {
|
||||
this.actionManager.executeAction(actionToggleGridMode);
|
||||
this.setState({
|
||||
gridSize: this.state.gridSize ? null : GRID_SIZE,
|
||||
});
|
||||
};
|
||||
|
||||
toggleStats = () => {
|
||||
if (!this.state.showStats) {
|
||||
trackEvent("dialog", "stats");
|
||||
}
|
||||
this.actionManager.executeAction(actionToggleStats);
|
||||
this.setState({
|
||||
showStats: !this.state.showStats,
|
||||
});
|
||||
};
|
||||
|
||||
setScrollToCenter = (remoteElements: readonly ExcalidrawElement[]) => {
|
||||
@@ -1232,10 +1253,23 @@ class App extends React.Component<ExcalidrawProps, AppState> {
|
||||
});
|
||||
}
|
||||
|
||||
if (!event[KEYS.CTRL_OR_CMD] && event.altKey && event.code === CODES.Z) {
|
||||
this.toggleZenMode();
|
||||
}
|
||||
|
||||
if (event[KEYS.CTRL_OR_CMD] && event.code === CODES.QUOTE) {
|
||||
this.toggleGridMode();
|
||||
}
|
||||
if (event[KEYS.CTRL_OR_CMD]) {
|
||||
this.setState({ isBindingEnabled: false });
|
||||
}
|
||||
|
||||
if (event.code === CODES.C && event.altKey && event.shiftKey) {
|
||||
this.copyToClipboardAsPng();
|
||||
event.preventDefault();
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.actionManager.handleKeyDown(event)) {
|
||||
return;
|
||||
}
|
||||
@@ -1744,8 +1778,8 @@ class App extends React.Component<ExcalidrawProps, AppState> {
|
||||
const scaleFactor = distance / gesture.initialDistance;
|
||||
|
||||
this.setState(({ zoom, scrollX, scrollY, offsetLeft, offsetTop }) => ({
|
||||
scrollX: scrollX + deltaX / zoom.value,
|
||||
scrollY: scrollY + deltaY / zoom.value,
|
||||
scrollX: normalizeScroll(scrollX + deltaX / zoom.value),
|
||||
scrollY: normalizeScroll(scrollY + deltaY / zoom.value),
|
||||
zoom: getNewZoom(
|
||||
getNormalizedZoom(initialScale * scaleFactor),
|
||||
zoom,
|
||||
@@ -2156,8 +2190,12 @@ class App extends React.Component<ExcalidrawProps, AppState> {
|
||||
}
|
||||
|
||||
this.setState({
|
||||
scrollX: this.state.scrollX - deltaX / this.state.zoom.value,
|
||||
scrollY: this.state.scrollY - deltaY / this.state.zoom.value,
|
||||
scrollX: normalizeScroll(
|
||||
this.state.scrollX - deltaX / this.state.zoom.value,
|
||||
),
|
||||
scrollY: normalizeScroll(
|
||||
this.state.scrollY - deltaY / this.state.zoom.value,
|
||||
),
|
||||
});
|
||||
});
|
||||
const teardown = withBatchedUpdates(
|
||||
@@ -2971,7 +3009,9 @@ class App extends React.Component<ExcalidrawProps, AppState> {
|
||||
const x = event.clientX;
|
||||
const dx = x - pointerDownState.lastCoords.x;
|
||||
this.setState({
|
||||
scrollX: this.state.scrollX - dx / this.state.zoom.value,
|
||||
scrollX: normalizeScroll(
|
||||
this.state.scrollX - dx / this.state.zoom.value,
|
||||
),
|
||||
});
|
||||
pointerDownState.lastCoords.x = x;
|
||||
return true;
|
||||
@@ -2981,7 +3021,9 @@ class App extends React.Component<ExcalidrawProps, AppState> {
|
||||
const y = event.clientY;
|
||||
const dy = y - pointerDownState.lastCoords.y;
|
||||
this.setState({
|
||||
scrollY: this.state.scrollY - dy / this.state.zoom.value,
|
||||
scrollY: normalizeScroll(
|
||||
this.state.scrollY - dy / this.state.zoom.value,
|
||||
),
|
||||
});
|
||||
pointerDownState.lastCoords.y = y;
|
||||
return true;
|
||||
@@ -3574,56 +3616,52 @@ class App extends React.Component<ExcalidrawProps, AppState> {
|
||||
this.state,
|
||||
);
|
||||
|
||||
const maybeGroupAction = actionGroup.contextItemPredicate!(
|
||||
this.actionManager.getElementsIncludingDeleted(),
|
||||
this.actionManager.getAppState(),
|
||||
);
|
||||
|
||||
const maybeUngroupAction = actionUngroup.contextItemPredicate!(
|
||||
this.actionManager.getElementsIncludingDeleted(),
|
||||
this.actionManager.getAppState(),
|
||||
);
|
||||
|
||||
const separator = "separator";
|
||||
|
||||
const _isMobile = isMobile();
|
||||
|
||||
const elements = this.scene.getElements();
|
||||
const element = this.getElementAtPosition(x, y);
|
||||
if (!element) {
|
||||
ContextMenu.push({
|
||||
options: [
|
||||
_isMobile &&
|
||||
navigator.clipboard && {
|
||||
name: "paste",
|
||||
perform: (elements, appStates) => {
|
||||
this.pasteFromClipboard(null);
|
||||
return {
|
||||
commitToHistory: false,
|
||||
};
|
||||
},
|
||||
contextItemLabel: "labels.paste",
|
||||
},
|
||||
_isMobile && navigator.clipboard && separator,
|
||||
navigator.clipboard && {
|
||||
shortcutName: "paste",
|
||||
label: t("labels.paste"),
|
||||
action: () => this.pasteFromClipboard(null),
|
||||
},
|
||||
probablySupportsClipboardBlob &&
|
||||
elements.length > 0 &&
|
||||
actionCopyAsPng,
|
||||
elements.length > 0 && {
|
||||
shortcutName: "copyAsPng",
|
||||
label: t("labels.copyAsPng"),
|
||||
action: this.copyToClipboardAsPng,
|
||||
},
|
||||
probablySupportsClipboardWriteText &&
|
||||
elements.length > 0 &&
|
||||
actionCopyAsSvg,
|
||||
((probablySupportsClipboardBlob && elements.length > 0) ||
|
||||
(probablySupportsClipboardWriteText && elements.length > 0)) &&
|
||||
separator,
|
||||
actionSelectAll,
|
||||
separator,
|
||||
actionToggleGridMode,
|
||||
actionToggleZenMode,
|
||||
actionToggleStats,
|
||||
elements.length > 0 && {
|
||||
shortcutName: "copyAsSvg",
|
||||
label: t("labels.copyAsSvg"),
|
||||
action: this.copyToClipboardAsSvg,
|
||||
},
|
||||
...this.actionManager.getContextMenuItems((action) =>
|
||||
CANVAS_ONLY_ACTIONS.includes(action.name),
|
||||
),
|
||||
{
|
||||
checked: this.state.gridSize !== null,
|
||||
shortcutName: "gridMode",
|
||||
label: t("labels.gridMode"),
|
||||
action: this.toggleGridMode,
|
||||
},
|
||||
{
|
||||
checked: this.state.zenModeEnabled,
|
||||
shortcutName: "zenMode",
|
||||
label: t("buttons.zenMode"),
|
||||
action: this.toggleZenMode,
|
||||
},
|
||||
{
|
||||
checked: this.state.showStats,
|
||||
shortcutName: "stats",
|
||||
label: t("stats.title"),
|
||||
action: this.toggleStats,
|
||||
},
|
||||
],
|
||||
top: clientY,
|
||||
left: clientX,
|
||||
actionManager: this.actionManager,
|
||||
appState: this.state,
|
||||
});
|
||||
return;
|
||||
}
|
||||
@@ -3634,43 +3672,37 @@ class App extends React.Component<ExcalidrawProps, AppState> {
|
||||
|
||||
ContextMenu.push({
|
||||
options: [
|
||||
_isMobile && actionCut,
|
||||
_isMobile && navigator.clipboard && actionCopy,
|
||||
_isMobile &&
|
||||
navigator.clipboard && {
|
||||
name: "paste",
|
||||
perform: (elements, appStates) => {
|
||||
this.pasteFromClipboard(null);
|
||||
return {
|
||||
commitToHistory: false,
|
||||
};
|
||||
},
|
||||
contextItemLabel: "labels.paste",
|
||||
},
|
||||
_isMobile && separator,
|
||||
probablySupportsClipboardBlob && actionCopyAsPng,
|
||||
probablySupportsClipboardWriteText && actionCopyAsSvg,
|
||||
separator,
|
||||
actionCopyStyles,
|
||||
actionPasteStyles,
|
||||
separator,
|
||||
maybeGroupAction && actionGroup,
|
||||
maybeUngroupAction && actionUngroup,
|
||||
(maybeGroupAction || maybeUngroupAction) && separator,
|
||||
actionAddToLibrary,
|
||||
separator,
|
||||
actionSendBackward,
|
||||
actionBringForward,
|
||||
actionSendToBack,
|
||||
actionBringToFront,
|
||||
separator,
|
||||
actionDuplicateSelection,
|
||||
actionDeleteSelected,
|
||||
{
|
||||
shortcutName: "cut",
|
||||
label: t("labels.cut"),
|
||||
action: this.cutAll,
|
||||
},
|
||||
navigator.clipboard && {
|
||||
shortcutName: "copy",
|
||||
label: t("labels.copy"),
|
||||
action: this.copyAll,
|
||||
},
|
||||
navigator.clipboard && {
|
||||
shortcutName: "paste",
|
||||
label: t("labels.paste"),
|
||||
action: () => this.pasteFromClipboard(null),
|
||||
},
|
||||
probablySupportsClipboardBlob && {
|
||||
shortcutName: "copyAsPng",
|
||||
label: t("labels.copyAsPng"),
|
||||
action: this.copyToClipboardAsPng,
|
||||
},
|
||||
probablySupportsClipboardWriteText && {
|
||||
shortcutName: "copyAsSvg",
|
||||
label: t("labels.copyAsSvg"),
|
||||
action: this.copyToClipboardAsSvg,
|
||||
},
|
||||
...this.actionManager.getContextMenuItems(
|
||||
(action) => !CANVAS_ONLY_ACTIONS.includes(action.name),
|
||||
),
|
||||
],
|
||||
top: clientY,
|
||||
left: clientX,
|
||||
actionManager: this.actionManager,
|
||||
appState: this.state,
|
||||
});
|
||||
};
|
||||
|
||||
@@ -3701,15 +3733,9 @@ class App extends React.Component<ExcalidrawProps, AppState> {
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
let newZoom = this.state.zoom.value - delta / 100;
|
||||
// increase zoom steps the more zoomed-in we are (applies to >100% only)
|
||||
newZoom += Math.log10(Math.max(1, this.state.zoom.value)) * -sign;
|
||||
// round to nearest step
|
||||
newZoom = Math.round(newZoom * ZOOM_STEP * 100) / (ZOOM_STEP * 100);
|
||||
|
||||
this.setState(({ zoom, offsetLeft, offsetTop }) => ({
|
||||
zoom: getNewZoom(
|
||||
getNormalizedZoom(newZoom),
|
||||
getNormalizedZoom(zoom.value - delta / 100),
|
||||
zoom,
|
||||
{ left: offsetLeft, top: offsetTop },
|
||||
{
|
||||
@@ -3732,14 +3758,14 @@ class App extends React.Component<ExcalidrawProps, AppState> {
|
||||
if (event.shiftKey) {
|
||||
this.setState(({ zoom, scrollX }) => ({
|
||||
// on Mac, shift+wheel tends to result in deltaX
|
||||
scrollX: scrollX - (deltaY || deltaX) / zoom.value,
|
||||
scrollX: normalizeScroll(scrollX - (deltaY || deltaX) / zoom.value),
|
||||
}));
|
||||
return;
|
||||
}
|
||||
|
||||
this.setState(({ zoom, scrollX, scrollY }) => ({
|
||||
scrollX: scrollX - deltaX / zoom.value,
|
||||
scrollY: scrollY - deltaY / zoom.value,
|
||||
scrollX: normalizeScroll(scrollX - deltaX / zoom.value),
|
||||
scrollY: normalizeScroll(scrollY - deltaY / zoom.value),
|
||||
}));
|
||||
});
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
@import "../css/variables.module";
|
||||
@import "../css/_variables";
|
||||
|
||||
.excalidraw {
|
||||
.Avatar {
|
||||
|
||||
@@ -14,11 +14,11 @@ export const ButtonIconCycle = <T extends any>({
|
||||
}) => {
|
||||
const current = options.find((op) => op.value === value);
|
||||
|
||||
const cycle = () => {
|
||||
function cycle() {
|
||||
const index = options.indexOf(current!);
|
||||
const next = (index + 1) % options.length;
|
||||
onChange(options[next].value);
|
||||
};
|
||||
}
|
||||
|
||||
return (
|
||||
<label key={group} className={clsx({ active: current!.value !== null })}>
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
@import "../css/variables.module";
|
||||
@import "../css/_variables";
|
||||
|
||||
.excalidraw {
|
||||
.CollabButton.is-collaborating {
|
||||
background-color: var(--button-special-active-bg-color);
|
||||
background-color: var(--button-special-active-background-color);
|
||||
|
||||
.ToolIcon__icon svg {
|
||||
color: var(--icon-green-fill-color);
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
@import "../css/variables.module";
|
||||
@import "../css/_variables";
|
||||
|
||||
.excalidraw {
|
||||
.color-picker {
|
||||
background: var(--popup-bg-color);
|
||||
border: 0 solid transparentize($oc-white, 0.75);
|
||||
box-shadow: transparentize($oc-black, 0.75) 0 1px 4px;
|
||||
background: var(--popup-background-color);
|
||||
border: 0px solid transparentize($oc-white, 0.75);
|
||||
box-shadow: transparentize($oc-black, 0.75) 0px 1px 4px;
|
||||
border-radius: 4px;
|
||||
position: absolute;
|
||||
|
||||
@@ -24,11 +24,11 @@
|
||||
}
|
||||
|
||||
.color-picker-triangle {
|
||||
width: 0;
|
||||
height: 0;
|
||||
width: 0px;
|
||||
height: 0px;
|
||||
border-style: solid;
|
||||
border-width: 0 9px 10px;
|
||||
border-color: transparent transparent var(--popup-bg-color);
|
||||
border-width: 0px 9px 10px;
|
||||
border-color: transparent transparent var(--popup-background-color);
|
||||
position: absolute;
|
||||
top: -10px;
|
||||
|
||||
@@ -84,12 +84,12 @@
|
||||
|
||||
.color-picker-transparent {
|
||||
border-radius: 4px;
|
||||
box-shadow: transparentize($oc-black, 0.9) 0 0 0 1px inset;
|
||||
box-shadow: transparentize($oc-black, 0.9) 0px 0px 0px 1px inset;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
top: 0px;
|
||||
right: 0px;
|
||||
bottom: 0px;
|
||||
left: 0px;
|
||||
}
|
||||
|
||||
.color-picker-transparent,
|
||||
@@ -104,11 +104,11 @@
|
||||
width: 1.875rem;
|
||||
|
||||
:root[dir="ltr"] & {
|
||||
border-radius: 4px 0 0 4px;
|
||||
border-radius: 4px 0px 0px 4px;
|
||||
}
|
||||
|
||||
:root[dir="rtl"] & {
|
||||
border-radius: 0 4px 4px 0;
|
||||
border-radius: 0px 4px 4px 0px;
|
||||
}
|
||||
|
||||
color: var(--input-label-color);
|
||||
@@ -144,7 +144,7 @@
|
||||
}
|
||||
|
||||
.color-input-container:focus-within .color-picker-hash::after {
|
||||
background: var(--input-bg-color);
|
||||
background: var(--input-background-color);
|
||||
|
||||
:root[dir="ltr"] & {
|
||||
right: -2px;
|
||||
@@ -163,19 +163,19 @@
|
||||
width: 12ch; /* length of `transparent` + 1 */
|
||||
margin: 0;
|
||||
font-size: 1rem;
|
||||
background-color: var(--input-bg-color);
|
||||
background-color: var(--input-background-color);
|
||||
color: var(--text-color-primary);
|
||||
border: 0;
|
||||
border: 0px;
|
||||
outline: none;
|
||||
height: 1.75em;
|
||||
box-shadow: var(--input-border-color) 0 0 0 1px inset;
|
||||
box-shadow: var(--input-border-color) 0px 0px 0px 1px inset;
|
||||
|
||||
:root[dir="ltr"] & {
|
||||
border-radius: 0 4px 4px 0;
|
||||
border-radius: 0px 4px 4px 0px;
|
||||
}
|
||||
|
||||
:root[dir="rtl"] & {
|
||||
border-radius: 4px 0 0 4px;
|
||||
border-radius: 4px 0px 0px 4px;
|
||||
}
|
||||
|
||||
float: left;
|
||||
|
||||
@@ -1,18 +1,17 @@
|
||||
@import "../css/variables.module";
|
||||
@import "../css/_variables";
|
||||
|
||||
.excalidraw {
|
||||
.context-menu {
|
||||
position: relative;
|
||||
border-radius: 4px;
|
||||
box-shadow: 0 3px 10px transparentize($oc-black, 0.8);
|
||||
box-shadow: 0px 3px 10px transparentize($oc-black, 0.8);
|
||||
padding: 0;
|
||||
list-style: none;
|
||||
user-select: none;
|
||||
margin: -0.25rem 0 0 0.125rem;
|
||||
padding: 0.5rem 0;
|
||||
background-color: var(--popup-secondary-bg-color);
|
||||
padding: 0.25rem 0;
|
||||
background-color: var(--popup-secondary-background-color);
|
||||
border: 1px solid var(--button-gray-3);
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.context-menu button {
|
||||
@@ -61,12 +60,12 @@
|
||||
}
|
||||
|
||||
.context-menu-option:hover {
|
||||
color: var(--popup-bg-color);
|
||||
color: var(--popup-background-color);
|
||||
background-color: var(--select-highlight-color);
|
||||
|
||||
&.dangerous {
|
||||
.context-menu-option__label {
|
||||
color: var(--popup-bg-color);
|
||||
color: var(--popup-background-color);
|
||||
}
|
||||
background-color: $oc-red-6;
|
||||
}
|
||||
@@ -89,9 +88,4 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.context-menu-option-separator {
|
||||
border: none;
|
||||
border-top: 1px solid $oc-gray-5;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,36 +2,28 @@ import React from "react";
|
||||
import { render, unmountComponentAtNode } from "react-dom";
|
||||
import clsx from "clsx";
|
||||
import { Popover } from "./Popover";
|
||||
import { t } from "../i18n";
|
||||
|
||||
import "./ContextMenu.scss";
|
||||
import {
|
||||
getShortcutFromShortcutName,
|
||||
ShortcutName,
|
||||
} from "../actions/shortcuts";
|
||||
import { Action } from "../actions/types";
|
||||
import { ActionManager } from "../actions/manager";
|
||||
import { AppState } from "../types";
|
||||
|
||||
type ContextMenuOption = "separator" | Action;
|
||||
type ContextMenuOption = {
|
||||
checked?: boolean;
|
||||
shortcutName: ShortcutName;
|
||||
label: string;
|
||||
action(): void;
|
||||
};
|
||||
|
||||
type ContextMenuProps = {
|
||||
type Props = {
|
||||
options: ContextMenuOption[];
|
||||
onCloseRequest?(): void;
|
||||
top: number;
|
||||
left: number;
|
||||
actionManager: ActionManager;
|
||||
appState: Readonly<AppState>;
|
||||
};
|
||||
|
||||
const ContextMenu = ({
|
||||
options,
|
||||
onCloseRequest,
|
||||
top,
|
||||
left,
|
||||
actionManager,
|
||||
appState,
|
||||
}: ContextMenuProps) => {
|
||||
const ContextMenu = ({ options, onCloseRequest, top, left }: Props) => {
|
||||
const isDarkTheme = !!document
|
||||
.querySelector(".excalidraw")
|
||||
?.classList.contains("Appearance_dark");
|
||||
@@ -51,34 +43,23 @@ const ContextMenu = ({
|
||||
className="context-menu"
|
||||
onContextMenu={(event) => event.preventDefault()}
|
||||
>
|
||||
{options.map((option, idx) => {
|
||||
if (option === "separator") {
|
||||
return <hr key={idx} className="context-menu-option-separator" />;
|
||||
}
|
||||
|
||||
const actionName = option.name;
|
||||
const label = option.contextItemLabel
|
||||
? t(option.contextItemLabel)
|
||||
: "";
|
||||
return (
|
||||
<li key={idx} data-testid={actionName} onClick={onCloseRequest}>
|
||||
<button
|
||||
className={clsx("context-menu-option", {
|
||||
dangerous: actionName === "deleteSelectedElements",
|
||||
checkmark: option.checked?.(appState),
|
||||
})}
|
||||
onClick={() => actionManager.executeAction(option)}
|
||||
>
|
||||
<div className="context-menu-option__label">{label}</div>
|
||||
<kbd className="context-menu-option__shortcut">
|
||||
{actionName
|
||||
? getShortcutFromShortcutName(actionName as ShortcutName)
|
||||
: ""}
|
||||
</kbd>
|
||||
</button>
|
||||
</li>
|
||||
);
|
||||
})}
|
||||
{options.map(({ action, checked, shortcutName, label }, idx) => (
|
||||
<li data-testid={shortcutName} key={idx} onClick={onCloseRequest}>
|
||||
<button
|
||||
className={`context-menu-option
|
||||
${shortcutName === "delete" ? "dangerous" : ""}
|
||||
${checked ? "checkmark" : ""}`}
|
||||
onClick={action}
|
||||
>
|
||||
<div className="context-menu-option__label">{label}</div>
|
||||
<kbd className="context-menu-option__shortcut">
|
||||
{shortcutName
|
||||
? getShortcutFromShortcutName(shortcutName)
|
||||
: ""}
|
||||
</kbd>
|
||||
</button>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</Popover>
|
||||
</div>
|
||||
@@ -97,10 +78,8 @@ const getContextMenuNode = (): HTMLDivElement => {
|
||||
|
||||
type ContextMenuParams = {
|
||||
options: (ContextMenuOption | false | null | undefined)[];
|
||||
top: ContextMenuProps["top"];
|
||||
left: ContextMenuProps["left"];
|
||||
actionManager: ContextMenuProps["actionManager"];
|
||||
appState: Readonly<AppState>;
|
||||
top: number;
|
||||
left: number;
|
||||
};
|
||||
|
||||
const handleClose = () => {
|
||||
@@ -122,8 +101,6 @@ export default {
|
||||
left={params.left}
|
||||
options={options}
|
||||
onCloseRequest={handleClose}
|
||||
actionManager={params.actionManager}
|
||||
appState={params.appState}
|
||||
/>,
|
||||
getContextMenuNode(),
|
||||
);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
@import "../css/variables.module";
|
||||
@import "../css/_variables";
|
||||
|
||||
.excalidraw {
|
||||
.Dialog {
|
||||
@@ -45,7 +45,7 @@
|
||||
position: sticky;
|
||||
top: 0;
|
||||
padding: calc(var(--space-factor) * 2);
|
||||
background: var(--island-bg-color);
|
||||
background: var(--bg-color-island);
|
||||
font-size: 1.25em;
|
||||
|
||||
box-sizing: border-box;
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import clsx from "clsx";
|
||||
import React, { useEffect } from "react";
|
||||
import { useCallbackRefState } from "../hooks/useCallbackRefState";
|
||||
import React, { useCallback, useEffect, useState } from "react";
|
||||
import { t } from "../i18n";
|
||||
import useIsMobile from "../is-mobile";
|
||||
import { KEYS } from "../keys";
|
||||
@@ -9,6 +8,14 @@ import { back, close } from "./icons";
|
||||
import { Island } from "./Island";
|
||||
import { Modal } from "./Modal";
|
||||
|
||||
const useRefState = <T,>() => {
|
||||
const [refValue, setRefValue] = useState<T | null>(null);
|
||||
const refCallback = useCallback((value: T) => {
|
||||
setRefValue(value);
|
||||
}, []);
|
||||
return [refValue, refCallback] as const;
|
||||
};
|
||||
|
||||
export const Dialog = (props: {
|
||||
children: React.ReactNode;
|
||||
className?: string;
|
||||
@@ -17,7 +24,7 @@ export const Dialog = (props: {
|
||||
title: React.ReactNode;
|
||||
autofocus?: boolean;
|
||||
}) => {
|
||||
const [islandNode, setIslandNode] = useCallbackRefState<HTMLDivElement>();
|
||||
const [islandNode, setIslandNode] = useRefState<HTMLDivElement>();
|
||||
|
||||
useEffect(() => {
|
||||
if (!islandNode) {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
@import "../css/variables.module";
|
||||
@import "../css/_variables";
|
||||
|
||||
.excalidraw {
|
||||
.ExportDialog__preview {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
@import "../css/variables.module";
|
||||
@import "../css/_variables";
|
||||
|
||||
.excalidraw {
|
||||
.HelpDialog h3 {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React from "react";
|
||||
import { t } from "../i18n";
|
||||
import { isDarwin, isWindows } from "../keys";
|
||||
import { isDarwin } from "../keys";
|
||||
import { Dialog } from "./Dialog";
|
||||
import { getShortcutKey } from "../utils";
|
||||
import "./HelpDialog.scss";
|
||||
@@ -328,14 +328,7 @@ export const HelpDialog = ({ onClose }: { onClose?: () => void }) => {
|
||||
/>
|
||||
<Shortcut
|
||||
label={t("buttons.redo")}
|
||||
shortcuts={
|
||||
isWindows
|
||||
? [
|
||||
getShortcutKey("CtrlOrCmd+Y"),
|
||||
getShortcutKey("CtrlOrCmd+Shift+Z"),
|
||||
]
|
||||
: [getShortcutKey("CtrlOrCmd+Shift+Z")]
|
||||
}
|
||||
shortcuts={[getShortcutKey("CtrlOrCmd+Shift+Z")]}
|
||||
/>
|
||||
<Shortcut
|
||||
label={t("labels.group")}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
@import "../css/variables.module";
|
||||
@import "../css/_variables";
|
||||
|
||||
// this is loosely based on the longest hint text
|
||||
$wide-viewport-width: 1000px;
|
||||
@@ -26,7 +26,7 @@ $wide-viewport-width: 1000px;
|
||||
|
||||
> span {
|
||||
padding: 0.2rem 0.4rem;
|
||||
background-color: var(--overlay-bg-color);
|
||||
background-color: var(--overlay-background-color);
|
||||
border-radius: 4px;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
@import "../css/variables.module";
|
||||
@import "../css/_variables";
|
||||
|
||||
.excalidraw {
|
||||
.picker-container {
|
||||
@@ -8,9 +8,9 @@
|
||||
}
|
||||
|
||||
.picker {
|
||||
background: var(--popup-bg-color);
|
||||
border: 0 solid transparentize($oc-white, 0.75);
|
||||
box-shadow: transparentize($oc-black, 0.75) 0 1px 4px;
|
||||
background: var(--popup-background-color);
|
||||
border: 0px solid transparentize($oc-white, 0.75);
|
||||
box-shadow: transparentize($oc-black, 0.75) 0px 1px 4px;
|
||||
border-radius: 4px;
|
||||
position: absolute;
|
||||
}
|
||||
@@ -56,8 +56,8 @@
|
||||
}
|
||||
|
||||
.picker-triangle {
|
||||
width: 0;
|
||||
height: 0;
|
||||
width: 0px;
|
||||
height: 0px;
|
||||
position: relative;
|
||||
top: -10px;
|
||||
:root[dir="ltr"] & {
|
||||
@@ -73,7 +73,7 @@
|
||||
content: "";
|
||||
position: absolute;
|
||||
border-style: solid;
|
||||
border-width: 0 9px 10px;
|
||||
border-width: 0px 9px 10px;
|
||||
border-color: transparent transparent transparentize($oc-black, 0.9);
|
||||
top: -1px;
|
||||
}
|
||||
@@ -82,8 +82,8 @@
|
||||
content: "";
|
||||
position: absolute;
|
||||
border-style: solid;
|
||||
border-width: 0 9px 10px;
|
||||
border-color: transparent transparent var(--popup-bg-color);
|
||||
border-width: 0px 9px 10px;
|
||||
border-color: transparent transparent var(--popup-background-color);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
.excalidraw {
|
||||
.Island {
|
||||
--padding: 0;
|
||||
background-color: var(--island-bg-color);
|
||||
background-color: var(--bg-color-island);
|
||||
backdrop-filter: saturate(100%) blur(10px);
|
||||
box-shadow: var(--shadow-island);
|
||||
border-radius: 4px;
|
||||
|
||||
@@ -71,7 +71,7 @@
|
||||
&__footer {
|
||||
position: absolute;
|
||||
z-index: 100;
|
||||
bottom: 0;
|
||||
bottom: 0px;
|
||||
|
||||
:root[dir="ltr"] & {
|
||||
right: 0;
|
||||
@@ -92,7 +92,7 @@
|
||||
}
|
||||
|
||||
:root[dir="ltr"] &.transition-right {
|
||||
transform: translate(999px, 0);
|
||||
transform: translate(999px, 0px);
|
||||
}
|
||||
|
||||
:root[dir="rtl"] &.transition-left {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
@import "../css/variables.module";
|
||||
@import "../css/_variables";
|
||||
|
||||
.excalidraw {
|
||||
.Modal {
|
||||
@@ -39,7 +39,7 @@
|
||||
overflow-y: auto;
|
||||
|
||||
// for modals, reset blurry bg
|
||||
background: var(--island-bg-color);
|
||||
background: var(--bg-color-island);
|
||||
backdrop-filter: none;
|
||||
|
||||
border: 1px solid var(--dialog-border);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
@import "../css/variables.module";
|
||||
@import "../css/_variables";
|
||||
|
||||
.excalidraw {
|
||||
.PasteChartDialog {
|
||||
|
||||
+41
-43
@@ -1,53 +1,51 @@
|
||||
@import "../css/variables.module";
|
||||
@import "../css/_variables";
|
||||
|
||||
.excalidraw {
|
||||
.Stats {
|
||||
position: fixed;
|
||||
top: 64px;
|
||||
right: 12px;
|
||||
font-size: 12px;
|
||||
z-index: 999;
|
||||
.Stats {
|
||||
position: fixed;
|
||||
top: 64px;
|
||||
right: 12px;
|
||||
font-size: 12px;
|
||||
z-index: 999;
|
||||
|
||||
h3 {
|
||||
margin: 0 24px 8px 0;
|
||||
white-space: nowrap;
|
||||
}
|
||||
h3 {
|
||||
margin: 0 24px 8px 0;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.close {
|
||||
float: right;
|
||||
height: 16px;
|
||||
width: 16px;
|
||||
cursor: pointer;
|
||||
svg {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
table {
|
||||
.close {
|
||||
float: right;
|
||||
height: 16px;
|
||||
width: 16px;
|
||||
cursor: pointer;
|
||||
svg {
|
||||
width: 100%;
|
||||
th {
|
||||
border-bottom: 1px solid var(--input-border-color);
|
||||
padding: 4px;
|
||||
}
|
||||
tr {
|
||||
td:nth-child(2) {
|
||||
min-width: 24px;
|
||||
text-align: right;
|
||||
}
|
||||
}
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
:root[dir="rtl"] & {
|
||||
left: 12px;
|
||||
right: initial;
|
||||
|
||||
h3 {
|
||||
margin: 0 0 8px 24px;
|
||||
}
|
||||
.close {
|
||||
float: left;
|
||||
table {
|
||||
width: 100%;
|
||||
th {
|
||||
border-bottom: 1px solid var(--input-border-color);
|
||||
padding: 4px;
|
||||
}
|
||||
tr {
|
||||
td:nth-child(2) {
|
||||
min-width: 24px;
|
||||
text-align: right;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
:root[dir="rtl"] & {
|
||||
left: 12px;
|
||||
right: initial;
|
||||
|
||||
h3 {
|
||||
margin: 0 0 8px 24px;
|
||||
}
|
||||
.close {
|
||||
float: left;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
@import "../css/variables.module";
|
||||
@import "../css/_variables.scss";
|
||||
|
||||
.excalidraw {
|
||||
.TextInput {
|
||||
@@ -9,11 +9,11 @@
|
||||
padding: 0.75rem;
|
||||
white-space: nowrap;
|
||||
border-radius: var(--space-factor);
|
||||
background-color: var(--input-bg-color);
|
||||
background-color: var(--input-background-color);
|
||||
|
||||
&:not(:focus) {
|
||||
&:hover {
|
||||
background-color: var(--input-hover-bg-color);
|
||||
background-color: var(--input-hover-background-color);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
@import "../css/variables.module";
|
||||
@import "../css/_variables";
|
||||
|
||||
.excalidraw {
|
||||
.Toast {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
@import "open-color/open-color.scss";
|
||||
@import "../css/variables.module";
|
||||
@import "../css/variables";
|
||||
|
||||
.excalidraw {
|
||||
.ToolIcon {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
@import "../css/variables.module";
|
||||
@import "../css/_variables";
|
||||
.excalidraw {
|
||||
.Tooltip {
|
||||
position: relative;
|
||||
|
||||
+23
-25
@@ -2,50 +2,50 @@ import { FontFamily } from "./element/types";
|
||||
|
||||
export const APP_NAME = "Excalidraw";
|
||||
|
||||
export const DRAGGING_THRESHOLD = 10;
|
||||
export const LINE_CONFIRM_THRESHOLD = 10;
|
||||
export const DRAGGING_THRESHOLD = 10; // 10px
|
||||
export const LINE_CONFIRM_THRESHOLD = 10; // 10px
|
||||
export const ELEMENT_SHIFT_TRANSLATE_AMOUNT = 5;
|
||||
export const ELEMENT_TRANSLATE_AMOUNT = 1;
|
||||
export const TEXT_TO_CENTER_SNAP_THRESHOLD = 30;
|
||||
export const SHIFT_LOCKING_ANGLE = Math.PI / 12;
|
||||
export const CURSOR_TYPE = {
|
||||
AUTO: "",
|
||||
TEXT: "text",
|
||||
CROSSHAIR: "crosshair",
|
||||
GRABBING: "grabbing",
|
||||
MOVE: "move",
|
||||
POINTER: "pointer",
|
||||
TEXT: "text",
|
||||
MOVE: "move",
|
||||
AUTO: "",
|
||||
};
|
||||
export const POINTER_BUTTON = {
|
||||
MAIN: 0,
|
||||
WHEEL: 1,
|
||||
SECONDARY: 2,
|
||||
TOUCH: -1,
|
||||
WHEEL: 1,
|
||||
};
|
||||
|
||||
export enum EVENT {
|
||||
BEFORE_UNLOAD = "beforeunload",
|
||||
BLUR = "blur",
|
||||
COPY = "copy",
|
||||
PASTE = "paste",
|
||||
CUT = "cut",
|
||||
DRAG_OVER = "dragover",
|
||||
DROP = "drop",
|
||||
GESTURE_CHANGE = "gesturechange",
|
||||
GESTURE_END = "gestureend",
|
||||
GESTURE_START = "gesturestart",
|
||||
HASHCHANGE = "hashchange",
|
||||
KEYDOWN = "keydown",
|
||||
KEYUP = "keyup",
|
||||
MOUSE_MOVE = "mousemove",
|
||||
PASTE = "paste",
|
||||
RESIZE = "resize",
|
||||
UNLOAD = "unload",
|
||||
BLUR = "blur",
|
||||
DRAG_OVER = "dragover",
|
||||
DROP = "drop",
|
||||
GESTURE_END = "gestureend",
|
||||
BEFORE_UNLOAD = "beforeunload",
|
||||
GESTURE_START = "gesturestart",
|
||||
GESTURE_CHANGE = "gesturechange",
|
||||
POINTER_MOVE = "pointermove",
|
||||
POINTER_UP = "pointerup",
|
||||
RESIZE = "resize",
|
||||
STATE_CHANGE = "statechange",
|
||||
TOUCH_END = "touchend",
|
||||
TOUCH_START = "touchstart",
|
||||
UNLOAD = "unload",
|
||||
WHEEL = "wheel",
|
||||
TOUCH_START = "touchstart",
|
||||
TOUCH_END = "touchend",
|
||||
HASHCHANGE = "hashchange",
|
||||
}
|
||||
|
||||
export const ENV = {
|
||||
@@ -66,11 +66,11 @@ export const FONT_FAMILY = {
|
||||
|
||||
export const WINDOWS_EMOJI_FALLBACK_FONT = "Segoe UI Emoji";
|
||||
|
||||
export const DEFAULT_FONT_FAMILY: FontFamily = 1;
|
||||
export const DEFAULT_FONT_SIZE = 20;
|
||||
export const DEFAULT_FONT_FAMILY: FontFamily = 1;
|
||||
export const DEFAULT_TEXT_ALIGN = "left";
|
||||
export const DEFAULT_VERSION = "{version}";
|
||||
export const DEFAULT_VERTICAL_ALIGN = "top";
|
||||
export const DEFAULT_VERSION = "{version}";
|
||||
|
||||
export const CANVAS_ONLY_ACTIONS = ["selectAll"];
|
||||
|
||||
@@ -85,11 +85,9 @@ export const STORAGE_KEYS = {
|
||||
LOCAL_STORAGE_LIBRARY: "excalidraw-library",
|
||||
};
|
||||
|
||||
// Time in milliseconds
|
||||
// time in milliseconds
|
||||
export const TAP_TWICE_TIMEOUT = 300;
|
||||
export const TOUCH_CTX_MENU_TIMEOUT = 500;
|
||||
export const TITLE_TIMEOUT = 10000;
|
||||
export const TOAST_TIMEOUT = 5000;
|
||||
export const TOUCH_CTX_MENU_TIMEOUT = 500;
|
||||
export const VERSION_TIMEOUT = 15000;
|
||||
|
||||
export const ZOOM_STEP = 0.1;
|
||||
|
||||
@@ -1,42 +0,0 @@
|
||||
import React from "react";
|
||||
|
||||
export const createInverseContext = <T extends unknown = null>(
|
||||
initialValue: T,
|
||||
) => {
|
||||
const Context = React.createContext(initialValue) as React.Context<T> & {
|
||||
_updateProviderValue?: (value: T) => void;
|
||||
};
|
||||
|
||||
class InverseConsumer extends React.Component {
|
||||
state = { value: initialValue };
|
||||
constructor(props: any) {
|
||||
super(props);
|
||||
Context._updateProviderValue = (value: T) => this.setState({ value });
|
||||
}
|
||||
render() {
|
||||
return (
|
||||
<Context.Provider value={this.state.value}>
|
||||
{this.props.children}
|
||||
</Context.Provider>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class InverseProvider extends React.Component<{ value: T }> {
|
||||
componentDidMount() {
|
||||
Context._updateProviderValue?.(this.props.value);
|
||||
}
|
||||
componentDidUpdate() {
|
||||
Context._updateProviderValue?.(this.props.value);
|
||||
}
|
||||
render() {
|
||||
return <Context.Consumer>{() => this.props.children}</Context.Consumer>;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
Context,
|
||||
Consumer: InverseConsumer,
|
||||
Provider: InverseProvider,
|
||||
};
|
||||
};
|
||||
@@ -1,8 +1,4 @@
|
||||
@import "open-color/open-color.scss";
|
||||
|
||||
// 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)";
|
||||
|
||||
:export {
|
||||
isMobileQuery: unquote($is-mobile-query);
|
||||
}
|
||||
+11
-11
@@ -1,4 +1,4 @@
|
||||
@import "./variables.module";
|
||||
@import "./_variables";
|
||||
@import "./theme";
|
||||
|
||||
.excalidraw {
|
||||
@@ -43,10 +43,10 @@
|
||||
}
|
||||
|
||||
.FixedSideContainer {
|
||||
padding-top: var(--sat, 0);
|
||||
padding-right: var(--sar, 0);
|
||||
padding-bottom: var(--sab, 0);
|
||||
padding-left: var(--sal, 0);
|
||||
padding-top: var(--sat, 0px);
|
||||
padding-right: var(--sar, 0px);
|
||||
padding-bottom: var(--sab, 0px);
|
||||
padding-left: var(--sal, 0px);
|
||||
}
|
||||
|
||||
.panelRow {
|
||||
@@ -223,10 +223,10 @@
|
||||
left: 0;
|
||||
right: 0;
|
||||
--bar-padding: calc(4 * var(--space-factor));
|
||||
padding-top: #{"max(var(--bar-padding), var(--sat, 0))"};
|
||||
padding-right: var(--sar, 0);
|
||||
padding-bottom: var(--sab, 0);
|
||||
padding-left: var(--sal, 0);
|
||||
padding-top: #{"max(var(--bar-padding), var(--sat, 0px))"};
|
||||
padding-right: var(--sar, 0px);
|
||||
padding-bottom: var(--sab, 0px);
|
||||
padding-left: var(--sal, 0px);
|
||||
z-index: 4;
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
@@ -243,7 +243,7 @@
|
||||
pointer-events: initial;
|
||||
|
||||
.panelColumn {
|
||||
padding: 8px 8px 0 8px;
|
||||
padding: 8px 8px 0px 8px;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -447,7 +447,7 @@
|
||||
display: none;
|
||||
}
|
||||
.scroll-back-to-content {
|
||||
bottom: calc(80px + var(--sab, 0));
|
||||
bottom: calc(80px + var(--sab, 0px));
|
||||
z-index: -1;
|
||||
}
|
||||
}
|
||||
|
||||
+41
-39
@@ -1,43 +1,43 @@
|
||||
@import "open-color/open-color.scss";
|
||||
|
||||
:root {
|
||||
--appearance-filter: none;
|
||||
--button-destructive-bg-color: #{$oc-red-1};
|
||||
--button-destructive-color: #{$oc-red-9};
|
||||
--bg-color-island: rgba(255, 255, 255, 0.9);
|
||||
--popup-background-color: #{$oc-white};
|
||||
--space-factor: 0.25rem;
|
||||
--button-gray-1: #{$oc-gray-2};
|
||||
--button-gray-2: #{$oc-gray-4};
|
||||
--button-gray-3: #{$oc-gray-5};
|
||||
--button-special-active-bg-color: #{$oc-green-0};
|
||||
--dialog-border: #{$oc-gray-6};
|
||||
--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};
|
||||
--input-border-color: #{$oc-gray-3};
|
||||
--input-background-color: #{$oc-white};
|
||||
--input-hover-background-color: #{$oc-gray-1};
|
||||
--input-label-color: #{$oc-gray-7};
|
||||
--icon-fill-color: #{$oc-black};
|
||||
--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: #{transparentize($oc-white, 0.12)};
|
||||
--keybinding-color: #{$oc-gray-5};
|
||||
--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};
|
||||
--sat: env(safe-area-inset-top);
|
||||
--sab: env(safe-area-inset-bottom);
|
||||
--sal: env(safe-area-inset-left);
|
||||
--sar: env(safe-area-inset-right);
|
||||
--sat: env(safe-area-inset-top);
|
||||
--select-highlight-color: #{$oc-blue-5};
|
||||
--shadow-island: 0 1px 5px #{transparentize($oc-black, 0.85)};
|
||||
--space-factor: 0.25rem;
|
||||
--text-color-primary: #{$oc-gray-8};
|
||||
--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};
|
||||
--appearance-filter: none;
|
||||
--button-special-active-background-color: #{$oc-green-0};
|
||||
--button-destructive-color: #{$oc-red-9};
|
||||
--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 {
|
||||
&.Appearance_dark {
|
||||
background: $oc-black;
|
||||
background: #000;
|
||||
|
||||
&.Appearance_dark-background-none {
|
||||
background: none;
|
||||
@@ -45,29 +45,31 @@
|
||||
}
|
||||
|
||||
&.Appearance_dark {
|
||||
--appearance-filter: invert(93%) hue-rotate(180deg);
|
||||
--button-destructive-bg-color: #5a0000;
|
||||
--button-destructive-color: #{$oc-red-3};
|
||||
--text-color-primary: #{$oc-gray-4};
|
||||
--bg-color-island: #1e1e1e;
|
||||
--popup-background-color: #2c2c2c;
|
||||
--button-gray-1: #363636;
|
||||
--button-gray-2: #272727;
|
||||
--button-gray-3: #222;
|
||||
--button-special-active-bg-color: #204624;
|
||||
--dialog-border: #{$oc-gray-9};
|
||||
--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};
|
||||
--input-border-color: #2e2e2e;
|
||||
--input-background-color: #121212;
|
||||
--input-hover-background-color: #181818;
|
||||
--input-label-color: #{$oc-gray-2};
|
||||
--icon-fill-color: #{$oc-gray-4};
|
||||
--icon-green-fill-color: #{$oc-green-4};
|
||||
--input-bg-color: #121212;
|
||||
--input-border-color: #2e2e2e;
|
||||
--input-hover-bg-color: #181818;
|
||||
--input-label-color: #{$oc-gray-2};
|
||||
--island-bg-color: #1e1e1e;
|
||||
--keybinding-color: #{$oc-gray-6};
|
||||
--overlay-bg-color: rgba(30, 30, 30, 0.88);
|
||||
--popup-secondary-bg-color: #222;
|
||||
--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>');
|
||||
--focus-highlight-color: #{$oc-blue-6};
|
||||
--select-highlight-color: #{$oc-blue-4};
|
||||
--appearance-filter: invert(93%) hue-rotate(180deg);
|
||||
--button-special-active-background-color: #204624;
|
||||
--button-destructive-color: #{$oc-red-3};
|
||||
--button-destructive-background-color: #5a0000;
|
||||
--popup-secondary-background-color: #222;
|
||||
--popup-text-color: #{$oc-gray-4};
|
||||
--popup-text-inverted-color: #2c2c2c;
|
||||
--select-highlight-color: #{$oc-blue-4};
|
||||
--shadow-island: 0 1px 5px #{transparentize($oc-black, 0.7)};
|
||||
--dialog-border: #{$oc-gray-9};
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
import { fileSave } from "browser-fs-access";
|
||||
import { fileSave } from "browser-nativefs";
|
||||
import {
|
||||
copyCanvasToClipboardAsPng,
|
||||
copyTextToSystemClipboard,
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
import { fileOpen, fileSave } from "browser-fs-access";
|
||||
import { fileOpen, fileSave } from "browser-nativefs";
|
||||
import { cleanAppStateForExport } from "../appState";
|
||||
import { MIME_TYPES } from "../constants";
|
||||
import { clearElementsForExport } from "../element";
|
||||
|
||||
@@ -6,11 +6,10 @@ import { APP_NAME, ENV, EVENT } from "../../constants";
|
||||
import { ImportedDataState } from "../../data/types";
|
||||
import { ExcalidrawElement } from "../../element/types";
|
||||
import {
|
||||
getElementMap,
|
||||
getSceneVersion,
|
||||
getSyncableElements,
|
||||
} from "../../packages/excalidraw/index";
|
||||
import { Collaborator, Gesture } from "../../types";
|
||||
import { AppState, Collaborator, Gesture } from "../../types";
|
||||
import { resolvablePromise, withBatchedUpdates } from "../../utils";
|
||||
import {
|
||||
INITIAL_SCENE_UPDATE_TIMEOUT,
|
||||
@@ -32,7 +31,6 @@ import {
|
||||
} from "../data/localStorage";
|
||||
import Portal from "./Portal";
|
||||
import RoomDialog from "./RoomDialog";
|
||||
import { createInverseContext } from "../../createInverseContext";
|
||||
|
||||
interface CollabState {
|
||||
isCollaborating: boolean;
|
||||
@@ -58,21 +56,17 @@ type ReconciledElements = readonly ExcalidrawElement[] & {
|
||||
};
|
||||
|
||||
interface Props {
|
||||
excalidrawAPI: ExcalidrawImperativeAPI;
|
||||
children: (collab: CollabAPI) => React.ReactNode;
|
||||
// NOTE not type-safe because the refObject may in fact not be initialized
|
||||
// with ExcalidrawImperativeAPI yet
|
||||
excalidrawRef: React.MutableRefObject<ExcalidrawImperativeAPI>;
|
||||
}
|
||||
|
||||
const {
|
||||
Context: CollabContext,
|
||||
Consumer: CollabContextConsumer,
|
||||
Provider: CollabContextProvider,
|
||||
} = createInverseContext<{ api: CollabAPI | null }>({ api: null });
|
||||
|
||||
export { CollabContext, CollabContextConsumer };
|
||||
|
||||
class CollabWrapper extends PureComponent<Props, CollabState> {
|
||||
portal: Portal;
|
||||
excalidrawAPI: Props["excalidrawAPI"];
|
||||
private socketInitializationTimer?: NodeJS.Timeout;
|
||||
private excalidrawRef: Props["excalidrawRef"];
|
||||
excalidrawAppState?: AppState;
|
||||
private lastBroadcastedOrReceivedSceneVersion: number = -1;
|
||||
private collaborators = new Map<string, Collaborator>();
|
||||
|
||||
@@ -86,7 +80,7 @@ class CollabWrapper extends PureComponent<Props, CollabState> {
|
||||
activeRoomLink: "",
|
||||
};
|
||||
this.portal = new Portal(this);
|
||||
this.excalidrawAPI = props.excalidrawAPI;
|
||||
this.excalidrawRef = props.excalidrawRef;
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
@@ -148,7 +142,7 @@ class CollabWrapper extends PureComponent<Props, CollabState> {
|
||||
|
||||
saveCollabRoomToFirebase = async (
|
||||
syncableElements: ExcalidrawElement[] = getSyncableElements(
|
||||
this.excalidrawAPI.getSceneElementsIncludingDeleted(),
|
||||
this.excalidrawRef.current!.getSceneElementsIncludingDeleted(),
|
||||
),
|
||||
) => {
|
||||
try {
|
||||
@@ -160,13 +154,13 @@ class CollabWrapper extends PureComponent<Props, CollabState> {
|
||||
|
||||
openPortal = async () => {
|
||||
window.history.pushState({}, APP_NAME, await generateCollaborationLink());
|
||||
const elements = this.excalidrawAPI.getSceneElements();
|
||||
const elements = this.excalidrawRef.current!.getSceneElements();
|
||||
// remove deleted elements from elements array & history to ensure we don't
|
||||
// expose potentially sensitive user data in case user manually deletes
|
||||
// existing elements (or clears scene), which would otherwise be persisted
|
||||
// to database even if deleted before creating the room.
|
||||
this.excalidrawAPI.history.clear();
|
||||
this.excalidrawAPI.updateScene({
|
||||
this.excalidrawRef.current!.history.clear();
|
||||
this.excalidrawRef.current!.updateScene({
|
||||
elements,
|
||||
commitToHistory: true,
|
||||
});
|
||||
@@ -181,7 +175,7 @@ class CollabWrapper extends PureComponent<Props, CollabState> {
|
||||
|
||||
private destroySocketClient = () => {
|
||||
this.collaborators = new Map();
|
||||
this.excalidrawAPI.updateScene({
|
||||
this.excalidrawRef.current!.updateScene({
|
||||
collaborators: this.collaborators,
|
||||
});
|
||||
this.setState({
|
||||
@@ -271,7 +265,7 @@ class CollabWrapper extends PureComponent<Props, CollabState> {
|
||||
user.selectedElementIds = selectedElementIds;
|
||||
user.username = username;
|
||||
collaborators.set(socketId, user);
|
||||
this.excalidrawAPI.updateScene({
|
||||
this.excalidrawRef.current!.updateScene({
|
||||
collaborators,
|
||||
});
|
||||
break;
|
||||
@@ -306,55 +300,7 @@ class CollabWrapper extends PureComponent<Props, CollabState> {
|
||||
private reconcileElements = (
|
||||
elements: readonly ExcalidrawElement[],
|
||||
): ReconciledElements => {
|
||||
const currentElements = this.getSceneElementsIncludingDeleted();
|
||||
// create a map of ids so we don't have to iterate
|
||||
// over the array more than once.
|
||||
const localElementMap = getElementMap(currentElements);
|
||||
|
||||
const appState = this.excalidrawAPI.getAppState();
|
||||
|
||||
// Reconcile
|
||||
const newElements: readonly ExcalidrawElement[] = elements
|
||||
.reduce((elements, element) => {
|
||||
// if the remote element references one that's currently
|
||||
// edited on local, skip it (it'll be added in the next step)
|
||||
if (
|
||||
element.id === appState.editingElement?.id ||
|
||||
element.id === appState.resizingElement?.id ||
|
||||
element.id === appState.draggingElement?.id
|
||||
) {
|
||||
return elements;
|
||||
}
|
||||
|
||||
if (
|
||||
localElementMap.hasOwnProperty(element.id) &&
|
||||
localElementMap[element.id].version > element.version
|
||||
) {
|
||||
elements.push(localElementMap[element.id]);
|
||||
delete localElementMap[element.id];
|
||||
} else if (
|
||||
localElementMap.hasOwnProperty(element.id) &&
|
||||
localElementMap[element.id].version === element.version &&
|
||||
localElementMap[element.id].versionNonce !== element.versionNonce
|
||||
) {
|
||||
// resolve conflicting edits deterministically by taking the one with the lowest versionNonce
|
||||
if (localElementMap[element.id].versionNonce < element.versionNonce) {
|
||||
elements.push(localElementMap[element.id]);
|
||||
} else {
|
||||
// it should be highly unlikely that the two versionNonces are the same. if we are
|
||||
// really worried about this, we can replace the versionNonce with the socket id.
|
||||
elements.push(element);
|
||||
}
|
||||
delete localElementMap[element.id];
|
||||
} else {
|
||||
elements.push(element);
|
||||
delete localElementMap[element.id];
|
||||
}
|
||||
|
||||
return elements;
|
||||
}, [] as Mutable<typeof elements>)
|
||||
// add local elements that weren't deleted or on remote
|
||||
.concat(...Object.values(localElementMap));
|
||||
const newElements = this.portal.reconcileElements(elements);
|
||||
|
||||
// Avoid broadcasting to the rest of the collaborators the scene
|
||||
// we just received!
|
||||
@@ -373,10 +319,10 @@ class CollabWrapper extends PureComponent<Props, CollabState> {
|
||||
}: { init?: boolean; initFromSnapshot?: boolean } = {},
|
||||
) => {
|
||||
if (init || initFromSnapshot) {
|
||||
this.excalidrawAPI.setScrollToCenter(elements);
|
||||
this.excalidrawRef.current!.setScrollToCenter(elements);
|
||||
}
|
||||
|
||||
this.excalidrawAPI.updateScene({
|
||||
this.excalidrawRef.current!.updateScene({
|
||||
elements,
|
||||
commitToHistory: !!init,
|
||||
});
|
||||
@@ -385,7 +331,7 @@ class CollabWrapper extends PureComponent<Props, CollabState> {
|
||||
// when we receive any messages from another peer. This UX can be pretty rough -- if you
|
||||
// undo, a user makes a change, and then try to redo, your element(s) will be lost. However,
|
||||
// right now we think this is the right tradeoff.
|
||||
this.excalidrawAPI.history.clear();
|
||||
this.excalidrawRef.current!.history.clear();
|
||||
};
|
||||
|
||||
setCollaborators(sockets: string[]) {
|
||||
@@ -401,7 +347,7 @@ class CollabWrapper extends PureComponent<Props, CollabState> {
|
||||
}
|
||||
}
|
||||
this.collaborators = collaborators;
|
||||
this.excalidrawAPI.updateScene({ collaborators });
|
||||
this.excalidrawRef.current!.updateScene({ collaborators });
|
||||
});
|
||||
}
|
||||
|
||||
@@ -414,7 +360,7 @@ class CollabWrapper extends PureComponent<Props, CollabState> {
|
||||
};
|
||||
|
||||
public getSceneElementsIncludingDeleted = () => {
|
||||
return this.excalidrawAPI.getSceneElementsIncludingDeleted();
|
||||
return this.excalidrawRef.current!.getSceneElementsIncludingDeleted();
|
||||
};
|
||||
|
||||
onPointerUpdate = (payload: {
|
||||
@@ -427,7 +373,11 @@ class CollabWrapper extends PureComponent<Props, CollabState> {
|
||||
this.portal.broadcastMouseLocation(payload);
|
||||
};
|
||||
|
||||
broadcastElements = (elements: readonly ExcalidrawElement[]) => {
|
||||
broadcastElements = (
|
||||
elements: readonly ExcalidrawElement[],
|
||||
state: AppState,
|
||||
) => {
|
||||
this.excalidrawAppState = state;
|
||||
if (
|
||||
getSceneVersion(elements) >
|
||||
this.getLastBroadcastedOrReceivedSceneVersion()
|
||||
@@ -446,7 +396,7 @@ class CollabWrapper extends PureComponent<Props, CollabState> {
|
||||
this.portal.broadcastScene(
|
||||
SCENE.UPDATE,
|
||||
getSyncableElements(
|
||||
this.excalidrawAPI.getSceneElementsIncludingDeleted(),
|
||||
this.excalidrawRef.current!.getSceneElementsIncludingDeleted(),
|
||||
),
|
||||
true,
|
||||
);
|
||||
@@ -475,23 +425,8 @@ class CollabWrapper extends PureComponent<Props, CollabState> {
|
||||
});
|
||||
};
|
||||
|
||||
/** PRIVATE. Use `this.getContextValue()` instead. */
|
||||
private contextValue: CollabAPI | null = null;
|
||||
|
||||
/** Getter of context value. Returned object is stable. */
|
||||
getContextValue = (): CollabAPI => {
|
||||
this.contextValue = this.contextValue || ({} as CollabAPI);
|
||||
|
||||
this.contextValue.isCollaborating = this.state.isCollaborating;
|
||||
this.contextValue.username = this.state.username;
|
||||
this.contextValue.onPointerUpdate = this.onPointerUpdate;
|
||||
this.contextValue.initializeSocketClient = this.initializeSocketClient;
|
||||
this.contextValue.onCollabButtonClick = this.onCollabButtonClick;
|
||||
this.contextValue.broadcastElements = this.broadcastElements;
|
||||
return this.contextValue;
|
||||
};
|
||||
|
||||
render() {
|
||||
const { children } = this.props;
|
||||
const { modalIsShown, username, errorMessage, activeRoomLink } = this.state;
|
||||
|
||||
return (
|
||||
@@ -515,11 +450,14 @@ class CollabWrapper extends PureComponent<Props, CollabState> {
|
||||
onClose={() => this.setState({ errorMessage: "" })}
|
||||
/>
|
||||
)}
|
||||
<CollabContextProvider
|
||||
value={{
|
||||
api: this.getContextValue(),
|
||||
}}
|
||||
/>
|
||||
{children({
|
||||
isCollaborating: this.state.isCollaborating,
|
||||
username: this.state.username,
|
||||
onPointerUpdate: this.onPointerUpdate,
|
||||
initializeSocketClient: this.initializeSocketClient,
|
||||
onCollabButtonClick: this.onCollabButtonClick,
|
||||
broadcastElements: this.broadcastElements,
|
||||
})}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -6,20 +6,23 @@ import {
|
||||
|
||||
import CollabWrapper from "./CollabWrapper";
|
||||
|
||||
import { getSyncableElements } from "../../packages/excalidraw/index";
|
||||
import {
|
||||
getElementMap,
|
||||
getSyncableElements,
|
||||
} from "../../packages/excalidraw/index";
|
||||
import { ExcalidrawElement } from "../../element/types";
|
||||
import { BROADCAST, SCENE } from "../app_constants";
|
||||
|
||||
class Portal {
|
||||
collab: CollabWrapper;
|
||||
app: CollabWrapper;
|
||||
socket: SocketIOClient.Socket | null = null;
|
||||
socketInitialized: boolean = false; // we don't want the socket to emit any updates until it is fully initialized
|
||||
roomId: string | null = null;
|
||||
roomKey: string | null = null;
|
||||
broadcastedElementVersions: Map<string, number> = new Map();
|
||||
|
||||
constructor(collab: CollabWrapper) {
|
||||
this.collab = collab;
|
||||
constructor(app: CollabWrapper) {
|
||||
this.app = app;
|
||||
}
|
||||
|
||||
open(socket: SocketIOClient.Socket, id: string, key: string) {
|
||||
@@ -27,7 +30,7 @@ class Portal {
|
||||
this.roomId = id;
|
||||
this.roomKey = key;
|
||||
|
||||
// Initialize socket listeners
|
||||
// Initialize socket listeners (moving from App)
|
||||
this.socket.on("init-room", () => {
|
||||
if (this.socket) {
|
||||
this.socket.emit("join-room", this.roomId);
|
||||
@@ -36,12 +39,12 @@ class Portal {
|
||||
this.socket.on("new-user", async (_socketId: string) => {
|
||||
this.broadcastScene(
|
||||
SCENE.INIT,
|
||||
getSyncableElements(this.collab.getSceneElementsIncludingDeleted()),
|
||||
getSyncableElements(this.app.getSceneElementsIncludingDeleted()),
|
||||
/* syncAll */ true,
|
||||
);
|
||||
});
|
||||
this.socket.on("room-user-change", (clients: string[]) => {
|
||||
this.collab.setCollaborators(clients);
|
||||
this.app.setCollaborators(clients);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -122,10 +125,10 @@ class Portal {
|
||||
data as SocketUpdateData,
|
||||
);
|
||||
|
||||
if (syncAll && this.collab.state.isCollaborating) {
|
||||
if (syncAll && this.app.state.isCollaborating) {
|
||||
await Promise.all([
|
||||
broadcastPromise,
|
||||
this.collab.saveCollabRoomToFirebase(syncableElements),
|
||||
this.app.saveCollabRoomToFirebase(syncableElements),
|
||||
]);
|
||||
} else {
|
||||
await broadcastPromise;
|
||||
@@ -143,9 +146,9 @@ class Portal {
|
||||
socketId: this.socket.id,
|
||||
pointer: payload.pointer,
|
||||
button: payload.button || "up",
|
||||
selectedElementIds: this.collab.excalidrawAPI.getAppState()
|
||||
.selectedElementIds,
|
||||
username: this.collab.state.username,
|
||||
selectedElementIds:
|
||||
this.app.excalidrawAppState?.selectedElementIds || {},
|
||||
username: this.app.state.username,
|
||||
},
|
||||
};
|
||||
return this._broadcastSocketData(
|
||||
@@ -154,6 +157,62 @@ class Portal {
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
reconcileElements = (
|
||||
sceneElements: readonly ExcalidrawElement[],
|
||||
): readonly ExcalidrawElement[] => {
|
||||
const currentElements = this.app.getSceneElementsIncludingDeleted();
|
||||
// create a map of ids so we don't have to iterate
|
||||
// over the array more than once.
|
||||
const localElementMap = getElementMap(currentElements);
|
||||
|
||||
// Reconcile
|
||||
return (
|
||||
sceneElements
|
||||
.reduce((elements, element) => {
|
||||
// if the remote element references one that's currently
|
||||
// edited on local, skip it (it'll be added in the next step)
|
||||
if (
|
||||
element.id === this.app.excalidrawAppState?.editingElement?.id ||
|
||||
element.id === this.app.excalidrawAppState?.resizingElement?.id ||
|
||||
element.id === this.app.excalidrawAppState?.draggingElement?.id
|
||||
) {
|
||||
return elements;
|
||||
}
|
||||
|
||||
if (
|
||||
localElementMap.hasOwnProperty(element.id) &&
|
||||
localElementMap[element.id].version > element.version
|
||||
) {
|
||||
elements.push(localElementMap[element.id]);
|
||||
delete localElementMap[element.id];
|
||||
} else if (
|
||||
localElementMap.hasOwnProperty(element.id) &&
|
||||
localElementMap[element.id].version === element.version &&
|
||||
localElementMap[element.id].versionNonce !== element.versionNonce
|
||||
) {
|
||||
// resolve conflicting edits deterministically by taking the one with the lowest versionNonce
|
||||
if (
|
||||
localElementMap[element.id].versionNonce < element.versionNonce
|
||||
) {
|
||||
elements.push(localElementMap[element.id]);
|
||||
} else {
|
||||
// it should be highly unlikely that the two versionNonces are the same. if we are
|
||||
// really worried about this, we can replace the versionNonce with the socket id.
|
||||
elements.push(element);
|
||||
}
|
||||
delete localElementMap[element.id];
|
||||
} else {
|
||||
elements.push(element);
|
||||
delete localElementMap[element.id];
|
||||
}
|
||||
|
||||
return elements;
|
||||
}, [] as Mutable<typeof sceneElements>)
|
||||
// add local elements that weren't deleted or on remote
|
||||
.concat(...Object.values(localElementMap))
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
export default Portal;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
@import "../../css/variables.module";
|
||||
@import "../../css/_variables";
|
||||
|
||||
.excalidraw {
|
||||
.RoomDialog-linkContainer {
|
||||
@@ -35,7 +35,7 @@
|
||||
}
|
||||
|
||||
.RoomDialog-username {
|
||||
background-color: var(--input-bg-color);
|
||||
background-color: var(--input-background-color);
|
||||
border-color: var(--input-border-color);
|
||||
appearance: none;
|
||||
min-width: 0;
|
||||
@@ -53,7 +53,7 @@
|
||||
}
|
||||
|
||||
.Modal .RoomDialog-stopSession {
|
||||
background-color: var(--button-destructive-bg-color);
|
||||
background-color: var(--button-destructive-background-color);
|
||||
|
||||
.ToolIcon__label,
|
||||
.ToolIcon__icon svg {
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import LanguageDetector from "i18next-browser-languagedetector";
|
||||
import React, {
|
||||
useCallback,
|
||||
useContext,
|
||||
useEffect,
|
||||
useLayoutEffect,
|
||||
useRef,
|
||||
@@ -18,13 +17,12 @@ import {
|
||||
ExcalidrawElement,
|
||||
NonDeletedExcalidrawElement,
|
||||
} from "../element/types";
|
||||
import { useCallbackRefState } from "../hooks/useCallbackRefState";
|
||||
import { Language, t } from "../i18n";
|
||||
import Excalidraw, {
|
||||
defaultLang,
|
||||
languages,
|
||||
} from "../packages/excalidraw/index";
|
||||
import { AppState } from "../types";
|
||||
import { AppState, ExcalidrawAPIRefValue } from "../types";
|
||||
import {
|
||||
debounce,
|
||||
getVersion,
|
||||
@@ -32,11 +30,7 @@ import {
|
||||
resolvablePromise,
|
||||
} from "../utils";
|
||||
import { SAVE_TO_LOCAL_STORAGE_TIMEOUT } from "./app_constants";
|
||||
import CollabWrapper, {
|
||||
CollabAPI,
|
||||
CollabContext,
|
||||
CollabContextConsumer,
|
||||
} from "./collab/CollabWrapper";
|
||||
import CollabWrapper, { CollabAPI } from "./collab/CollabWrapper";
|
||||
import { LanguageList } from "./components/LanguageList";
|
||||
import { exportToBackend, getCollaborationLinkData, loadScene } from "./data";
|
||||
import { loadFromFirebase } from "./data/firebase";
|
||||
@@ -55,6 +49,15 @@ languageDetector.init({
|
||||
checkWhitelist: false,
|
||||
});
|
||||
|
||||
const excalidrawRef: React.MutableRefObject<
|
||||
MarkRequired<ExcalidrawAPIRefValue, "ready" | "readyPromise">
|
||||
> = {
|
||||
current: {
|
||||
readyPromise: resolvablePromise(),
|
||||
ready: false,
|
||||
},
|
||||
};
|
||||
|
||||
const saveDebounced = debounce(
|
||||
(elements: readonly ExcalidrawElement[], state: AppState) => {
|
||||
saveToLocalStorage(elements, state);
|
||||
@@ -188,7 +191,7 @@ const initializeScene = async (opts: {
|
||||
return null;
|
||||
};
|
||||
|
||||
const ExcalidrawWrapper = () => {
|
||||
function ExcalidrawWrapper(props: { collab: CollabAPI }) {
|
||||
// dimensions
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
@@ -223,40 +226,35 @@ const ExcalidrawWrapper = () => {
|
||||
initialStatePromiseRef.current.promise = resolvablePromise<ImportedDataState | null>();
|
||||
}
|
||||
|
||||
const { collab } = props;
|
||||
|
||||
useEffect(() => {
|
||||
// Delayed so that the app has a time to load the latest SW
|
||||
setTimeout(() => {
|
||||
trackEvent("load", "version", getVersion());
|
||||
}, VERSION_TIMEOUT);
|
||||
}, []);
|
||||
|
||||
const [
|
||||
excalidrawAPI,
|
||||
excalidrawRefCallback,
|
||||
] = useCallbackRefState<ExcalidrawImperativeAPI>();
|
||||
|
||||
const collabAPI = useContext(CollabContext)?.api;
|
||||
|
||||
useEffect(() => {
|
||||
if (!collabAPI || !excalidrawAPI) {
|
||||
return;
|
||||
}
|
||||
|
||||
initializeScene({
|
||||
resetScene: excalidrawAPI.resetScene,
|
||||
initializeSocketClient: collabAPI.initializeSocketClient,
|
||||
}).then((scene) => {
|
||||
initialStatePromiseRef.current.promise.resolve(scene);
|
||||
excalidrawRef.current!.readyPromise.then((excalidrawApi) => {
|
||||
initializeScene({
|
||||
resetScene: excalidrawApi.resetScene,
|
||||
initializeSocketClient: collab.initializeSocketClient,
|
||||
}).then((scene) => {
|
||||
initialStatePromiseRef.current.promise.resolve(scene);
|
||||
});
|
||||
});
|
||||
|
||||
const onHashChange = (_: HashChangeEvent) => {
|
||||
const api = excalidrawRef.current!;
|
||||
if (!api.ready) {
|
||||
return;
|
||||
}
|
||||
if (window.location.hash.length > 1) {
|
||||
initializeScene({
|
||||
resetScene: excalidrawAPI.resetScene,
|
||||
initializeSocketClient: collabAPI.initializeSocketClient,
|
||||
resetScene: api.resetScene,
|
||||
initializeSocketClient: collab.initializeSocketClient,
|
||||
}).then((scene) => {
|
||||
if (scene) {
|
||||
excalidrawAPI.updateScene(scene);
|
||||
api.updateScene(scene);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -275,7 +273,7 @@ const ExcalidrawWrapper = () => {
|
||||
window.removeEventListener(EVENT.BLUR, onBlur, false);
|
||||
clearTimeout(titleTimeout);
|
||||
};
|
||||
}, [collabAPI, excalidrawAPI]);
|
||||
}, [collab.initializeSocketClient]);
|
||||
|
||||
useEffect(() => {
|
||||
languageDetector.cacheUserLanguage(langCode);
|
||||
@@ -286,8 +284,8 @@ const ExcalidrawWrapper = () => {
|
||||
appState: AppState,
|
||||
) => {
|
||||
saveDebounced(elements, appState);
|
||||
if (collabAPI?.isCollaborating) {
|
||||
collabAPI.broadcastElements(elements);
|
||||
if (collab.isCollaborating) {
|
||||
collab.broadcastElements(elements, appState);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -345,20 +343,19 @@ const ExcalidrawWrapper = () => {
|
||||
return (
|
||||
<>
|
||||
<Excalidraw
|
||||
ref={excalidrawRefCallback}
|
||||
ref={excalidrawRef}
|
||||
onChange={onChange}
|
||||
width={dimensions.width}
|
||||
height={dimensions.height}
|
||||
initialData={initialStatePromiseRef.current.promise}
|
||||
user={{ name: collabAPI?.username }}
|
||||
onCollabButtonClick={collabAPI?.onCollabButtonClick}
|
||||
isCollaborating={collabAPI?.isCollaborating}
|
||||
onPointerUpdate={collabAPI?.onPointerUpdate}
|
||||
user={{ name: collab.username }}
|
||||
onCollabButtonClick={collab.onCollabButtonClick}
|
||||
isCollaborating={collab.isCollaborating}
|
||||
onPointerUpdate={collab.onPointerUpdate}
|
||||
onExportToBackend={onExportToBackend}
|
||||
renderFooter={renderFooter}
|
||||
langCode={langCode}
|
||||
/>
|
||||
{excalidrawAPI && <CollabWrapper excalidrawAPI={excalidrawAPI} />}
|
||||
{errorMessage && (
|
||||
<ErrorDialog
|
||||
message={errorMessage}
|
||||
@@ -367,14 +364,18 @@ const ExcalidrawWrapper = () => {
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
export default function ExcalidrawApp() {
|
||||
return (
|
||||
<TopErrorBoundary>
|
||||
<CollabContextConsumer>
|
||||
<ExcalidrawWrapper />
|
||||
</CollabContextConsumer>
|
||||
<CollabWrapper
|
||||
excalidrawRef={
|
||||
excalidrawRef as React.MutableRefObject<ExcalidrawImperativeAPI>
|
||||
}
|
||||
>
|
||||
{(collab) => <ExcalidrawWrapper collab={collab} />}
|
||||
</CollabWrapper>
|
||||
</TopErrorBoundary>
|
||||
);
|
||||
}
|
||||
|
||||
+3
-2
@@ -1,10 +1,11 @@
|
||||
import { PointerCoords } from "./types";
|
||||
import { normalizeScroll } from "./scene";
|
||||
|
||||
export const getCenter = (pointers: Map<number, PointerCoords>) => {
|
||||
const allCoords = Array.from(pointers.values());
|
||||
return {
|
||||
x: sum(allCoords, (coords) => coords.x) / allCoords.length,
|
||||
y: sum(allCoords, (coords) => coords.y) / allCoords.length,
|
||||
x: normalizeScroll(sum(allCoords, (coords) => coords.x) / allCoords.length),
|
||||
y: normalizeScroll(sum(allCoords, (coords) => coords.y) / allCoords.length),
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
Vendored
+1
-1
@@ -85,6 +85,6 @@ type ForwardRef<T, P = any> = Parameters<
|
||||
// --------------------------------------------------------------------------—
|
||||
|
||||
interface Blob {
|
||||
handle?: import("browser-fs-acces").FileSystemHandle;
|
||||
handle?: import("browser-nativefs").FileSystemHandle;
|
||||
name?: string;
|
||||
}
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
import { useCallback, useState } from "react";
|
||||
|
||||
export const useCallbackRefState = <T>() => {
|
||||
const [refValue, setRefValue] = useState<T | null>(null);
|
||||
const refCallback = useCallback((value: T | null) => setRefValue(value), []);
|
||||
return [refValue, refCallback] as const;
|
||||
};
|
||||
+10
-14
@@ -1,18 +1,7 @@
|
||||
import React, { useState, useEffect, useRef, useContext } from "react";
|
||||
import variables from "./css/variables.module.scss";
|
||||
|
||||
const context = React.createContext(false);
|
||||
|
||||
const getIsMobileMatcher = () => {
|
||||
return window.matchMedia
|
||||
? window.matchMedia(variables.isMobileQuery)
|
||||
: (({
|
||||
matches: false,
|
||||
addListener: () => {},
|
||||
removeListener: () => {},
|
||||
} as any) as MediaQueryList);
|
||||
};
|
||||
|
||||
export const IsMobileProvider = ({
|
||||
children,
|
||||
}: {
|
||||
@@ -20,7 +9,16 @@ export const IsMobileProvider = ({
|
||||
}) => {
|
||||
const query = useRef<MediaQueryList>();
|
||||
if (!query.current) {
|
||||
query.current = getIsMobileMatcher();
|
||||
query.current = window.matchMedia
|
||||
? window.matchMedia(
|
||||
// keep up to date with _variables.scss
|
||||
"(max-width: 640px), (max-height: 500px) and (max-width: 1000px)",
|
||||
)
|
||||
: (({
|
||||
matches: false,
|
||||
addListener: () => {},
|
||||
removeListener: () => {},
|
||||
} as any) as MediaQueryList);
|
||||
}
|
||||
const [isMobile, setMobile] = useState(query.current.matches);
|
||||
|
||||
@@ -33,8 +31,6 @@ export const IsMobileProvider = ({
|
||||
return <context.Provider value={isMobile}>{children}</context.Provider>;
|
||||
};
|
||||
|
||||
export const isMobile = () => getIsMobileMatcher().matches;
|
||||
|
||||
export default function useIsMobile() {
|
||||
return useContext(context);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
export const isDarwin = /Mac|iPod|iPhone|iPad/.test(window.navigator.platform);
|
||||
export const isWindows = /^Win/.test(window.navigator.platform);
|
||||
|
||||
export const CODES = {
|
||||
EQUAL: "Equal",
|
||||
@@ -19,7 +18,6 @@ export const CODES = {
|
||||
F: "KeyF",
|
||||
H: "KeyH",
|
||||
V: "KeyV",
|
||||
X: "KeyX",
|
||||
Z: "KeyZ",
|
||||
} as const;
|
||||
|
||||
@@ -50,7 +48,6 @@ export const KEYS = {
|
||||
T: "t",
|
||||
V: "v",
|
||||
X: "x",
|
||||
Y: "y",
|
||||
Z: "z",
|
||||
} as const;
|
||||
|
||||
|
||||
@@ -201,24 +201,24 @@
|
||||
},
|
||||
"helpDialog": {
|
||||
"blog": "",
|
||||
"click": "клик",
|
||||
"click": "",
|
||||
"curvedArrow": "",
|
||||
"curvedLine": "",
|
||||
"documentation": "",
|
||||
"drag": "плъзнете",
|
||||
"editor": "Редактор",
|
||||
"drag": "",
|
||||
"editor": "",
|
||||
"github": "",
|
||||
"howto": "",
|
||||
"or": "или",
|
||||
"or": "",
|
||||
"preventBinding": "",
|
||||
"shapes": "Фигури",
|
||||
"shortcuts": "Клавиши за бърз достъп",
|
||||
"shapes": "",
|
||||
"shortcuts": "",
|
||||
"textFinish": "",
|
||||
"textNewLine": "",
|
||||
"title": "",
|
||||
"view": "Преглед",
|
||||
"view": "",
|
||||
"zoomToFit": "",
|
||||
"zoomToSelection": "Приближи селекцията"
|
||||
"zoomToSelection": ""
|
||||
},
|
||||
"encrypted": {
|
||||
"tooltip": "Вашите рисунки са криптирани от край до край, така че сървърите на Excalidraw няма да могат да ги виждат."
|
||||
|
||||
+18
-18
@@ -200,25 +200,25 @@
|
||||
"title": "خطا"
|
||||
},
|
||||
"helpDialog": {
|
||||
"blog": "بلاگ ما را بخوانید",
|
||||
"blog": "",
|
||||
"click": "",
|
||||
"curvedArrow": "فلش خمیده",
|
||||
"curvedLine": "منحنی",
|
||||
"documentation": "مستندات",
|
||||
"curvedArrow": "",
|
||||
"curvedLine": "",
|
||||
"documentation": "",
|
||||
"drag": "",
|
||||
"editor": "ویرایشگر",
|
||||
"github": "اشکالی می بینید؟ گزارش دهید",
|
||||
"howto": "راهنمای ما را دنبال کنید",
|
||||
"or": "یا",
|
||||
"preventBinding": "مانع شدن از چسبیدن فلش ها",
|
||||
"shapes": "شکلها",
|
||||
"shortcuts": "میانبرهای صفحه کلید",
|
||||
"editor": "",
|
||||
"github": "",
|
||||
"howto": "",
|
||||
"or": "",
|
||||
"preventBinding": "",
|
||||
"shapes": "",
|
||||
"shortcuts": "",
|
||||
"textFinish": "",
|
||||
"textNewLine": "یک خط جدید اضافه کنید (متن)",
|
||||
"title": "راهنما",
|
||||
"view": "مشاهده",
|
||||
"zoomToFit": "بزرگنمایی برای دیدن تمام آیتم ها",
|
||||
"zoomToSelection": "بزرگنمایی قسمت انتخاب شده"
|
||||
"textNewLine": "",
|
||||
"title": "",
|
||||
"view": "",
|
||||
"zoomToFit": "",
|
||||
"zoomToSelection": ""
|
||||
},
|
||||
"encrypted": {
|
||||
"tooltip": "شما در یک محیط رمزگزاری شده دو طرفه در حال طراحی هستید پس Excalidraw هرگز طرح های شما را نمیبند."
|
||||
@@ -236,7 +236,7 @@
|
||||
"width": "عرض"
|
||||
},
|
||||
"toast": {
|
||||
"copyStyles": "کپی سبک.",
|
||||
"copyToClipboardAsPng": "کپی در حافطه موقت به صورت PNG."
|
||||
"copyStyles": "",
|
||||
"copyToClipboardAsPng": ""
|
||||
}
|
||||
}
|
||||
|
||||
+19
-19
@@ -200,25 +200,25 @@
|
||||
"title": "Kesalahan"
|
||||
},
|
||||
"helpDialog": {
|
||||
"blog": "Baca blog kami",
|
||||
"click": "klik",
|
||||
"curvedArrow": "Panah lengkung",
|
||||
"curvedLine": "Garis lengkung",
|
||||
"documentation": "Dokumentasi",
|
||||
"drag": "seret",
|
||||
"editor": "Editor",
|
||||
"github": "Menemukan masalah? Kirimkan",
|
||||
"howto": "Ikuti panduan kami",
|
||||
"or": "atau",
|
||||
"preventBinding": "Cegah pengikatan panah",
|
||||
"shapes": "Bentuk",
|
||||
"shortcuts": "Pintasan keyboard",
|
||||
"textFinish": "Selesai mengedit (teks)",
|
||||
"textNewLine": "Tambahkan baris baru (teks)",
|
||||
"title": "Bantuan",
|
||||
"view": "Tampilan",
|
||||
"zoomToFit": "Perbesar agar sesuai dengan semua elemen",
|
||||
"zoomToSelection": "Perbesar ke seleksi"
|
||||
"blog": "",
|
||||
"click": "",
|
||||
"curvedArrow": "",
|
||||
"curvedLine": "",
|
||||
"documentation": "",
|
||||
"drag": "",
|
||||
"editor": "",
|
||||
"github": "",
|
||||
"howto": "",
|
||||
"or": "",
|
||||
"preventBinding": "",
|
||||
"shapes": "",
|
||||
"shortcuts": "",
|
||||
"textFinish": "",
|
||||
"textNewLine": "",
|
||||
"title": "",
|
||||
"view": "",
|
||||
"zoomToFit": "",
|
||||
"zoomToSelection": ""
|
||||
},
|
||||
"encrypted": {
|
||||
"tooltip": "Gambar anda terenkripsi end-to-end sehingga server Excalidraw tidak akan pernah dapat melihatnya."
|
||||
|
||||
+19
-19
@@ -200,25 +200,25 @@
|
||||
"title": "Errore"
|
||||
},
|
||||
"helpDialog": {
|
||||
"blog": "Leggi il nostro blog",
|
||||
"click": "click",
|
||||
"curvedArrow": "Freccia curva",
|
||||
"curvedLine": "Linea curva",
|
||||
"documentation": "Documentazione",
|
||||
"drag": "trascina",
|
||||
"editor": "Editor",
|
||||
"github": "Trovato un problema? Segnalalo",
|
||||
"howto": "Segui le nostre guide",
|
||||
"or": "oppure",
|
||||
"preventBinding": "Impedisci legame della freccia",
|
||||
"shapes": "Forme",
|
||||
"shortcuts": "Scorciatoie da tastiera",
|
||||
"textFinish": "Termina la modifica (testo)",
|
||||
"textNewLine": "Aggiungi nuova riga (testo)",
|
||||
"title": "Guida",
|
||||
"view": "Vista",
|
||||
"zoomToFit": "Adatta zoom per mostrare tutti gli elementi",
|
||||
"zoomToSelection": "Zoom alla selezione"
|
||||
"blog": "",
|
||||
"click": "",
|
||||
"curvedArrow": "",
|
||||
"curvedLine": "",
|
||||
"documentation": "",
|
||||
"drag": "",
|
||||
"editor": "",
|
||||
"github": "",
|
||||
"howto": "",
|
||||
"or": "",
|
||||
"preventBinding": "",
|
||||
"shapes": "",
|
||||
"shortcuts": "",
|
||||
"textFinish": "",
|
||||
"textNewLine": "",
|
||||
"title": "",
|
||||
"view": "",
|
||||
"zoomToFit": "",
|
||||
"zoomToSelection": ""
|
||||
},
|
||||
"encrypted": {
|
||||
"tooltip": "I tuoi disegni sono crittografati end-to-end in modo che i server di Excalidraw non li possano mai vedere."
|
||||
|
||||
+19
-19
@@ -200,25 +200,25 @@
|
||||
"title": "ਗਲਤੀ"
|
||||
},
|
||||
"helpDialog": {
|
||||
"blog": "ਸਾਡਾ ਬਲੌਗ ਪੜ੍ਹੋ",
|
||||
"click": "ਕਲਿੱਕ",
|
||||
"curvedArrow": "ਵਿੰਗਾ ਤੀਰ",
|
||||
"curvedLine": "ਵਿੰਗੀ ਲਕੀਰ",
|
||||
"documentation": "ਕਾਗਜ਼ਾਤ",
|
||||
"drag": "ਘਸੀਟੋ",
|
||||
"editor": "ਸੋਧਕ",
|
||||
"github": "ਕੋਈ ਸਮੱਸਿਆ ਲੱਭੀ? ਜਮ੍ਹਾਂ ਕਰਵਾਓ",
|
||||
"howto": "ਸਾਡੀਆਂ ਗਾਈਡਾਂ ਦੀ ਪਾਲਣਾ ਕਰੋ",
|
||||
"or": "ਜਾਂ",
|
||||
"preventBinding": "ਤੀਰ ਬੱਝਣਾ ਰੋਕੋ",
|
||||
"shapes": "ਆਕ੍ਰਿਤੀਆਂ",
|
||||
"shortcuts": "ਕੀਬੋਰਡ ਸ਼ਾਰਟਕੱਟ",
|
||||
"textFinish": "ਸੋਧ ਮੁਕੰਮਲ ਕਰੋ (ਪਾਠ)",
|
||||
"textNewLine": "ਨਵੀਂ ਪੰਕਤੀ ਜੋੜੋ (ਪਾਠ)",
|
||||
"title": "ਮਦਦ",
|
||||
"view": "ਦਿੱਖ",
|
||||
"zoomToFit": "ਸਾਰੇ ਐਲੀਮੈਂਟਾਂ ਨੂੰ ਫਿੱਟ ਕਰਨ ਲਈ ਜ਼ੂਮ ਕਰੋ",
|
||||
"zoomToSelection": "ਚੋਣ ਤੱਕ ਜ਼ੂਮ ਕਰੋ"
|
||||
"blog": "",
|
||||
"click": "",
|
||||
"curvedArrow": "",
|
||||
"curvedLine": "",
|
||||
"documentation": "",
|
||||
"drag": "",
|
||||
"editor": "",
|
||||
"github": "",
|
||||
"howto": "",
|
||||
"or": "",
|
||||
"preventBinding": "",
|
||||
"shapes": "",
|
||||
"shortcuts": "",
|
||||
"textFinish": "",
|
||||
"textNewLine": "",
|
||||
"title": "",
|
||||
"view": "",
|
||||
"zoomToFit": "",
|
||||
"zoomToSelection": ""
|
||||
},
|
||||
"encrypted": {
|
||||
"tooltip": "ਤੁਹਾਡੀ ਡਰਾਇੰਗਾਂ ਸਿਰੇ-ਤੋਂ-ਸਿਰੇ ਤੱਕ ਇਨਕਰਿਪਟ ਕੀਤੀਆਂ ਹੋਈਆਂ ਹਨ, ਇਸ ਲਈ Excalidraw ਦੇ ਸਰਵਰ ਉਹਨਾਂ ਨੂੰ ਕਦੇ ਵੀ ਨਹੀਂ ਦੇਖਣਗੇ।"
|
||||
|
||||
@@ -1,35 +1,35 @@
|
||||
{
|
||||
"ar-SA": 90,
|
||||
"bg-BG": 94,
|
||||
"bg-BG": 90,
|
||||
"ca-ES": 90,
|
||||
"de-DE": 100,
|
||||
"el-GR": 100,
|
||||
"en": 100,
|
||||
"es-ES": 100,
|
||||
"fa-IR": 98,
|
||||
"fa-IR": 90,
|
||||
"fi-FI": 100,
|
||||
"fr-FR": 100,
|
||||
"he-IL": 90,
|
||||
"hi-IN": 90,
|
||||
"hu-HU": 90,
|
||||
"id-ID": 100,
|
||||
"it-IT": 100,
|
||||
"id-ID": 91,
|
||||
"it-IT": 91,
|
||||
"ja-JP": 81,
|
||||
"ko-KR": 90,
|
||||
"my-MM": 83,
|
||||
"nb-NO": 100,
|
||||
"nl-NL": 100,
|
||||
"nn-NO": 90,
|
||||
"pa-IN": 100,
|
||||
"pa-IN": 91,
|
||||
"pl-PL": 90,
|
||||
"pt-BR": 100,
|
||||
"pt-PT": 100,
|
||||
"ro-RO": 100,
|
||||
"ru-RU": 100,
|
||||
"sk-SK": 100,
|
||||
"ru-RU": 91,
|
||||
"sk-SK": 91,
|
||||
"sv-SE": 100,
|
||||
"tr-TR": 90,
|
||||
"uk-UA": 100,
|
||||
"zh-CN": 100,
|
||||
"zh-CN": 90,
|
||||
"zh-TW": 100
|
||||
}
|
||||
|
||||
+19
-19
@@ -200,25 +200,25 @@
|
||||
"title": "Ошибка"
|
||||
},
|
||||
"helpDialog": {
|
||||
"blog": "Прочитайте наш блог",
|
||||
"click": "нажать",
|
||||
"curvedArrow": "Изогнутая стрелка",
|
||||
"curvedLine": "Изогнутая линия",
|
||||
"documentation": "Документация",
|
||||
"drag": "перетащить",
|
||||
"editor": "Редактор",
|
||||
"github": "Нашли проблему? Отправьте",
|
||||
"howto": "Следуйте нашим инструкциям",
|
||||
"or": "или",
|
||||
"preventBinding": "Предотвращать привязку стрелок",
|
||||
"shapes": "Фигуры",
|
||||
"shortcuts": "Горячие клавиши",
|
||||
"textFinish": "Закончить редактирование (текст)",
|
||||
"textNewLine": "Добавить новую строку (текст)",
|
||||
"title": "Помощь",
|
||||
"view": "Просмотр",
|
||||
"zoomToFit": "Отмастштабировать, чтобы поместились все элементы",
|
||||
"zoomToSelection": "Увеличить до выделенного"
|
||||
"blog": "",
|
||||
"click": "",
|
||||
"curvedArrow": "",
|
||||
"curvedLine": "",
|
||||
"documentation": "",
|
||||
"drag": "",
|
||||
"editor": "",
|
||||
"github": "",
|
||||
"howto": "",
|
||||
"or": "",
|
||||
"preventBinding": "",
|
||||
"shapes": "",
|
||||
"shortcuts": "",
|
||||
"textFinish": "",
|
||||
"textNewLine": "",
|
||||
"title": "",
|
||||
"view": "",
|
||||
"zoomToFit": "",
|
||||
"zoomToSelection": ""
|
||||
},
|
||||
"encrypted": {
|
||||
"tooltip": "Ваши данные защищены сквозным (End-to-end) шифрованием. Серверы Excalidraw никогда не получат доступ к ним."
|
||||
|
||||
+19
-19
@@ -200,25 +200,25 @@
|
||||
"title": "Chyba"
|
||||
},
|
||||
"helpDialog": {
|
||||
"blog": "Prečítajte si náš blog",
|
||||
"click": "kliknutie",
|
||||
"curvedArrow": "Zakrivená šípka",
|
||||
"curvedLine": "Zakrivená čiara",
|
||||
"documentation": "Dokumentácia",
|
||||
"drag": "potiahnutie",
|
||||
"editor": "Editovanie",
|
||||
"github": "Objavili ste problém? Nahláste ho",
|
||||
"howto": "Postupujte podľa naších návodov",
|
||||
"or": "alebo",
|
||||
"preventBinding": "Zakázať pripájanie šípky",
|
||||
"shapes": "Tvary",
|
||||
"shortcuts": "Klávesové skratky",
|
||||
"textFinish": "Ukončenie editovania (text)",
|
||||
"textNewLine": "Vložiť nový riadok (text)",
|
||||
"title": "Pomocník",
|
||||
"view": "Zobrazenie",
|
||||
"zoomToFit": "Priblížiť aby boli zahrnuté všetky prvky",
|
||||
"zoomToSelection": "Priblížiť na výber"
|
||||
"blog": "",
|
||||
"click": "",
|
||||
"curvedArrow": "",
|
||||
"curvedLine": "",
|
||||
"documentation": "",
|
||||
"drag": "",
|
||||
"editor": "",
|
||||
"github": "",
|
||||
"howto": "",
|
||||
"or": "",
|
||||
"preventBinding": "",
|
||||
"shapes": "",
|
||||
"shortcuts": "",
|
||||
"textFinish": "",
|
||||
"textNewLine": "",
|
||||
"title": "",
|
||||
"view": "",
|
||||
"zoomToFit": "",
|
||||
"zoomToSelection": ""
|
||||
},
|
||||
"encrypted": {
|
||||
"tooltip": "Vaše kresby používajú end-to-end šifrovanie, takže ich Excalidraw server nedokáže prečítať."
|
||||
|
||||
+27
-27
@@ -60,10 +60,10 @@
|
||||
"extraBold": "超粗",
|
||||
"architect": "朴素",
|
||||
"artist": "艺术",
|
||||
"cartoonist": "漫画家",
|
||||
"cartoonist": "漫画师",
|
||||
"fileTitle": "文件标题",
|
||||
"colorPicker": "调色盘",
|
||||
"canvasBackground": "画布背景",
|
||||
"canvasBackground": "Canvas 背景",
|
||||
"drawingCanvas": "绘制 Canvas",
|
||||
"layers": "图层",
|
||||
"actions": "操作",
|
||||
@@ -128,10 +128,10 @@
|
||||
"clearReset": "这将会清除整个 画板。您是否要继续?",
|
||||
"couldNotCreateShareableLink": "无法创建共享链接",
|
||||
"couldNotCreateShareableLinkTooBig": "无法创建可共享链接:画布过大",
|
||||
"couldNotLoadInvalidFile": "无法加载无效的文件",
|
||||
"importBackendFailed": "从后端导入失败。",
|
||||
"cannotExportEmptyCanvas": "无法导出空白画布。",
|
||||
"couldNotCopyToClipboard": "无法复制到剪贴板,请尝试使用 Chrome 浏览器。",
|
||||
"couldNotLoadInvalidFile": "无法加载错误文件",
|
||||
"importBackendFailed": "从后端导入失败",
|
||||
"cannotExportEmptyCanvas": "无法导出空画布。",
|
||||
"couldNotCopyToClipboard": "无法复制到剪贴板。请尝试使用 Chrome 浏览器。",
|
||||
"decryptFailed": "无法解密数据。",
|
||||
"uploadedSecurly": "上传已被端到端加密保护,这意味着 Excalidraw 的服务器和第三方都无法读取内容。",
|
||||
"loadSceneOverridePrompt": "加载外部绘图将取代您现有的内容。您想要继续吗?",
|
||||
@@ -200,25 +200,25 @@
|
||||
"title": "错误"
|
||||
},
|
||||
"helpDialog": {
|
||||
"blog": "浏览我们的博客",
|
||||
"click": "单击",
|
||||
"curvedArrow": "曲线箭头",
|
||||
"curvedLine": "曲线",
|
||||
"documentation": "文档",
|
||||
"drag": "拖动",
|
||||
"editor": "编辑器",
|
||||
"github": "提交问题",
|
||||
"howto": "帮助文档",
|
||||
"or": "或",
|
||||
"preventBinding": "禁用箭头吸附",
|
||||
"shapes": "形状",
|
||||
"shortcuts": "快捷键列表",
|
||||
"textFinish": "完成文本编辑",
|
||||
"textNewLine": "文本换行",
|
||||
"title": "帮助",
|
||||
"view": "视图",
|
||||
"zoomToFit": "缩放以适应所有元素",
|
||||
"zoomToSelection": "缩放到选区"
|
||||
"blog": "",
|
||||
"click": "",
|
||||
"curvedArrow": "",
|
||||
"curvedLine": "",
|
||||
"documentation": "",
|
||||
"drag": "",
|
||||
"editor": "",
|
||||
"github": "",
|
||||
"howto": "",
|
||||
"or": "",
|
||||
"preventBinding": "",
|
||||
"shapes": "",
|
||||
"shortcuts": "",
|
||||
"textFinish": "",
|
||||
"textNewLine": "",
|
||||
"title": "",
|
||||
"view": "",
|
||||
"zoomToFit": "",
|
||||
"zoomToSelection": ""
|
||||
},
|
||||
"encrypted": {
|
||||
"tooltip": "您的绘图采用的端到端加密,其内容对于Excalidraw服务器是不可见的。"
|
||||
@@ -236,7 +236,7 @@
|
||||
"width": "宽度"
|
||||
},
|
||||
"toast": {
|
||||
"copyStyles": "复制样式",
|
||||
"copyToClipboardAsPng": "复制为 PNG 到剪贴板"
|
||||
"copyStyles": "",
|
||||
"copyToClipboardAsPng": ""
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,23 +12,6 @@ The change should be grouped under one of the below section and must contain PR
|
||||
Please add the latest change on the top under the correct section.
|
||||
-->
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
## Excalidraw API
|
||||
|
||||
- Expose `getAppState` on `excalidrawRef` [#2834](https://github.com/excalidraw/excalidraw/pull/2834).
|
||||
|
||||
## Excalidraw Library
|
||||
|
||||
### Features
|
||||
|
||||
- Remove `copy`, `cut`, and `paste` actions from contextmenu [#2872](https://github.com/excalidraw/excalidraw/pull/2872)
|
||||
- Support `Ctrl-Y` shortcut to redo on Windows [#2831](https://github.com/excalidraw/excalidraw/pull/2831).
|
||||
|
||||
### Fixes
|
||||
|
||||
- Fix remote pointers not accounting for offset [#2855](https://github.com/excalidraw/excalidraw/pull/2855).
|
||||
|
||||
## 0.2.1
|
||||
|
||||
## Excalidraw API
|
||||
|
||||
@@ -147,7 +147,7 @@ export default function App() {
|
||||
|
||||
<pre>
|
||||
import { getSceneVersion } from "@excalidraw/excalidraw";
|
||||
getSceneVersion(elements: <a href="https://github.com/excalidraw/excalidraw/blob/master/src/element/types.ts#L78">ExcalidrawElement[]</a>)
|
||||
getSceneVersion(elements: <a href="https://github.com/excalidraw/excalidraw/blob/master/src/element/types.ts#L78">ExcalidrawElement []</a>)
|
||||
</pre>
|
||||
|
||||
This function returns the current scene version.
|
||||
@@ -157,7 +157,7 @@ This function returns the current scene version.
|
||||
**_Signature_**
|
||||
|
||||
<pre>
|
||||
getSyncableElements(elements: <a href="https://github.com/excalidraw/excalidraw/blob/master/src/element/types.ts#L78">ExcalidrawElement[]</a>):<a href="https://github.com/excalidraw/excalidraw/blob/master/src/element/types.ts#L78">ExcalidrawElement[]</a>
|
||||
getSyncableElements(elements: <a href="https://github.com/excalidraw/excalidraw/blob/master/src/element/types.ts#L78">ExcalidrawElement []</a>):<a href="https://github.com/excalidraw/excalidraw/blob/master/src/element/types.ts#L78">ExcalidrawElement []</a>
|
||||
</pre>
|
||||
|
||||
**How to use**
|
||||
@@ -173,7 +173,7 @@ This function returns all the deleted elements of the scene.
|
||||
**_Signature_**
|
||||
|
||||
<pre>
|
||||
getElementsMap(elements: <a href="https://github.com/excalidraw/excalidraw/blob/master/src/element/types.ts#L78">ExcalidrawElement[]</a>): {[id: string]: <a href="https://github.com/excalidraw/excalidraw/blob/master/src/element/types.ts#L78">ExcalidrawElement</a>}
|
||||
getElementsMap(elements: <a href="https://github.com/excalidraw/excalidraw/blob/master/src/element/types.ts#L78">ExcalidrawElement []</a>): {[id: string]: <a href="https://github.com/excalidraw/excalidraw/blob/master/src/element/types.ts#L78">ExcalidrawElement</a>}
|
||||
</pre>
|
||||
|
||||
**How to use**
|
||||
@@ -220,7 +220,7 @@ This helps to load Excalidraw with `initialData`. It must be an object or a [pro
|
||||
|
||||
| name | type |
|
||||
| --- | --- |
|
||||
| elements | [ExcalidrawElement[]](https://github.com/excalidraw/excalidraw/blob/master/src/element/types.ts#L78) |
|
||||
| elements | [ExcalidrawElement []](https://github.com/excalidraw/excalidraw/blob/master/src/element/types.ts#L78) |
|
||||
| appState | [AppState](https://github.com/excalidraw/excalidraw/blob/master/src/types.ts#L37) |
|
||||
|
||||
```json
|
||||
@@ -268,9 +268,8 @@ You can pass a `ref` when you want to access some excalidraw APIs. We expose the
|
||||
| readyPromise | [resolvablePromise](https://github.com/excalidraw/excalidraw/blob/master/src/utils.ts#L317) | This promise will be resolved with the api once excalidraw has rendered. This will be helpful when you want do some action on the host app once this promise resolves. For this to work you will have to pass ref as shown [here](#readyPromise) |
|
||||
| updateScene | <pre>(<a href="https://github.com/excalidraw/excalidraw/blob/master/src/types.ts#L189">sceneData</a>)) => void </pre> | updates the scene with the sceneData |
|
||||
| resetScene | `({ resetLoadingState: boolean }) => void` | Resets the scene. If `resetLoadingState` is passed as true then it will also force set the loading state to false. |
|
||||
| getSceneElementsIncludingDeleted | <pre> () => <a href="https://github.com/excalidraw/excalidraw/blob/master/src/element/types.ts#L78">ExcalidrawElement[]</a></pre> | Returns all the elements including the deleted in the scene |
|
||||
| getSceneElements | <pre> () => <a href="https://github.com/excalidraw/excalidraw/blob/master/src/element/types.ts#L78">ExcalidrawElement[]</a></pre> | Returns all the elements excluding the deleted in the scene |
|
||||
| getAppState | <pre> () => <a href="https://github.com/excalidraw/excalidraw/blob/master/src/types.ts#L37">AppState</a></pre> | Returns current appState |
|
||||
| getSceneElementsIncludingDeleted | <pre> () => <a href="https://github.com/excalidraw/excalidraw/blob/master/src/element/types.ts#L78">ExcalidrawElement []</a></pre> | Returns all the elements including the deleted in the scene |
|
||||
| getSceneElements | <pre> () => <a href="https://github.com/excalidraw/excalidraw/blob/master/src/element/types.ts#L78">ExcalidrawElement []</a></pre> | Returns all the elements excluding the deleted in the scene |
|
||||
| history | `{ clear: () => void }` | This is the history API. `history.clear()` will clear the history |
|
||||
| setScrollToCenter | <pre> (<a href="https://github.com/excalidraw/excalidraw/blob/master/src/element/types.ts#L78">ExcalidrawElement[]</a>) => void </pre> | sets the elements to center |
|
||||
|
||||
@@ -325,7 +324,7 @@ import { defaultLang, languages } from "@excalidraw/excalidraw";
|
||||
| name | type |
|
||||
| --- | --- |
|
||||
| defaultLang | string |
|
||||
| languages | [Language[]](https://github.com/excalidraw/excalidraw/blob/master/src/i18n.ts#L8) |
|
||||
| languages | [Language []](https://github.com/excalidraw/excalidraw/blob/master/src/i18n.ts#L8) |
|
||||
|
||||
#### `renderFooter`
|
||||
|
||||
|
||||
+35
-90
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@excalidraw/excalidraw",
|
||||
"version": "0.2.1",
|
||||
"version": "0.2.0",
|
||||
"lockfileVersion": 1,
|
||||
"requires": true,
|
||||
"dependencies": {
|
||||
@@ -1182,9 +1182,9 @@
|
||||
}
|
||||
},
|
||||
"@types/estree": {
|
||||
"version": "0.0.46",
|
||||
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.46.tgz",
|
||||
"integrity": "sha512-laIjwTQaD+5DukBZaygQ79K1Z0jb1bPEMRrkXSLjtCcZm+abyp5YbrqpSLzD42FwWW6gK/aS4NYpJ804nG2brg==",
|
||||
"version": "0.0.45",
|
||||
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.45.tgz",
|
||||
"integrity": "sha512-jnqIUKDUqJbDIUxm0Uj7bnlMnRm1T/eZ9N+AVMqhPgzrba2GhGG5o/jCTwmdPK709nEZsGoMzXEDUjcXHa3W0g==",
|
||||
"dev": true
|
||||
},
|
||||
"@types/json-schema": {
|
||||
@@ -1345,12 +1345,6 @@
|
||||
"@xtuc/long": "4.2.2"
|
||||
}
|
||||
},
|
||||
"@webpack-cli/configtest": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@webpack-cli/configtest/-/configtest-1.0.0.tgz",
|
||||
"integrity": "sha512-Un0SdBoN1h4ACnIO7EiCjWuyhNI0Jl96JC+63q6xi4HDUYRZn8Auluea9D+v9NWKc5J4sICVEltdBaVjLX39xw==",
|
||||
"dev": true
|
||||
},
|
||||
"@webpack-cli/info": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/@webpack-cli/info/-/info-1.2.1.tgz",
|
||||
@@ -1361,9 +1355,9 @@
|
||||
}
|
||||
},
|
||||
"@webpack-cli/serve": {
|
||||
"version": "1.2.2",
|
||||
"resolved": "https://registry.npmjs.org/@webpack-cli/serve/-/serve-1.2.2.tgz",
|
||||
"integrity": "sha512-03GkWxcgFfm8+WIwcsqJb9agrSDNDDoxaNnexPnCCexP5SCE4IgFd9lNpSy+K2nFqVMpgTFw6SwbmVAVTndVew==",
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/@webpack-cli/serve/-/serve-1.2.1.tgz",
|
||||
"integrity": "sha512-Zj1z6AyS+vqV6Hfi7ngCjFGdHV5EwZNIHo6QfFTNe9PyW+zBU1zJ9BiOW1pmUEq950RC4+Dym6flyA/61/vhyw==",
|
||||
"dev": true
|
||||
},
|
||||
"@xtuc/ieee754": {
|
||||
@@ -1385,9 +1379,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"acorn-walk": {
|
||||
"version": "8.0.1",
|
||||
"resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.0.1.tgz",
|
||||
"integrity": "sha512-zn/7dYtoTVkG4EoMU55QlQU4F+m+T7Kren6Vj3C2DapWPnakG/DL9Ns5aPAPW5Ixd3uxXrV/BoMKKVFIazPcdg==",
|
||||
"version": "8.0.0",
|
||||
"resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.0.0.tgz",
|
||||
"integrity": "sha512-oZRad/3SMOI/pxbbmqyurIx7jHw1wZDcR9G44L8pUVFEomX/0dH89SrM1KaDXuv1NpzAXz6Op/Xu/Qd5XXzdEA==",
|
||||
"dev": true
|
||||
},
|
||||
"ajv": {
|
||||
@@ -1717,17 +1711,6 @@
|
||||
"tslib": "^1.9.0"
|
||||
}
|
||||
},
|
||||
"clone-deep": {
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz",
|
||||
"integrity": "sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"is-plain-object": "^2.0.4",
|
||||
"kind-of": "^6.0.2",
|
||||
"shallow-clone": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"color-convert": {
|
||||
"version": "1.9.3",
|
||||
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
|
||||
@@ -2262,15 +2245,6 @@
|
||||
"integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
|
||||
"dev": true
|
||||
},
|
||||
"is-plain-object": {
|
||||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz",
|
||||
"integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"isobject": "^3.0.1"
|
||||
}
|
||||
},
|
||||
"is-stream": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.0.tgz",
|
||||
@@ -2289,12 +2263,6 @@
|
||||
"integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=",
|
||||
"dev": true
|
||||
},
|
||||
"isobject": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz",
|
||||
"integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=",
|
||||
"dev": true
|
||||
},
|
||||
"jest-worker": {
|
||||
"version": "26.6.2",
|
||||
"resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-26.6.2.tgz",
|
||||
@@ -2356,12 +2324,6 @@
|
||||
"minimist": "^1.2.5"
|
||||
}
|
||||
},
|
||||
"kind-of": {
|
||||
"version": "6.0.3",
|
||||
"resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz",
|
||||
"integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==",
|
||||
"dev": true
|
||||
},
|
||||
"klona": {
|
||||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmjs.org/klona/-/klona-2.0.4.tgz",
|
||||
@@ -2473,9 +2435,9 @@
|
||||
}
|
||||
},
|
||||
"mime": {
|
||||
"version": "2.5.0",
|
||||
"resolved": "https://registry.npmjs.org/mime/-/mime-2.5.0.tgz",
|
||||
"integrity": "sha512-ft3WayFSFUVBuJj7BMLKAQcSlItKtfjsKDDsii3rqFDAZ7t11zRe8ASw/GlmivGwVUYtwkQrxiGGpL6gFvB0ag==",
|
||||
"version": "2.4.7",
|
||||
"resolved": "https://registry.npmjs.org/mime/-/mime-2.4.7.tgz",
|
||||
"integrity": "sha512-dhNd1uA2u397uQk3Nv5LM4lm93WYDUXFn3Fu291FJerns4jyTudqhIWe4W04YLy7Uk1tm1Ore04NpjRvQp/NPA==",
|
||||
"dev": true
|
||||
},
|
||||
"mime-db": {
|
||||
@@ -2500,9 +2462,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"mini-css-extract-plugin": {
|
||||
"version": "1.3.5",
|
||||
"resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-1.3.5.tgz",
|
||||
"integrity": "sha512-tvmzcwqJJXau4OQE5vT72pRT18o2zF+tQJp8CWchqvfQnTlflkzS+dANYcRdyPRWUWRkfmeNTKltx0NZI/b5dQ==",
|
||||
"version": "1.3.4",
|
||||
"resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-1.3.4.tgz",
|
||||
"integrity": "sha512-dNjqyeogUd8ucUgw5sxm1ahvSfSUgef7smbmATRSbDm4EmNx5kQA6VdUEhEeCKSjX6CTYjb5vxgMUvRjqP3uHg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"loader-utils": "^2.0.0",
|
||||
@@ -2956,15 +2918,6 @@
|
||||
"randombytes": "^2.1.0"
|
||||
}
|
||||
},
|
||||
"shallow-clone": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz",
|
||||
"integrity": "sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"kind-of": "^6.0.2"
|
||||
}
|
||||
},
|
||||
"shebang-command": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
|
||||
@@ -3304,13 +3257,13 @@
|
||||
}
|
||||
},
|
||||
"webpack": {
|
||||
"version": "5.19.0",
|
||||
"resolved": "https://registry.npmjs.org/webpack/-/webpack-5.19.0.tgz",
|
||||
"integrity": "sha512-egX19vAQ8fZ4cVYtA9Y941eqJtcZAK68mQq87MMv+GTXKZOc3TpKBBxdGX+HXUYlquPxiluNsJ1VHvwwklW7CQ==",
|
||||
"version": "5.15.0",
|
||||
"resolved": "https://registry.npmjs.org/webpack/-/webpack-5.15.0.tgz",
|
||||
"integrity": "sha512-y/xG+ONDz78yn3VvP6gAvGr1/gkxOgitvHSXBmquyN8KDtrGEyE3K9WkXOPB7QmfcOBCpO4ELXwNcCYQnEmexA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/eslint-scope": "^3.7.0",
|
||||
"@types/estree": "^0.0.46",
|
||||
"@types/estree": "^0.0.45",
|
||||
"@webassemblyjs/ast": "1.11.0",
|
||||
"@webassemblyjs/wasm-edit": "1.11.0",
|
||||
"@webassemblyjs/wasm-parser": "1.11.0",
|
||||
@@ -3427,9 +3380,9 @@
|
||||
}
|
||||
},
|
||||
"webpack-bundle-analyzer": {
|
||||
"version": "4.4.0",
|
||||
"resolved": "https://registry.npmjs.org/webpack-bundle-analyzer/-/webpack-bundle-analyzer-4.4.0.tgz",
|
||||
"integrity": "sha512-9DhNa+aXpqdHk8LkLPTBU/dMfl84Y+WE2+KnfI6rSpNRNVKa0VGLjPd2pjFubDeqnWmulFggxmWBxhfJXZnR0g==",
|
||||
"version": "4.3.0",
|
||||
"resolved": "https://registry.npmjs.org/webpack-bundle-analyzer/-/webpack-bundle-analyzer-4.3.0.tgz",
|
||||
"integrity": "sha512-J3TPm54bPARx6QG8z4cKBszahnUglcv70+N+8gUqv2I5KOFHJbzBiLx+pAp606so0X004fxM7hqRu10MLjJifA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"acorn": "^8.0.4",
|
||||
@@ -3501,15 +3454,14 @@
|
||||
}
|
||||
},
|
||||
"webpack-cli": {
|
||||
"version": "4.4.0",
|
||||
"resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-4.4.0.tgz",
|
||||
"integrity": "sha512-/Qh07CXfXEkMu5S8wEpjuaw2Zj/CC0hf/qbTDp6N8N7JjdGuaOjZ7kttz+zhuJO/J5m7alQEhNk9lsc4rC6xgQ==",
|
||||
"version": "4.3.1",
|
||||
"resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-4.3.1.tgz",
|
||||
"integrity": "sha512-/F4+9QNZM/qKzzL9/06Am8NXIkGV+/NqQ62Dx7DSqudxxpAgBqYn6V7+zp+0Y7JuWksKUbczRY3wMTd+7Uj6OA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@discoveryjs/json-ext": "^0.5.0",
|
||||
"@webpack-cli/configtest": "^1.0.0",
|
||||
"@webpack-cli/info": "^1.2.1",
|
||||
"@webpack-cli/serve": "^1.2.2",
|
||||
"@webpack-cli/serve": "^1.2.1",
|
||||
"colorette": "^1.2.1",
|
||||
"commander": "^6.2.0",
|
||||
"enquirer": "^2.3.6",
|
||||
@@ -3519,7 +3471,7 @@
|
||||
"interpret": "^2.2.0",
|
||||
"rechoir": "^0.7.0",
|
||||
"v8-compile-cache": "^2.2.0",
|
||||
"webpack-merge": "^5.7.3"
|
||||
"webpack-merge": "^4.2.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"commander": {
|
||||
@@ -3531,13 +3483,12 @@
|
||||
}
|
||||
},
|
||||
"webpack-merge": {
|
||||
"version": "5.7.3",
|
||||
"resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-5.7.3.tgz",
|
||||
"integrity": "sha512-6/JUQv0ELQ1igjGDzHkXbVDRxkfA57Zw7PfiupdLFJYrgFqY5ZP8xxbpp2lU3EPwYx89ht5Z/aDkD40hFCm5AA==",
|
||||
"version": "4.2.2",
|
||||
"resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-4.2.2.tgz",
|
||||
"integrity": "sha512-TUE1UGoTX2Cd42j3krGYqObZbOD+xF7u28WB7tfUordytSjbWTIjK/8V0amkBfTYN4/pB/GIDlJZZ657BGG19g==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"clone-deep": "^4.0.1",
|
||||
"wildcard": "^2.0.0"
|
||||
"lodash": "^4.17.15"
|
||||
}
|
||||
},
|
||||
"webpack-sources": {
|
||||
@@ -3567,16 +3518,10 @@
|
||||
"isexe": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"wildcard": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/wildcard/-/wildcard-2.0.0.tgz",
|
||||
"integrity": "sha512-JcKqAHLPxcdb9KM49dufGXn2x3ssnfjbcaQdLlfZsL9rH9wgDQjUtDxbo8NE0F6SFvydeu1VhZe7hZuHsB2/pw==",
|
||||
"dev": true
|
||||
},
|
||||
"ws": {
|
||||
"version": "7.4.2",
|
||||
"resolved": "https://registry.npmjs.org/ws/-/ws-7.4.2.tgz",
|
||||
"integrity": "sha512-T4tewALS3+qsrpGI/8dqNMLIVdq/g/85U98HPMa6F0m6xTbvhXU6RCQLqPH3+SlomNV/LdY6RXEbBpMH6EOJnA==",
|
||||
"version": "7.4.1",
|
||||
"resolved": "https://registry.npmjs.org/ws/-/ws-7.4.1.tgz",
|
||||
"integrity": "sha512-pTsP8UAfhy3sk1lSk/O/s4tjD0CRwvMnzvwr4OKGX7ZvqZtUyx4KIJB5JWbkykPoc55tixMGgTNoh3k4FkNGFQ==",
|
||||
"dev": true
|
||||
},
|
||||
"yallist": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@excalidraw/excalidraw",
|
||||
"version": "0.2.1",
|
||||
"version": "0.2.2",
|
||||
"main": "dist/excalidraw.min.js",
|
||||
"files": [
|
||||
"dist/*"
|
||||
@@ -54,13 +54,13 @@
|
||||
"cross-env": "7.0.3",
|
||||
"css-loader": "5.0.1",
|
||||
"file-loader": "6.2.0",
|
||||
"mini-css-extract-plugin": "1.3.5",
|
||||
"mini-css-extract-plugin": "1.3.4",
|
||||
"sass-loader": "10.1.1",
|
||||
"terser-webpack-plugin": "5.1.1",
|
||||
"ts-loader": "8.0.14",
|
||||
"webpack": "5.19.0",
|
||||
"webpack-bundle-analyzer": "4.4.0",
|
||||
"webpack-cli": "4.4.0"
|
||||
"webpack": "5.15.0",
|
||||
"webpack-bundle-analyzer": "4.3.0",
|
||||
"webpack-cli": "4.3.1"
|
||||
},
|
||||
"bugs": "https://github.com/excalidraw/excalidraw/issues",
|
||||
"repository": "https://github.com/excalidraw/excalidraw",
|
||||
|
||||
@@ -2,6 +2,7 @@ const path = require("path");
|
||||
const TerserPlugin = require("terser-webpack-plugin");
|
||||
const BundleAnalyzerPlugin = require("webpack-bundle-analyzer")
|
||||
.BundleAnalyzerPlugin;
|
||||
const webpack = require("webpack");
|
||||
|
||||
module.exports = {
|
||||
mode: "production",
|
||||
@@ -24,7 +25,16 @@ module.exports = {
|
||||
{
|
||||
test: /\.(sa|sc|c)ss$/,
|
||||
exclude: /node_modules/,
|
||||
use: ["style-loader", { loader: "css-loader" }, "sass-loader"],
|
||||
use: [
|
||||
"style-loader",
|
||||
{
|
||||
loader: "css-loader",
|
||||
options: {
|
||||
sourceMap: false,
|
||||
},
|
||||
},
|
||||
"sass-loader",
|
||||
],
|
||||
},
|
||||
{
|
||||
test: /\.(ts|tsx|js|jsx|mjs)$/,
|
||||
@@ -89,6 +99,10 @@ module.exports = {
|
||||
},
|
||||
plugins: [
|
||||
...(process.env.ANALYZER === "true" ? [new BundleAnalyzerPlugin()] : []),
|
||||
new webpack.SourceMapDevToolPlugin({
|
||||
filename: "sourcemaps/[name].js.map",
|
||||
publicPath: "https://unpkg.com/@excalidraw/excalidraw@0.2.2/dist/",
|
||||
}),
|
||||
],
|
||||
externals: {
|
||||
react: {
|
||||
|
||||
Generated
+34
-89
@@ -1097,9 +1097,9 @@
|
||||
}
|
||||
},
|
||||
"@types/estree": {
|
||||
"version": "0.0.46",
|
||||
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.46.tgz",
|
||||
"integrity": "sha512-laIjwTQaD+5DukBZaygQ79K1Z0jb1bPEMRrkXSLjtCcZm+abyp5YbrqpSLzD42FwWW6gK/aS4NYpJ804nG2brg==",
|
||||
"version": "0.0.45",
|
||||
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.45.tgz",
|
||||
"integrity": "sha512-jnqIUKDUqJbDIUxm0Uj7bnlMnRm1T/eZ9N+AVMqhPgzrba2GhGG5o/jCTwmdPK709nEZsGoMzXEDUjcXHa3W0g==",
|
||||
"dev": true
|
||||
},
|
||||
"@types/json-schema": {
|
||||
@@ -1109,9 +1109,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"@types/node": {
|
||||
"version": "14.14.22",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-14.14.22.tgz",
|
||||
"integrity": "sha512-g+f/qj/cNcqKkc3tFqlXOYjrmZA+jNBiDzbP3kH+B+otKFqAdPgVTGP1IeKRdMml/aE69as5S4FqtxAbl+LaMw==",
|
||||
"version": "14.14.21",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-14.14.21.tgz",
|
||||
"integrity": "sha512-cHYfKsnwllYhjOzuC5q1VpguABBeecUp24yFluHpn/BQaVxB1CuQ1FSRZCzrPxrkIfWISXV2LbeoBthLWg0+0A==",
|
||||
"dev": true
|
||||
},
|
||||
"@webassemblyjs/ast": {
|
||||
@@ -1260,12 +1260,6 @@
|
||||
"@xtuc/long": "4.2.2"
|
||||
}
|
||||
},
|
||||
"@webpack-cli/configtest": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@webpack-cli/configtest/-/configtest-1.0.0.tgz",
|
||||
"integrity": "sha512-Un0SdBoN1h4ACnIO7EiCjWuyhNI0Jl96JC+63q6xi4HDUYRZn8Auluea9D+v9NWKc5J4sICVEltdBaVjLX39xw==",
|
||||
"dev": true
|
||||
},
|
||||
"@webpack-cli/info": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/@webpack-cli/info/-/info-1.2.1.tgz",
|
||||
@@ -1276,9 +1270,9 @@
|
||||
}
|
||||
},
|
||||
"@webpack-cli/serve": {
|
||||
"version": "1.2.2",
|
||||
"resolved": "https://registry.npmjs.org/@webpack-cli/serve/-/serve-1.2.2.tgz",
|
||||
"integrity": "sha512-03GkWxcgFfm8+WIwcsqJb9agrSDNDDoxaNnexPnCCexP5SCE4IgFd9lNpSy+K2nFqVMpgTFw6SwbmVAVTndVew==",
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/@webpack-cli/serve/-/serve-1.2.1.tgz",
|
||||
"integrity": "sha512-Zj1z6AyS+vqV6Hfi7ngCjFGdHV5EwZNIHo6QfFTNe9PyW+zBU1zJ9BiOW1pmUEq950RC4+Dym6flyA/61/vhyw==",
|
||||
"dev": true
|
||||
},
|
||||
"@xtuc/ieee754": {
|
||||
@@ -1300,9 +1294,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"acorn-walk": {
|
||||
"version": "8.0.1",
|
||||
"resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.0.1.tgz",
|
||||
"integrity": "sha512-zn/7dYtoTVkG4EoMU55QlQU4F+m+T7Kren6Vj3C2DapWPnakG/DL9Ns5aPAPW5Ixd3uxXrV/BoMKKVFIazPcdg==",
|
||||
"version": "8.0.0",
|
||||
"resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.0.0.tgz",
|
||||
"integrity": "sha512-oZRad/3SMOI/pxbbmqyurIx7jHw1wZDcR9G44L8pUVFEomX/0dH89SrM1KaDXuv1NpzAXz6Op/Xu/Qd5XXzdEA==",
|
||||
"dev": true
|
||||
},
|
||||
"ajv": {
|
||||
@@ -1626,17 +1620,6 @@
|
||||
"tslib": "^1.9.0"
|
||||
}
|
||||
},
|
||||
"clone-deep": {
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz",
|
||||
"integrity": "sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"is-plain-object": "^2.0.4",
|
||||
"kind-of": "^6.0.2",
|
||||
"shallow-clone": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"color-convert": {
|
||||
"version": "1.9.3",
|
||||
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
|
||||
@@ -2100,15 +2083,6 @@
|
||||
"integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
|
||||
"dev": true
|
||||
},
|
||||
"is-plain-object": {
|
||||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz",
|
||||
"integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"isobject": "^3.0.1"
|
||||
}
|
||||
},
|
||||
"is-stream": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.0.tgz",
|
||||
@@ -2127,12 +2101,6 @@
|
||||
"integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=",
|
||||
"dev": true
|
||||
},
|
||||
"isobject": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz",
|
||||
"integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=",
|
||||
"dev": true
|
||||
},
|
||||
"jest-worker": {
|
||||
"version": "26.6.2",
|
||||
"resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-26.6.2.tgz",
|
||||
@@ -2194,12 +2162,6 @@
|
||||
"minimist": "^1.2.5"
|
||||
}
|
||||
},
|
||||
"kind-of": {
|
||||
"version": "6.0.3",
|
||||
"resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz",
|
||||
"integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==",
|
||||
"dev": true
|
||||
},
|
||||
"loader-runner": {
|
||||
"version": "4.2.0",
|
||||
"resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.2.0.tgz",
|
||||
@@ -2305,9 +2267,9 @@
|
||||
}
|
||||
},
|
||||
"mime": {
|
||||
"version": "2.5.0",
|
||||
"resolved": "https://registry.npmjs.org/mime/-/mime-2.5.0.tgz",
|
||||
"integrity": "sha512-ft3WayFSFUVBuJj7BMLKAQcSlItKtfjsKDDsii3rqFDAZ7t11zRe8ASw/GlmivGwVUYtwkQrxiGGpL6gFvB0ag==",
|
||||
"version": "2.4.7",
|
||||
"resolved": "https://registry.npmjs.org/mime/-/mime-2.4.7.tgz",
|
||||
"integrity": "sha512-dhNd1uA2u397uQk3Nv5LM4lm93WYDUXFn3Fu291FJerns4jyTudqhIWe4W04YLy7Uk1tm1Ore04NpjRvQp/NPA==",
|
||||
"dev": true
|
||||
},
|
||||
"mime-db": {
|
||||
@@ -2629,15 +2591,6 @@
|
||||
"randombytes": "^2.1.0"
|
||||
}
|
||||
},
|
||||
"shallow-clone": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz",
|
||||
"integrity": "sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"kind-of": "^6.0.2"
|
||||
}
|
||||
},
|
||||
"shebang-command": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
|
||||
@@ -2971,13 +2924,13 @@
|
||||
}
|
||||
},
|
||||
"webpack": {
|
||||
"version": "5.19.0",
|
||||
"resolved": "https://registry.npmjs.org/webpack/-/webpack-5.19.0.tgz",
|
||||
"integrity": "sha512-egX19vAQ8fZ4cVYtA9Y941eqJtcZAK68mQq87MMv+GTXKZOc3TpKBBxdGX+HXUYlquPxiluNsJ1VHvwwklW7CQ==",
|
||||
"version": "5.15.0",
|
||||
"resolved": "https://registry.npmjs.org/webpack/-/webpack-5.15.0.tgz",
|
||||
"integrity": "sha512-y/xG+ONDz78yn3VvP6gAvGr1/gkxOgitvHSXBmquyN8KDtrGEyE3K9WkXOPB7QmfcOBCpO4ELXwNcCYQnEmexA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/eslint-scope": "^3.7.0",
|
||||
"@types/estree": "^0.0.46",
|
||||
"@types/estree": "^0.0.45",
|
||||
"@webassemblyjs/ast": "1.11.0",
|
||||
"@webassemblyjs/wasm-edit": "1.11.0",
|
||||
"@webassemblyjs/wasm-parser": "1.11.0",
|
||||
@@ -3078,9 +3031,9 @@
|
||||
}
|
||||
},
|
||||
"webpack-bundle-analyzer": {
|
||||
"version": "4.4.0",
|
||||
"resolved": "https://registry.npmjs.org/webpack-bundle-analyzer/-/webpack-bundle-analyzer-4.4.0.tgz",
|
||||
"integrity": "sha512-9DhNa+aXpqdHk8LkLPTBU/dMfl84Y+WE2+KnfI6rSpNRNVKa0VGLjPd2pjFubDeqnWmulFggxmWBxhfJXZnR0g==",
|
||||
"version": "4.3.0",
|
||||
"resolved": "https://registry.npmjs.org/webpack-bundle-analyzer/-/webpack-bundle-analyzer-4.3.0.tgz",
|
||||
"integrity": "sha512-J3TPm54bPARx6QG8z4cKBszahnUglcv70+N+8gUqv2I5KOFHJbzBiLx+pAp606so0X004fxM7hqRu10MLjJifA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"acorn": "^8.0.4",
|
||||
@@ -3152,15 +3105,14 @@
|
||||
}
|
||||
},
|
||||
"webpack-cli": {
|
||||
"version": "4.4.0",
|
||||
"resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-4.4.0.tgz",
|
||||
"integrity": "sha512-/Qh07CXfXEkMu5S8wEpjuaw2Zj/CC0hf/qbTDp6N8N7JjdGuaOjZ7kttz+zhuJO/J5m7alQEhNk9lsc4rC6xgQ==",
|
||||
"version": "4.3.1",
|
||||
"resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-4.3.1.tgz",
|
||||
"integrity": "sha512-/F4+9QNZM/qKzzL9/06Am8NXIkGV+/NqQ62Dx7DSqudxxpAgBqYn6V7+zp+0Y7JuWksKUbczRY3wMTd+7Uj6OA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@discoveryjs/json-ext": "^0.5.0",
|
||||
"@webpack-cli/configtest": "^1.0.0",
|
||||
"@webpack-cli/info": "^1.2.1",
|
||||
"@webpack-cli/serve": "^1.2.2",
|
||||
"@webpack-cli/serve": "^1.2.1",
|
||||
"colorette": "^1.2.1",
|
||||
"commander": "^6.2.0",
|
||||
"enquirer": "^2.3.6",
|
||||
@@ -3170,7 +3122,7 @@
|
||||
"interpret": "^2.2.0",
|
||||
"rechoir": "^0.7.0",
|
||||
"v8-compile-cache": "^2.2.0",
|
||||
"webpack-merge": "^5.7.3"
|
||||
"webpack-merge": "^4.2.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"commander": {
|
||||
@@ -3182,13 +3134,12 @@
|
||||
}
|
||||
},
|
||||
"webpack-merge": {
|
||||
"version": "5.7.3",
|
||||
"resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-5.7.3.tgz",
|
||||
"integrity": "sha512-6/JUQv0ELQ1igjGDzHkXbVDRxkfA57Zw7PfiupdLFJYrgFqY5ZP8xxbpp2lU3EPwYx89ht5Z/aDkD40hFCm5AA==",
|
||||
"version": "4.2.2",
|
||||
"resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-4.2.2.tgz",
|
||||
"integrity": "sha512-TUE1UGoTX2Cd42j3krGYqObZbOD+xF7u28WB7tfUordytSjbWTIjK/8V0amkBfTYN4/pB/GIDlJZZ657BGG19g==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"clone-deep": "^4.0.1",
|
||||
"wildcard": "^2.0.0"
|
||||
"lodash": "^4.17.15"
|
||||
}
|
||||
},
|
||||
"webpack-sources": {
|
||||
@@ -3218,16 +3169,10 @@
|
||||
"isexe": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"wildcard": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/wildcard/-/wildcard-2.0.0.tgz",
|
||||
"integrity": "sha512-JcKqAHLPxcdb9KM49dufGXn2x3ssnfjbcaQdLlfZsL9rH9wgDQjUtDxbo8NE0F6SFvydeu1VhZe7hZuHsB2/pw==",
|
||||
"dev": true
|
||||
},
|
||||
"ws": {
|
||||
"version": "7.4.2",
|
||||
"resolved": "https://registry.npmjs.org/ws/-/ws-7.4.2.tgz",
|
||||
"integrity": "sha512-T4tewALS3+qsrpGI/8dqNMLIVdq/g/85U98HPMa6F0m6xTbvhXU6RCQLqPH3+SlomNV/LdY6RXEbBpMH6EOJnA==",
|
||||
"version": "7.4.1",
|
||||
"resolved": "https://registry.npmjs.org/ws/-/ws-7.4.1.tgz",
|
||||
"integrity": "sha512-pTsP8UAfhy3sk1lSk/O/s4tjD0CRwvMnzvwr4OKGX7ZvqZtUyx4KIJB5JWbkykPoc55tixMGgTNoh3k4FkNGFQ==",
|
||||
"dev": true
|
||||
},
|
||||
"yallist": {
|
||||
|
||||
@@ -46,9 +46,9 @@
|
||||
"cross-env": "7.0.3",
|
||||
"file-loader": "6.2.0",
|
||||
"ts-loader": "8.0.14",
|
||||
"webpack": "5.19.0",
|
||||
"webpack-bundle-analyzer": "4.4.0",
|
||||
"webpack-cli": "4.4.0"
|
||||
"webpack": "5.15.0",
|
||||
"webpack-bundle-analyzer": "4.3.0",
|
||||
"webpack-cli": "4.3.1"
|
||||
},
|
||||
"bugs": "https://github.com/excalidraw/excalidraw/issues",
|
||||
"repository": "https://github.com/excalidraw/excalidraw",
|
||||
|
||||
@@ -349,12 +349,12 @@ const generateElementShape = (
|
||||
if (element.type === "arrow") {
|
||||
const { startArrowhead = null, endArrowhead = "arrow" } = element;
|
||||
|
||||
const getArrowheadShapes = (
|
||||
function getArrowheadShapes(
|
||||
element: ExcalidrawLinearElement,
|
||||
shape: Drawable[],
|
||||
position: "start" | "end",
|
||||
arrowhead: Arrowhead,
|
||||
) => {
|
||||
) {
|
||||
const arrowheadPoints = getArrowheadPoints(
|
||||
element,
|
||||
shape,
|
||||
@@ -392,7 +392,7 @@ const generateElementShape = (
|
||||
generator.line(x3, y3, x2, y2, options),
|
||||
generator.line(x4, y4, x2, y2, options),
|
||||
];
|
||||
};
|
||||
}
|
||||
|
||||
if (startArrowhead !== null) {
|
||||
const shapes = getArrowheadShapes(
|
||||
|
||||
@@ -418,9 +418,7 @@ export const renderScene = (
|
||||
// Paint remote pointers
|
||||
for (const clientId in sceneState.remotePointerViewportCoords) {
|
||||
let { x, y } = sceneState.remotePointerViewportCoords[clientId];
|
||||
|
||||
x -= appState.offsetLeft;
|
||||
y -= appState.offsetTop;
|
||||
const username = sceneState.remotePointerUsernames[clientId];
|
||||
|
||||
const width = 9;
|
||||
const height = 14;
|
||||
@@ -475,8 +473,6 @@ export const renderScene = (
|
||||
context.fill();
|
||||
context.stroke();
|
||||
|
||||
const username = sceneState.remotePointerUsernames[clientId];
|
||||
|
||||
if (!isOutOfBounds && username) {
|
||||
const offsetX = x + width;
|
||||
const offsetY = y + height;
|
||||
|
||||
+3
-2
@@ -5,6 +5,7 @@ import { NonDeletedExcalidrawElement } from "../element/types";
|
||||
import { getCommonBounds } from "../element/bounds";
|
||||
import { renderScene, renderSceneToSvg } from "../renderer/renderScene";
|
||||
import { distance, SVG_NS } from "../utils";
|
||||
import { normalizeScroll } from "./scroll";
|
||||
import { AppState } from "../types";
|
||||
import { t } from "../i18n";
|
||||
import { DEFAULT_FONT_FAMILY, DEFAULT_VERTICAL_ALIGN } from "../constants";
|
||||
@@ -58,8 +59,8 @@ export const exportToCanvas = (
|
||||
tempCanvas,
|
||||
{
|
||||
viewBackgroundColor: exportBackground ? viewBackgroundColor : null,
|
||||
scrollX: -minX + exportPadding,
|
||||
scrollY: -minY + exportPadding,
|
||||
scrollX: normalizeScroll(-minX + exportPadding),
|
||||
scrollY: normalizeScroll(-minY + exportPadding),
|
||||
zoom: getDefaultAppState().zoom,
|
||||
remotePointerViewportCoords: {},
|
||||
remoteSelectedElementIds: {},
|
||||
|
||||
+1
-1
@@ -6,7 +6,7 @@ export {
|
||||
getSelectedElements,
|
||||
getTargetElements,
|
||||
} from "./selection";
|
||||
export { calculateScrollCenter } from "./scroll";
|
||||
export { normalizeScroll, calculateScrollCenter } from "./scroll";
|
||||
export {
|
||||
hasBackground,
|
||||
hasStroke,
|
||||
|
||||
+15
-10
@@ -1,4 +1,4 @@
|
||||
import { AppState, PointerCoords, Zoom } from "../types";
|
||||
import { AppState, FlooredNumber, PointerCoords, Zoom } from "../types";
|
||||
import { ExcalidrawElement } from "../element/types";
|
||||
import { getCommonBounds, getClosestElementBounds } from "../element";
|
||||
|
||||
@@ -7,6 +7,9 @@ import {
|
||||
viewportCoordsToSceneCoords,
|
||||
} from "../utils";
|
||||
|
||||
export const normalizeScroll = (pos: number) =>
|
||||
Math.floor(pos) as FlooredNumber;
|
||||
|
||||
const isOutsideViewPort = (
|
||||
appState: AppState,
|
||||
canvas: HTMLCanvasElement | null,
|
||||
@@ -37,14 +40,16 @@ export const centerScrollOn = ({
|
||||
zoom: Zoom;
|
||||
}) => {
|
||||
return {
|
||||
scrollX:
|
||||
scrollX: normalizeScroll(
|
||||
(viewportDimensions.width / 2) * (1 / zoom.value) -
|
||||
scenePoint.x -
|
||||
zoom.translation.x * (1 / zoom.value),
|
||||
scrollY:
|
||||
scenePoint.x -
|
||||
zoom.translation.x * (1 / zoom.value),
|
||||
),
|
||||
scrollY: normalizeScroll(
|
||||
(viewportDimensions.height / 2) * (1 / zoom.value) -
|
||||
scenePoint.y -
|
||||
zoom.translation.y * (1 / zoom.value),
|
||||
scenePoint.y -
|
||||
zoom.translation.y * (1 / zoom.value),
|
||||
),
|
||||
};
|
||||
};
|
||||
|
||||
@@ -52,11 +57,11 @@ export const calculateScrollCenter = (
|
||||
elements: readonly ExcalidrawElement[],
|
||||
appState: AppState,
|
||||
canvas: HTMLCanvasElement | null,
|
||||
): { scrollX: number; scrollY: number } => {
|
||||
): { scrollX: FlooredNumber; scrollY: FlooredNumber } => {
|
||||
if (!elements.length) {
|
||||
return {
|
||||
scrollX: 0,
|
||||
scrollY: 0,
|
||||
scrollX: normalizeScroll(0),
|
||||
scrollY: normalizeScroll(0),
|
||||
};
|
||||
}
|
||||
let [x1, y1, x2, y2] = getCommonBounds(elements);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { ExcalidrawElement } from "../element/types";
|
||||
import { getCommonBounds } from "../element";
|
||||
import { Zoom } from "../types";
|
||||
import { FlooredNumber, Zoom } from "../types";
|
||||
import { ScrollBars } from "./types";
|
||||
import { getGlobalCSSVariable } from "../utils";
|
||||
import { getLanguage } from "../i18n";
|
||||
@@ -18,8 +18,8 @@ export const getScrollBars = (
|
||||
scrollY,
|
||||
zoom,
|
||||
}: {
|
||||
scrollX: number;
|
||||
scrollY: number;
|
||||
scrollX: FlooredNumber;
|
||||
scrollY: FlooredNumber;
|
||||
zoom: Zoom;
|
||||
},
|
||||
): ScrollBars => {
|
||||
|
||||
+5
-5
@@ -1,9 +1,9 @@
|
||||
import { ExcalidrawTextElement } from "../element/types";
|
||||
import { Zoom } from "../types";
|
||||
import { FlooredNumber, Zoom } from "../types";
|
||||
|
||||
export type SceneState = {
|
||||
scrollX: number;
|
||||
scrollY: number;
|
||||
scrollX: FlooredNumber;
|
||||
scrollY: FlooredNumber;
|
||||
// null indicates transparent bg
|
||||
viewBackgroundColor: string | null;
|
||||
zoom: Zoom;
|
||||
@@ -15,8 +15,8 @@ export type SceneState = {
|
||||
};
|
||||
|
||||
export type SceneScroll = {
|
||||
scrollX: number;
|
||||
scrollY: number;
|
||||
scrollX: FlooredNumber;
|
||||
scrollY: FlooredNumber;
|
||||
};
|
||||
|
||||
export interface Scene {
|
||||
|
||||
+1
-1
@@ -25,6 +25,6 @@ export const getNewZoom = (
|
||||
|
||||
export const getNormalizedZoom = (zoom: number): NormalizedZoomValue => {
|
||||
const normalizedZoom = parseFloat(zoom.toFixed(2));
|
||||
const clampedZoom = Math.max(0.1, Math.min(normalizedZoom, 10));
|
||||
const clampedZoom = Math.max(0.1, Math.min(normalizedZoom, 2));
|
||||
return clampedZoom as NormalizedZoomValue;
|
||||
};
|
||||
|
||||
@@ -457,7 +457,7 @@ Object {
|
||||
|
||||
exports[`given element A and group of elements B and given both are selected when user clicks on B, on pointer up only elements from B should be selected: [end of test] number of elements 1`] = `3`;
|
||||
|
||||
exports[`given element A and group of elements B and given both are selected when user clicks on B, on pointer up only elements from B should be selected: [end of test] number of renders 1`] = `26`;
|
||||
exports[`given element A and group of elements B and given both are selected when user clicks on B, on pointer up only elements from B should be selected: [end of test] number of renders 1`] = `25`;
|
||||
|
||||
exports[`given element A and group of elements B and given both are selected when user shift-clicks on B, on pointer up only element A should be selected: [end of test] appState 1`] = `
|
||||
Object {
|
||||
@@ -922,7 +922,7 @@ Object {
|
||||
|
||||
exports[`given element A and group of elements B and given both are selected when user shift-clicks on B, on pointer up only element A should be selected: [end of test] number of elements 1`] = `3`;
|
||||
|
||||
exports[`given element A and group of elements B and given both are selected when user shift-clicks on B, on pointer up only element A should be selected: [end of test] number of renders 1`] = `22`;
|
||||
exports[`given element A and group of elements B and given both are selected when user shift-clicks on B, on pointer up only element A should be selected: [end of test] number of renders 1`] = `21`;
|
||||
|
||||
exports[`regression tests Cmd/Ctrl-click exclusively select element under pointer: [end of test] appState 1`] = `
|
||||
Object {
|
||||
@@ -1696,7 +1696,7 @@ Object {
|
||||
|
||||
exports[`regression tests Cmd/Ctrl-click exclusively select element under pointer: [end of test] number of elements 1`] = `3`;
|
||||
|
||||
exports[`regression tests Cmd/Ctrl-click exclusively select element under pointer: [end of test] number of renders 1`] = `41`;
|
||||
exports[`regression tests Cmd/Ctrl-click exclusively select element under pointer: [end of test] number of renders 1`] = `40`;
|
||||
|
||||
exports[`regression tests Drags selected element when hitting only bounding box and keeps element selected: [end of test] appState 1`] = `
|
||||
Object {
|
||||
@@ -1898,7 +1898,7 @@ Object {
|
||||
|
||||
exports[`regression tests Drags selected element when hitting only bounding box and keeps element selected: [end of test] number of elements 1`] = `1`;
|
||||
|
||||
exports[`regression tests Drags selected element when hitting only bounding box and keeps element selected: [end of test] number of renders 1`] = `10`;
|
||||
exports[`regression tests Drags selected element when hitting only bounding box and keeps element selected: [end of test] number of renders 1`] = `9`;
|
||||
|
||||
exports[`regression tests adjusts z order when grouping: [end of test] appState 1`] = `
|
||||
Object {
|
||||
@@ -2354,7 +2354,7 @@ Object {
|
||||
|
||||
exports[`regression tests adjusts z order when grouping: [end of test] number of elements 1`] = `3`;
|
||||
|
||||
exports[`regression tests adjusts z order when grouping: [end of test] number of renders 1`] = `20`;
|
||||
exports[`regression tests adjusts z order when grouping: [end of test] number of renders 1`] = `19`;
|
||||
|
||||
exports[`regression tests alt-drag duplicates an element: [end of test] appState 1`] = `
|
||||
Object {
|
||||
@@ -2605,7 +2605,7 @@ Object {
|
||||
|
||||
exports[`regression tests alt-drag duplicates an element: [end of test] number of elements 1`] = `2`;
|
||||
|
||||
exports[`regression tests alt-drag duplicates an element: [end of test] number of renders 1`] = `10`;
|
||||
exports[`regression tests alt-drag duplicates an element: [end of test] number of renders 1`] = `9`;
|
||||
|
||||
exports[`regression tests arrow keys: [end of test] appState 1`] = `
|
||||
Object {
|
||||
@@ -2767,7 +2767,7 @@ Object {
|
||||
|
||||
exports[`regression tests arrow keys: [end of test] number of elements 1`] = `1`;
|
||||
|
||||
exports[`regression tests arrow keys: [end of test] number of renders 1`] = `19`;
|
||||
exports[`regression tests arrow keys: [end of test] number of renders 1`] = `18`;
|
||||
|
||||
exports[`regression tests can drag element that covers another element, while another elem is selected: [end of test] appState 1`] = `
|
||||
Object {
|
||||
@@ -3242,7 +3242,7 @@ Object {
|
||||
|
||||
exports[`regression tests can drag element that covers another element, while another elem is selected: [end of test] number of elements 1`] = `3`;
|
||||
|
||||
exports[`regression tests can drag element that covers another element, while another elem is selected: [end of test] number of renders 1`] = `18`;
|
||||
exports[`regression tests can drag element that covers another element, while another elem is selected: [end of test] number of renders 1`] = `17`;
|
||||
|
||||
exports[`regression tests change the properties of a shape: [end of test] appState 1`] = `
|
||||
Object {
|
||||
@@ -3548,7 +3548,7 @@ Object {
|
||||
|
||||
exports[`regression tests change the properties of a shape: [end of test] number of elements 1`] = `1`;
|
||||
|
||||
exports[`regression tests change the properties of a shape: [end of test] number of renders 1`] = `11`;
|
||||
exports[`regression tests change the properties of a shape: [end of test] number of renders 1`] = `10`;
|
||||
|
||||
exports[`regression tests click on an element and drag it: [dragged] appState 1`] = `
|
||||
Object {
|
||||
@@ -3750,7 +3750,7 @@ Object {
|
||||
|
||||
exports[`regression tests click on an element and drag it: [dragged] number of elements 1`] = `1`;
|
||||
|
||||
exports[`regression tests click on an element and drag it: [dragged] number of renders 1`] = `10`;
|
||||
exports[`regression tests click on an element and drag it: [dragged] number of renders 1`] = `9`;
|
||||
|
||||
exports[`regression tests click on an element and drag it: [end of test] appState 1`] = `
|
||||
Object {
|
||||
@@ -3992,7 +3992,7 @@ Object {
|
||||
|
||||
exports[`regression tests click on an element and drag it: [end of test] number of elements 1`] = `1`;
|
||||
|
||||
exports[`regression tests click on an element and drag it: [end of test] number of renders 1`] = `13`;
|
||||
exports[`regression tests click on an element and drag it: [end of test] number of renders 1`] = `12`;
|
||||
|
||||
exports[`regression tests click to select a shape: [end of test] appState 1`] = `
|
||||
Object {
|
||||
@@ -4242,7 +4242,7 @@ Object {
|
||||
|
||||
exports[`regression tests click to select a shape: [end of test] number of elements 1`] = `2`;
|
||||
|
||||
exports[`regression tests click to select a shape: [end of test] number of renders 1`] = `13`;
|
||||
exports[`regression tests click to select a shape: [end of test] number of renders 1`] = `12`;
|
||||
|
||||
exports[`regression tests click-drag to select a group: [end of test] appState 1`] = `
|
||||
Object {
|
||||
@@ -4601,7 +4601,7 @@ Object {
|
||||
|
||||
exports[`regression tests click-drag to select a group: [end of test] number of elements 1`] = `3`;
|
||||
|
||||
exports[`regression tests click-drag to select a group: [end of test] number of renders 1`] = `19`;
|
||||
exports[`regression tests click-drag to select a group: [end of test] number of renders 1`] = `18`;
|
||||
|
||||
exports[`regression tests deselects group of selected elements on pointer down when pointer doesn't hit any element: [end of test] appState 1`] = `
|
||||
Object {
|
||||
@@ -4894,7 +4894,7 @@ Object {
|
||||
|
||||
exports[`regression tests deselects group of selected elements on pointer down when pointer doesn't hit any element: [end of test] number of elements 1`] = `2`;
|
||||
|
||||
exports[`regression tests deselects group of selected elements on pointer down when pointer doesn't hit any element: [end of test] number of renders 1`] = `14`;
|
||||
exports[`regression tests deselects group of selected elements on pointer down when pointer doesn't hit any element: [end of test] number of renders 1`] = `13`;
|
||||
|
||||
exports[`regression tests deselects group of selected elements on pointer up when pointer hits common bounding box without hitting any element: [end of test] appState 1`] = `
|
||||
Object {
|
||||
@@ -5199,7 +5199,7 @@ Object {
|
||||
|
||||
exports[`regression tests deselects group of selected elements on pointer up when pointer hits common bounding box without hitting any element: [end of test] number of elements 1`] = `2`;
|
||||
|
||||
exports[`regression tests deselects group of selected elements on pointer up when pointer hits common bounding box without hitting any element: [end of test] number of renders 1`] = `15`;
|
||||
exports[`regression tests deselects group of selected elements on pointer up when pointer hits common bounding box without hitting any element: [end of test] number of renders 1`] = `14`;
|
||||
|
||||
exports[`regression tests deselects selected element on pointer down when pointer doesn't hit any element: [end of test] appState 1`] = `
|
||||
Object {
|
||||
@@ -5405,7 +5405,7 @@ Object {
|
||||
|
||||
exports[`regression tests deselects selected element on pointer down when pointer doesn't hit any element: [end of test] number of elements 1`] = `1`;
|
||||
|
||||
exports[`regression tests deselects selected element on pointer down when pointer doesn't hit any element: [end of test] number of renders 1`] = `8`;
|
||||
exports[`regression tests deselects selected element on pointer down when pointer doesn't hit any element: [end of test] number of renders 1`] = `7`;
|
||||
|
||||
exports[`regression tests deselects selected element, on pointer up, when click hits element bounding box but doesn't hit the element: [end of test] appState 1`] = `
|
||||
Object {
|
||||
@@ -5589,7 +5589,7 @@ Object {
|
||||
|
||||
exports[`regression tests deselects selected element, on pointer up, when click hits element bounding box but doesn't hit the element: [end of test] number of elements 1`] = `1`;
|
||||
|
||||
exports[`regression tests deselects selected element, on pointer up, when click hits element bounding box but doesn't hit the element: [end of test] number of renders 1`] = `9`;
|
||||
exports[`regression tests deselects selected element, on pointer up, when click hits element bounding box but doesn't hit the element: [end of test] number of renders 1`] = `8`;
|
||||
|
||||
exports[`regression tests double click to edit a group: [end of test] appState 1`] = `
|
||||
Object {
|
||||
@@ -6040,7 +6040,7 @@ Object {
|
||||
|
||||
exports[`regression tests double click to edit a group: [end of test] number of elements 1`] = `3`;
|
||||
|
||||
exports[`regression tests double click to edit a group: [end of test] number of renders 1`] = `18`;
|
||||
exports[`regression tests double click to edit a group: [end of test] number of renders 1`] = `17`;
|
||||
|
||||
exports[`regression tests drags selected elements from point inside common bounding box that doesn't hit any element and keeps elements selected after dragging: [end of test] appState 1`] = `
|
||||
Object {
|
||||
@@ -6356,7 +6356,7 @@ Object {
|
||||
|
||||
exports[`regression tests drags selected elements from point inside common bounding box that doesn't hit any element and keeps elements selected after dragging: [end of test] number of elements 1`] = `2`;
|
||||
|
||||
exports[`regression tests drags selected elements from point inside common bounding box that doesn't hit any element and keeps elements selected after dragging: [end of test] number of renders 1`] = `16`;
|
||||
exports[`regression tests drags selected elements from point inside common bounding box that doesn't hit any element and keeps elements selected after dragging: [end of test] number of renders 1`] = `15`;
|
||||
|
||||
exports[`regression tests draw every type of shape: [end of test] appState 1`] = `
|
||||
Object {
|
||||
@@ -8388,7 +8388,7 @@ Object {
|
||||
|
||||
exports[`regression tests draw every type of shape: [end of test] number of elements 1`] = `8`;
|
||||
|
||||
exports[`regression tests draw every type of shape: [end of test] number of renders 1`] = `51`;
|
||||
exports[`regression tests draw every type of shape: [end of test] number of renders 1`] = `50`;
|
||||
|
||||
exports[`regression tests given a group of selected elements with an element that is not selected inside the group common bounding box when element that is not selected is clicked should switch selection to not selected element on pointer up: [end of test] appState 1`] = `
|
||||
Object {
|
||||
@@ -8748,7 +8748,7 @@ Object {
|
||||
|
||||
exports[`regression tests given a group of selected elements with an element that is not selected inside the group common bounding box when element that is not selected is clicked should switch selection to not selected element on pointer up: [end of test] number of elements 1`] = `3`;
|
||||
|
||||
exports[`regression tests given a group of selected elements with an element that is not selected inside the group common bounding box when element that is not selected is clicked should switch selection to not selected element on pointer up: [end of test] number of renders 1`] = `19`;
|
||||
exports[`regression tests given a group of selected elements with an element that is not selected inside the group common bounding box when element that is not selected is clicked should switch selection to not selected element on pointer up: [end of test] number of renders 1`] = `18`;
|
||||
|
||||
exports[`regression tests given a selected element A and a not selected element B with higher z-index than A and given B partialy overlaps A when there's a shift-click on the overlapped section B is added to the selection: [end of test] appState 1`] = `
|
||||
Object {
|
||||
@@ -9001,7 +9001,7 @@ Object {
|
||||
|
||||
exports[`regression tests given a selected element A and a not selected element B with higher z-index than A and given B partialy overlaps A when there's a shift-click on the overlapped section B is added to the selection: [end of test] number of elements 1`] = `2`;
|
||||
|
||||
exports[`regression tests given a selected element A and a not selected element B with higher z-index than A and given B partialy overlaps A when there's a shift-click on the overlapped section B is added to the selection: [end of test] number of renders 1`] = `17`;
|
||||
exports[`regression tests given a selected element A and a not selected element B with higher z-index than A and given B partialy overlaps A when there's a shift-click on the overlapped section B is added to the selection: [end of test] number of renders 1`] = `16`;
|
||||
|
||||
exports[`regression tests given selected element A with lower z-index than unselected element B and given B is partially over A when clicking intersection between A and B B should be selected on pointer up: [end of test] appState 1`] = `
|
||||
Object {
|
||||
@@ -9252,7 +9252,7 @@ Object {
|
||||
|
||||
exports[`regression tests given selected element A with lower z-index than unselected element B and given B is partially over A when clicking intersection between A and B B should be selected on pointer up: [end of test] number of elements 1`] = `2`;
|
||||
|
||||
exports[`regression tests given selected element A with lower z-index than unselected element B and given B is partially over A when clicking intersection between A and B B should be selected on pointer up: [end of test] number of renders 1`] = `17`;
|
||||
exports[`regression tests given selected element A with lower z-index than unselected element B and given B is partially over A when clicking intersection between A and B B should be selected on pointer up: [end of test] number of renders 1`] = `16`;
|
||||
|
||||
exports[`regression tests given selected element A with lower z-index than unselected element B and given B is partially over A when dragging on intersection between A and B A should be dragged and keep being selected: [end of test] appState 1`] = `
|
||||
Object {
|
||||
@@ -9565,7 +9565,7 @@ Object {
|
||||
|
||||
exports[`regression tests given selected element A with lower z-index than unselected element B and given B is partially over A when dragging on intersection between A and B A should be dragged and keep being selected: [end of test] number of elements 1`] = `2`;
|
||||
|
||||
exports[`regression tests given selected element A with lower z-index than unselected element B and given B is partially over A when dragging on intersection between A and B A should be dragged and keep being selected: [end of test] number of renders 1`] = `18`;
|
||||
exports[`regression tests given selected element A with lower z-index than unselected element B and given B is partially over A when dragging on intersection between A and B A should be dragged and keep being selected: [end of test] number of renders 1`] = `17`;
|
||||
|
||||
exports[`regression tests key 2 selects rectangle tool: [end of test] appState 1`] = `
|
||||
Object {
|
||||
@@ -9727,7 +9727,7 @@ Object {
|
||||
|
||||
exports[`regression tests key 2 selects rectangle tool: [end of test] number of elements 1`] = `1`;
|
||||
|
||||
exports[`regression tests key 2 selects rectangle tool: [end of test] number of renders 1`] = `7`;
|
||||
exports[`regression tests key 2 selects rectangle tool: [end of test] number of renders 1`] = `6`;
|
||||
|
||||
exports[`regression tests key 3 selects diamond tool: [end of test] appState 1`] = `
|
||||
Object {
|
||||
@@ -9889,7 +9889,7 @@ Object {
|
||||
|
||||
exports[`regression tests key 3 selects diamond tool: [end of test] number of elements 1`] = `1`;
|
||||
|
||||
exports[`regression tests key 3 selects diamond tool: [end of test] number of renders 1`] = `7`;
|
||||
exports[`regression tests key 3 selects diamond tool: [end of test] number of renders 1`] = `6`;
|
||||
|
||||
exports[`regression tests key 4 selects ellipse tool: [end of test] appState 1`] = `
|
||||
Object {
|
||||
@@ -10051,7 +10051,7 @@ Object {
|
||||
|
||||
exports[`regression tests key 4 selects ellipse tool: [end of test] number of elements 1`] = `1`;
|
||||
|
||||
exports[`regression tests key 4 selects ellipse tool: [end of test] number of renders 1`] = `7`;
|
||||
exports[`regression tests key 4 selects ellipse tool: [end of test] number of renders 1`] = `6`;
|
||||
|
||||
exports[`regression tests key 5 selects arrow tool: [end of test] appState 1`] = `
|
||||
Object {
|
||||
@@ -10243,7 +10243,7 @@ Object {
|
||||
|
||||
exports[`regression tests key 5 selects arrow tool: [end of test] number of elements 1`] = `1`;
|
||||
|
||||
exports[`regression tests key 5 selects arrow tool: [end of test] number of renders 1`] = `8`;
|
||||
exports[`regression tests key 5 selects arrow tool: [end of test] number of renders 1`] = `7`;
|
||||
|
||||
exports[`regression tests key 6 selects line tool: [end of test] appState 1`] = `
|
||||
Object {
|
||||
@@ -10435,7 +10435,7 @@ Object {
|
||||
|
||||
exports[`regression tests key 6 selects line tool: [end of test] number of elements 1`] = `1`;
|
||||
|
||||
exports[`regression tests key 6 selects line tool: [end of test] number of renders 1`] = `7`;
|
||||
exports[`regression tests key 6 selects line tool: [end of test] number of renders 1`] = `6`;
|
||||
|
||||
exports[`regression tests key 7 selects draw tool: [end of test] appState 1`] = `
|
||||
Object {
|
||||
@@ -10627,7 +10627,7 @@ Object {
|
||||
|
||||
exports[`regression tests key 7 selects draw tool: [end of test] number of elements 1`] = `1`;
|
||||
|
||||
exports[`regression tests key 7 selects draw tool: [end of test] number of renders 1`] = `7`;
|
||||
exports[`regression tests key 7 selects draw tool: [end of test] number of renders 1`] = `6`;
|
||||
|
||||
exports[`regression tests key a selects arrow tool: [end of test] appState 1`] = `
|
||||
Object {
|
||||
@@ -10819,7 +10819,7 @@ Object {
|
||||
|
||||
exports[`regression tests key a selects arrow tool: [end of test] number of elements 1`] = `1`;
|
||||
|
||||
exports[`regression tests key a selects arrow tool: [end of test] number of renders 1`] = `8`;
|
||||
exports[`regression tests key a selects arrow tool: [end of test] number of renders 1`] = `7`;
|
||||
|
||||
exports[`regression tests key d selects diamond tool: [end of test] appState 1`] = `
|
||||
Object {
|
||||
@@ -10981,7 +10981,7 @@ Object {
|
||||
|
||||
exports[`regression tests key d selects diamond tool: [end of test] number of elements 1`] = `1`;
|
||||
|
||||
exports[`regression tests key d selects diamond tool: [end of test] number of renders 1`] = `7`;
|
||||
exports[`regression tests key d selects diamond tool: [end of test] number of renders 1`] = `6`;
|
||||
|
||||
exports[`regression tests key e selects ellipse tool: [end of test] appState 1`] = `
|
||||
Object {
|
||||
@@ -11143,7 +11143,7 @@ Object {
|
||||
|
||||
exports[`regression tests key e selects ellipse tool: [end of test] number of elements 1`] = `1`;
|
||||
|
||||
exports[`regression tests key e selects ellipse tool: [end of test] number of renders 1`] = `7`;
|
||||
exports[`regression tests key e selects ellipse tool: [end of test] number of renders 1`] = `6`;
|
||||
|
||||
exports[`regression tests key l selects line tool: [end of test] appState 1`] = `
|
||||
Object {
|
||||
@@ -11335,7 +11335,7 @@ Object {
|
||||
|
||||
exports[`regression tests key l selects line tool: [end of test] number of elements 1`] = `1`;
|
||||
|
||||
exports[`regression tests key l selects line tool: [end of test] number of renders 1`] = `7`;
|
||||
exports[`regression tests key l selects line tool: [end of test] number of renders 1`] = `6`;
|
||||
|
||||
exports[`regression tests key r selects rectangle tool: [end of test] appState 1`] = `
|
||||
Object {
|
||||
@@ -11497,7 +11497,7 @@ Object {
|
||||
|
||||
exports[`regression tests key r selects rectangle tool: [end of test] number of elements 1`] = `1`;
|
||||
|
||||
exports[`regression tests key r selects rectangle tool: [end of test] number of renders 1`] = `7`;
|
||||
exports[`regression tests key r selects rectangle tool: [end of test] number of renders 1`] = `6`;
|
||||
|
||||
exports[`regression tests key x selects draw tool: [end of test] appState 1`] = `
|
||||
Object {
|
||||
@@ -11689,7 +11689,7 @@ Object {
|
||||
|
||||
exports[`regression tests key x selects draw tool: [end of test] number of elements 1`] = `1`;
|
||||
|
||||
exports[`regression tests key x selects draw tool: [end of test] number of renders 1`] = `7`;
|
||||
exports[`regression tests key x selects draw tool: [end of test] number of renders 1`] = `6`;
|
||||
|
||||
exports[`regression tests make a group and duplicate it: [end of test] appState 1`] = `
|
||||
Object {
|
||||
@@ -12403,7 +12403,7 @@ Object {
|
||||
|
||||
exports[`regression tests make a group and duplicate it: [end of test] number of elements 1`] = `6`;
|
||||
|
||||
exports[`regression tests make a group and duplicate it: [end of test] number of renders 1`] = `22`;
|
||||
exports[`regression tests make a group and duplicate it: [end of test] number of renders 1`] = `21`;
|
||||
|
||||
exports[`regression tests noop interaction after undo shouldn't create history entry: [end of test] appState 1`] = `
|
||||
Object {
|
||||
@@ -12654,7 +12654,7 @@ Object {
|
||||
|
||||
exports[`regression tests noop interaction after undo shouldn't create history entry: [end of test] number of elements 1`] = `2`;
|
||||
|
||||
exports[`regression tests noop interaction after undo shouldn't create history entry: [end of test] number of renders 1`] = `19`;
|
||||
exports[`regression tests noop interaction after undo shouldn't create history entry: [end of test] number of renders 1`] = `18`;
|
||||
|
||||
exports[`regression tests pinch-to-zoom works: [end of test] appState 1`] = `
|
||||
Object {
|
||||
@@ -12705,7 +12705,7 @@ Object {
|
||||
},
|
||||
"previousSelectedElementIds": Object {},
|
||||
"resizingElement": null,
|
||||
"scrollX": -5.416666666666667,
|
||||
"scrollX": -6,
|
||||
"scrollY": 0,
|
||||
"scrolledOutside": false,
|
||||
"selectedElementIds": Object {
|
||||
@@ -12725,7 +12725,7 @@ Object {
|
||||
"zenModeEnabled": false,
|
||||
"zoom": Object {
|
||||
"translation": Object {
|
||||
"x": 0.4166666666666714,
|
||||
"x": 0.3333333333333357,
|
||||
"y": 0,
|
||||
},
|
||||
"value": 1,
|
||||
@@ -12754,7 +12754,7 @@ Object {
|
||||
|
||||
exports[`regression tests pinch-to-zoom works: [end of test] number of elements 1`] = `0`;
|
||||
|
||||
exports[`regression tests pinch-to-zoom works: [end of test] number of renders 1`] = `9`;
|
||||
exports[`regression tests pinch-to-zoom works: [end of test] number of renders 1`] = `8`;
|
||||
|
||||
exports[`regression tests rerenders UI on language change: [end of test] appState 1`] = `
|
||||
Object {
|
||||
@@ -12852,7 +12852,7 @@ Object {
|
||||
|
||||
exports[`regression tests rerenders UI on language change: [end of test] number of elements 1`] = `0`;
|
||||
|
||||
exports[`regression tests rerenders UI on language change: [end of test] number of renders 1`] = `8`;
|
||||
exports[`regression tests rerenders UI on language change: [end of test] number of renders 1`] = `7`;
|
||||
|
||||
exports[`regression tests selecting 'Add to library' in context menu adds element to library: [end of test] appState 1`] = `
|
||||
Object {
|
||||
@@ -13014,7 +13014,7 @@ Object {
|
||||
|
||||
exports[`regression tests selecting 'Add to library' in context menu adds element to library: [end of test] number of elements 1`] = `1`;
|
||||
|
||||
exports[`regression tests selecting 'Add to library' in context menu adds element to library: [end of test] number of renders 1`] = `7`;
|
||||
exports[`regression tests selecting 'Add to library' in context menu adds element to library: [end of test] number of renders 1`] = `6`;
|
||||
|
||||
exports[`regression tests selecting 'Bring forward' in context menu brings element forward: [end of test] appState 1`] = `
|
||||
Object {
|
||||
@@ -13320,7 +13320,7 @@ Object {
|
||||
|
||||
exports[`regression tests selecting 'Bring forward' in context menu brings element forward: [end of test] number of elements 1`] = `2`;
|
||||
|
||||
exports[`regression tests selecting 'Bring forward' in context menu brings element forward: [end of test] number of renders 1`] = `13`;
|
||||
exports[`regression tests selecting 'Bring forward' in context menu brings element forward: [end of test] number of renders 1`] = `12`;
|
||||
|
||||
exports[`regression tests selecting 'Bring to front' in context menu brings element to front: [end of test] appState 1`] = `
|
||||
Object {
|
||||
@@ -13626,7 +13626,7 @@ Object {
|
||||
|
||||
exports[`regression tests selecting 'Bring to front' in context menu brings element to front: [end of test] number of elements 1`] = `2`;
|
||||
|
||||
exports[`regression tests selecting 'Bring to front' in context menu brings element to front: [end of test] number of renders 1`] = `13`;
|
||||
exports[`regression tests selecting 'Bring to front' in context menu brings element to front: [end of test] number of renders 1`] = `12`;
|
||||
|
||||
exports[`regression tests selecting 'Copy styles' in context menu copies styles: [end of test] appState 1`] = `
|
||||
Object {
|
||||
@@ -13788,7 +13788,7 @@ Object {
|
||||
|
||||
exports[`regression tests selecting 'Copy styles' in context menu copies styles: [end of test] number of elements 1`] = `1`;
|
||||
|
||||
exports[`regression tests selecting 'Copy styles' in context menu copies styles: [end of test] number of renders 1`] = `8`;
|
||||
exports[`regression tests selecting 'Copy styles' in context menu copies styles: [end of test] number of renders 1`] = `7`;
|
||||
|
||||
exports[`regression tests selecting 'Delete' in context menu deletes element: [end of test] appState 1`] = `
|
||||
Object {
|
||||
@@ -13982,7 +13982,7 @@ Object {
|
||||
|
||||
exports[`regression tests selecting 'Delete' in context menu deletes element: [end of test] number of elements 1`] = `1`;
|
||||
|
||||
exports[`regression tests selecting 'Delete' in context menu deletes element: [end of test] number of renders 1`] = `8`;
|
||||
exports[`regression tests selecting 'Delete' in context menu deletes element: [end of test] number of renders 1`] = `7`;
|
||||
|
||||
exports[`regression tests selecting 'Duplicate' in context menu duplicates element: [end of test] appState 1`] = `
|
||||
Object {
|
||||
@@ -14229,7 +14229,7 @@ Object {
|
||||
|
||||
exports[`regression tests selecting 'Duplicate' in context menu duplicates element: [end of test] number of elements 1`] = `2`;
|
||||
|
||||
exports[`regression tests selecting 'Duplicate' in context menu duplicates element: [end of test] number of renders 1`] = `8`;
|
||||
exports[`regression tests selecting 'Duplicate' in context menu duplicates element: [end of test] number of renders 1`] = `7`;
|
||||
|
||||
exports[`regression tests selecting 'Group selection' in context menu groups selected elements: [end of test] appState 1`] = `
|
||||
Object {
|
||||
@@ -14551,7 +14551,7 @@ Object {
|
||||
|
||||
exports[`regression tests selecting 'Group selection' in context menu groups selected elements: [end of test] number of elements 1`] = `2`;
|
||||
|
||||
exports[`regression tests selecting 'Group selection' in context menu groups selected elements: [end of test] number of renders 1`] = `14`;
|
||||
exports[`regression tests selecting 'Group selection' in context menu groups selected elements: [end of test] number of renders 1`] = `13`;
|
||||
|
||||
exports[`regression tests selecting 'Paste styles' in context menu pastes styles: [end of test] appState 1`] = `
|
||||
Object {
|
||||
@@ -15388,7 +15388,7 @@ Object {
|
||||
|
||||
exports[`regression tests selecting 'Paste styles' in context menu pastes styles: [end of test] number of elements 1`] = `2`;
|
||||
|
||||
exports[`regression tests selecting 'Paste styles' in context menu pastes styles: [end of test] number of renders 1`] = `23`;
|
||||
exports[`regression tests selecting 'Paste styles' in context menu pastes styles: [end of test] number of renders 1`] = `22`;
|
||||
|
||||
exports[`regression tests selecting 'Send backward' in context menu sends element backward: [end of test] appState 1`] = `
|
||||
Object {
|
||||
@@ -15694,7 +15694,7 @@ Object {
|
||||
|
||||
exports[`regression tests selecting 'Send backward' in context menu sends element backward: [end of test] number of elements 1`] = `2`;
|
||||
|
||||
exports[`regression tests selecting 'Send backward' in context menu sends element backward: [end of test] number of renders 1`] = `12`;
|
||||
exports[`regression tests selecting 'Send backward' in context menu sends element backward: [end of test] number of renders 1`] = `11`;
|
||||
|
||||
exports[`regression tests selecting 'Send to back' in context menu sends element to back: [end of test] appState 1`] = `
|
||||
Object {
|
||||
@@ -16000,7 +16000,7 @@ Object {
|
||||
|
||||
exports[`regression tests selecting 'Send to back' in context menu sends element to back: [end of test] number of elements 1`] = `2`;
|
||||
|
||||
exports[`regression tests selecting 'Send to back' in context menu sends element to back: [end of test] number of renders 1`] = `12`;
|
||||
exports[`regression tests selecting 'Send to back' in context menu sends element to back: [end of test] number of renders 1`] = `11`;
|
||||
|
||||
exports[`regression tests selecting 'Ungroup selection' in context menu ungroups selected group: [end of test] appState 1`] = `
|
||||
Object {
|
||||
@@ -16377,7 +16377,7 @@ Object {
|
||||
|
||||
exports[`regression tests selecting 'Ungroup selection' in context menu ungroups selected group: [end of test] number of elements 1`] = `2`;
|
||||
|
||||
exports[`regression tests selecting 'Ungroup selection' in context menu ungroups selected group: [end of test] number of renders 1`] = `15`;
|
||||
exports[`regression tests selecting 'Ungroup selection' in context menu ungroups selected group: [end of test] number of renders 1`] = `14`;
|
||||
|
||||
exports[`regression tests shift click on selected element should deselect it on pointer up: [end of test] appState 1`] = `
|
||||
Object {
|
||||
@@ -16542,7 +16542,7 @@ Object {
|
||||
|
||||
exports[`regression tests shift click on selected element should deselect it on pointer up: [end of test] number of elements 1`] = `1`;
|
||||
|
||||
exports[`regression tests shift click on selected element should deselect it on pointer up: [end of test] number of renders 1`] = `9`;
|
||||
exports[`regression tests shift click on selected element should deselect it on pointer up: [end of test] number of renders 1`] = `8`;
|
||||
|
||||
exports[`regression tests shift-click to multiselect, then drag: [end of test] appState 1`] = `
|
||||
Object {
|
||||
@@ -16861,7 +16861,7 @@ Object {
|
||||
|
||||
exports[`regression tests shift-click to multiselect, then drag: [end of test] number of elements 1`] = `2`;
|
||||
|
||||
exports[`regression tests shift-click to multiselect, then drag: [end of test] number of renders 1`] = `18`;
|
||||
exports[`regression tests shift-click to multiselect, then drag: [end of test] number of renders 1`] = `17`;
|
||||
|
||||
exports[`regression tests should show fill icons when element has non transparent background: [end of test] appState 1`] = `
|
||||
Object {
|
||||
@@ -17098,7 +17098,7 @@ Object {
|
||||
|
||||
exports[`regression tests should show fill icons when element has non transparent background: [end of test] number of elements 1`] = `1`;
|
||||
|
||||
exports[`regression tests should show fill icons when element has non transparent background: [end of test] number of renders 1`] = `11`;
|
||||
exports[`regression tests should show fill icons when element has non transparent background: [end of test] number of renders 1`] = `10`;
|
||||
|
||||
exports[`regression tests shows 'Group selection' in context menu for multiple selected elements: [end of test] appState 1`] = `
|
||||
Object {
|
||||
@@ -17351,7 +17351,7 @@ Object {
|
||||
|
||||
exports[`regression tests shows 'Group selection' in context menu for multiple selected elements: [end of test] number of elements 1`] = `2`;
|
||||
|
||||
exports[`regression tests shows 'Group selection' in context menu for multiple selected elements: [end of test] number of renders 1`] = `15`;
|
||||
exports[`regression tests shows 'Group selection' in context menu for multiple selected elements: [end of test] number of renders 1`] = `14`;
|
||||
|
||||
exports[`regression tests shows 'Ungroup selection' in context menu for group inside selected elements: [end of test] appState 1`] = `
|
||||
Object {
|
||||
@@ -17676,7 +17676,7 @@ Object {
|
||||
|
||||
exports[`regression tests shows 'Ungroup selection' in context menu for group inside selected elements: [end of test] number of elements 1`] = `2`;
|
||||
|
||||
exports[`regression tests shows 'Ungroup selection' in context menu for group inside selected elements: [end of test] number of renders 1`] = `16`;
|
||||
exports[`regression tests shows 'Ungroup selection' in context menu for group inside selected elements: [end of test] number of renders 1`] = `15`;
|
||||
|
||||
exports[`regression tests shows context menu for canvas: [end of test] appState 1`] = `
|
||||
Object {
|
||||
@@ -17774,7 +17774,7 @@ Object {
|
||||
|
||||
exports[`regression tests shows context menu for canvas: [end of test] number of elements 1`] = `0`;
|
||||
|
||||
exports[`regression tests shows context menu for canvas: [end of test] number of renders 1`] = `3`;
|
||||
exports[`regression tests shows context menu for canvas: [end of test] number of renders 1`] = `2`;
|
||||
|
||||
exports[`regression tests shows context menu for element: [end of test] appState 1`] = `
|
||||
Object {
|
||||
@@ -17936,7 +17936,7 @@ Object {
|
||||
|
||||
exports[`regression tests shows context menu for element: [end of test] number of elements 1`] = `1`;
|
||||
|
||||
exports[`regression tests shows context menu for element: [end of test] number of renders 1`] = `7`;
|
||||
exports[`regression tests shows context menu for element: [end of test] number of renders 1`] = `6`;
|
||||
|
||||
exports[`regression tests single-clicking on a subgroup of a selected group should not alter selection: [end of test] appState 1`] = `
|
||||
Object {
|
||||
@@ -18755,7 +18755,7 @@ Object {
|
||||
|
||||
exports[`regression tests single-clicking on a subgroup of a selected group should not alter selection: [end of test] number of elements 1`] = `4`;
|
||||
|
||||
exports[`regression tests single-clicking on a subgroup of a selected group should not alter selection: [end of test] number of renders 1`] = `37`;
|
||||
exports[`regression tests single-clicking on a subgroup of a selected group should not alter selection: [end of test] number of renders 1`] = `36`;
|
||||
|
||||
exports[`regression tests spacebar + drag scrolls the canvas: [end of test] appState 1`] = `
|
||||
Object {
|
||||
@@ -18853,7 +18853,7 @@ Object {
|
||||
|
||||
exports[`regression tests spacebar + drag scrolls the canvas: [end of test] number of elements 1`] = `0`;
|
||||
|
||||
exports[`regression tests spacebar + drag scrolls the canvas: [end of test] number of renders 1`] = `6`;
|
||||
exports[`regression tests spacebar + drag scrolls the canvas: [end of test] number of renders 1`] = `5`;
|
||||
|
||||
exports[`regression tests supports nested groups: [end of test] appState 1`] = `
|
||||
Object {
|
||||
@@ -19583,7 +19583,7 @@ Object {
|
||||
|
||||
exports[`regression tests supports nested groups: [end of test] number of elements 1`] = `3`;
|
||||
|
||||
exports[`regression tests supports nested groups: [end of test] number of renders 1`] = `30`;
|
||||
exports[`regression tests supports nested groups: [end of test] number of renders 1`] = `29`;
|
||||
|
||||
exports[`regression tests switches from group of selected elements to another element on pointer down: [end of test] appState 1`] = `
|
||||
Object {
|
||||
@@ -19986,7 +19986,7 @@ Object {
|
||||
|
||||
exports[`regression tests switches from group of selected elements to another element on pointer down: [end of test] number of elements 1`] = `3`;
|
||||
|
||||
exports[`regression tests switches from group of selected elements to another element on pointer down: [end of test] number of renders 1`] = `18`;
|
||||
exports[`regression tests switches from group of selected elements to another element on pointer down: [end of test] number of renders 1`] = `17`;
|
||||
|
||||
exports[`regression tests switches selected element on pointer down: [end of test] appState 1`] = `
|
||||
Object {
|
||||
@@ -20279,7 +20279,7 @@ Object {
|
||||
|
||||
exports[`regression tests switches selected element on pointer down: [end of test] number of elements 1`] = `2`;
|
||||
|
||||
exports[`regression tests switches selected element on pointer down: [end of test] number of renders 1`] = `12`;
|
||||
exports[`regression tests switches selected element on pointer down: [end of test] number of renders 1`] = `11`;
|
||||
|
||||
exports[`regression tests two-finger scroll works: [end of test] appState 1`] = `
|
||||
Object {
|
||||
@@ -20330,7 +20330,7 @@ Object {
|
||||
},
|
||||
"previousSelectedElementIds": Object {},
|
||||
"resizingElement": null,
|
||||
"scrollX": 11.046099290780141,
|
||||
"scrollX": 11,
|
||||
"scrollY": -5,
|
||||
"scrolledOutside": false,
|
||||
"selectedElementIds": Object {
|
||||
@@ -20350,7 +20350,7 @@ Object {
|
||||
"zenModeEnabled": false,
|
||||
"zoom": Object {
|
||||
"translation": Object {
|
||||
"x": -59.425,
|
||||
"x": -60.420000000000016,
|
||||
"y": -48.66347517730496,
|
||||
},
|
||||
"value": 1.99,
|
||||
@@ -20379,7 +20379,7 @@ Object {
|
||||
|
||||
exports[`regression tests two-finger scroll works: [end of test] number of elements 1`] = `0`;
|
||||
|
||||
exports[`regression tests two-finger scroll works: [end of test] number of renders 1`] = `11`;
|
||||
exports[`regression tests two-finger scroll works: [end of test] number of renders 1`] = `10`;
|
||||
|
||||
exports[`regression tests undo/redo drawing an element: [end of test] appState 1`] = `
|
||||
Object {
|
||||
@@ -20875,7 +20875,7 @@ Object {
|
||||
|
||||
exports[`regression tests undo/redo drawing an element: [end of test] number of elements 1`] = `3`;
|
||||
|
||||
exports[`regression tests undo/redo drawing an element: [end of test] number of renders 1`] = `28`;
|
||||
exports[`regression tests undo/redo drawing an element: [end of test] number of renders 1`] = `27`;
|
||||
|
||||
exports[`regression tests updates fontSize & fontFamily appState: [end of test] appState 1`] = `
|
||||
Object {
|
||||
@@ -20973,7 +20973,7 @@ Object {
|
||||
|
||||
exports[`regression tests updates fontSize & fontFamily appState: [end of test] number of elements 1`] = `0`;
|
||||
|
||||
exports[`regression tests updates fontSize & fontFamily appState: [end of test] number of renders 1`] = `5`;
|
||||
exports[`regression tests updates fontSize & fontFamily appState: [end of test] number of renders 1`] = `4`;
|
||||
|
||||
exports[`regression tests zoom hotkeys: [end of test] appState 1`] = `
|
||||
Object {
|
||||
@@ -21071,4 +21071,4 @@ Object {
|
||||
|
||||
exports[`regression tests zoom hotkeys: [end of test] number of elements 1`] = `0`;
|
||||
|
||||
exports[`regression tests zoom hotkeys: [end of test] number of renders 1`] = `5`;
|
||||
exports[`regression tests zoom hotkeys: [end of test] number of renders 1`] = `4`;
|
||||
|
||||
@@ -37,7 +37,7 @@ describe("add element to the scene when pointer dragging long enough", () => {
|
||||
// finish (position does not matter)
|
||||
fireEvent.pointerUp(canvas);
|
||||
|
||||
expect(renderScene).toHaveBeenCalledTimes(7);
|
||||
expect(renderScene).toHaveBeenCalledTimes(6);
|
||||
expect(h.state.selectionElement).toBeNull();
|
||||
|
||||
expect(h.elements.length).toEqual(1);
|
||||
@@ -68,7 +68,7 @@ describe("add element to the scene when pointer dragging long enough", () => {
|
||||
// finish (position does not matter)
|
||||
fireEvent.pointerUp(canvas);
|
||||
|
||||
expect(renderScene).toHaveBeenCalledTimes(7);
|
||||
expect(renderScene).toHaveBeenCalledTimes(6);
|
||||
expect(h.state.selectionElement).toBeNull();
|
||||
|
||||
expect(h.elements.length).toEqual(1);
|
||||
@@ -99,7 +99,7 @@ describe("add element to the scene when pointer dragging long enough", () => {
|
||||
// finish (position does not matter)
|
||||
fireEvent.pointerUp(canvas);
|
||||
|
||||
expect(renderScene).toHaveBeenCalledTimes(7);
|
||||
expect(renderScene).toHaveBeenCalledTimes(6);
|
||||
expect(h.state.selectionElement).toBeNull();
|
||||
|
||||
expect(h.elements.length).toEqual(1);
|
||||
@@ -130,7 +130,7 @@ describe("add element to the scene when pointer dragging long enough", () => {
|
||||
// finish (position does not matter)
|
||||
fireEvent.pointerUp(canvas);
|
||||
|
||||
expect(renderScene).toHaveBeenCalledTimes(7);
|
||||
expect(renderScene).toHaveBeenCalledTimes(6);
|
||||
expect(h.state.selectionElement).toBeNull();
|
||||
|
||||
expect(h.elements.length).toEqual(1);
|
||||
@@ -165,7 +165,7 @@ describe("add element to the scene when pointer dragging long enough", () => {
|
||||
// finish (position does not matter)
|
||||
fireEvent.pointerUp(canvas);
|
||||
|
||||
expect(renderScene).toHaveBeenCalledTimes(7);
|
||||
expect(renderScene).toHaveBeenCalledTimes(6);
|
||||
expect(h.state.selectionElement).toBeNull();
|
||||
|
||||
expect(h.elements.length).toEqual(1);
|
||||
@@ -198,7 +198,7 @@ describe("do not add element to the scene if size is too small", () => {
|
||||
// finish (position does not matter)
|
||||
fireEvent.pointerUp(canvas);
|
||||
|
||||
expect(renderScene).toHaveBeenCalledTimes(6);
|
||||
expect(renderScene).toHaveBeenCalledTimes(5);
|
||||
expect(h.state.selectionElement).toBeNull();
|
||||
expect(h.elements.length).toEqual(0);
|
||||
});
|
||||
@@ -217,7 +217,7 @@ describe("do not add element to the scene if size is too small", () => {
|
||||
// finish (position does not matter)
|
||||
fireEvent.pointerUp(canvas);
|
||||
|
||||
expect(renderScene).toHaveBeenCalledTimes(6);
|
||||
expect(renderScene).toHaveBeenCalledTimes(5);
|
||||
expect(h.state.selectionElement).toBeNull();
|
||||
expect(h.elements.length).toEqual(0);
|
||||
});
|
||||
@@ -236,7 +236,7 @@ describe("do not add element to the scene if size is too small", () => {
|
||||
// finish (position does not matter)
|
||||
fireEvent.pointerUp(canvas);
|
||||
|
||||
expect(renderScene).toHaveBeenCalledTimes(6);
|
||||
expect(renderScene).toHaveBeenCalledTimes(5);
|
||||
expect(h.state.selectionElement).toBeNull();
|
||||
expect(h.elements.length).toEqual(0);
|
||||
});
|
||||
@@ -258,7 +258,7 @@ describe("do not add element to the scene if size is too small", () => {
|
||||
// we need to finalize it because arrows and lines enter multi-mode
|
||||
fireEvent.keyDown(document, { key: KEYS.ENTER });
|
||||
|
||||
expect(renderScene).toHaveBeenCalledTimes(7);
|
||||
expect(renderScene).toHaveBeenCalledTimes(6);
|
||||
expect(h.state.selectionElement).toBeNull();
|
||||
expect(h.elements.length).toEqual(0);
|
||||
});
|
||||
@@ -280,7 +280,7 @@ describe("do not add element to the scene if size is too small", () => {
|
||||
// we need to finalize it because arrows and lines enter multi-mode
|
||||
fireEvent.keyDown(document, { key: KEYS.ENTER });
|
||||
|
||||
expect(renderScene).toHaveBeenCalledTimes(7);
|
||||
expect(renderScene).toHaveBeenCalledTimes(6);
|
||||
expect(h.state.selectionElement).toBeNull();
|
||||
expect(h.elements.length).toEqual(0);
|
||||
});
|
||||
|
||||
@@ -38,7 +38,7 @@ describe("move element", () => {
|
||||
fireEvent.pointerMove(canvas, { clientX: 60, clientY: 70 });
|
||||
fireEvent.pointerUp(canvas);
|
||||
|
||||
expect(renderScene).toHaveBeenCalledTimes(7);
|
||||
expect(renderScene).toHaveBeenCalledTimes(6);
|
||||
expect(h.state.selectionElement).toBeNull();
|
||||
expect(h.elements.length).toEqual(1);
|
||||
expect(h.state.selectedElementIds[h.elements[0].id]).toBeTruthy();
|
||||
@@ -77,7 +77,7 @@ describe("move element", () => {
|
||||
// select the second rectangles
|
||||
new Pointer("mouse").clickOn(rectB);
|
||||
|
||||
expect(renderScene).toHaveBeenCalledTimes(21);
|
||||
expect(renderScene).toHaveBeenCalledTimes(20);
|
||||
expect(h.state.selectionElement).toBeNull();
|
||||
expect(h.elements.length).toEqual(3);
|
||||
expect(h.state.selectedElementIds[rectB.id]).toBeTruthy();
|
||||
@@ -120,7 +120,7 @@ describe("duplicate element on move when ALT is clicked", () => {
|
||||
fireEvent.pointerMove(canvas, { clientX: 60, clientY: 70 });
|
||||
fireEvent.pointerUp(canvas);
|
||||
|
||||
expect(renderScene).toHaveBeenCalledTimes(7);
|
||||
expect(renderScene).toHaveBeenCalledTimes(6);
|
||||
expect(h.state.selectionElement).toBeNull();
|
||||
expect(h.elements.length).toEqual(1);
|
||||
expect(h.state.selectedElementIds[h.elements[0].id]).toBeTruthy();
|
||||
|
||||
@@ -30,7 +30,7 @@ describe("remove shape in non linear elements", () => {
|
||||
fireEvent.pointerDown(canvas, { clientX: 30, clientY: 20 });
|
||||
fireEvent.pointerUp(canvas, { clientX: 30, clientY: 30 });
|
||||
|
||||
expect(renderScene).toHaveBeenCalledTimes(6);
|
||||
expect(renderScene).toHaveBeenCalledTimes(5);
|
||||
expect(h.elements.length).toEqual(0);
|
||||
});
|
||||
|
||||
@@ -44,7 +44,7 @@ describe("remove shape in non linear elements", () => {
|
||||
fireEvent.pointerDown(canvas, { clientX: 30, clientY: 20 });
|
||||
fireEvent.pointerUp(canvas, { clientX: 30, clientY: 30 });
|
||||
|
||||
expect(renderScene).toHaveBeenCalledTimes(6);
|
||||
expect(renderScene).toHaveBeenCalledTimes(5);
|
||||
expect(h.elements.length).toEqual(0);
|
||||
});
|
||||
|
||||
@@ -58,7 +58,7 @@ describe("remove shape in non linear elements", () => {
|
||||
fireEvent.pointerDown(canvas, { clientX: 30, clientY: 20 });
|
||||
fireEvent.pointerUp(canvas, { clientX: 30, clientY: 30 });
|
||||
|
||||
expect(renderScene).toHaveBeenCalledTimes(6);
|
||||
expect(renderScene).toHaveBeenCalledTimes(5);
|
||||
expect(h.elements.length).toEqual(0);
|
||||
});
|
||||
});
|
||||
@@ -88,7 +88,7 @@ describe("multi point mode in linear elements", () => {
|
||||
fireEvent.pointerUp(canvas);
|
||||
fireEvent.keyDown(document, { key: KEYS.ENTER });
|
||||
|
||||
expect(renderScene).toHaveBeenCalledTimes(13);
|
||||
expect(renderScene).toHaveBeenCalledTimes(12);
|
||||
expect(h.elements.length).toEqual(1);
|
||||
|
||||
const element = h.elements[0] as ExcalidrawLinearElement;
|
||||
@@ -129,7 +129,7 @@ describe("multi point mode in linear elements", () => {
|
||||
fireEvent.pointerUp(canvas);
|
||||
fireEvent.keyDown(document, { key: KEYS.ENTER });
|
||||
|
||||
expect(renderScene).toHaveBeenCalledTimes(13);
|
||||
expect(renderScene).toHaveBeenCalledTimes(12);
|
||||
expect(h.elements.length).toEqual(1);
|
||||
|
||||
const element = h.elements[0] as ExcalidrawLinearElement;
|
||||
|
||||
@@ -618,7 +618,6 @@ describe("regression tests", () => {
|
||||
clientY: 1,
|
||||
});
|
||||
const contextMenu = document.querySelector(".context-menu");
|
||||
const contextMenuOptions = document.querySelectorAll(".context-menu li");
|
||||
const expectedShortcutNames: ShortcutName[] = [
|
||||
"selectAll",
|
||||
"gridMode",
|
||||
@@ -627,7 +626,7 @@ describe("regression tests", () => {
|
||||
];
|
||||
|
||||
expect(contextMenu).not.toBeNull();
|
||||
expect(contextMenuOptions.length).toBe(expectedShortcutNames.length);
|
||||
expect(contextMenu?.children.length).toBe(expectedShortcutNames.length);
|
||||
expectedShortcutNames.forEach((shortcutName) => {
|
||||
expect(
|
||||
contextMenu?.querySelector(`li[data-testid="${shortcutName}"]`),
|
||||
@@ -646,11 +645,11 @@ describe("regression tests", () => {
|
||||
clientY: 1,
|
||||
});
|
||||
const contextMenu = document.querySelector(".context-menu");
|
||||
const contextMenuOptions = document.querySelectorAll(".context-menu li");
|
||||
const expectedShortcutNames: ShortcutName[] = [
|
||||
"cut",
|
||||
"copyStyles",
|
||||
"pasteStyles",
|
||||
"deleteSelectedElements",
|
||||
"delete",
|
||||
"addToLibrary",
|
||||
"sendBackward",
|
||||
"bringForward",
|
||||
@@ -660,7 +659,7 @@ describe("regression tests", () => {
|
||||
];
|
||||
|
||||
expect(contextMenu).not.toBeNull();
|
||||
expect(contextMenuOptions.length).toBe(expectedShortcutNames.length);
|
||||
expect(contextMenu?.children.length).toBe(expectedShortcutNames.length);
|
||||
expectedShortcutNames.forEach((shortcutName) => {
|
||||
expect(
|
||||
contextMenu?.querySelector(`li[data-testid="${shortcutName}"]`),
|
||||
@@ -690,11 +689,11 @@ describe("regression tests", () => {
|
||||
});
|
||||
|
||||
const contextMenu = document.querySelector(".context-menu");
|
||||
const contextMenuOptions = document.querySelectorAll(".context-menu li");
|
||||
const expectedShortcutNames: ShortcutName[] = [
|
||||
"cut",
|
||||
"copyStyles",
|
||||
"pasteStyles",
|
||||
"deleteSelectedElements",
|
||||
"delete",
|
||||
"group",
|
||||
"addToLibrary",
|
||||
"sendBackward",
|
||||
@@ -705,7 +704,7 @@ describe("regression tests", () => {
|
||||
];
|
||||
|
||||
expect(contextMenu).not.toBeNull();
|
||||
expect(contextMenuOptions.length).toBe(expectedShortcutNames.length);
|
||||
expect(contextMenu?.children.length).toBe(expectedShortcutNames.length);
|
||||
expectedShortcutNames.forEach((shortcutName) => {
|
||||
expect(
|
||||
contextMenu?.querySelector(`li[data-testid="${shortcutName}"]`),
|
||||
@@ -739,11 +738,11 @@ describe("regression tests", () => {
|
||||
});
|
||||
|
||||
const contextMenu = document.querySelector(".context-menu");
|
||||
const contextMenuOptions = document.querySelectorAll(".context-menu li");
|
||||
const expectedShortcutNames: ShortcutName[] = [
|
||||
"cut",
|
||||
"copyStyles",
|
||||
"pasteStyles",
|
||||
"deleteSelectedElements",
|
||||
"delete",
|
||||
"ungroup",
|
||||
"addToLibrary",
|
||||
"sendBackward",
|
||||
@@ -754,7 +753,7 @@ describe("regression tests", () => {
|
||||
];
|
||||
|
||||
expect(contextMenu).not.toBeNull();
|
||||
expect(contextMenuOptions.length).toBe(expectedShortcutNames.length);
|
||||
expect(contextMenu?.children.length).toBe(expectedShortcutNames.length);
|
||||
expectedShortcutNames.forEach((shortcutName) => {
|
||||
expect(
|
||||
contextMenu?.querySelector(`li[data-testid="${shortcutName}"]`),
|
||||
|
||||
@@ -122,12 +122,12 @@ describe("resize rectangle ellipses and diamond elements", () => {
|
||||
);
|
||||
});
|
||||
|
||||
const resize = (
|
||||
function resize(
|
||||
element: ExcalidrawElement,
|
||||
handleDir: TransformHandleDirection,
|
||||
mouseMove: [number, number],
|
||||
keyboardModifiers: KeyboardModifiers = {},
|
||||
) => {
|
||||
) {
|
||||
mouse.select(element);
|
||||
const handle = getTransformHandles(element, h.state.zoom, "mouse")[
|
||||
handleDir
|
||||
@@ -140,4 +140,4 @@ const resize = (
|
||||
mouse.move(mouseMove[0], mouseMove[1]);
|
||||
mouse.up();
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
@@ -28,7 +28,7 @@ describe("selection element", () => {
|
||||
const canvas = container.querySelector("canvas")!;
|
||||
fireEvent.pointerDown(canvas, { clientX: 60, clientY: 100 });
|
||||
|
||||
expect(renderScene).toHaveBeenCalledTimes(4);
|
||||
expect(renderScene).toHaveBeenCalledTimes(3);
|
||||
const selectionElement = h.state.selectionElement!;
|
||||
expect(selectionElement).not.toBeNull();
|
||||
expect(selectionElement.type).toEqual("selection");
|
||||
@@ -49,7 +49,7 @@ describe("selection element", () => {
|
||||
fireEvent.pointerDown(canvas, { clientX: 60, clientY: 100 });
|
||||
fireEvent.pointerMove(canvas, { clientX: 150, clientY: 30 });
|
||||
|
||||
expect(renderScene).toHaveBeenCalledTimes(5);
|
||||
expect(renderScene).toHaveBeenCalledTimes(4);
|
||||
const selectionElement = h.state.selectionElement!;
|
||||
expect(selectionElement).not.toBeNull();
|
||||
expect(selectionElement.type).toEqual("selection");
|
||||
@@ -71,7 +71,7 @@ describe("selection element", () => {
|
||||
fireEvent.pointerMove(canvas, { clientX: 150, clientY: 30 });
|
||||
fireEvent.pointerUp(canvas);
|
||||
|
||||
expect(renderScene).toHaveBeenCalledTimes(6);
|
||||
expect(renderScene).toHaveBeenCalledTimes(5);
|
||||
expect(h.state.selectionElement).toBeNull();
|
||||
});
|
||||
});
|
||||
@@ -96,7 +96,7 @@ describe("select single element on the scene", () => {
|
||||
fireEvent.pointerDown(canvas, { clientX: 45, clientY: 20 });
|
||||
fireEvent.pointerUp(canvas);
|
||||
|
||||
expect(renderScene).toHaveBeenCalledTimes(10);
|
||||
expect(renderScene).toHaveBeenCalledTimes(9);
|
||||
expect(h.state.selectionElement).toBeNull();
|
||||
expect(h.elements.length).toEqual(1);
|
||||
expect(h.state.selectedElementIds[h.elements[0].id]).toBeTruthy();
|
||||
@@ -123,7 +123,7 @@ describe("select single element on the scene", () => {
|
||||
fireEvent.pointerDown(canvas, { clientX: 45, clientY: 20 });
|
||||
fireEvent.pointerUp(canvas);
|
||||
|
||||
expect(renderScene).toHaveBeenCalledTimes(10);
|
||||
expect(renderScene).toHaveBeenCalledTimes(9);
|
||||
expect(h.state.selectionElement).toBeNull();
|
||||
expect(h.elements.length).toEqual(1);
|
||||
expect(h.state.selectedElementIds[h.elements[0].id]).toBeTruthy();
|
||||
@@ -150,7 +150,7 @@ describe("select single element on the scene", () => {
|
||||
fireEvent.pointerDown(canvas, { clientX: 45, clientY: 20 });
|
||||
fireEvent.pointerUp(canvas);
|
||||
|
||||
expect(renderScene).toHaveBeenCalledTimes(10);
|
||||
expect(renderScene).toHaveBeenCalledTimes(9);
|
||||
expect(h.state.selectionElement).toBeNull();
|
||||
expect(h.elements.length).toEqual(1);
|
||||
expect(h.state.selectedElementIds[h.elements[0].id]).toBeTruthy();
|
||||
@@ -190,7 +190,7 @@ describe("select single element on the scene", () => {
|
||||
fireEvent.pointerDown(canvas, { clientX: 40, clientY: 40 });
|
||||
fireEvent.pointerUp(canvas);
|
||||
|
||||
expect(renderScene).toHaveBeenCalledTimes(10);
|
||||
expect(renderScene).toHaveBeenCalledTimes(9);
|
||||
expect(h.state.selectionElement).toBeNull();
|
||||
expect(h.elements.length).toEqual(1);
|
||||
expect(h.state.selectedElementIds[h.elements[0].id]).toBeTruthy();
|
||||
@@ -229,7 +229,7 @@ describe("select single element on the scene", () => {
|
||||
fireEvent.pointerDown(canvas, { clientX: 40, clientY: 40 });
|
||||
fireEvent.pointerUp(canvas);
|
||||
|
||||
expect(renderScene).toHaveBeenCalledTimes(10);
|
||||
expect(renderScene).toHaveBeenCalledTimes(9);
|
||||
expect(h.state.selectionElement).toBeNull();
|
||||
expect(h.elements.length).toEqual(1);
|
||||
expect(h.state.selectedElementIds[h.elements[0].id]).toBeTruthy();
|
||||
|
||||
@@ -100,5 +100,5 @@ const initLocalStorage = (data: ImportedDataState) => {
|
||||
};
|
||||
|
||||
export const updateSceneData = (data: SceneData) => {
|
||||
(window.h.collab as any).excalidrawAPI.updateScene(data);
|
||||
(window.h.collab as any).excalidrawRef.current.updateScene(data);
|
||||
};
|
||||
|
||||
+8
-4
@@ -21,6 +21,7 @@ import type { ResolvablePromise } from "./utils";
|
||||
import { Spreadsheet } from "./charts";
|
||||
import { Language } from "./i18n";
|
||||
|
||||
export type FlooredNumber = number & { _brand: "FlooredNumber" };
|
||||
export type Point = Readonly<RoughPoint>;
|
||||
|
||||
export type Collaborator = {
|
||||
@@ -67,8 +68,8 @@ export type AppState = {
|
||||
currentItemEndArrowhead: Arrowhead | null;
|
||||
currentItemLinearStrokeSharpness: ExcalidrawElement["strokeSharpness"];
|
||||
viewBackgroundColor: string;
|
||||
scrollX: number;
|
||||
scrollY: number;
|
||||
scrollX: FlooredNumber;
|
||||
scrollY: FlooredNumber;
|
||||
cursorButton: "up" | "down";
|
||||
scrolledOutside: boolean;
|
||||
name: string;
|
||||
@@ -97,7 +98,7 @@ export type AppState = {
|
||||
offsetLeft: number;
|
||||
|
||||
isLibraryOpen: boolean;
|
||||
fileHandle: import("browser-fs-access").FileSystemHandle | null;
|
||||
fileHandle: import("browser-nativefs").FileSystemHandle | null;
|
||||
collaborators: Map<string, Collaborator>;
|
||||
showStats: boolean;
|
||||
currentChartType: ChartType;
|
||||
@@ -145,7 +146,10 @@ export type LibraryItems = readonly LibraryItem[];
|
||||
// NOTE ready/readyPromise props are optional for host apps' sake (our own
|
||||
// implem guarantees existence)
|
||||
export type ExcalidrawAPIRefValue =
|
||||
| ExcalidrawImperativeAPI
|
||||
| (ExcalidrawImperativeAPI & {
|
||||
readyPromise?: ResolvablePromise<ExcalidrawImperativeAPI>;
|
||||
ready?: true;
|
||||
})
|
||||
| {
|
||||
readyPromise?: ResolvablePromise<ExcalidrawImperativeAPI>;
|
||||
ready?: false;
|
||||
|
||||
Reference in New Issue
Block a user