Compare commits

...

66 Commits

Author SHA1 Message Date
Panayiotis Lipiridis c12dab7a60 refactor: Remove the grid size and have a boolean instead 2020-12-24 20:15:12 +02:00
dependabot[bot] 39e7b8cf4f chore(deps-dev): bump firebase-tools from 8.19.0 to 9.0.1 (#2629)
Bumps [firebase-tools](https://github.com/firebase/firebase-tools) from 8.19.0 to 9.0.1.
- [Release notes](https://github.com/firebase/firebase-tools/releases)
- [Changelog](https://github.com/firebase/firebase-tools/blob/master/CHANGELOG.md)
- [Commits](https://github.com/firebase/firebase-tools/compare/v8.19.0...v9.0.1)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2020-12-22 11:20:57 +01:00
dependabot[bot] e0ece680a6 chore(deps): bump firebase from 8.2.0 to 8.2.1 (#2631)
Bumps [firebase](https://github.com/firebase/firebase-js-sdk) from 8.2.0 to 8.2.1.
- [Release notes](https://github.com/firebase/firebase-js-sdk/releases)
- [Changelog](https://github.com/firebase/firebase-js-sdk/blob/master/CHANGELOG.md)
- [Commits](https://github.com/firebase/firebase-js-sdk/commits)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2020-12-22 11:01:03 +01:00
David Luzar 8dfea49ec1 improvement: change hint for 2-point lines on resize (#2655) 2020-12-22 11:00:51 +01:00
Lipis d7f314cda8 refactor: Remove duplicate entry from en.json (#2654) 2020-12-22 11:42:01 +02:00
Lipis 6adb45ef5a feat: Change title to Excalidraw after a timeout (#2656)
* feat: Change title to Excalidraw after a timeout

* clear timeout
2020-12-22 10:34:06 +01:00
Lipis b0eeb8e6e6 refactor: Remove the word toggle from labels (#2648) 2020-12-21 14:58:43 +01:00
dependabot[bot] c3c20b6087 chore(deps): bump browser-nativefs from 0.11.2 to 0.12.0 (#2634)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2020-12-21 12:35:22 +02:00
Rene 0faec7efb6 feat: Checkmark to toggle context-menu-items (#2645) 2020-12-21 00:20:03 +02:00
Rene aff817c667 align items in context menu (#2640) 2020-12-20 12:15:40 -08:00
Christopher Chedeau 9a3a3ecb44 fix: Center zoom on iPhone and iPad (#2642)
My last attempt removed the gesture handler altogether but broke macbooks. This one keeps it working on macbook but makes sure it zooms properly on iPhone and iPad.
2020-12-20 12:13:15 -08:00
Christopher Chedeau 81f8039ec7 fix: Don't break zoom when zooming in on UI (#2638)
If you start the gesture on the chrome, gesture is not defined and the zoom will be set to NaN and you will have to refresh.

Typescript was actually right and we shouldn't have overridden the bang ;)

Repro:
- On iPhone touch down on a shape, touch down on the chrome, start moving around
- See that the selection is off, but the zoom is not being modified like crazy.

https://user-images.githubusercontent.com/197597/102722206-6c182400-42b4-11eb-9865-6ae1cd0af9be.MP4
2020-12-20 12:07:58 -08:00
Lipis 14759d5b72 chore: Remove changelog check and graphql (#2639) 2020-12-20 21:52:21 +02:00
David Luzar b997e69ebc improvement: Tweak error message on image import (#2619)
* improvement: tweak error message on image import

* tweak copy
2020-12-20 16:11:44 +01:00
dependabot[bot] 4a89aba682 chore(deps-dev): bump webpack in /src/packages/excalidraw (#2625)
Bumps [webpack](https://github.com/webpack/webpack) from 5.10.1 to 5.11.0.
- [Release notes](https://github.com/webpack/webpack/releases)
- [Commits](https://github.com/webpack/webpack/compare/v5.10.1...v5.11.0)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2020-12-20 20:35:49 +05:30
David Luzar 34dcf998bd docs: excalidraw package usage example tweaks (#2608)
Co-authored-by: Aakansha Doshi <monstershome@gmail.com>
2020-12-20 16:04:12 +01:00
Aakansha Doshi 325d1bec91 feat: Add onExportToBackend prop so host can handle it (#2612)
Co-authored-by: dwelle <luzar.david@gmail.com>
2020-12-20 15:14:04 +01:00
Lipis b917e42694 fix: Consistent case for export locale strings (#2622) 2020-12-20 14:40:11 +02:00
Christopher Chedeau eb9e67e36a improvement: Support numbers with commas in them (#2636) 2020-12-20 14:08:22 +02:00
dependabot[bot] f14ae52e94 chore(deps-dev): bump @babel/preset-env in /src/packages/excalidraw (#2624)
Bumps [@babel/preset-env](https://github.com/babel/babel/tree/HEAD/packages/babel-preset-env) from 7.12.10 to 7.12.11.
- [Release notes](https://github.com/babel/babel/releases)
- [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md)
- [Commits](https://github.com/babel/babel/commits/v7.12.11/packages/babel-preset-env)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2020-12-20 16:49:20 +05:30
dependabot[bot] f93eb658d6 chore(deps-dev): bump webpack-bundle-analyzer (#2623)
Bumps [webpack-bundle-analyzer](https://github.com/webpack-contrib/webpack-bundle-analyzer) from 4.2.0 to 4.3.0.
- [Release notes](https://github.com/webpack-contrib/webpack-bundle-analyzer/releases)
- [Changelog](https://github.com/webpack-contrib/webpack-bundle-analyzer/blob/master/CHANGELOG.md)
- [Commits](https://github.com/webpack-contrib/webpack-bundle-analyzer/compare/v4.2.0...v4.3.0)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2020-12-20 16:46:37 +05:30
Aakansha Doshi 9bac44ee75 fix(appstate.ts): Remove unnecessary console.error as it was polluting Sentry (#2637) 2020-12-20 16:28:46 +05:30
dependabot[bot] 91b4109f67 chore(deps-dev): bump webpack-bundle-analyzer in /src/packages/utils (#2626)
Bumps [webpack-bundle-analyzer](https://github.com/webpack-contrib/webpack-bundle-analyzer) from 4.2.0 to 4.3.0.
- [Release notes](https://github.com/webpack-contrib/webpack-bundle-analyzer/releases)
- [Changelog](https://github.com/webpack-contrib/webpack-bundle-analyzer/blob/master/CHANGELOG.md)
- [Commits](https://github.com/webpack-contrib/webpack-bundle-analyzer/compare/v4.2.0...v4.3.0)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2020-12-20 16:21:17 +05:30
dependabot[bot] 6e45cb95db chore(deps-dev): bump webpack in /src/packages/utils (#2627)
Bumps [webpack](https://github.com/webpack/webpack) from 5.10.1 to 5.11.0.
- [Release notes](https://github.com/webpack/webpack/releases)
- [Commits](https://github.com/webpack/webpack/compare/v5.10.1...v5.11.0)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2020-12-20 15:20:35 +05:30
dependabot[bot] 5edf82898b chore(deps-dev): bump @babel/preset-env in /src/packages/utils (#2628)
Bumps [@babel/preset-env](https://github.com/babel/babel/tree/HEAD/packages/babel-preset-env) from 7.12.10 to 7.12.11.
- [Release notes](https://github.com/babel/babel/releases)
- [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md)
- [Commits](https://github.com/babel/babel/commits/v7.12.11/packages/babel-preset-env)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2020-12-20 15:19:20 +05:30
dependabot[bot] 60b82e3055 chore(deps-dev): bump eslint-config-prettier from 7.0.0 to 7.1.0 (#2632)
Bumps [eslint-config-prettier](https://github.com/prettier/eslint-config-prettier) from 7.0.0 to 7.1.0.
- [Release notes](https://github.com/prettier/eslint-config-prettier/releases)
- [Changelog](https://github.com/prettier/eslint-config-prettier/blob/main/CHANGELOG.md)
- [Commits](https://github.com/prettier/eslint-config-prettier/compare/v7.0.0...v7.1.0)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2020-12-20 03:16:05 +02:00
Lipis 5d6590c200 ci: Update the coverage report for i18n PRs (#2592)
Co-authored-by: Kostas Bariotis <konmpar@gmail.com>
2020-12-19 22:44:01 +02:00
Lipis d3bebbc68d ci: Better locale coverage comment (#2616)
Co-authored-by: kbariotis <konmpar@gmail.com>
2020-12-19 20:35:03 +02:00
Kostas Bariotis 4ff8f3b006 add commenting on translations PRs, resolves #2587 (#2615) 2020-12-19 13:52:50 +00:00
Aakansha Doshi abbc756887 ci: Add semantic pr title action (#2610) 2020-12-18 21:32:37 +05:30
Lipis 10e07e434c chore: Remove support for deprecated Excalidraw for Desktop (#2465) 2020-12-18 14:02:29 +02:00
Andrés Rivera fb582b45db fix: Fix centering element on init (#2445)
Co-authored-by: Andrés Rivera <andres@MBP.local>
Co-authored-by: Lipis <lipiridis@gmail.com>
Co-authored-by: dwelle <luzar.david@gmail.com>
2020-12-16 18:17:39 +01:00
Aakansha Doshi 23f21434ff Fix changelog workflow (#2604)
Fix the workflow to make sure it runs on pull requests against forked branches and also don't run on l10_master and dependabot branches
2020-12-16 20:19:05 +05:30
Jaiwanth 7e9fdf85a0 fix: Dropdown for Arrowheads overflow (#2596)
Co-authored-by: Lipis <lipiridis@gmail.com>
Co-authored-by: dwelle <luzar.david@gmail.com>
2020-12-16 13:06:55 +01:00
David Luzar 98c26642d0 fix: hide collab button when onCollabButtonClick not supplied (#2598) 2020-12-15 16:21:14 +01:00
Lipis 2b434db062 chore: Don't run docker build on PRs (#2601) 2020-12-15 14:24:00 +01:00
Kartik Prajapati f919907855 Enhance delete button in context menu (#2591) 2020-12-15 00:59:00 +02:00
Aakansha Doshi bfeb3c7dfd fix(changelog-check.yml): ignore l10n_master and dependabot branches in changelog workflow (#2594) 2020-12-15 02:25:45 +05:30
Aakansha Doshi 8729ab3c54 chore(package/utils): update deps (#2593) 2020-12-15 02:13:19 +05:30
Aakansha Doshi 37f53bccbf Revert the changelog check of excluding the branches (#2590)
* temp commit

* fix

* fix

* Update actionAlign.tsx
2020-12-14 22:41:00 +02:00
Aakansha Doshi c783763307 chore(package/excalidraw): update deps (#2589) 2020-12-14 19:21:22 +01:00
dependabot[bot] f151f45df8 Bump webpack from 4.42.0 to 4.44.2 in /src/packages/utils (#2554)
Bumps [webpack](https://github.com/webpack/webpack) from 4.42.0 to 4.44.2.
- [Release notes](https://github.com/webpack/webpack/releases)
- [Commits](https://github.com/webpack/webpack/compare/v4.42.0...v4.44.2)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2020-12-14 21:08:06 +05:30
dependabot[bot] f33880e005 Bump @babel/plugin-transform-arrow-functions in /src/packages/utils (#2558)
Bumps [@babel/plugin-transform-arrow-functions](https://github.com/babel/babel/tree/HEAD/packages/babel-plugin-transform-arrow-functions) from 7.8.3 to 7.12.1.
- [Release notes](https://github.com/babel/babel/releases)
- [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md)
- [Commits](https://github.com/babel/babel/commits/v7.12.1/packages/babel-plugin-transform-arrow-functions)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2020-12-14 21:02:52 +05:30
dependabot[bot] 29ed50f6ea Bump webpack-cli from 3.3.11 to 4.2.0 in /src/packages/excalidraw (#2571)
Bumps [webpack-cli](https://github.com/webpack/webpack-cli) from 3.3.11 to 4.2.0.
- [Release notes](https://github.com/webpack/webpack-cli/releases)
- [Changelog](https://github.com/webpack/webpack-cli/blob/master/CHANGELOG.md)
- [Commits](https://github.com/webpack/webpack-cli/compare/v3.3.11...webpack-cli@4.2.0)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Aakansha Doshi <aakansha1216@gmail.com>
2020-12-14 21:00:06 +05:30
Lipis ce52c18382 fix: Add width/height for the lines in charts (#2586)
Co-authored-by: David Luzar <luzar.david@gmail.com>
2020-12-14 16:29:39 +01:00
Thomas Steiner 7c3e1d8d1b Add declarative link capturing
See https://github.com/WICG/sw-launch/blob/master/declarative_link_capturing.md#user-content-proposal:~:text=new_client,-%E2%80%9D%20%E2%80%94 for context.
2020-12-14 16:25:48 +01:00
dependabot[bot] 0d15934a96 Bump firebase-tools from 8.17.0 to 8.19.0 (#2574)
Bumps [firebase-tools](https://github.com/firebase/firebase-tools) from 8.17.0 to 8.19.0.
- [Release notes](https://github.com/firebase/firebase-tools/releases)
- [Commits](https://github.com/firebase/firebase-tools/compare/v8.17.0...v8.19.0)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2020-12-14 17:15:38 +02:00
dependabot[bot] a0069d04f0 Bump firebase from 8.1.2 to 8.2.0 (#2580)
Bumps [firebase](https://github.com/firebase/firebase-js-sdk) from 8.1.2 to 8.2.0.
- [Release notes](https://github.com/firebase/firebase-js-sdk/releases)
- [Changelog](https://github.com/firebase/firebase-js-sdk/blob/master/CHANGELOG.md)
- [Commits](https://github.com/firebase/firebase-js-sdk/commits)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2020-12-14 17:15:16 +02:00
Lipis 3b86944365 chore: New Crowdin updates (#2480) 2020-12-14 16:49:01 +02:00
João Forja 4c7b1a2269 fix: Visibility and zooming when canvas offset is not zero (#2534) 2020-12-14 15:14:56 +01:00
Kartik Prajapati 60864ace54 feat: Add tooltip with icon for embedding scenes (#2532)
Co-authored-by: dwelle <luzar.david@gmail.com>
2020-12-14 14:24:54 +01:00
Daishi Kato 4b32c03994 feat: export exportToCanvas in utils (#2583) 2020-12-14 14:11:48 +01:00
dependabot[bot] 222dbdcc00 Bump @types/jest from 26.0.16 to 26.0.19 (#2578)
Bumps [@types/jest](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/jest) from 26.0.16 to 26.0.19.
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/jest)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2020-12-14 07:59:56 +02:00
dependabot[bot] 1346227d30 Bump @sentry/browser from 5.28.0 to 5.29.0 (#2561)
Bumps [@sentry/browser](https://github.com/getsentry/sentry-javascript) from 5.28.0 to 5.29.0.
- [Release notes](https://github.com/getsentry/sentry-javascript/releases)
- [Changelog](https://github.com/getsentry/sentry-javascript/blob/master/CHANGELOG.md)
- [Commits](https://github.com/getsentry/sentry-javascript/compare/5.28.0...5.29.0)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2020-12-14 07:59:48 +02:00
dependabot[bot] ecbddd214c Bump pepjs from 0.5.2 to 0.5.3 (#2550)
Bumps [pepjs](https://github.com/jquery/PEP) from 0.5.2 to 0.5.3.
- [Release notes](https://github.com/jquery/PEP/releases)
- [Commits](https://github.com/jquery/PEP/commits/0.5.3)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2020-12-14 05:29:51 +02:00
dependabot[bot] 4999ca5c82 Bump husky from 4.3.0 to 4.3.6 (#2575)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2020-12-14 05:29:16 +02:00
dependabot[bot] 174638889d Bump typescript from 4.0.5 to 4.1.3 (#2569)
Bumps [typescript](https://github.com/Microsoft/TypeScript) from 4.0.5 to 4.1.3.
- [Release notes](https://github.com/Microsoft/TypeScript/releases)
- [Commits](https://github.com/Microsoft/TypeScript/commits)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2020-12-14 05:28:55 +02:00
dependabot[bot] 480998582e Bump eslint-plugin-prettier from 3.1.4 to 3.3.0 (#2573)
Bumps [eslint-plugin-prettier](https://github.com/prettier/eslint-plugin-prettier) from 3.1.4 to 3.3.0.
- [Release notes](https://github.com/prettier/eslint-plugin-prettier/releases)
- [Changelog](https://github.com/prettier/eslint-plugin-prettier/blob/master/CHANGELOG.md)
- [Commits](https://github.com/prettier/eslint-plugin-prettier/compare/v3.1.4...v3.3.0)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2020-12-14 05:28:40 +02:00
dependabot[bot] 94544e458c Bump browser-nativefs from 0.11.1 to 0.11.2 (#2577)
Bumps [browser-nativefs](https://github.com/GoogleChromeLabs/browser-nativefs) from 0.11.1 to 0.11.2.
- [Release notes](https://github.com/GoogleChromeLabs/browser-nativefs/releases)
- [Commits](https://github.com/GoogleChromeLabs/browser-nativefs/commits)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2020-12-14 05:28:16 +02:00
dependabot[bot] f664ba9e1e Bump @sentry/integrations from 5.28.0 to 5.29.0 (#2579)
Bumps [@sentry/integrations](https://github.com/getsentry/sentry-javascript) from 5.28.0 to 5.29.0.
- [Release notes](https://github.com/getsentry/sentry-javascript/releases)
- [Changelog](https://github.com/getsentry/sentry-javascript/blob/master/CHANGELOG.md)
- [Commits](https://github.com/getsentry/sentry-javascript/compare/5.28.0...5.29.0)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2020-12-14 05:27:54 +02:00
Panayiotis Lipiridis 015a60638e chore: Update user for packages 2020-12-14 03:49:43 +02:00
Lipis 7abb80530f chore: Add dependabot configuration (#2535) 2020-12-14 03:47:13 +02:00
Zen Tang 5abe9b93e8 feat: Add zoom to fit for selected elements (#2522) 2020-12-13 22:54:35 +02:00
Aakansha Doshi 59cff0f219 ci: Add github action to make sure changelog for @excalidraw/excalidraw is updated (#2518)
Add guidelines for changelog and group the commits
update the changelog with the latest commits since the last release
Co-authored-by: Lipis <lipiridis@gmail.com>
2020-12-13 18:53:14 +05:30
Lipis 81c17a56fb RTL support for the stats dialog (#2530) 2020-12-13 13:39:45 +01:00
Zen Tang 802b8c50d5 Insert Library items in the middle of the screen (#2527)
Co-authored-by: Zen Tang <zen@wayve.ai>
2020-12-13 13:46:42 +02:00
97 changed files with 4499 additions and 10355 deletions
+37
View File
@@ -0,0 +1,37 @@
version: 2
updates:
- package-ecosystem: npm
directory: "/"
schedule:
interval: weekly
day: sunday
time: "01:00"
open-pull-requests-limit: 99
reviewers:
- lipis
assignees:
- lipis
- package-ecosystem: npm
directory: "/src/packages/excalidraw/"
schedule:
interval: weekly
day: sunday
time: "01:00"
open-pull-requests-limit: 99
reviewers:
- ad1992
assignees:
- ad1992
- package-ecosystem: npm
directory: "/src/packages/utils/"
schedule:
interval: weekly
day: sunday
time: "01:00"
open-pull-requests-limit: 99
reviewers:
- ad1992
assignees:
- ad1992
-1
View File
@@ -4,7 +4,6 @@ on:
push:
branches:
- master
pull_request:
jobs:
build-docker:
-33
View File
@@ -1,33 +0,0 @@
name: "CodeQL"
on:
push:
branches: [master]
pull_request:
branches: [master]
schedule:
- cron: "18 7 * * 0"
jobs:
analyze:
name: Analyze
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
language: ["typescript"]
steps:
- name: Checkout repository
uses: actions/checkout@v2
- name: Initialize CodeQL
uses: github/codeql-action/init@v1
with:
languages: ${{ matrix.language }}
- name: Autobuild
uses: github/codeql-action/autobuild@v1
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v1
+15
View File
@@ -30,3 +30,18 @@ jobs:
git commit -am "Auto commit: Calculate translation coverage"
git push
fi
- name: Construct comment body
id: getCommentBody
run: |
body=$(npm run locales-coverage:description | grep '^[^>]')
body="${body//'%'/'%25'}"
body="${body//$'\n'/'%0A'}"
body="${body//$'\r'/'%0D'}"
echo ::set-output name=body::$body
- name: Update description with coverage
uses: kt3k/update-pr-description@v1.0.1
with:
pr_body: ${{ steps.getCommentBody.outputs.body }}
pr_title: "chore: New Crowdin updates"
github_token: ${{ secrets.PUSH_TRANSLATIONS_COVERAGE_PAT }}
+16
View File
@@ -0,0 +1,16 @@
name: "Semantic PR title"
on:
pull_request_target:
types:
- opened
- edited
- synchronize
jobs:
main:
runs-on: ubuntu-latest
steps:
- uses: amannn/action-semantic-pull-request@v2.1.0
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+383 -586
View File
File diff suppressed because it is too large Load Diff
+14 -14
View File
@@ -19,18 +19,18 @@
]
},
"dependencies": {
"@sentry/browser": "5.28.0",
"@sentry/integrations": "5.28.0",
"@sentry/browser": "5.29.0",
"@sentry/integrations": "5.29.0",
"@testing-library/jest-dom": "5.11.6",
"@testing-library/react": "11.2.2",
"@types/jest": "26.0.16",
"@types/jest": "26.0.19",
"@types/nanoid": "2.1.0",
"@types/react": "17.0.0",
"@types/react-dom": "17.0.0",
"@types/socket.io-client": "1.4.34",
"browser-nativefs": "0.11.1",
"browser-nativefs": "0.12.0",
"clsx": "1.1.1",
"firebase": "8.1.2",
"firebase": "8.2.1",
"i18next-browser-languagedetector": "6.0.1",
"lodash.throttle": "4.1.1",
"nanoid": "2.1.11",
@@ -47,19 +47,18 @@
"react-scripts": "4.0.1",
"roughjs": "4.3.1",
"socket.io-client": "2.3.1",
"typescript": "4.0.5"
"typescript": "4.1.3"
},
"devDependencies": {
"@types/lodash.throttle": "4.1.6",
"@types/pako": "1.0.1",
"asar": "3.0.3",
"eslint-config-prettier": "7.0.0",
"eslint-plugin-prettier": "3.1.4",
"firebase-tools": "8.17.0",
"husky": "4.3.0",
"eslint-config-prettier": "7.1.0",
"eslint-plugin-prettier": "3.3.0",
"firebase-tools": "9.0.1",
"husky": "4.3.6",
"jest-canvas-mock": "2.3.0",
"lint-staged": "10.5.3",
"pepjs": "0.5.2",
"pepjs": "0.5.3",
"prettier": "2.2.1",
"rewire": "5.0.0"
},
@@ -84,13 +83,14 @@
"build-node": "node ./scripts/build-node.js",
"build:app:docker": "REACT_APP_INCLUDE_GTAG=false REACT_APP_DISABLE_SENTRY=true react-scripts build",
"build:app": "REACT_APP_INCLUDE_GTAG=true REACT_APP_GIT_SHA=$NOW_GITHUB_COMMIT_SHA react-scripts build",
"build:zip": "node ./scripts/build-version.js",
"build": "npm run build:app && npm run build:zip",
"build:version": "node ./scripts/build-version.js",
"build": "npm run build:app && npm run build:version",
"eject": "react-scripts eject",
"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:all": "npm run test:typecheck && npm run test:code && npm run test:other && npm run test:app -- --watchAll=false",
+2
View File
@@ -55,6 +55,8 @@
<meta name="twitter:image" content="https://excalidraw.com/og-image.png" />
<link rel="shortcut icon" href="favicon.ico" type="image/x-icon" />
<!-- Excalidraw version -->
<meta name="version" content="{version}" />
<link
rel="preload"
href="FG_Virgil.woff2"
+2 -1
View File
@@ -25,5 +25,6 @@
"application/vnd.excalidraw+json": [".excalidraw"]
}
}
]
],
"capture_links": "new_client"
}
+15 -8
View File
@@ -2,7 +2,8 @@
const fs = require("fs");
const path = require("path");
const asar = require("asar");
const versionFile = path.join("build", "version.json");
const indexFile = path.join("build", "index.html");
const zero = (digit) => `0${digit}`.slice(-2);
@@ -20,18 +21,24 @@ const now = new Date();
const data = JSON.stringify(
{
asar: "excalidraw.asar",
version: versionDate(now),
},
undefined,
2,
);
fs.writeFileSync(path.join("build", "version.json"), data);
fs.writeFileSync(versionFile, data);
(async () => {
const src = "build/";
const dest = path.join("build", `excalidraw.asar`);
// https://stackoverflow.com/a/14181136/8418
fs.readFile(indexFile, "utf8", (error, data) => {
if (error) {
return console.error(error);
}
const result = data.replace(/{version}/g, versionDate(now));
await asar.createPackage(src, dest);
})();
fs.writeFile(indexFile, result, "utf8", (error) => {
if (error) {
return console.error(error);
}
});
});
+156
View File
@@ -0,0 +1,156 @@
const fs = require("fs");
const THRESSHOLD = 85;
const crowdinMap = {
"ar-SA": "en-ar",
"el-GR": "en-el",
"fi-FI": "en-fi",
"ja-JP": "en-ja",
"bg-BG": "en-bg",
"ca-ES": "en-ca",
"de-DE": "en-de",
"es-ES": "en-es",
"fa-IR": "en-fa",
"fr-FR": "en-fr",
"he-IL": "en-he",
"hi-IN": "en-hi",
"hu-HU": "en-hu",
"id-ID": "en-id",
"it-IT": "en-it",
"ko-KR": "en-ko",
"my-MM": "en-my",
"nb-NO": "en-nb",
"nl-NL": "en-nl",
"nn-NO": "en-nnno",
"pl-PL": "en-pl",
"pt-PT": "en-pt",
"ro-RO": "en-ro",
"ru-RU": "en-ru",
"sk-SK": "en-sk",
"sv-SE": "en-sv",
"tr-TR": "en-tr",
"uk-UA": "en-uk",
"zh-CN": "en-zhcn",
"zh-TW": "en-zhtw",
};
const flags = {
"ar-SA": "🇸🇦",
"bg-BG": "🇧🇬",
"ca-ES": "🇪🇸",
"de-DE": "🇩🇪",
"el-GR": "🇬🇷",
"es-ES": "🇪🇸",
"fa-IR": "🇮🇷",
"fi-FI": "🇫🇮",
"fr-FR": "🇫🇷",
"he-IL": "🇮🇱",
"hi-IN": "🇮🇳",
"hu-HU": "🇭🇺",
"id-ID": "🇮🇩",
"it-IT": "🇮🇹",
"ja-JP": "🇯🇵",
"ko-KR": "🇰🇷",
"my-MM": "🇲🇲",
"nb-NO": "🇳🇴",
"nl-NL": "🇳🇱",
"nn-NO": "🇳🇴",
"pl-PL": "🇵🇱",
"pt-PT": "🇵🇹",
"ro-RO": "🇷🇴",
"ru-RU": "🇷🇺",
"sk-SK": "🇸🇰",
"sv-SE": "🇸🇪",
"tr-TR": "🇹🇷",
"uk-UA": "🇺🇦",
"zh-CN": "🇨🇳",
"zh-TW": "🇹🇼",
};
const languages = {
"ar-SA": "العربية",
"bg-BG": "Български",
"ca-ES": "Catalan",
"de-DE": "Deutsch",
"el-GR": "Ελληνικά",
"es-ES": "Español",
"fa-IR": "فارسی",
"fi-FI": "Suomi",
"fr-FR": "Français",
"he-IL": "עברית",
"hi-IN": "हिन्दी",
"hu-HU": "Magyar",
"id-ID": "Bahasa Indonesia",
"it-IT": "Italiano",
"ja-JP": "日本語",
"ko-KR": "한국어",
"my-MM": "Burmese",
"nb-NO": "Norsk bokmål",
"nl-NL": "Nederlands",
"nn-NO": "Norsk nynorsk",
"pl-PL": "Polski",
"pt-PT": "Português",
"ro-RO": "Română",
"ru-RU": "Русский",
"sk-SK": "Slovenčina",
"sv-SE": "Svenska",
"tr-TR": "Türkçe",
"uk-UA": "Українська",
"zh-CN": "简体中文",
"zh-TW": "繁體中文",
};
const percentages = fs.readFileSync(
`${__dirname}/../src/locales/percentages.json`,
);
const rowData = JSON.parse(percentages);
const coverages = Object.entries(rowData)
.sort(([, a], [, b]) => b - a)
.reduce((r, [k, v]) => ({ ...r, [k]: v }), {});
const boldIf = (text, condition) => (condition ? `**${text}**` : text);
const printHeader = () => {
let result = "| | Flag | Locale | % |\n";
result += "| --: | :--: | -- | --: |";
return result;
};
const printRow = (id, locale, coverage) => {
const isOver = coverage > THRESSHOLD;
let result = `| ${boldIf(id, isOver)} | `;
result += `${locale in flags ? flags[locale] : ""} | `;
const language = locale in languages ? languages[locale] : locale;
if (locale in crowdinMap && crowdinMap[locale]) {
result += `[${boldIf(
language,
isOver,
)}](https://crowdin.com/translate/excalidraw/10/${crowdinMap[locale]}) | `;
} else {
result += `${boldIf(language, isOver)} | `;
}
result += `${boldIf(coverage, isOver)} |`;
return result;
};
console.info("## Languages check");
console.info("\n\r");
console.info(
`Our translations for every languages should be at least **${THRESSHOLD}%** to appear on Excalidraw. Join our project in [Crowdin](https://crowdin.com/project/excalidraw) and help us translate it in your language. **Can't find your own?** Open an [issue](https://github.com/excalidraw/excalidraw/issues/new) and we'll add it to the list.`,
);
console.info("\n\r");
console.info(printHeader());
let index = 1;
for (const coverage in coverages) {
if (coverage === "en") {
continue;
}
console.info(printRow(index, coverage, coverages[coverage]));
index++;
}
console.info("\n\r");
console.info("\\* Languages in **bold** are going to appear on production.");
+70 -36
View File
@@ -4,12 +4,14 @@ import { getDefaultAppState } from "../appState";
import { trash, zoomIn, zoomOut, resetZoom } from "../components/icons";
import { ToolButton } from "../components/ToolButton";
import { t } from "../i18n";
import { getNormalizedZoom } from "../scene";
import { getNormalizedZoom, getSelectedElements } from "../scene";
import { getNonDeletedElements } from "../element";
import { CODES, KEYS } from "../keys";
import { getShortcutKey } from "../utils";
import useIsMobile from "../is-mobile";
import { register } from "./register";
import { newElementWith } from "../element/mutateElement";
import { ExcalidrawElement } from "../element/types";
import { AppState, NormalizedZoomValue } from "../types";
import { getCommonBounds } from "../element";
import { getNewZoom } from "../scene/zoom";
@@ -62,7 +64,7 @@ export const actionClearCanvas = register({
elementLocked: appState.elementLocked,
exportBackground: appState.exportBackground,
exportEmbedScene: appState.exportEmbedScene,
gridSize: appState.gridSize,
showGrid: appState.showGrid,
shouldAddWatermark: appState.shouldAddWatermark,
showStats: appState.showStats,
},
@@ -93,6 +95,7 @@ export const actionZoomIn = register({
const zoom = getNewZoom(
getNormalizedZoom(appState.zoom.value + ZOOM_STEP),
appState.zoom,
{ left: appState.offsetLeft, top: appState.offsetTop },
{ x: appState.width / 2, y: appState.height / 2 },
);
trackEvent(EVENT_ACTION, "zoom", "in", zoom.value * 100);
@@ -126,6 +129,7 @@ export const actionZoomOut = register({
const zoom = getNewZoom(
getNormalizedZoom(appState.zoom.value - ZOOM_STEP),
appState.zoom,
{ left: appState.offsetLeft, top: appState.offsetTop },
{ x: appState.width / 2, y: appState.height / 2 },
);
@@ -161,10 +165,15 @@ export const actionResetZoom = register({
return {
appState: {
...appState,
zoom: getNewZoom(1 as NormalizedZoomValue, appState.zoom, {
x: appState.width / 2,
y: appState.height / 2,
}),
zoom: getNewZoom(
1 as NormalizedZoomValue,
appState.zoom,
{ left: appState.offsetLeft, top: appState.offsetTop },
{
x: appState.width / 2,
y: appState.height / 2,
},
),
},
commitToHistory: false,
};
@@ -204,38 +213,63 @@ const zoomValueToFitBoundsOnViewport = (
return clampedZoomValueToFitElements as NormalizedZoomValue;
};
const zoomToFitElements = (
elements: readonly ExcalidrawElement[],
appState: Readonly<AppState>,
zoomToSelection: boolean,
) => {
const nonDeletedElements = getNonDeletedElements(elements);
const selectedElements = getSelectedElements(nonDeletedElements, appState);
const commonBounds =
zoomToSelection && selectedElements.length > 0
? getCommonBounds(selectedElements)
: getCommonBounds(nonDeletedElements);
const zoomValue = zoomValueToFitBoundsOnViewport(commonBounds, {
width: appState.width,
height: appState.height,
});
const newZoom = getNewZoom(zoomValue, appState.zoom, {
left: appState.offsetLeft,
top: appState.offsetTop,
});
const action = zoomToSelection ? "selection" : "fit";
const [x1, y1, x2, y2] = commonBounds;
const centerX = (x1 + x2) / 2;
const centerY = (y1 + y2) / 2;
trackEvent(EVENT_ACTION, "zoom", action, newZoom.value * 100);
return {
appState: {
...appState,
...centerScrollOn({
scenePoint: { x: centerX, y: centerY },
viewportDimensions: {
width: appState.width,
height: appState.height,
},
zoom: newZoom,
}),
zoom: newZoom,
},
commitToHistory: false,
};
};
export const actionZoomToSelected = register({
name: "zoomToSelection",
perform: (elements, appState) => zoomToFitElements(elements, appState, true),
keyTest: (event) =>
event.code === CODES.TWO &&
event.shiftKey &&
!event.altKey &&
!event[KEYS.CTRL_OR_CMD],
});
export const actionZoomToFit = register({
name: "zoomToFit",
perform: (elements, appState) => {
const nonDeletedElements = elements.filter((element) => !element.isDeleted);
const commonBounds = getCommonBounds(nonDeletedElements);
const zoomValue = zoomValueToFitBoundsOnViewport(commonBounds, {
width: appState.width,
height: appState.height,
});
const newZoom = getNewZoom(zoomValue, appState.zoom);
const [x1, y1, x2, y2] = commonBounds;
const centerX = (x1 + x2) / 2;
const centerY = (y1 + y2) / 2;
trackEvent(EVENT_ACTION, "zoom", "fit", newZoom.value * 100);
return {
appState: {
...appState,
...centerScrollOn({
scenePoint: { x: centerX, y: centerY },
viewportDimensions: {
width: appState.width,
height: appState.height,
},
zoom: newZoom,
}),
zoom: newZoom,
},
commitToHistory: false,
};
},
perform: (elements, appState) => zoomToFitElements(elements, appState, false),
keyTest: (event) =>
event.code === CODES.ONE &&
event.shiftKey &&
+1 -1
View File
@@ -136,7 +136,7 @@ export const actionDeleteSelected = register({
};
},
contextItemLabel: "labels.delete",
contextMenuOrder: 3,
contextMenuOrder: 999999,
keyTest: (event) => event.key === KEYS.BACKSPACE || event.key === KEYS.DELETE,
PanelComponent: ({ elements, appState, updateData }) => (
<ToolButton
+11 -1
View File
@@ -3,12 +3,15 @@ import { EVENT_CHANGE, EVENT_IO, trackEvent } from "../analytics";
import { load, save, saveAs } from "../components/icons";
import { ProjectName } from "../components/ProjectName";
import { ToolButton } from "../components/ToolButton";
import { Tooltip } from "../components/Tooltip";
import { questionCircle } from "../components/icons";
import { loadFromJSON, saveAsJSON } from "../data";
import { t } from "../i18n";
import useIsMobile from "../is-mobile";
import { KEYS } from "../keys";
import { muteFSAbortError } from "../utils";
import { register } from "./register";
import "../components/ToolIcon.scss";
export const actionChangeProjectName = register({
name: "changeProjectName",
@@ -54,13 +57,20 @@ export const actionChangeExportEmbedScene = register({
};
},
PanelComponent: ({ appState, updateData }) => (
<label title={t("labels.exportEmbedScene_details")}>
<label style={{ display: "flex" }}>
<input
type="checkbox"
checked={appState.exportEmbedScene}
onChange={(event) => updateData(event.target.checked)}
/>{" "}
{t("labels.exportEmbedScene")}
<Tooltip
label={t("labels.exportEmbedScene_details")}
position="above"
long={true}
>
<div className="TooltipIcon">{questionCircle}</div>
</Tooltip>
</label>
),
});
+4 -4
View File
@@ -19,8 +19,8 @@ export type ShortcutName =
| "copyAsSvg"
| "group"
| "ungroup"
| "toggleGridMode"
| "toggleStats"
| "gridMode"
| "stats"
| "addToLibrary";
const shortcutMap: Record<ShortcutName, string[]> = {
@@ -51,8 +51,8 @@ const shortcutMap: Record<ShortcutName, string[]> = {
copyAsSvg: [],
group: [getShortcutKey("CtrlOrCmd+G")],
ungroup: [getShortcutKey("CtrlOrCmd+Shift+G")],
toggleGridMode: [getShortcutKey("CtrlOrCmd+'")],
toggleStats: [],
gridMode: [getShortcutKey("CtrlOrCmd+'")],
stats: [],
addToLibrary: [],
};
+1
View File
@@ -58,6 +58,7 @@ export type ActionName =
| "zoomOut"
| "resetZoom"
| "zoomToFit"
| "zoomToSelection"
| "changeFontFamily"
| "changeTextAlign"
| "toggleFullScreen"
+14 -11
View File
@@ -11,14 +11,17 @@ export const EVENT_SHAPE = "shape";
export const EVENT_SHARE = "share";
export const EVENT_MAGIC = "magic";
export const trackEvent = window.gtag
? (category: string, name: string, label?: string, value?: number) => {
window.gtag("event", name, {
event_category: category,
event_label: label,
value,
});
}
: (category: string, name: string, label?: string, value?: number) => {
console.info("Track Event", category, name, label, value);
};
export const trackEvent =
typeof window !== "undefined" && window.gtag
? (category: string, name: string, label?: string, value?: number) => {
window.gtag("event", name, {
event_category: category,
event_label: label,
value,
});
}
: typeof process !== "undefined" && process?.env?.JEST_WORKER_ID
? (category: string, name: string, label?: string, value?: number) => {}
: (category: string, name: string, label?: string, value?: number) => {
console.info("Track Event", category, name, label, value);
};
+2 -7
View File
@@ -65,7 +65,7 @@ export const getDefaultAppState = (): Omit<
showShortcutsDialog: false,
suggestedBindings: [],
zenModeEnabled: false,
gridSize: null,
showGrid: false,
editingGroupId: null,
selectedGroupIds: {},
width: window.innerWidth,
@@ -120,7 +120,7 @@ const APP_STATE_STORAGE_CONF = (<
errorMessage: { browser: false, export: false },
exportBackground: { browser: true, export: false },
exportEmbedScene: { browser: true, export: false },
gridSize: { browser: true, export: true },
showGrid: { browser: true, export: false },
height: { browser: false, export: false },
isBindingEnabled: { browser: false, export: false },
isLibraryOpen: { browser: false, export: false },
@@ -166,11 +166,6 @@ const _clearAppStateForStorage = <ExportType extends "export" | "browser">(
const stateForExport = {} as { [K in ExportableKeys]?: typeof appState[K] };
for (const key of Object.keys(appState) as (keyof typeof appState)[]) {
const propConfig = APP_STATE_STORAGE_CONF[key];
if (!propConfig) {
console.error(
`_clearAppStateForStorage: appState key "${key}" config doesn't exist for "${exportType}" export type`,
);
}
if (propConfig?.[exportType]) {
// @ts-ignore see https://github.com/microsoft/TypeScript/issues/31445
stateForExport[key] = appState[key];
+18 -12
View File
@@ -19,15 +19,15 @@ export const NOT_SPREADSHEET = "NOT_SPREADSHEET";
export const VALID_SPREADSHEET = "VALID_SPREADSHEET";
type ParseSpreadsheetResult =
| { type: typeof NOT_SPREADSHEET }
| { type: typeof NOT_SPREADSHEET; reason: string }
| { type: typeof VALID_SPREADSHEET; spreadsheet: Spreadsheet };
const tryParseNumber = (s: string): number | null => {
const match = /^[$€£¥₩]?([0-9]+(\.[0-9]+)?)$/.exec(s);
const match = /^[$€£¥₩]?([0-9,]+(\.[0-9]+)?)$/.exec(s);
if (!match) {
return null;
}
return parseFloat(match[1]);
return parseFloat(match[1].replace(/,/g, ""));
};
const isNumericColumn = (lines: string[][], columnIndex: number) =>
@@ -37,12 +37,12 @@ const tryParseCells = (cells: string[][]): ParseSpreadsheetResult => {
const numCols = cells[0].length;
if (numCols > 2) {
return { type: NOT_SPREADSHEET };
return { type: NOT_SPREADSHEET, reason: "More than 2 columns" };
}
if (numCols === 1) {
if (!isNumericColumn(cells, 0)) {
return { type: NOT_SPREADSHEET };
return { type: NOT_SPREADSHEET, reason: "Value is not numeric" };
}
const hasHeader = tryParseNumber(cells[0][0]) === null;
@@ -51,7 +51,7 @@ const tryParseCells = (cells: string[][]): ParseSpreadsheetResult => {
);
if (values.length < 2) {
return { type: NOT_SPREADSHEET };
return { type: NOT_SPREADSHEET, reason: "Less than two rows" };
}
return {
@@ -67,7 +67,7 @@ const tryParseCells = (cells: string[][]): ParseSpreadsheetResult => {
const valueColumnIndex = isNumericColumn(cells, 0) ? 0 : 1;
if (!isNumericColumn(cells, valueColumnIndex)) {
return { type: NOT_SPREADSHEET };
return { type: NOT_SPREADSHEET, reason: "Value is not numeric" };
}
const labelColumnIndex = (valueColumnIndex + 1) % 2;
@@ -75,7 +75,7 @@ const tryParseCells = (cells: string[][]): ParseSpreadsheetResult => {
const rows = hasHeader ? cells.slice(1) : cells;
if (rows.length < 2) {
return { type: NOT_SPREADSHEET };
return { type: NOT_SPREADSHEET, reason: "Less than 2 rows" };
}
return {
@@ -104,13 +104,13 @@ export const tryParseSpreadsheet = (text: string): ParseSpreadsheetResult => {
// Copy/paste from excel, spreadhseets, tsv, csv.
// For now we only accept 2 columns with an optional header
// Check for tab separeted values
// Check for tab separated values
let lines = text
.trim()
.split("\n")
.map((line) => line.trim().split("\t"));
// Check for comma separeted files
// Check for comma separated files
if (lines.length && lines[0].length !== 2) {
lines = text
.trim()
@@ -119,14 +119,17 @@ export const tryParseSpreadsheet = (text: string): ParseSpreadsheetResult => {
}
if (lines.length === 0) {
return { type: NOT_SPREADSHEET };
return { type: NOT_SPREADSHEET, reason: "No values" };
}
const numColsFirstLine = lines[0].length;
const isSpreadsheet = lines.every((line) => line.length === numColsFirstLine);
if (!isSpreadsheet) {
return { type: NOT_SPREADSHEET };
return {
type: NOT_SPREADSHEET,
reason: "All rows don't have same number of columns",
};
}
const result = tryParseCells(lines);
@@ -192,6 +195,7 @@ export const renderSpreadsheet = (
y,
startArrowhead: null,
endArrowhead: null,
width: chartWidth,
points: [
[0, 0],
[chartWidth, 0],
@@ -205,6 +209,7 @@ export const renderSpreadsheet = (
y,
startArrowhead: null,
endArrowhead: null,
height: chartHeight,
points: [
[0, 0],
[0, -chartHeight],
@@ -220,6 +225,7 @@ export const renderSpreadsheet = (
endArrowhead: null,
...commonProps,
strokeStyle: "dotted",
width: chartWidth,
points: [
[0, 0],
[chartWidth, 0],
+80 -41
View File
@@ -124,6 +124,7 @@ import {
MIME_TYPES,
TAP_TWICE_TIMEOUT,
TOUCH_CTX_MENU_TIMEOUT,
APP_NAME,
} from "../constants";
import LayerUI from "./LayerUI";
@@ -345,12 +346,15 @@ class App extends React.Component<ExcalidrawProps, AppState> {
offsetLeft,
} = this.state;
const { onCollabButtonClick } = this.props;
const { onCollabButtonClick, onExportToBackend } = this.props;
const canvasScale = window.devicePixelRatio;
const canvasWidth = canvasDOMWidth * canvasScale;
const canvasHeight = canvasDOMHeight * canvasScale;
const DEFAULT_PASTE_X = canvasDOMWidth / 2;
const DEFAULT_PASTE_Y = canvasDOMHeight / 2;
return (
<div
className="excalidraw"
@@ -371,12 +375,17 @@ class App extends React.Component<ExcalidrawProps, AppState> {
onCollabButtonClick={onCollabButtonClick}
onLockToggle={this.toggleLock}
onInsertShape={(elements) =>
this.addElementsFromPasteOrLibrary(elements)
this.addElementsFromPasteOrLibrary(
elements,
DEFAULT_PASTE_X,
DEFAULT_PASTE_Y,
)
}
zenModeEnabled={zenModeEnabled}
toggleZenMode={this.toggleZenMode}
lng={getLanguage().lng}
isCollaborating={this.props.isCollaborating || false}
onExportToBackend={onExportToBackend}
/>
{this.state.showStats && (
<Stats
@@ -494,7 +503,7 @@ class App extends React.Component<ExcalidrawProps, AppState> {
};
private importLibraryFromUrl = async (url: string) => {
window.history.replaceState({}, "Excalidraw", window.location.origin);
window.history.replaceState({}, APP_NAME, window.location.origin);
try {
const request = await fetch(url);
const blob = await request.blob();
@@ -586,6 +595,8 @@ class App extends React.Component<ExcalidrawProps, AppState> {
scene.elements,
{
...scene.appState,
width: this.state.width,
height: this.state.height,
offsetTop: this.state.offsetTop,
offsetLeft: this.state.offsetLeft,
},
@@ -1025,7 +1036,7 @@ class App extends React.Component<ExcalidrawProps, AppState> {
const dy = y - elementsCenterY;
const groupIdMap = new Map();
const [gridX, gridY] = getGridPoint(dx, dy, this.state.gridSize);
const [gridX, gridY] = getGridPoint(dx, dy, this.state.showGrid);
const oldIdToDuplicatedId = new Map();
const newElements = clipboardElements.map((element) => {
@@ -1138,7 +1149,7 @@ class App extends React.Component<ExcalidrawProps, AppState> {
toggleGridMode = () => {
this.setState({
gridSize: this.state.gridSize ? null : GRID_SIZE,
showGrid: !this.state.showGrid,
});
};
@@ -1264,8 +1275,8 @@ class App extends React.Component<ExcalidrawProps, AppState> {
if (isArrowKey(event.key)) {
const step =
(this.state.gridSize &&
(event.shiftKey ? ELEMENT_TRANSLATE_AMOUNT : this.state.gridSize)) ||
(this.state.showGrid &&
(event.shiftKey ? ELEMENT_TRANSLATE_AMOUNT : GRID_SIZE)) ||
(event.shiftKey
? ELEMENT_SHIFT_TRANSLATE_AMOUNT
: ELEMENT_TRANSLATE_AMOUNT);
@@ -1416,13 +1427,27 @@ class App extends React.Component<ExcalidrawProps, AppState> {
private onGestureChange = withBatchedUpdates((event: GestureEvent) => {
event.preventDefault();
this.setState(({ zoom }) => ({
zoom: getNewZoom(
getNormalizedZoom(gesture.initialScale! * event.scale),
zoom,
{ x: cursorX, y: cursorY },
),
}));
// onGestureChange only has zoom factor but not the center.
// If we're on iPad or iPhone, then we recognize multi-touch and will
// zoom in at the right location on the touchMove handler already.
// On Macbook, we don't have those events so will zoom in at the
// current location instead.
if (gesture.pointers.size === 2) {
return;
}
const initialScale = gesture.initialScale;
if (initialScale) {
this.setState(({ zoom, offsetLeft, offsetTop }) => ({
zoom: getNewZoom(
getNormalizedZoom(initialScale * event.scale),
zoom,
{ left: offsetLeft, top: offsetTop },
{ x: cursorX, y: cursorY },
),
}));
}
});
private onGestureEnd = withBatchedUpdates((event: GestureEvent) => {
@@ -1734,21 +1759,28 @@ class App extends React.Component<ExcalidrawProps, AppState> {
});
}
if (gesture.pointers.size === 2) {
const initialScale = gesture.initialScale;
if (
gesture.pointers.size === 2 &&
gesture.lastCenter &&
initialScale &&
gesture.initialDistance
) {
const center = getCenter(gesture.pointers);
const deltaX = center.x - gesture.lastCenter!.x;
const deltaY = center.y - gesture.lastCenter!.y;
const deltaX = center.x - gesture.lastCenter.x;
const deltaY = center.y - gesture.lastCenter.y;
gesture.lastCenter = center;
const distance = getDistance(Array.from(gesture.pointers.values()));
const scaleFactor = distance / gesture.initialDistance!;
const scaleFactor = distance / gesture.initialDistance;
this.setState(({ zoom, scrollX, scrollY }) => ({
this.setState(({ zoom, scrollX, scrollY, offsetLeft, offsetTop }) => ({
scrollX: normalizeScroll(scrollX + deltaX / zoom.value),
scrollY: normalizeScroll(scrollY + deltaY / zoom.value),
zoom: getNewZoom(
getNormalizedZoom(gesture.initialScale! * scaleFactor),
getNormalizedZoom(initialScale * scaleFactor),
zoom,
{ left: offsetLeft, top: offsetTop },
center,
),
shouldCacheIgnoreZoom: true,
@@ -1787,7 +1819,7 @@ class App extends React.Component<ExcalidrawProps, AppState> {
scenePointerX,
scenePointerY,
this.state.editingLinearElement,
this.state.gridSize,
this.state.showGrid,
);
if (editingLinearElement !== this.state.editingLinearElement) {
this.setState({ editingLinearElement });
@@ -2217,7 +2249,7 @@ class App extends React.Component<ExcalidrawProps, AppState> {
return {
origin,
originInGrid: tupleToCoors(
getGridPoint(origin.x, origin.y, this.state.gridSize),
getGridPoint(origin.x, origin.y, this.state.showGrid),
),
scrollbars: isOverScrollBars(
currentScrollBars,
@@ -2575,13 +2607,13 @@ class App extends React.Component<ExcalidrawProps, AppState> {
const [gridX, gridY] = getGridPoint(
pointerDownState.origin.x,
pointerDownState.origin.y,
elementType === "draw" ? null : this.state.gridSize,
elementType === "draw" ? false : this.state.showGrid,
);
/* If arrow is pre-arrowheads, it will have undefined for both start and end arrowheads.
If so, we want it to be null for start and "arrow" for end. If the linear item is not
an arrow, we want it to be null for both. Otherwise, we want it to use the
values from appState. */
If so, we want it to be null for start and "arrow" for end. If the linear item is not
an arrow, we want it to be null for both. Otherwise, we want it to use the
values from appState. */
const { currentItemStartArrowhead, currentItemEndArrowhead } = this.state;
const [startArrowhead, endArrowhead] =
@@ -2637,7 +2669,7 @@ class App extends React.Component<ExcalidrawProps, AppState> {
const [gridX, gridY] = getGridPoint(
pointerDownState.origin.x,
pointerDownState.origin.y,
this.state.gridSize,
this.state.showGrid,
);
const element = newElement({
type: elementType,
@@ -2726,7 +2758,7 @@ class App extends React.Component<ExcalidrawProps, AppState> {
const [gridX, gridY] = getGridPoint(
pointerCoords.x,
pointerCoords.y,
this.state.gridSize,
this.state.showGrid,
);
// for arrows/lines, don't start dragging until a given threshold
@@ -2798,7 +2830,7 @@ class App extends React.Component<ExcalidrawProps, AppState> {
const [dragX, dragY] = getGridPoint(
pointerCoords.x - pointerDownState.drag.offset.x,
pointerCoords.y - pointerDownState.drag.offset.y,
this.state.gridSize,
this.state.showGrid,
);
const [dragDistanceX, dragDistanceY] = [
@@ -2850,7 +2882,7 @@ class App extends React.Component<ExcalidrawProps, AppState> {
const [originDragX, originDragY] = getGridPoint(
pointerDownState.origin.x - pointerDownState.drag.offset.x,
pointerDownState.origin.y - pointerDownState.drag.offset.y,
this.state.gridSize,
this.state.showGrid,
);
mutateElement(duplicatedElement, {
x: duplicatedElement.x + (originDragX - dragX),
@@ -3510,7 +3542,7 @@ class App extends React.Component<ExcalidrawProps, AppState> {
const [gridX, gridY] = getGridPoint(
pointerCoords.x,
pointerCoords.y,
this.state.gridSize,
this.state.showGrid,
);
dragNewElement(
draggingElement,
@@ -3548,7 +3580,7 @@ class App extends React.Component<ExcalidrawProps, AppState> {
const [resizeX, resizeY] = getGridPoint(
pointerCoords.x - pointerDownState.resize.offset.x,
pointerCoords.y - pointerDownState.resize.offset.y,
this.state.gridSize,
this.state.showGrid,
);
if (
transformElements(
@@ -3612,13 +3644,15 @@ class App extends React.Component<ExcalidrawProps, AppState> {
CANVAS_ONLY_ACTIONS.includes(action.name),
),
{
shortcutName: "toggleGridMode",
label: t("labels.toggleGridMode"),
checked: this.state.showGrid,
shortcutName: "gridMode",
label: t("labels.gridMode"),
action: this.toggleGridMode,
},
{
shortcutName: "toggleStats",
label: t("labels.toggleStats"),
checked: this.state.showStats,
shortcutName: "stats",
label: t("stats.title"),
action: this.toggleStats,
},
],
@@ -3695,11 +3729,16 @@ class App extends React.Component<ExcalidrawProps, AppState> {
}, 1000);
}
this.setState(({ zoom }) => ({
zoom: getNewZoom(getNormalizedZoom(zoom.value - delta / 100), zoom, {
x: cursorX,
y: cursorY,
}),
this.setState(({ zoom, offsetLeft, offsetTop }) => ({
zoom: getNewZoom(
getNormalizedZoom(zoom.value - delta / 100),
zoom,
{ left: offsetLeft, top: offsetTop },
{
x: cursorX,
y: cursorY,
},
),
selectedElementIds: {},
previousSelectedElementIds:
Object.keys(selectedElementIds).length !== 0
+22
View File
@@ -32,6 +32,21 @@
display: grid;
grid-template-columns: 1fr 0.2fr;
align-items: center;
&.checkmark::before {
position: absolute;
left: 6px;
margin-bottom: 1px;
content: "\2713";
}
&.dangerous {
div:nth-child(1) {
color: $oc-red-7;
}
}
div:nth-child(1) {
justify-self: start;
margin-inline-end: 20px;
@@ -46,6 +61,13 @@
.context-menu-option:hover {
color: var(--popup-background-color);
background-color: var(--select-highlight-color);
&.dangerous {
div:nth-child(1) {
color: var(--popup-background-color);
}
background-color: $oc-red-6;
}
}
.context-menu-option:focus {
+8 -3
View File
@@ -10,6 +10,7 @@ import {
} from "../actions/shortcuts";
type ContextMenuOption = {
checked?: boolean;
shortcutName: ShortcutName;
label: string;
action(): void;
@@ -26,7 +27,6 @@ const ContextMenu = ({ options, onCloseRequest, top, left }: Props) => {
const isDarkTheme = !!document
.querySelector(".excalidraw")
?.classList.contains("Appearance_dark");
return (
<div
className={clsx("excalidraw", {
@@ -43,9 +43,14 @@ const ContextMenu = ({ options, onCloseRequest, top, left }: Props) => {
className="context-menu"
onContextMenu={(event) => event.preventDefault()}
>
{options.map(({ action, shortcutName, label }, idx) => (
{options.map(({ action, checked, shortcutName, label }, idx) => (
<li data-testid={shortcutName} key={idx} onClick={onCloseRequest}>
<button className="context-menu-option" onClick={action}>
<button
className={`context-menu-option
${shortcutName === "delete" ? "dangerous" : ""}
${checked ? "checkmark" : ""}`}
onClick={action}
>
<div>{label}</div>
<div>
{shortcutName
+8 -2
View File
@@ -14,7 +14,9 @@ export const DarkModeToggle = (props: {
return (
<label
className={`ToolIcon ToolIcon_type_floating ToolIcon_size_M`}
title={t("buttons.toggleDarkMode")}
title={
props.value === "dark" ? t("buttons.lightMode") : t("buttons.darkMode")
}
>
<input
className="ToolIcon_type_checkbox ToolIcon_toggle_opaque"
@@ -23,7 +25,11 @@ export const DarkModeToggle = (props: {
props.onChange(event.target.checked ? "dark" : "light")
}
checked={props.value === "dark"}
aria-label={t("buttons.toggleDarkMode")}
aria-label={
props.value === "dark"
? t("buttons.lightMode")
: t("buttons.darkMode")
}
/>
<div className="ToolIcon__icon">
{props.value === "light" ? ICONS.MOON : ICONS.SUN}
+8 -1
View File
@@ -28,7 +28,14 @@ export const ErrorDialog = ({
onCloseRequest={handleClose}
title={t("errorDialog.title")}
>
<div>{message}</div>
<div>
{message.split("\n").map((line) => (
<>
{line}
<br />
</>
))}
</div>
</Dialog>
)}
</>
+11 -9
View File
@@ -67,7 +67,7 @@ const ExportModal = ({
onExportToPng: ExportCB;
onExportToSvg: ExportCB;
onExportToClipboard: ExportCB;
onExportToBackend: ExportCB;
onExportToBackend?: ExportCB;
onCloseRequest: () => void;
}) => {
const someElementIsSelected = isSomeElementSelected(elements, appState);
@@ -155,13 +155,15 @@ const ExportModal = ({
onClick={() => onExportToClipboard(exportedElements, scale)}
/>
)}
<ToolButton
type="button"
icon={link}
title={t("buttons.getShareableLink")}
aria-label={t("buttons.getShareableLink")}
onClick={() => onExportToBackend(exportedElements)}
/>
{onExportToBackend && (
<ToolButton
type="button"
icon={link}
title={t("buttons.getShareableLink")}
aria-label={t("buttons.getShareableLink")}
onClick={() => onExportToBackend(exportedElements)}
/>
)}
</Stack.Row>
<div className="ExportDialog__name">
{actionManager.renderAction("changeProjectName")}
@@ -235,7 +237,7 @@ export const ExportDialog = ({
onExportToPng: ExportCB;
onExportToSvg: ExportCB;
onExportToClipboard: ExportCB;
onExportToBackend: ExportCB;
onExportToBackend?: ExportCB;
}) => {
const [modalIsShown, setModalIsShown] = useState(false);
const triggerButton = useRef<HTMLButtonElement>(null);
+2 -2
View File
@@ -38,8 +38,8 @@ const getHints = ({ appState, elements }: Hint) => {
selectedElements.length === 1
) {
const targetElement = selectedElements[0];
if (isLinearElement(targetElement) && targetElement.points.length > 2) {
return null;
if (isLinearElement(targetElement) && targetElement.points.length === 2) {
return t("hints.lockAngle");
}
return t("hints.resize");
}
+3
View File
@@ -93,6 +93,9 @@
grid-auto-flow: column;
grid-gap: 0.5rem;
border-radius: 4px;
:root[dir="rtl"] & {
padding: 0.4rem;
}
}
.picker-keybinding {
-58
View File
@@ -39,64 +39,6 @@
width: 1.2rem;
height: 1.2rem;
}
&.tooltip .tooltip-text {
visibility: hidden;
width: 20rem;
bottom: calc(50% + 0.8rem + 6px);
:root[dir="ltr"] & {
left: -5px;
}
:root[dir="rtl"] & {
right: -5px;
}
background-color: $oc-black;
color: $oc-white;
text-align: center;
border-radius: 6px;
padding: 5px;
position: absolute;
z-index: 10;
font-size: 13px;
line-height: 1.5;
white-space: pre-wrap;
&::after {
--size: 6px;
content: "";
border: var(--size) solid transparent;
border-top-color: $oc-black;
position: absolute;
bottom: calc(-2 * var(--size));
:root[dir="ltr"] & {
left: calc(5px + var(--size) / 2);
}
:root[dir="rtl"] & {
right: calc(5px + var(--size) / 2);
}
}
}
// the following 3 rules ensure that the tooltip doesn't show (nor affect
// the cursor) when you drag over when you draw on canvas, but at the same
// time it still works when clicking on the link/shield
body:active &.tooltip:not(:hover) {
pointer-events: none;
}
body:not(:active) &.tooltip:hover .tooltip-text {
visibility: visible;
}
.tooltip-text:hover {
visibility: visible;
}
}
&__github-corner {
+17 -26
View File
@@ -65,6 +65,11 @@ interface LayerUIProps {
toggleZenMode: () => void;
lng: string;
isCollaborating: boolean;
onExportToBackend?: (
exportedElements: readonly NonDeletedExcalidrawElement[],
appState: AppState,
canvas: HTMLCanvasElement | null,
) => void;
}
const useOnClickOutside = (
@@ -317,10 +322,10 @@ const LayerUI = ({
zenModeEnabled,
toggleZenMode,
isCollaborating,
onExportToBackend,
}: LayerUIProps) => {
const isMobile = useIsMobile();
// TODO: Extend tooltip component and use here.
const renderEncryptedIcon = () => (
<a
className={clsx("encrypted-icon tooltip zen-mode-visibility", {
@@ -333,10 +338,9 @@ const LayerUI = ({
trackEvent(EVENT_EXIT, "e2ee shield");
}}
>
<span className="tooltip-text" dir="auto">
{t("encrypted.tooltip")}
</span>
{shield}
<Tooltip label={t("encrypted.tooltip")} position="above" long={true}>
{shield}
</Tooltip>
</a>
);
@@ -360,6 +364,7 @@ const LayerUI = ({
});
}
};
return (
<ExportDialog
elements={elements}
@@ -368,28 +373,14 @@ const LayerUI = ({
onExportToPng={createExporter("png")}
onExportToSvg={createExporter("svg")}
onExportToClipboard={createExporter("clipboard")}
onExportToBackend={async (exportedElements) => {
if (canvas) {
try {
await exportCanvas(
"backend",
exportedElements,
{
...appState,
selectedElementIds: {},
},
canvas,
appState,
);
} catch (error) {
if (error.name !== "AbortError") {
const { width, height } = canvas;
console.error(error, { width, height });
setAppState({ errorMessage: error.message });
onExportToBackend={
onExportToBackend
? (elements) => {
onExportToBackend &&
onExportToBackend(elements, appState, canvas);
}
}
}
}}
: undefined
}
/>
);
};
+5 -4
View File
@@ -207,15 +207,16 @@ export const ShortcutsDialog = ({ onClose }: { onClose?: () => void }) => {
shortcuts={["Shift+1"]}
/>
<Shortcut
label={t("buttons.toggleFullScreen")}
shortcuts={["F"]}
label={t("shortcutsDialog.zoomToSelection")}
shortcuts={["Shift+2"]}
/>
<Shortcut label={t("buttons.fullScreen")} shortcuts={["F"]} />
<Shortcut
label={t("buttons.toggleZenMode")}
label={t("buttons.zenMode")}
shortcuts={[getShortcutKey("Alt+Z")]}
/>
<Shortcut
label={t("labels.toggleGridMode")}
label={t("labels.gridMode")}
shortcuts={[getShortcutKey("CtrlOrCmd+'")]}
/>
</ShortcutIsland>
+15 -1
View File
@@ -6,8 +6,10 @@
right: 12px;
font-size: 12px;
z-index: 999;
h3 {
margin: 0 24px 8px 0;
white-space: nowrap;
}
.close {
@@ -29,9 +31,21 @@
}
tr {
td:nth-child(2) {
min-width: 48px;
min-width: 24px;
text-align: right;
}
}
}
:root[dir="rtl"] & {
left: 12px;
right: initial;
h3 {
margin: 0 0 8px 24px;
}
.close {
float: left;
}
}
}
+13
View File
@@ -1,4 +1,6 @@
@import "open-color/open-color.scss";
@import "../css/variables";
.excalidraw {
.ToolIcon {
display: inline-flex;
@@ -181,6 +183,17 @@
}
}
.TooltipIcon {
width: 0.9em;
height: 0.9em;
margin-left: 5px;
margin-top: 1px;
@media #{$media-query} {
display: none;
}
}
.unlocked-icon {
:root[dir="ltr"] & {
left: 2px;
+27 -10
View File
@@ -7,39 +7,56 @@
.Tooltip__label {
--arrow-size: 4px;
visibility: hidden;
width: 10ch;
background: $oc-black;
color: $oc-white;
text-align: center;
border-radius: 4px;
padding: 4px;
border-radius: 6px;
padding: 8px;
position: absolute;
z-index: 10;
font-size: 0.7rem;
font-size: 13px;
line-height: 1.5;
top: calc(100% + var(--arrow-size) + 3px);
font-weight: 500;
// extra pixel offset for unknown reasons
left: calc(-50% + var(--arrow-size) / 2 - 1px);
left: calc(50% + var(--arrow-size) / 2 - 1px);
transform: translateX(-50%);
word-wrap: break-word;
&::after {
content: "";
border: var(--arrow-size) solid transparent;
border-bottom-color: $oc-black;
position: absolute;
bottom: 100%;
left: calc(50% - var(--arrow-size));
}
&--above {
bottom: calc(100% + var(--arrow-size) + 3px);
&::after {
border-top-color: $oc-black;
top: 100%;
}
}
&--below {
top: calc(100% + var(--arrow-size) + 3px);
&::after {
border-bottom-color: $oc-black;
bottom: 100%;
}
}
}
// the following 3 rules ensure that the tooltip doesn't show (nor affect
// the cursor) when you drag over when you draw on canvas, but at the same
// time it still works when clicking on the link/shield
body:active .Tooltip:not(:hover) {
body:active & .Tooltip:not(:hover) {
pointer-events: none;
}
body:not(:active) .Tooltip:hover .Tooltip__label {
body:not(:active) & .Tooltip:hover .Tooltip__label {
visibility: visible;
}
+18 -2
View File
@@ -5,11 +5,27 @@ import React from "react";
type TooltipProps = {
children: React.ReactNode;
label: string;
position?: "above" | "below";
long?: boolean;
};
export const Tooltip = ({ children, label }: TooltipProps) => (
export const Tooltip = ({
children,
label,
position = "below",
long = false,
}: TooltipProps) => (
<div className="Tooltip">
<span className="Tooltip__label">{label}</span>
<span
className={
position === "above"
? "Tooltip__label Tooltip__label--above"
: "Tooltip__label Tooltip__label--below"
}
style={{ width: long ? "50ch" : "10ch" }}
>
{label}
</span>
{children}
</div>
);
+5
View File
@@ -108,6 +108,11 @@ export const redo = createIcon(
{ mirror: true },
);
export const questionCircle = createIcon(
"M504 256c0 136.997-111.043 248-248 248S8 392.997 8 256C8 119.083 119.043 8 256 8s248 111.083 248 248zM262.655 90c-54.497 0-89.255 22.957-116.549 63.758-3.536 5.286-2.353 12.415 2.715 16.258l34.699 26.31c5.205 3.947 12.621 3.008 16.665-2.122 17.864-22.658 30.113-35.797 57.303-35.797 20.429 0 45.698 13.148 45.698 32.958 0 14.976-12.363 22.667-32.534 33.976C247.128 238.528 216 254.941 216 296v4c0 6.627 5.373 12 12 12h56c6.627 0 12-5.373 12-12v-1.333c0-28.462 83.186-29.647 83.186-106.667 0-58.002-60.165-102-116.531-102zM256 338c-25.365 0-46 20.635-46 46 0 25.364 20.635 46 46 46s46-20.636 46-46c0-25.365-20.635-46-46-46z",
{ mirror: true },
);
// Icon imported form Storybook
// Storybook is licensed under MIT https://github.com/storybookjs/storybook/blob/next/LICENSE
export const resetZoom = createIcon(
+4 -1
View File
@@ -1,5 +1,7 @@
import { FontFamily } from "./element/types";
export const APP_NAME = "Excalidraw";
export const DRAGGING_THRESHOLD = 10; // 10px
export const LINE_CONFIRM_THRESHOLD = 10; // 10px
export const ELEMENT_SHIFT_TRANSLATE_AMOUNT = 5;
@@ -71,7 +73,7 @@ export const DEFAULT_VERTICAL_ALIGN = "top";
export const CANVAS_ONLY_ACTIONS = ["selectAll"];
export const GRID_SIZE = 20; // TODO make it configurable?
export const GRID_SIZE = 20;
export const MIME_TYPES = {
excalidraw: "application/vnd.excalidraw+json",
@@ -85,3 +87,4 @@ export const STORAGE_KEYS = {
// time in milliseconds
export const TAP_TWICE_TIMEOUT = 300;
export const TOUCH_CTX_MENU_TIMEOUT = 500;
export const TITLE_TIMEOUT = 10000;
+1 -1
View File
@@ -9,7 +9,7 @@ import { AppState } from "../types";
import { restore } from "./restore";
import { ImportedDataState, LibraryData } from "./types";
export const parseFileContents = async (blob: Blob | File) => {
const parseFileContents = async (blob: Blob | File) => {
let contents: string;
if (blob.type === "image/png") {
+1 -71
View File
@@ -1,14 +1,10 @@
import { fileSave } from "browser-nativefs";
import { EVENT_IO, trackEvent } from "../analytics";
import { getDefaultAppState } from "../appState";
import {
copyCanvasToClipboardAsPng,
copyTextToSystemClipboard,
} from "../clipboard";
import {
ExcalidrawElement,
NonDeletedExcalidrawElement,
} from "../element/types";
import { NonDeletedExcalidrawElement } from "../element/types";
import { t } from "../i18n";
import { exportToCanvas, exportToSvg } from "../scene/export";
import { ExportType } from "../scene/types";
@@ -19,65 +15,6 @@ import { serializeAsJSON } from "./json";
export { loadFromBlob } from "./blob";
export { loadFromJSON, saveAsJSON } from "./json";
const BACKEND_V2_POST = process.env.REACT_APP_BACKEND_V2_POST_URL;
export const exportToBackend = async (
elements: readonly ExcalidrawElement[],
appState: AppState,
) => {
const json = serializeAsJSON(elements, appState);
const encoded = new TextEncoder().encode(json);
const key = await window.crypto.subtle.generateKey(
{
name: "AES-GCM",
length: 128,
},
true, // extractable
["encrypt", "decrypt"],
);
// The iv is set to 0. We are never going to reuse the same key so we don't
// need to have an iv. (I hope that's correct...)
const iv = new Uint8Array(12);
// We use symmetric encryption. AES-GCM is the recommended algorithm and
// includes checks that the ciphertext has not been modified by an attacker.
const encrypted = await window.crypto.subtle.encrypt(
{
name: "AES-GCM",
iv,
},
key,
encoded,
);
// We use jwk encoding to be able to extract just the base64 encoded key.
// We will hardcode the rest of the attributes when importing back the key.
const exportedKey = await window.crypto.subtle.exportKey("jwk", key);
try {
const response = await fetch(BACKEND_V2_POST, {
method: "POST",
body: encrypted,
});
const json = await response.json();
if (json.id) {
const url = new URL(window.location.href);
// We need to store the key (and less importantly the id) as hash instead
// of queryParam in order to never send it to the server
url.hash = `json=${json.id},${exportedKey.k!}`;
const urlString = url.toString();
window.prompt(`🔒${t("alerts.uploadedSecurly")}`, urlString);
trackEvent(EVENT_IO, "export", "backend");
} else if (json.error_class === "RequestTooLargeError") {
window.alert(t("alerts.couldNotCreateShareableLinkTooBig"));
} else {
window.alert(t("alerts.couldNotCreateShareableLink"));
}
} catch (error) {
console.error(error);
window.alert(t("alerts.couldNotCreateShareableLink"));
}
};
export const exportCanvas = async (
type: ExportType,
elements: readonly NonDeletedExcalidrawElement[],
@@ -169,13 +106,6 @@ export const exportCanvas = async (
}
throw new Error(t("alerts.couldNotCopyToClipboard"));
}
} else if (type === "backend") {
exportToBackend(elements, {
...appState,
viewBackgroundColor: exportBackground
? appState.viewBackgroundColor
: getDefaultAppState().viewBackgroundColor,
});
}
// clean up the DOM
+6 -6
View File
@@ -102,7 +102,7 @@ export class LinearElementEditor {
element,
scenePointerX - editingLinearElement.pointerOffset.x,
scenePointerY - editingLinearElement.pointerOffset.y,
appState.gridSize,
appState.showGrid,
);
LinearElementEditor.movePoint(element, activePointIndex, newPoint);
if (isBindingElement(element)) {
@@ -198,7 +198,7 @@ export class LinearElementEditor {
element,
scenePointer.x,
scenePointer.y,
appState.gridSize,
appState.showGrid,
),
],
});
@@ -282,7 +282,7 @@ export class LinearElementEditor {
scenePointerX: number,
scenePointerY: number,
editingLinearElement: LinearElementEditor,
gridSize: number | null,
isGridOn: boolean,
): LinearElementEditor {
const { elementId, lastUncommittedPoint } = editingLinearElement;
const element = LinearElementEditor.getElement(elementId);
@@ -304,7 +304,7 @@ export class LinearElementEditor {
element,
scenePointerX - editingLinearElement.pointerOffset.x,
scenePointerY - editingLinearElement.pointerOffset.y,
gridSize,
isGridOn,
);
if (lastPoint === lastUncommittedPoint) {
@@ -398,9 +398,9 @@ export class LinearElementEditor {
element: NonDeleted<ExcalidrawLinearElement>,
scenePointerX: number,
scenePointerY: number,
gridSize: number | null,
isGridOn: boolean,
): Point {
const pointerOnGrid = getGridPoint(scenePointerX, scenePointerY, gridSize);
const pointerOnGrid = getGridPoint(scenePointerX, scenePointerY, isGridOn);
const [x1, y1, x2, y2] = getElementAbsoluteCoords(element);
const cx = (x1 + x2) / 2;
const cy = (y1 + y2) / 2;
+3 -7
View File
@@ -1,7 +1,7 @@
import React, { PureComponent } from "react";
import throttle from "lodash.throttle";
import { ENV, EVENT } from "../../constants";
import { APP_NAME, ENV, EVENT } from "../../constants";
import {
decryptAESGEM,
@@ -157,11 +157,7 @@ class CollabWrapper extends PureComponent<Props, CollabState> {
};
openPortal = async () => {
window.history.pushState(
{},
"Excalidraw",
await generateCollaborationLink(),
);
window.history.pushState({}, APP_NAME, await generateCollaborationLink());
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
@@ -178,7 +174,7 @@ class CollabWrapper extends PureComponent<Props, CollabState> {
closePortal = () => {
this.saveCollabRoomToFirebase();
window.history.pushState({}, "Excalidraw", window.location.origin);
window.history.pushState({}, APP_NAME, window.location.origin);
this.destroySocketClient();
trackEvent(EVENT_SHARE, "session end");
};
+60 -1
View File
@@ -3,12 +3,14 @@ import { ExcalidrawElement } from "../../element/types";
import { AppState } from "../../types";
import { ImportedDataState } from "../../data/types";
import { restore } from "../../data/restore";
import { EVENT_ACTION, trackEvent } from "../../analytics";
import { EVENT_ACTION, EVENT_IO, trackEvent } from "../../analytics";
import { serializeAsJSON } from "../../data/json";
const byteToHex = (byte: number): string => `0${byte.toString(16)}`.slice(-2);
const BACKEND_GET = process.env.REACT_APP_BACKEND_V1_GET_URL;
const BACKEND_V2_GET = process.env.REACT_APP_BACKEND_V2_GET_URL;
const BACKEND_V2_POST = process.env.REACT_APP_BACKEND_V2_POST_URL;
const generateRandomID = async () => {
const arr = new Uint8Array(10);
@@ -228,3 +230,60 @@ export const loadScene = async (
commitToHistory: false,
};
};
export const exportToBackend = async (
elements: readonly ExcalidrawElement[],
appState: AppState,
) => {
const json = serializeAsJSON(elements, appState);
const encoded = new TextEncoder().encode(json);
const key = await window.crypto.subtle.generateKey(
{
name: "AES-GCM",
length: 128,
},
true, // extractable
["encrypt", "decrypt"],
);
// The iv is set to 0. We are never going to reuse the same key so we don't
// need to have an iv. (I hope that's correct...)
const iv = new Uint8Array(12);
// We use symmetric encryption. AES-GCM is the recommended algorithm and
// includes checks that the ciphertext has not been modified by an attacker.
const encrypted = await window.crypto.subtle.encrypt(
{
name: "AES-GCM",
iv,
},
key,
encoded,
);
// We use jwk encoding to be able to extract just the base64 encoded key.
// We will hardcode the rest of the attributes when importing back the key.
const exportedKey = await window.crypto.subtle.exportKey("jwk", key);
try {
const response = await fetch(BACKEND_V2_POST, {
method: "POST",
body: encrypted,
});
const json = await response.json();
if (json.id) {
const url = new URL(window.location.href);
// We need to store the key (and less importantly the id) as hash instead
// of queryParam in order to never send it to the server
url.hash = `json=${json.id},${exportedKey.k!}`;
const urlString = url.toString();
window.prompt(`🔒${t("alerts.uploadedSecurly")}`, urlString);
trackEvent(EVENT_IO, "export", "backend");
} else if (json.error_class === "RequestTooLargeError") {
window.alert(t("alerts.couldNotCreateShareableLinkTooBig"));
} else {
window.alert(t("alerts.couldNotCreateShareableLink"));
}
} catch (error) {
console.error(error);
window.alert(t("alerts.couldNotCreateShareableLink"));
}
};
+61 -15
View File
@@ -13,16 +13,22 @@ import { ImportedDataState } from "../data/types";
import CollabWrapper, { CollabAPI } from "./collab/CollabWrapper";
import { TopErrorBoundary } from "../components/TopErrorBoundary";
import { t } from "../i18n";
import { loadScene } from "./data";
import { exportToBackend, loadScene } from "./data";
import { getCollaborationLinkData } from "./data";
import { EVENT } from "../constants";
import { loadFromFirebase } from "./data/firebase";
import { ExcalidrawImperativeAPI } from "../components/App";
import { debounce, ResolvablePromise, resolvablePromise } from "../utils";
import { AppState, ExcalidrawAPIRefValue } from "../types";
import { ExcalidrawElement } from "../element/types";
import {
ExcalidrawElement,
NonDeletedExcalidrawElement,
} from "../element/types";
import { SAVE_TO_LOCAL_STORAGE_TIMEOUT } from "./app_constants";
import { EVENT_LOAD, EVENT_SHARE, trackEvent } from "../analytics";
import { ErrorDialog } from "../components/ErrorDialog";
import { getDefaultAppState } from "../appState";
import { APP_NAME, TITLE_TIMEOUT } from "../constants";
const excalidrawRef: React.MutableRefObject<
MarkRequired<ExcalidrawAPIRefValue, "ready" | "readyPromise">
@@ -113,7 +119,7 @@ const initializeScene = async (opts: {
scene = await loadScene(jsonMatch[1], jsonMatch[2], initialData);
}
if (!isCollabScene) {
window.history.replaceState({}, "Excalidraw", window.location.origin);
window.history.replaceState({}, APP_NAME, window.location.origin);
}
} else {
// https://github.com/excalidraw/excalidraw/issues/1919
@@ -132,7 +138,7 @@ const initializeScene = async (opts: {
}
isCollabScene = false;
window.history.replaceState({}, "Excalidraw", window.location.origin);
window.history.replaceState({}, APP_NAME, window.location.origin);
}
}
if (isCollabScene) {
@@ -178,6 +184,7 @@ function ExcalidrawWrapper(props: { collab: CollabAPI }) {
width: window.innerWidth,
height: window.innerHeight,
});
const [errorMessage, setErrorMessage] = useState("");
useLayoutEffect(() => {
const onResize = () => {
@@ -240,6 +247,10 @@ function ExcalidrawWrapper(props: { collab: CollabAPI }) {
}
};
const titleTimeout = setTimeout(
() => (document.title = APP_NAME),
TITLE_TIMEOUT,
);
window.addEventListener(EVENT.HASHCHANGE, onHashChange, false);
window.addEventListener(EVENT.UNLOAD, onBlur, false);
window.addEventListener(EVENT.BLUR, onBlur, false);
@@ -247,6 +258,7 @@ function ExcalidrawWrapper(props: { collab: CollabAPI }) {
window.removeEventListener(EVENT.HASHCHANGE, onHashChange, false);
window.removeEventListener(EVENT.UNLOAD, onBlur, false);
window.removeEventListener(EVENT.BLUR, onBlur, false);
clearTimeout(titleTimeout);
};
}, [collab.initializeSocketClient]);
@@ -260,18 +272,52 @@ function ExcalidrawWrapper(props: { collab: CollabAPI }) {
}
};
const onExportToBackend = async (
exportedElements: readonly NonDeletedExcalidrawElement[],
appState: AppState,
canvas: HTMLCanvasElement | null,
) => {
if (exportedElements.length === 0) {
return window.alert(t("alerts.cannotExportEmptyCanvas"));
}
if (canvas) {
try {
await exportToBackend(exportedElements, {
...appState,
viewBackgroundColor: appState.exportBackground
? appState.viewBackgroundColor
: getDefaultAppState().viewBackgroundColor,
});
} catch (error) {
if (error.name !== "AbortError") {
const { width, height } = canvas;
console.error(error, { width, height });
setErrorMessage(error.message);
}
}
}
};
return (
<Excalidraw
ref={excalidrawRef}
onChange={onChange}
width={dimensions.width}
height={dimensions.height}
initialData={initialStatePromiseRef.current.promise}
user={{ name: collab.username }}
onCollabButtonClick={collab.onCollabButtonClick}
isCollaborating={collab.isCollaborating}
onPointerUpdate={collab.onPointerUpdate}
/>
<>
<Excalidraw
ref={excalidrawRef}
onChange={onChange}
width={dimensions.width}
height={dimensions.height}
initialData={initialStatePromiseRef.current.promise}
user={{ name: collab.username }}
onCollabButtonClick={collab.onCollabButtonClick}
isCollaborating={collab.isCollaborating}
onPointerUpdate={collab.onPointerUpdate}
onExportToBackend={onExportToBackend}
/>
{errorMessage && (
<ErrorDialog
message={errorMessage}
onClose={() => setErrorMessage("")}
/>
)}
</>
);
}
+1
View File
@@ -9,6 +9,7 @@ export const CODES = {
BRACKET_RIGHT: "BracketRight",
BRACKET_LEFT: "BracketLeft",
ONE: "Digit1",
TWO: "Digit2",
NINE: "Digit9",
QUOTE: "Quote",
ZERO: "Digit0",
+20 -3
View File
@@ -4,6 +4,7 @@
"selectAll": "تحديد الكل",
"multiSelect": "إضافة عنصر للتحديد",
"moveCanvas": "نقل لوح رسم",
"cut": "",
"copy": "نسخ",
"copyAsPng": "نسخ إلى الحافظة بصيغة PNG",
"copyAsSvg": "نسخ بصيغة SVG",
@@ -28,6 +29,11 @@
"edges": "الحواف",
"sharp": "حادة",
"round": "دائرية",
"arrowheads": "",
"arrowhead_none": "",
"arrowhead_arrow": "",
"arrowhead_bar": "",
"arrowhead_dot": "",
"fontSize": "حجم الخط",
"fontFamily": "نوع الخط",
"onlySelected": "المحدد فقط",
@@ -71,9 +77,11 @@
"ungroup": "إلغاء تحديد مجموعة",
"collaborators": "المتعاونون",
"toggleGridMode": "التبديل إلى وضع الشبكة",
"toggleStats": "",
"addToLibrary": "أضف إلى المكتبة",
"removeFromLibrary": "حذف من المكتبة",
"libraryLoadingMessage": "جارٍ تحميل المكتبة...",
"libraries": "",
"loadingScene": "جاري تحميل المشهد...",
"align": "محاذاة",
"alignTop": "محاذاة إلى اﻷعلى",
@@ -205,13 +213,22 @@
"textNewLine": "إضافة سطر جديد (نص)",
"textFinish": "الانتهاء من تحرير (النص)",
"zoomToFit": "تكبير لتلائم جميع العناصر",
"zoomToSelection": "",
"preventBinding": "منع ربط السهم"
},
"encrypted": {
"tooltip": "رسوماتك مشفرة من النهاية إلى النهاية حتى أن خوادم Excalidraw لن تراها أبدا."
},
"charts": {
"noNumericColumn": "قمت بلصق جدول بيانات دون عمود رقمي.",
"tooManyColumns": "قمت بلصق جدول بيانات دون عمود رقمي."
"stats": {
"angle": "",
"element": "",
"elements": "",
"height": "",
"scene": "",
"selected": "",
"storage": "",
"title": "",
"total": "",
"width": ""
}
}
+20 -3
View File
@@ -4,6 +4,7 @@
"selectAll": "Маркирай всичко",
"multiSelect": "",
"moveCanvas": "",
"cut": "",
"copy": "Копирай",
"copyAsPng": "Копиране в клипборда",
"copyAsSvg": "Копиране в клипборда",
@@ -28,6 +29,11 @@
"edges": "",
"sharp": "",
"round": "",
"arrowheads": "",
"arrowhead_none": "",
"arrowhead_arrow": "",
"arrowhead_bar": "",
"arrowhead_dot": "",
"fontSize": "Размер на шрифта",
"fontFamily": "Семейство шрифтове",
"onlySelected": "Само избраното",
@@ -71,9 +77,11 @@
"ungroup": "",
"collaborators": "",
"toggleGridMode": "",
"toggleStats": "",
"addToLibrary": "",
"removeFromLibrary": "",
"libraryLoadingMessage": "",
"libraries": "",
"loadingScene": "",
"align": "",
"alignTop": "",
@@ -205,13 +213,22 @@
"textNewLine": "Добавяне на нов ред (текст)",
"textFinish": "Завършете редактиране (текст)",
"zoomToFit": "",
"zoomToSelection": "",
"preventBinding": ""
},
"encrypted": {
"tooltip": ""
},
"charts": {
"noNumericColumn": "",
"tooManyColumns": ""
"stats": {
"angle": "",
"element": "",
"elements": "",
"height": "",
"scene": "",
"selected": "",
"storage": "",
"title": "",
"total": "",
"width": ""
}
}
+20 -3
View File
@@ -4,6 +4,7 @@
"selectAll": "Seleccionar tot",
"multiSelect": "Afegir element a la selecció",
"moveCanvas": "Moure el llenç",
"cut": "",
"copy": "Copiar",
"copyAsPng": "Copiar al porta-retalls com a PNG",
"copyAsSvg": "Copiar al porta-retalls com a SVG",
@@ -28,6 +29,11 @@
"edges": "Vores",
"sharp": "Agut",
"round": "Arrodonit",
"arrowheads": "",
"arrowhead_none": "",
"arrowhead_arrow": "",
"arrowhead_bar": "",
"arrowhead_dot": "",
"fontSize": "Mida de lletra",
"fontFamily": "Tipus de lletra",
"onlySelected": "Només seleccionats",
@@ -71,9 +77,11 @@
"ungroup": "Desagrupar la selecció",
"collaborators": "Col·laboradors",
"toggleGridMode": "Commutar línies de graella",
"toggleStats": "",
"addToLibrary": "Afegir a la biblioteca",
"removeFromLibrary": "Eliminar de la biblioteca",
"libraryLoadingMessage": "Carregant la biblioteca...",
"libraries": "",
"loadingScene": "Carregant escena ...",
"align": "",
"alignTop": "",
@@ -205,13 +213,22 @@
"textNewLine": "Afegir línea nova (text)",
"textFinish": "Acabar d'editar (text)",
"zoomToFit": "Zoom per veure tots els elements",
"zoomToSelection": "",
"preventBinding": "Prevenir vinculació de la fletxa"
},
"encrypted": {
"tooltip": "Els vostres dibuixos estan xifrats de punta a punta de manera que els servidors dExcalidraw no els veuran mai."
},
"charts": {
"noNumericColumn": "Has enganxat un full de càlcul sense columna numèrica.",
"tooManyColumns": "Has enganxat un full de càlcul amb més de dues columnes."
"stats": {
"angle": "",
"element": "",
"elements": "",
"height": "",
"scene": "",
"selected": "",
"storage": "",
"title": "",
"total": "",
"width": ""
}
}
+20 -3
View File
@@ -4,6 +4,7 @@
"selectAll": "Alle auswählen",
"multiSelect": "Element zur Auswahl hinzufügen",
"moveCanvas": "Leinwand verschieben",
"cut": "Ausschneiden",
"copy": "Kopieren",
"copyAsPng": "In Zwischenablage kopieren (PNG)",
"copyAsSvg": "In Zwischenablage kopieren (SVG)",
@@ -28,6 +29,11 @@
"edges": "Kanten",
"sharp": "Eckig",
"round": "Rund",
"arrowheads": "Pfeilspitzen",
"arrowhead_none": "Keine",
"arrowhead_arrow": "Pfeil",
"arrowhead_bar": "Balken",
"arrowhead_dot": "Punkt",
"fontSize": "Schriftgröße",
"fontFamily": "Schriftfamilie",
"onlySelected": "Nur ausgewählte",
@@ -71,9 +77,11 @@
"ungroup": "Gruppierung aufheben",
"collaborators": "Mitarbeitende",
"toggleGridMode": "Gitterlinien ein-/ausschalten",
"toggleStats": "Statistiken für Nerds ein-/ausschalten",
"addToLibrary": "Zur Bibliothek hinzufügen",
"removeFromLibrary": "Aus Bibliothek entfernen",
"libraryLoadingMessage": "Lade Bibliothek...",
"libraries": "Bibliotheken durchsuchen",
"loadingScene": "Lade Zeichnung...",
"align": "Ausrichten",
"alignTop": "Obere Kanten",
@@ -205,13 +213,22 @@
"textNewLine": "Neue Zeile hinzufügen (Text)",
"textFinish": "Bearbeiten beenden (Text)",
"zoomToFit": "Zoomen um alle Elemente einzupassen",
"zoomToSelection": "",
"preventBinding": "Pfeil-Bindung verhindern"
},
"encrypted": {
"tooltip": "Da deine Zeichnungen Ende-zu-Ende verschlüsselt werden, sehen auch unsere Excalidraw-Server sie niemals."
},
"charts": {
"noNumericColumn": "Du hast eine Tabelle ohne numerische Spalte eingefügt.",
"tooManyColumns": "Du hast eine Tabelle mit mehr als zwei Spalten eingefügt."
"stats": {
"angle": "Winkel",
"element": "Element",
"elements": "Elemente",
"height": "Höhe",
"scene": "Zeichnung",
"selected": "Ausgewählt",
"storage": "Speicher",
"title": "Statistiken für Nerds",
"total": "Gesamt",
"width": "Breite"
}
}
+20 -3
View File
@@ -4,6 +4,7 @@
"selectAll": "Επιλογή όλων",
"multiSelect": "Προσθέστε το στοιχείο στην επιλογή",
"moveCanvas": "Μετακίνηση καμβά",
"cut": "Αποκοπή",
"copy": "Αντιγραφή",
"copyAsPng": "Αντιγραφή στο πρόχειρο ως PNG",
"copyAsSvg": "Αντιγραφή στο πρόχειρο ως SVG",
@@ -28,6 +29,11 @@
"edges": "Άκρες",
"sharp": "Οξύ",
"round": "Στρογγυλό",
"arrowheads": "Σύμβολα βελών",
"arrowhead_none": "Κανένα",
"arrowhead_arrow": "Βέλος",
"arrowhead_bar": "Μπάρα",
"arrowhead_dot": "Τελεία",
"fontSize": "Μέγεθος γραμματοσειράς",
"fontFamily": "Γραμματοσειρά",
"onlySelected": "Μόνο τα Επιλεγμένα",
@@ -71,9 +77,11 @@
"ungroup": "Κατάργηση ομάδας από επιλογή",
"collaborators": "Συνεργάτες",
"toggleGridMode": "Εναλλαγή λειτουργίας πλέγματος",
"toggleStats": "",
"addToLibrary": "Προσθήκη στη βιβλιοθήκη",
"removeFromLibrary": "Αφαίρεση από τη βιβλιοθήκη",
"libraryLoadingMessage": "Φόρτωση βιβλιοθήκης...",
"libraries": "Άλλες βιβλιοθήκες",
"loadingScene": "Φόρτωση σκηνής...",
"align": "Στοίχιση",
"alignTop": "Στοίχιση πάνω",
@@ -205,13 +213,22 @@
"textNewLine": "Προσθήκη νέας γραμμής (κείμενο)",
"textFinish": "Ολοκλήρωση επεξεργασίας (κείμενο)",
"zoomToFit": "Zoom ώστε να χωρέσουν όλα τα στοιχεία",
"zoomToSelection": "",
"preventBinding": "Αποτροπή δέσμευσης βέλων"
},
"encrypted": {
"tooltip": "Τα σχέδιά σου είναι κρυπτογραφημένα από άκρο σε άκρο, έτσι δεν θα έιναι ποτέ ορατά μέσα από τους διακομιστές του Excalidraw."
},
"charts": {
"noNumericColumn": "Επικόλλησες ένα υπολογιστικό φύλλο χωρίς αριθμητική στήλη.",
"tooManyColumns": "Επικόλλησες ένα υπολογιστικό φύλλο με περισσότερες από δύο στήλες."
"stats": {
"angle": "Γωνία",
"element": "Στοιχείο",
"elements": "Στοιχεία",
"height": "Ύψος",
"scene": "",
"selected": "Επιλεγμένα",
"storage": "Χώρος",
"title": "",
"total": "Σύνολο ",
"width": "Πλάτος"
}
}
+9 -7
View File
@@ -37,7 +37,7 @@
"fontSize": "Font size",
"fontFamily": "Font family",
"onlySelected": "Only selected",
"withBackground": "With Background",
"withBackground": "With background",
"exportEmbedScene": "Embed scene into exported file",
"exportEmbedScene_details": "Scene data will be saved into the exported PNG/SVG file so that the scene can be restored from it.\nWill increase exported file size.",
"addWatermark": "Add \"Made with Excalidraw\"",
@@ -76,8 +76,7 @@
"group": "Group selection",
"ungroup": "Ungroup selection",
"collaborators": "Collaborators",
"toggleGridMode": "Toggle grid mode",
"toggleStats": "Toggle stats for nerds",
"gridMode": "Grid mode",
"addToLibrary": "Add to library",
"removeFromLibrary": "Remove from library",
"libraryLoadingMessage": "Loading library...",
@@ -118,9 +117,10 @@
"redo": "Redo",
"roomDialog": "Start live collaboration",
"createNewRoom": "Create new room",
"toggleFullScreen": "Toggle full screen",
"toggleDarkMode": "Toggle dark mode",
"toggleZenMode": "Toggle zen mode",
"fullScreen": "Full screen",
"darkMode": "Dark mode",
"lightMode": "Light mode",
"zenMode": "Zen mode",
"exitZenMode": "Exit zen mode"
},
"alerts": {
@@ -136,7 +136,7 @@
"loadSceneOverridePrompt": "Loading external drawing will replace your existing content. Do you wish to continue?",
"errorLoadingLibrary": "There was an error loading the third party library.",
"confirmAddLibrary": "This will add {{numShapes}} shape(s) to your library. Are you sure?",
"imageDoesNotContainScene": "Image file doesn't contain scene data. Have you enabled this during export?",
"imageDoesNotContainScene": "Importing images isn't supported at the moment.\n\nDid you want to import a scene? This image does not seem to contain any scene data. Have you enabled this during export?",
"cannotRestoreFromImage": "Scene couldn't be restored from this image file"
},
"toolBar": {
@@ -161,6 +161,7 @@
"freeDraw": "Click and drag, release when you're finished",
"text": "Tip: you can also add text by double-clicking anywhere with the selection tool",
"linearElementMulti": "Click on last point or press Escape or Enter to finish",
"lockAngle": "You can constrain angle by holding SHIFT",
"resize": "You can constrain proportions by holding SHIFT while resizing,\nhold ALT to resize from the center",
"rotate": "You can constrain angles by holding SHIFT while rotating",
"lineEditor_info": "Double-click or press Enter to edit points",
@@ -213,6 +214,7 @@
"textNewLine": "Add new line (text)",
"textFinish": "Finish editing (text)",
"zoomToFit": "Zoom to fit all elements",
"zoomToSelection": "Zoom to selection",
"preventBinding": "Prevent arrow binding"
},
"encrypted": {
+20 -3
View File
@@ -4,6 +4,7 @@
"selectAll": "Seleccionar todo",
"multiSelect": "Añadir elemento a la selección",
"moveCanvas": "Mover el lienzo",
"cut": "",
"copy": "Copiar",
"copyAsPng": "Copiar al portapapeles como PNG",
"copyAsSvg": "Copiar al portapapeles como SVG",
@@ -28,6 +29,11 @@
"edges": "Bordes",
"sharp": "Afilado",
"round": "Redondo",
"arrowheads": "",
"arrowhead_none": "",
"arrowhead_arrow": "",
"arrowhead_bar": "",
"arrowhead_dot": "",
"fontSize": "Tamaño de la fuente",
"fontFamily": "Tipo de fuente",
"onlySelected": "Sólo seleccionados",
@@ -71,9 +77,11 @@
"ungroup": "Desagrupar",
"collaborators": "Colaboradores",
"toggleGridMode": "Alternar modo cuadrícula",
"toggleStats": "",
"addToLibrary": "Añadir a la biblioteca",
"removeFromLibrary": "Eliminar de la biblioteca",
"libraryLoadingMessage": "Cargando biblioteca...",
"libraries": "",
"loadingScene": "Cargando escena...",
"align": "",
"alignTop": "",
@@ -205,13 +213,22 @@
"textNewLine": "Añadir nueva línea (texto)",
"textFinish": "Finalizar edición (texto)",
"zoomToFit": "Ajustar para mostrar todos los elementos",
"zoomToSelection": "",
"preventBinding": "Evitar enlace de flecha"
},
"encrypted": {
"tooltip": "Tus dibujos están cifrados de punto a punto, por lo que los servidores de Excalidraw nunca los verán."
},
"charts": {
"noNumericColumn": "Pegaste una hoja de cálculo sin una columna numérica.",
"tooManyColumns": "Pegaste una hoja de cálculo con más de dos columnas."
"stats": {
"angle": "",
"element": "",
"elements": "",
"height": "",
"scene": "",
"selected": "",
"storage": "",
"title": "",
"total": "",
"width": ""
}
}
+20 -3
View File
@@ -4,6 +4,7 @@
"selectAll": "انتخاب همه",
"multiSelect": "یک ایتم به انتخاب شده ها اضافه کنید.",
"moveCanvas": "بوم را حرکت بدهید",
"cut": "",
"copy": "کپی",
"copyAsPng": "کپی در حافطه موقت به صورت PNG",
"copyAsSvg": "کپی در حافطه موقت به صورت SVG",
@@ -28,6 +29,11 @@
"edges": "لبه ها",
"sharp": "تیز",
"round": "دور",
"arrowheads": "",
"arrowhead_none": "",
"arrowhead_arrow": "",
"arrowhead_bar": "",
"arrowhead_dot": "",
"fontSize": "اندازه قلم",
"fontFamily": "نوع قلم",
"onlySelected": "فقط انتخاب شده ها",
@@ -71,9 +77,11 @@
"ungroup": "حذف گروهبندی انتخابها",
"collaborators": "همکاران",
"toggleGridMode": "سويچ خطوط راهنما",
"toggleStats": "",
"addToLibrary": "افزودن به کتابخانه",
"removeFromLibrary": "حذف از کتابخانه",
"libraryLoadingMessage": "بارگذاری کتابخانه...",
"libraries": "",
"loadingScene": "باگذاری صحنه...",
"align": "تراز",
"alignTop": "تراز به بالا",
@@ -205,13 +213,22 @@
"textNewLine": "یک خط جدید اضافه کنید (متن)",
"textFinish": "پایان ویرایش (متن)",
"zoomToFit": "بزرگنمایی برای دیدن تمام آیتم ها",
"zoomToSelection": "",
"preventBinding": "مانع شدن از چسبیدن فلش ها"
},
"encrypted": {
"tooltip": "شما در یک محیط رمزگزاری شده دو طرفه در حال طراحی هستید پس Excalidraw هرگز طرح های شما را نمیبند."
},
"charts": {
"noNumericColumn": "شما یک صفحه گسترده را بدون ستون عددی کپی کرده اید.",
"tooManyColumns": "شما یک صفحه گسترده را با بیش از دو ستون کپی کرده اید."
"stats": {
"angle": "",
"element": "",
"elements": "",
"height": "",
"scene": "",
"selected": "",
"storage": "",
"title": "",
"total": "",
"width": ""
}
}
+20 -3
View File
@@ -4,6 +4,7 @@
"selectAll": "Valitse kaikki",
"multiSelect": "Lisää kohde valintaan",
"moveCanvas": "Siirrä piirtoaluetta",
"cut": "Leikkaa",
"copy": "Kopioi",
"copyAsPng": "Kopioi leikepöydälle PNG-tiedostona",
"copyAsSvg": "Kopioi leikepöydälle SVG-tiedostona",
@@ -28,6 +29,11 @@
"edges": "Reunat",
"sharp": "Terävä",
"round": "Pyöreä",
"arrowheads": "Nuolenkärjet",
"arrowhead_none": "Ei mitään",
"arrowhead_arrow": "Nuoli",
"arrowhead_bar": "Tasapää",
"arrowhead_dot": "Piste",
"fontSize": "Kirjasinkoko",
"fontFamily": "Kirjasintyyppi",
"onlySelected": "Vain valitut",
@@ -71,9 +77,11 @@
"ungroup": "Pura valittu ryhmä",
"collaborators": "Yhteistyökumppanit",
"toggleGridMode": "Ruudukko päälle/pois",
"toggleStats": "Nörttien tilastot päälle/pois",
"addToLibrary": "Lisää kirjastoon",
"removeFromLibrary": "Poista kirjastosta",
"libraryLoadingMessage": "Ladataan kirjastoa...",
"libraries": "Selaa kirjastoja",
"loadingScene": "Ladataan työtä...",
"align": "Tasaa",
"alignTop": "Tasaa ylös",
@@ -205,13 +213,22 @@
"textNewLine": "Lisää uusi rivi (teksti)",
"textFinish": "Lopeta muokkaus (teksti)",
"zoomToFit": "Zoomaa kaikki elementit näkyviin",
"zoomToSelection": "Zoomaa valintaan",
"preventBinding": "Estä nuolten sitominen"
},
"encrypted": {
"tooltip": "Piirroksesi ovat päästä päähän salattuja, joten Excalidrawin palvelimet eivät koskaan näe niitä."
},
"charts": {
"noNumericColumn": "Liitit taulukon ilman lukuja sisältävää saraketta.",
"tooManyColumns": "Liitit taulukon, jossa on enemmän kuin kaksi saraketta."
"stats": {
"angle": "Kulma",
"element": "Elementti",
"elements": "Elementit",
"height": "Korkeus",
"scene": "Teos",
"selected": "Valitut",
"storage": "Tallennustila",
"title": "Nörttien tilastot",
"total": "Yhteensä",
"width": "Leveys"
}
}
+20 -3
View File
@@ -4,6 +4,7 @@
"selectAll": "Tout sélectionner",
"multiSelect": "Ajouter l'élément à la sélection",
"moveCanvas": "Déplacer le canvas",
"cut": "Couper",
"copy": "Copier",
"copyAsPng": "Copier dans le presse-papier en PNG",
"copyAsSvg": "Copier dans le presse-papier en SVG",
@@ -28,6 +29,11 @@
"edges": "Angles",
"sharp": "Aigu",
"round": "Rond",
"arrowheads": "Extrémités de ligne",
"arrowhead_none": "Aucun",
"arrowhead_arrow": "Flèche",
"arrowhead_bar": "Barre",
"arrowhead_dot": "Point",
"fontSize": "Taille de la police",
"fontFamily": "Police",
"onlySelected": "Uniquement la sélection",
@@ -71,9 +77,11 @@
"ungroup": "Dégrouper la sélection",
"collaborators": "Collaborateurs",
"toggleGridMode": "Basculer le mode grille",
"toggleStats": "Activer/désactiver les stats pour les nerds",
"addToLibrary": "Ajouter à la bibliothèque",
"removeFromLibrary": "Supprimer de la bibliothèque",
"libraryLoadingMessage": "Chargement de la bibliothèque...",
"libraries": "Explorer les bibliothèques",
"loadingScene": "Chargement de la scène...",
"align": "Aligner",
"alignTop": "Aligner en haut",
@@ -205,13 +213,22 @@
"textNewLine": "Ajouter une nouvelle ligne (texte)",
"textFinish": "Terminer l'édition (texte)",
"zoomToFit": "Zoomer pour visualiser tous les éléments",
"zoomToSelection": "Zoom sur la sélection",
"preventBinding": "Empêcher la liaison de la flèche"
},
"encrypted": {
"tooltip": "Vos dessins sont chiffrés de bout en bout, les serveurs d'Excalidraw ne les verront jamais."
},
"charts": {
"noNumericColumn": "Vous avez collé une feuille de calcul sans données numérique.",
"tooManyColumns": "Vous avez collé une feuille de calcul avec plus de deux colonnes."
"stats": {
"angle": "Angle",
"element": "Élément",
"elements": "Éléments",
"height": "Hauteur",
"scene": "Scène",
"selected": "Sélectionné",
"storage": "Stockage",
"title": "Stats pour nerds",
"total": "Total",
"width": "Largeur"
}
}
+20 -3
View File
@@ -4,6 +4,7 @@
"selectAll": "בחר הכל",
"multiSelect": "",
"moveCanvas": "",
"cut": "",
"copy": "העתק",
"copyAsPng": "העתק ללוח כ PNG",
"copyAsSvg": "העתק ללוח כ SVG",
@@ -28,6 +29,11 @@
"edges": "",
"sharp": "",
"round": "",
"arrowheads": "",
"arrowhead_none": "",
"arrowhead_arrow": "",
"arrowhead_bar": "",
"arrowhead_dot": "",
"fontSize": "גודל גופן",
"fontFamily": "סוג הגופן",
"onlySelected": "רק מה שנבחר",
@@ -71,9 +77,11 @@
"ungroup": "פרק קבוצה",
"collaborators": "",
"toggleGridMode": "",
"toggleStats": "",
"addToLibrary": "",
"removeFromLibrary": "",
"libraryLoadingMessage": "",
"libraries": "",
"loadingScene": "",
"align": "",
"alignTop": "",
@@ -205,13 +213,22 @@
"textNewLine": "הוסף שורה חדשה (טקסט)",
"textFinish": "סיים עריכה (טקסט)",
"zoomToFit": "זום להתאמת כל האלמנטים למסך",
"zoomToSelection": "",
"preventBinding": ""
},
"encrypted": {
"tooltip": "הרישומים שלך מוצפנים מקצה לקצה כך שהשרתים של Excalidraw לא יראו אותם לעולם."
},
"charts": {
"noNumericColumn": "",
"tooManyColumns": ""
"stats": {
"angle": "",
"element": "",
"elements": "",
"height": "",
"scene": "",
"selected": "",
"storage": "",
"title": "",
"total": "",
"width": ""
}
}
+21 -4
View File
@@ -4,6 +4,7 @@
"selectAll": "सभी चुनें",
"multiSelect": "आकार को चयन में जोड़ें",
"moveCanvas": "कैनवास को स्थानांतरित करें",
"cut": "काटें",
"copy": "प्रतिलिपि",
"copyAsPng": "क्लिपबोर्ड पर कॉपी करें ,पीएनजी के रूप में",
"copyAsSvg": "क्लिपबोर्ड पर कॉपी करें,एसवीजी के रूप में",
@@ -28,6 +29,11 @@
"edges": "किनारा",
"sharp": "नुकीला",
"round": "गोल",
"arrowheads": "",
"arrowhead_none": "",
"arrowhead_arrow": "तीर",
"arrowhead_bar": "",
"arrowhead_dot": "",
"fontSize": "फ़ॉन्ट का आकार",
"fontFamily": "फ़ॉन्ट का परिवार",
"onlySelected": "केवल चयनित",
@@ -63,7 +69,7 @@
"language": "भाषा",
"createRoom": "अधिवेशन",
"duplicateSelection": "डुप्लिकेट",
"untitled": "",
"untitled": "अशीर्षित",
"name": "नाम",
"yourName": "आपका नाम",
"madeWithExcalidraw": "मेड विथ एक्सकैलिडराव",
@@ -71,9 +77,11 @@
"ungroup": "समूह चयन असमूहीकृत करें",
"collaborators": "सहयोगी",
"toggleGridMode": "टॉगल ग्रिड मोड",
"toggleStats": "",
"addToLibrary": "लाइब्रेरी से जोड़ें",
"removeFromLibrary": "लाइब्रेरी से निकालें",
"libraryLoadingMessage": "लाइब्रेरी खुल रही है",
"libraries": "",
"loadingScene": "दृश्य खुल रहा है",
"align": "",
"alignTop": "",
@@ -205,13 +213,22 @@
"textNewLine": "नई पंक्ति (पाठ) जोड़ें",
"textFinish": "संपादन समाप्त करें (पाठ)",
"zoomToFit": "सभी तत्वों को फिट करने के लिए ज़ूम करें",
"zoomToSelection": "",
"preventBinding": ""
},
"encrypted": {
"tooltip": "आपके चित्र अंत-से-अंत एन्क्रिप्टेड हैं, इसलिए एक्सक्लूसिव्रॉव के सर्वर उन्हें कभी नहीं देखेंगे।"
},
"charts": {
"noNumericColumn": "आपने एक संख्यात्मक कॉलम के बिना एक स्प्रेडशीट चिपकाई।",
"tooManyColumns": "आपने दो से अधिक कॉलम के साथ एक स्प्रेडशीट चिपकाई।"
"stats": {
"angle": "कोण",
"element": "",
"elements": "",
"height": "ऊंचाई",
"scene": "दृश्य",
"selected": "चयनित",
"storage": "संग्रह",
"title": "",
"total": "कुल",
"width": "चौड़ाई"
}
}
+20 -3
View File
@@ -4,6 +4,7 @@
"selectAll": "Összes kijelölése",
"multiSelect": "",
"moveCanvas": "",
"cut": "",
"copy": "Másolás",
"copyAsPng": "Vágólapra másolás mint PNG",
"copyAsSvg": "Vágólapra másolás mint SVG",
@@ -28,6 +29,11 @@
"edges": "",
"sharp": "",
"round": "",
"arrowheads": "",
"arrowhead_none": "",
"arrowhead_arrow": "",
"arrowhead_bar": "",
"arrowhead_dot": "",
"fontSize": "",
"fontFamily": "",
"onlySelected": "Csak a kiválasztott",
@@ -71,9 +77,11 @@
"ungroup": "",
"collaborators": "",
"toggleGridMode": "",
"toggleStats": "",
"addToLibrary": "",
"removeFromLibrary": "",
"libraryLoadingMessage": "",
"libraries": "",
"loadingScene": "",
"align": "",
"alignTop": "",
@@ -205,13 +213,22 @@
"textNewLine": "",
"textFinish": "",
"zoomToFit": "",
"zoomToSelection": "",
"preventBinding": ""
},
"encrypted": {
"tooltip": ""
},
"charts": {
"noNumericColumn": "",
"tooManyColumns": ""
"stats": {
"angle": "",
"element": "",
"elements": "",
"height": "",
"scene": "",
"selected": "",
"storage": "",
"title": "",
"total": "",
"width": ""
}
}
+20 -3
View File
@@ -4,6 +4,7 @@
"selectAll": "Pilih semua",
"multiSelect": "Tambahkan elemen ke pilihan",
"moveCanvas": "Pindahkan kanvas",
"cut": "Potong",
"copy": "Salin",
"copyAsPng": "Salin ke papan klip sebagai PNG",
"copyAsSvg": "Salin ke papan klip sebagai SVG",
@@ -28,6 +29,11 @@
"edges": "Tepi",
"sharp": "Tajam",
"round": "Bulat",
"arrowheads": "Mata panah",
"arrowhead_none": "Tidak ada",
"arrowhead_arrow": "Panah",
"arrowhead_bar": "Batang",
"arrowhead_dot": "Titik",
"fontSize": "Ukuran font",
"fontFamily": "Jenis font",
"onlySelected": "Hanya yang Dipilih",
@@ -71,9 +77,11 @@
"ungroup": "Pisahkan pilihan",
"collaborators": "Kolaborator",
"toggleGridMode": "Aktifkan/Matikan mode kisi",
"toggleStats": "Aktifkan statistik untuk nerd",
"addToLibrary": "Tambahkan ke pustaka",
"removeFromLibrary": "Hapus dari pustaka",
"libraryLoadingMessage": "Memuat pustaka...",
"libraries": "Telusur pustaka",
"loadingScene": "Memuat pemandangan...",
"align": "Perataan",
"alignTop": "Rata atas",
@@ -205,13 +213,22 @@
"textNewLine": "Tambahkan baris baru (teks)",
"textFinish": "Selesai mengedit (teks)",
"zoomToFit": "Perbesar agar sesuai dengan semua elemen",
"zoomToSelection": "",
"preventBinding": "Cegah pengikatan panah"
},
"encrypted": {
"tooltip": "Gambar anda terenkripsi end-to-end sehingga server Excalidraw tidak akan pernah dapat melihatnya."
},
"charts": {
"noNumericColumn": "Anda menempelkan sebuah lembar bentang tanpa sebuah kolom numerik.",
"tooManyColumns": "Anda menempelkan sebuah lembar bentang dengan lebih dari dua kolom."
"stats": {
"angle": "Sudut",
"element": "Elemen",
"elements": "Elemen",
"height": "Tinggi",
"scene": "Pemandangan",
"selected": "Terpilih",
"storage": "Penyimpanan",
"title": "Statistik untuk nerd",
"total": "Total",
"width": "Lebar"
}
}
+22 -5
View File
@@ -4,6 +4,7 @@
"selectAll": "Seleziona tutto",
"multiSelect": "Aggiungi elemento alla selezione",
"moveCanvas": "Sposta tela",
"cut": "Taglia",
"copy": "Copia",
"copyAsPng": "Copia negli appunti come PNG",
"copyAsSvg": "Copia negli appunti come SVG",
@@ -28,6 +29,11 @@
"edges": "Bordi",
"sharp": "Acuto",
"round": "Rotondo",
"arrowheads": "Punta della freccia",
"arrowhead_none": "Nessuno",
"arrowhead_arrow": "Freccia",
"arrowhead_bar": "Barra",
"arrowhead_dot": "Punto",
"fontSize": "Dimensione carattere",
"fontFamily": "Carattere",
"onlySelected": "Solo selezionati",
@@ -70,10 +76,12 @@
"group": "Crea gruppo da selezione",
"ungroup": "Dividi gruppo da selezione",
"collaborators": "Collaboratori",
"toggleGridMode": "Attiva/disattiva modalità quadrícula",
"toggleGridMode": "Attiva/disattiva modalità griglia",
"toggleStats": "Attiva/disattiva statistiche per nerd",
"addToLibrary": "Aggiungi alla biblioteca",
"removeFromLibrary": "Rimuovi dalla biblioteca",
"libraryLoadingMessage": "Caricamento della biblioteca...",
"libraries": "Sfoglia librerie",
"loadingScene": "Caricamento della scena...",
"align": "Allinea",
"alignTop": "Allinea in alto",
@@ -116,7 +124,7 @@
"exitZenMode": "Uscire dalla modalità zen"
},
"alerts": {
"clearReset": "Questo cancellerà l'intera tela. Sei sicuro?",
"clearReset": "Questa azione cancellerà l'intera tela. Sei sicuro?",
"couldNotCreateShareableLink": "Non riesco a creare un link condivisibile.",
"couldNotCreateShareableLinkTooBig": "Impossibile creare il link condivisibile: la scena è troppo grande",
"couldNotLoadInvalidFile": "Impossibile caricare un file no valido",
@@ -205,13 +213,22 @@
"textNewLine": "Aggiungi nuova riga (testo)",
"textFinish": "Completa la modifica (testo)",
"zoomToFit": "Adatta zoom per mostrare tutti gli elementi",
"zoomToSelection": "Zoom alla selezione",
"preventBinding": "Prevenire l'associazione freccia"
},
"encrypted": {
"tooltip": "I tuoi disegni sono crittografati end-to-end in modo che i server di Excalidraw non li possano mai vedere."
},
"charts": {
"noNumericColumn": "Hai incollato un foglio di calcolo senza una colonna numerica.",
"tooManyColumns": "Hai incollato un foglio di calcolo con più di due colonne."
"stats": {
"angle": "Angolo",
"element": "Elemento",
"elements": "Elementi",
"height": "Altezza",
"scene": "Scena",
"selected": "Selezionato",
"storage": "Memoria",
"title": "Statistiche per nerd",
"total": "Totale",
"width": "Larghezza"
}
}
+20 -3
View File
@@ -4,6 +4,7 @@
"selectAll": "すべて選択",
"multiSelect": "複数選択",
"moveCanvas": "キャンバスを移動",
"cut": "",
"copy": "コピー",
"copyAsPng": "PNGとしてクリップボードへコピー",
"copyAsSvg": "SVGとしてクリップボードへコピー",
@@ -28,6 +29,11 @@
"edges": "角",
"sharp": "四角",
"round": "丸",
"arrowheads": "",
"arrowhead_none": "",
"arrowhead_arrow": "",
"arrowhead_bar": "",
"arrowhead_dot": "",
"fontSize": "フォントの大きさ",
"fontFamily": "フォントの種類",
"onlySelected": "選択中のみ",
@@ -71,9 +77,11 @@
"ungroup": "グループ化を解除",
"collaborators": "共同編集者",
"toggleGridMode": "グリッドモードに切り替える",
"toggleStats": "",
"addToLibrary": "ライブラリに追加",
"removeFromLibrary": "ライブラリから削除",
"libraryLoadingMessage": "ライブラリを読み込み中...",
"libraries": "",
"loadingScene": "シーンを読み込み中...",
"align": "整列",
"alignTop": "上揃え",
@@ -205,13 +213,22 @@
"textNewLine": "テキストの改行",
"textFinish": "テキストの編集を終える",
"zoomToFit": "すべての図形が収まるよう拡大/縮小",
"zoomToSelection": "",
"preventBinding": "矢印を結合しない"
},
"encrypted": {
"tooltip": "描画内容はエンドツーエンド暗号化が施されており、Excalidrawサーバーが内容を見ることはできません。"
},
"charts": {
"noNumericColumn": "数値の列が存在しないスプレッドシートをペーストしました。",
"tooManyColumns": "2列以上のスプレッドシートを貼り付けました."
"stats": {
"angle": "",
"element": "",
"elements": "",
"height": "",
"scene": "",
"selected": "",
"storage": "",
"title": "",
"total": "",
"width": ""
}
}
+20 -3
View File
@@ -4,6 +4,7 @@
"selectAll": "전체 선택",
"multiSelect": "선택 영역에 추가하기",
"moveCanvas": "캔버스 이동",
"cut": "",
"copy": "복사하기",
"copyAsPng": "클립보드로 PNG 이미지 복사",
"copyAsSvg": "클립보드로 SVG 이미지 복사",
@@ -28,6 +29,11 @@
"edges": "가장자리",
"sharp": "선명하게",
"round": "둥글게",
"arrowheads": "",
"arrowhead_none": "",
"arrowhead_arrow": "",
"arrowhead_bar": "",
"arrowhead_dot": "",
"fontSize": "폰트 크기",
"fontFamily": "폰트 스타일",
"onlySelected": "선택한 항목만",
@@ -71,9 +77,11 @@
"ungroup": "그룹 해제",
"collaborators": "공동 작업자",
"toggleGridMode": "격자 모드 켜기/끄기",
"toggleStats": "",
"addToLibrary": "라이브러리에 추가",
"removeFromLibrary": "라이브러리에서 제거",
"libraryLoadingMessage": "라이브러리 불러오는 중...",
"libraries": "",
"loadingScene": "화면 불러오는 중...",
"align": "",
"alignTop": "",
@@ -205,13 +213,22 @@
"textNewLine": "줄바꾸기",
"textFinish": "편집 완료",
"zoomToFit": "",
"zoomToSelection": "",
"preventBinding": ""
},
"encrypted": {
"tooltip": ""
},
"charts": {
"noNumericColumn": "",
"tooManyColumns": ""
"stats": {
"angle": "",
"element": "",
"elements": "",
"height": "",
"scene": "",
"selected": "",
"storage": "",
"title": "",
"total": "",
"width": ""
}
}
+24 -7
View File
@@ -4,6 +4,7 @@
"selectAll": "အကုန်ရွေး",
"multiSelect": "ရွေးထားသည့်ထဲပုံထည့်",
"moveCanvas": "ကားချပ်ရွှေ့",
"cut": "",
"copy": "ကူး",
"copyAsPng": "PNG အနေဖြင့်ကူး",
"copyAsSvg": "SVG အနေဖြင့်ကူး",
@@ -28,6 +29,11 @@
"edges": "အစွန်း",
"sharp": "ထောင့်ချွန်",
"round": "ထောင့်ဝိုင်း",
"arrowheads": "မြှားခေါင်း",
"arrowhead_none": "ဘာမျှမရှိ",
"arrowhead_arrow": "မြှား",
"arrowhead_bar": "",
"arrowhead_dot": "",
"fontSize": "စာလုံးအရွယ်",
"fontFamily": "စာလုံးပုံစံ",
"onlySelected": "ရွေးထားသလောက်",
@@ -54,7 +60,7 @@
"architect": "ဗိသုကာ",
"artist": "ပန်းချီ",
"cartoonist": "ကာတွန်း",
"fileTitle": "",
"fileTitle": "ခေါင်းစဉ်",
"colorPicker": "အရောင်ရွေး",
"canvasBackground": "ကားချပ်နောက်ခံ",
"drawingCanvas": "ပုံဆွဲကားချပ်",
@@ -63,7 +69,7 @@
"language": "ဘာသာစကား",
"createRoom": "တိုက်ရိုက်ပူးပေါင်းဆောင်ရွက်ရန်အဖွဲ့ဖွဲ့",
"duplicateSelection": "ပွား",
"untitled": "",
"untitled": "အမည်မရှိ",
"name": "အမည်",
"yourName": "သင့်အမည်",
"madeWithExcalidraw": "Excalidraw ဖြင့်ဖန်တီးသည်။",
@@ -71,9 +77,11 @@
"ungroup": "အုပ်စုဖျက်သိမ်း",
"collaborators": "ပူးပေါင်းပါဝင်သူများ",
"toggleGridMode": "ဇယားကွက်ဖော်/ဖျောက်",
"toggleStats": "",
"addToLibrary": "မှတ်တမ်းတင်",
"removeFromLibrary": "မှတ်တမ်းမှထုတ်",
"libraryLoadingMessage": "မှတ်တမ်းအား တင်သွင်းနေသည်...",
"libraries": "စာကြည့်တိုက်တွင်ရှာဖွေပါ",
"loadingScene": "မြင်ကွင်းဖော်နေသည်...",
"align": "ချိန်ညှိ",
"alignTop": "ထိပ်ညှိ",
@@ -82,8 +90,8 @@
"alignRight": "ညာညှိ",
"centerVertically": "ဒေါင်လိုက်အလယ်ညှိ",
"centerHorizontally": "အလျားလိုက်အလယ်ညှိ",
"distributeHorizontally": "",
"distributeVertically": ""
"distributeHorizontally": "အလျားလိုက်",
"distributeVertically": "ထောင်လိုက်"
},
"buttons": {
"clearReset": "ကားချပ်ရှင်းလင်း",
@@ -205,13 +213,22 @@
"textNewLine": "စာသားဖြည့်သွင်း",
"textFinish": "စာသားဖြည့်သွင်းပြီး",
"zoomToFit": "ကားချပ်အပြည့်ဖေါ်",
"zoomToSelection": "",
"preventBinding": "မြှားများမပေါင်းစေရန်"
},
"encrypted": {
"tooltip": "ရေးဆွဲထားသောပုံများအား နှစ်ဘက်စွန်းတိုင်လျှို့ဝှက်ထားသဖြင့် Excalidraw ၏ဆာဗာများပင်လျှင်မြင်တွေ့ရမည်မဟုတ်ပါ။"
},
"charts": {
"noNumericColumn": "အမှတ်စဉ်ကော်လံမပါဝင်သောဇယားအားထည့်သွင်းလိုက်သည်။",
"tooManyColumns": "ကော်လံနှစ်ခုထက်ပိုပါနေသောဇယားအားထည့်သွင်းလိုက်သည်။"
"stats": {
"angle": "ထောင့်",
"element": "",
"elements": "",
"height": "အမြင့်",
"scene": "မြင်ကွင်း",
"selected": "ရွေးချယ်သည်",
"storage": "သိုလှောင်ခန်း",
"title": "အက္ခရာများအတွက်အချက်အလက်များ",
"total": "စုစုပေါင်း",
"width": "အကျယ်"
}
}
+20 -3
View File
@@ -4,6 +4,7 @@
"selectAll": "Velg alt",
"multiSelect": "Legg til element i utvalg",
"moveCanvas": "Flytt lerretet",
"cut": "Klipp ut",
"copy": "Kopier",
"copyAsPng": "Kopier til PNG",
"copyAsSvg": "Kopier til utklippstavlen som SVG",
@@ -28,6 +29,11 @@
"edges": "Kanter",
"sharp": "Skarp",
"round": "Rund",
"arrowheads": "Pilspisser",
"arrowhead_none": "Ingen",
"arrowhead_arrow": "Pil",
"arrowhead_bar": "Søyle",
"arrowhead_dot": "Prikk",
"fontSize": "Skriftstørrelse",
"fontFamily": "Fontfamilie",
"onlySelected": "Kun valgte",
@@ -71,9 +77,11 @@
"ungroup": "Avgruppér utvalg",
"collaborators": "Samarbeidspartnere",
"toggleGridMode": "Slå av/på rutenett",
"toggleStats": "Skru av/på statistikk for nerder",
"addToLibrary": "Legg til i bibliotek",
"removeFromLibrary": "Fjern fra bibliotek",
"libraryLoadingMessage": "Laster bibliotek...",
"libraries": "Bla gjennom biblioteker",
"loadingScene": "Laster inn scene...",
"align": "Juster",
"alignTop": "Juster øverst",
@@ -205,13 +213,22 @@
"textNewLine": "Legg til ny linje (tekst)",
"textFinish": "Fullfør redigering (tekst)",
"zoomToFit": "Zoom for å passe alle elementene",
"zoomToSelection": "Zoom til utvalg",
"preventBinding": "Forhindre pilbinding"
},
"encrypted": {
"tooltip": "Dine tegninger er ende-til-ende-krypterte slik at Excalidraw sine servere aldri vil se dem."
},
"charts": {
"noNumericColumn": "Du limte inn et regneark uten en numerisk kolonne.",
"tooManyColumns": "Du limte inn et regneark med mer enn to kolonner."
"stats": {
"angle": "Vinkel",
"element": "Element",
"elements": "Elementer",
"height": "Høyde",
"scene": "Scene",
"selected": "Valgt",
"storage": "Lagring",
"title": "Statistikk for nerder",
"total": "Totalt",
"width": "Bredde"
}
}
+20 -3
View File
@@ -4,6 +4,7 @@
"selectAll": "Alles selecteren",
"multiSelect": "Voeg element toe aan selectie",
"moveCanvas": "Canvas verplaatsen",
"cut": "",
"copy": "Kopiëren",
"copyAsPng": "Kopieer als PNG",
"copyAsSvg": "Kopieer als SVG",
@@ -28,6 +29,11 @@
"edges": "Randen",
"sharp": "Hoekig",
"round": "Rond",
"arrowheads": "",
"arrowhead_none": "",
"arrowhead_arrow": "",
"arrowhead_bar": "",
"arrowhead_dot": "",
"fontSize": "Tekstgrootte",
"fontFamily": "Lettertype",
"onlySelected": "Enkel geselecteerde",
@@ -71,9 +77,11 @@
"ungroup": "Groep opheffen",
"collaborators": "Deelnemers",
"toggleGridMode": "Rasterlijnen in-/uitschakelen",
"toggleStats": "",
"addToLibrary": "Voeg toe aan bibliotheek",
"removeFromLibrary": "Verwijder uit bibliotheek",
"libraryLoadingMessage": "Bibliotheek laden...",
"libraries": "",
"loadingScene": "Scène laden...",
"align": "",
"alignTop": "",
@@ -205,13 +213,22 @@
"textNewLine": "Nieuwe regel toevoegen (tekst)",
"textFinish": "Voltooi bewerken (tekst)",
"zoomToFit": "Zoom in op alle elementen",
"zoomToSelection": "",
"preventBinding": ""
},
"encrypted": {
"tooltip": "Je tekeningen zijn beveiligd met end-to-end encryptie, dus Excalidraw's servers zullen nooit zien wat je tekent."
},
"charts": {
"noNumericColumn": "Je hebt een werkblad geplakt zonder een numerieke kolom.",
"tooManyColumns": "Je hebt een werkblad geplakt met meer dan twee kolommen."
"stats": {
"angle": "",
"element": "",
"elements": "",
"height": "",
"scene": "",
"selected": "",
"storage": "",
"title": "",
"total": "",
"width": ""
}
}
+20 -3
View File
@@ -4,6 +4,7 @@
"selectAll": "Vel alt",
"multiSelect": "Legg til element i utval",
"moveCanvas": "Flytt lerretet",
"cut": "",
"copy": "Kopier",
"copyAsPng": "Kopier til utklippstavla som PNG",
"copyAsSvg": "Kopier til utklippstavla som SVG",
@@ -28,6 +29,11 @@
"edges": "Kanter",
"sharp": "Skarp",
"round": "Rund",
"arrowheads": "",
"arrowhead_none": "",
"arrowhead_arrow": "",
"arrowhead_bar": "",
"arrowhead_dot": "",
"fontSize": "Skriftstorleik",
"fontFamily": "Skrifttype",
"onlySelected": "Kun valde",
@@ -71,9 +77,11 @@
"ungroup": "Avgrupper utval",
"collaborators": "Samarbeidarar",
"toggleGridMode": "Sla på/av rutenett",
"toggleStats": "",
"addToLibrary": "Legg til i bibliotek",
"removeFromLibrary": "Fjern frå bibliotek",
"libraryLoadingMessage": "Laster bibliotek...",
"libraries": "",
"loadingScene": "Laster scene...",
"align": "",
"alignTop": "",
@@ -205,13 +213,22 @@
"textNewLine": "Legg til ny linje (tekst)",
"textFinish": "Fullfør redigering (tekst)",
"zoomToFit": "Zoom for å sjå alle elementa",
"zoomToSelection": "",
"preventBinding": "Hindre pilkobling"
},
"encrypted": {
"tooltip": "Teikningane dine er ende-til-ende-krypterte slik at Excalidraw sine serverar aldri får sjå dei."
},
"charts": {
"noNumericColumn": "Du limte inn eit rekneark utan ei numerisk kolonne.",
"tooManyColumns": "Du limte inn eit rekneark med meir enn to kolonnar."
"stats": {
"angle": "",
"element": "",
"elements": "",
"height": "",
"scene": "",
"selected": "",
"storage": "",
"title": "",
"total": "",
"width": ""
}
}
+23 -23
View File
@@ -1,33 +1,33 @@
{
"ar-SA": 98,
"bg-BG": 67,
"ca-ES": 89,
"de-DE": 100,
"el-GR": 96,
"ar-SA": 89,
"bg-BG": 61,
"ca-ES": 81,
"de-DE": 99,
"el-GR": 95,
"en": 100,
"es-ES": 89,
"fa-IR": 98,
"es-ES": 81,
"fa-IR": 89,
"fi-FI": 100,
"fr-FR": 100,
"he-IL": 76,
"hi-IN": 85,
"hu-HU": 48,
"id-ID": 100,
"he-IL": 69,
"hi-IN": 82,
"hu-HU": 44,
"id-ID": 99,
"it-IT": 100,
"ja-JP": 98,
"ko-KR": 75,
"my-MM": 97,
"ja-JP": 89,
"ko-KR": 68,
"my-MM": 96,
"nb-NO": 100,
"nl-NL": 88,
"nn-NO": 89,
"pl-PL": 88,
"pt-PT": 92,
"nl-NL": 80,
"nn-NO": 80,
"pl-PL": 79,
"pt-PT": 83,
"ro-RO": 100,
"ru-RU": 85,
"ru-RU": 81,
"sk-SK": 100,
"sv-SE": 100,
"tr-TR": 90,
"uk-UA": 100,
"zh-CN": 100,
"zh-TW": 100
"tr-TR": 81,
"uk-UA": 98,
"zh-CN": 95,
"zh-TW": 99
}
+20 -3
View File
@@ -4,6 +4,7 @@
"selectAll": "Zaznacz wszystko",
"multiSelect": "Dodaj element do zaznaczenia",
"moveCanvas": "Przesuń obszar roboczy",
"cut": "",
"copy": "Kopiuj",
"copyAsPng": "Skopiuj do schowka jako plik PNG",
"copyAsSvg": "Skopiuj do schowka jako plik SVG",
@@ -28,6 +29,11 @@
"edges": "Krawędzie",
"sharp": "Ostry",
"round": "Zaokrąglij",
"arrowheads": "",
"arrowhead_none": "",
"arrowhead_arrow": "",
"arrowhead_bar": "",
"arrowhead_dot": "",
"fontSize": "Rozmiar tekstu",
"fontFamily": "Krój pisma",
"onlySelected": "Tylko wybrane",
@@ -71,9 +77,11 @@
"ungroup": "Rozgrupuj wybrane",
"collaborators": "Współtwórcy",
"toggleGridMode": "Włącz siatkę",
"toggleStats": "",
"addToLibrary": "Dodaj do biblioteki",
"removeFromLibrary": "Usuń z biblioteki",
"libraryLoadingMessage": "Wczytywanie biblioteki...",
"libraries": "",
"loadingScene": "Ładowanie sceny...",
"align": "",
"alignTop": "",
@@ -205,13 +213,22 @@
"textNewLine": "Dodaj nową linię (tekst)",
"textFinish": "Zakończ edycję (tekst)",
"zoomToFit": "Powiększ, aby wyświetlić wszystkie elementy",
"zoomToSelection": "",
"preventBinding": "Zablokuj przywiązanie strzałek do obiektu"
},
"encrypted": {
"tooltip": "Twoje rysunki są zabezpieczone szyfrowaniem end-to-end, tak więc nawet w Excalidraw nie jesteśmy w stanie zobaczyć tego co tworzysz."
},
"charts": {
"noNumericColumn": "Wklejono arkusz kalkulacyjny bez kolumny numerycznej.",
"tooManyColumns": "Wklejono arkusz kalkulacyjny z więcej niż dwoma kolumnami."
"stats": {
"angle": "",
"element": "",
"elements": "",
"height": "",
"scene": "",
"selected": "",
"storage": "",
"title": "",
"total": "",
"width": ""
}
}
+20 -3
View File
@@ -4,6 +4,7 @@
"selectAll": "Selecionar tudo",
"multiSelect": "Adicionar elemento à seleção",
"moveCanvas": "Mover tela",
"cut": "",
"copy": "Copiar",
"copyAsPng": "Copiar para a área de transferência como PNG",
"copyAsSvg": "Copiar para a área de transferência como SVG",
@@ -28,6 +29,11 @@
"edges": "Arestas",
"sharp": "Aguçado",
"round": "Redondo",
"arrowheads": "",
"arrowhead_none": "",
"arrowhead_arrow": "",
"arrowhead_bar": "",
"arrowhead_dot": "",
"fontSize": "Tamanho da fonte",
"fontFamily": "Família da fontes",
"onlySelected": "Somente a seleção",
@@ -71,9 +77,11 @@
"ungroup": "Desagrupar seleção",
"collaborators": "Colaboradores",
"toggleGridMode": "Alternar modo de grade",
"toggleStats": "",
"addToLibrary": "Adicionar à biblioteca",
"removeFromLibrary": "Remover da biblioteca",
"libraryLoadingMessage": "Carregando biblioteca...",
"libraries": "",
"loadingScene": "Carregando cena...",
"align": "",
"alignTop": "",
@@ -205,13 +213,22 @@
"textNewLine": "Adicionar nova linha (texto)",
"textFinish": "Finalizar edição (texto)",
"zoomToFit": "Ajustar para caber todos os elementos",
"zoomToSelection": "",
"preventBinding": "Prevenir fixação de seta"
},
"encrypted": {
"tooltip": "Seus desenhos são criptografados de ponta a ponta, então os servidores do Excalidraw nunca os verão."
},
"charts": {
"noNumericColumn": "Você colou uma planilha sem uma coluna numérica.",
"tooManyColumns": "Você colou uma planilha com mais de duas colunas."
"stats": {
"angle": "",
"element": "",
"elements": "",
"height": "",
"scene": "",
"selected": "",
"storage": "",
"title": "",
"total": "",
"width": ""
}
}
+20 -3
View File
@@ -4,6 +4,7 @@
"selectAll": "Selectare totală",
"multiSelect": "Adaugă element la selecție",
"moveCanvas": "Mutare pânză",
"cut": "Decupare",
"copy": "Copiere",
"copyAsPng": "Copiere în memoria temporară ca PNG",
"copyAsSvg": "Copiere în memoria temporară ca SVG",
@@ -28,6 +29,11 @@
"edges": "Margini",
"sharp": "Ascuțite",
"round": "Rotunde",
"arrowheads": "Vârfuri de săgeată",
"arrowhead_none": "Niciunul",
"arrowhead_arrow": "Săgeată",
"arrowhead_bar": "Bară",
"arrowhead_dot": "Bulină",
"fontSize": "Dimensiune font",
"fontFamily": "Familia de fonturi",
"onlySelected": "Numai selecția",
@@ -71,9 +77,11 @@
"ungroup": "Degrupare selecție",
"collaborators": "Colaboratori",
"toggleGridMode": "Comută modul grilă",
"toggleStats": "Comută statisticile pentru pasionați",
"addToLibrary": "Adăugare la bibliotecă",
"removeFromLibrary": "Eliminare din bibliotecă",
"libraryLoadingMessage": "Se încarcă biblioteca...",
"libraries": "Răsfoiește bibliotecile",
"loadingScene": "Se încarcă scena...",
"align": "Aliniere",
"alignTop": "Aliniere sus",
@@ -205,13 +213,22 @@
"textNewLine": "Adaugă o linie nouă (text)",
"textFinish": "Finalizează editarea (text)",
"zoomToFit": "Apropiere/depărtare pentru a cuprinde totul",
"zoomToSelection": "Panoramare la selecție",
"preventBinding": "Împiedică legarea săgeții"
},
"encrypted": {
"tooltip": "Desenele tale sunt criptate integral, astfel că serverele Excalidraw nu le vor vedea niciodată."
},
"charts": {
"noNumericColumn": "Ai inserat o foaie de calcul fără o coloană numerică.",
"tooManyColumns": "Ai inserat o foaie de calcul cu mai mult de două coloane."
"stats": {
"angle": "Unghi",
"element": "Element",
"elements": "Elemente",
"height": "Înălțime",
"scene": "Scenă",
"selected": "Selectate",
"storage": "Stocare",
"title": "Statistici pentru pasionați",
"total": "Total",
"width": "Lățime"
}
}
+20 -3
View File
@@ -4,6 +4,7 @@
"selectAll": "Выбрать все",
"multiSelect": "Добавить элемент к выбору",
"moveCanvas": "Переместить холст",
"cut": "",
"copy": "Копировать",
"copyAsPng": "Скопировать в буфер обмена как PNG",
"copyAsSvg": "Скопировать в буфер обмена как SVG",
@@ -28,6 +29,11 @@
"edges": "Края",
"sharp": "Острые",
"round": "Скругленные",
"arrowheads": "",
"arrowhead_none": "",
"arrowhead_arrow": "Cтрелка",
"arrowhead_bar": "",
"arrowhead_dot": "Точка",
"fontSize": "Размер шрифта",
"fontFamily": "Семейство шрифтов",
"onlySelected": "Только выбранные",
@@ -71,9 +77,11 @@
"ungroup": "Разделить выделение",
"collaborators": "Сотрудники",
"toggleGridMode": "Переключить режим сетки",
"toggleStats": "",
"addToLibrary": "Добавить в библиотеку",
"removeFromLibrary": "Удалить из библиотеки",
"libraryLoadingMessage": "Загрузка библиотеки...",
"libraries": "",
"loadingScene": "Загрузка сцены...",
"align": "",
"alignTop": "",
@@ -205,13 +213,22 @@
"textNewLine": "Добавить новую строку (текст)",
"textFinish": "Закончить редактирование (текст)",
"zoomToFit": "",
"zoomToSelection": "",
"preventBinding": "Предотвратить привязку стрелок"
},
"encrypted": {
"tooltip": ""
},
"charts": {
"noNumericColumn": "",
"tooManyColumns": "Вы вставили таблицу с более чем двумя столбцами."
"stats": {
"angle": "Угол",
"element": "Элемент",
"elements": "Элементы",
"height": "Высота",
"scene": "",
"selected": "",
"storage": "",
"title": "",
"total": "",
"width": "Ширина"
}
}
+20 -3
View File
@@ -4,6 +4,7 @@
"selectAll": "Vybrať všetko",
"multiSelect": "Pridať prvok do výberu",
"moveCanvas": "Pohyb plátna",
"cut": "Vystrihnúť",
"copy": "Kopírovať",
"copyAsPng": "Kopírovať do schránky ako PNG",
"copyAsSvg": "Kopírovať do schránky ako SVG",
@@ -28,6 +29,11 @@
"edges": "Okraje",
"sharp": "Ostré",
"round": "Zaokrúhlené",
"arrowheads": "Zakončenie šípky",
"arrowhead_none": "Žiadne",
"arrowhead_arrow": "Šípka",
"arrowhead_bar": "Čiara",
"arrowhead_dot": "Bod",
"fontSize": "Veľkosť písma",
"fontFamily": "Písmo",
"onlySelected": "Iba vybrané",
@@ -71,9 +77,11 @@
"ungroup": "Zrušiť zoskupenie",
"collaborators": "Spolupracovníci",
"toggleGridMode": "Prepnúť mriežku",
"toggleStats": "Prepnúť štatistiky",
"addToLibrary": "Pridať do knižnice",
"removeFromLibrary": "Odstrániť z knižnice",
"libraryLoadingMessage": "Načítavanie knižnice...",
"libraries": "Prehliadať knižnice",
"loadingScene": "Načítavanie scény...",
"align": "Zarovnanie",
"alignTop": "Zarovnať nahor",
@@ -205,13 +213,22 @@
"textNewLine": "Vložiť nový riadok (text)",
"textFinish": "Ukončenie editovania (text)",
"zoomToFit": "Priblížiť aby boli zahrnuté všetky prvky",
"zoomToSelection": "Priblížiť na výber",
"preventBinding": "Zakázať pripájanie šípky"
},
"encrypted": {
"tooltip": "Vaše kresby používajú end-to-end šifrovanie, takže ich Excalidraw server nedokáže prečítať."
},
"charts": {
"noNumericColumn": "Prilepili ste tabuľku bez číselného stĺpca.",
"tooManyColumns": "Prilepili ste tabuľku s viac ako dvoma stĺpcami."
"stats": {
"angle": "Uhol",
"element": "Prvok",
"elements": "Prvky",
"height": "Výška",
"scene": "Scéna",
"selected": "Vybrané",
"storage": "Úložisko",
"title": "Štatistiky",
"total": "Celkom",
"width": "Šírka"
}
}
+20 -3
View File
@@ -4,6 +4,7 @@
"selectAll": "Markera alla",
"multiSelect": "Lägg till element till markering",
"moveCanvas": "Flytta canvas",
"cut": "Klipp ut",
"copy": "Kopiera",
"copyAsPng": "Kopiera till urklipp som PNG",
"copyAsSvg": "Kopiera till urklipp som SVG",
@@ -28,6 +29,11 @@
"edges": "Kanter",
"sharp": "Skarp",
"round": "Rund",
"arrowheads": "Pilhuvuden",
"arrowhead_none": "Inga",
"arrowhead_arrow": "Pil",
"arrowhead_bar": "Stolpe",
"arrowhead_dot": "Punkt",
"fontSize": "Teckenstorlek",
"fontFamily": "Teckensnitt",
"onlySelected": "Endast markering",
@@ -71,9 +77,11 @@
"ungroup": "Avgruppera markering",
"collaborators": "Medarbetare",
"toggleGridMode": "Växla rutnätsläge",
"toggleStats": "Visa/dölj statistik för nördar",
"addToLibrary": "Lägg till i biblioteket",
"removeFromLibrary": "Ta bort från bibliotek",
"libraryLoadingMessage": "Laddar bibliotek...",
"libraries": "Bläddra i bibliotek",
"loadingScene": "Laddar scen...",
"align": "Justera",
"alignTop": "Justera överkant",
@@ -205,13 +213,22 @@
"textNewLine": "Lägg till ny rad (text)",
"textFinish": "Slutför redigering (text)",
"zoomToFit": "Zooma för att rymma alla element",
"zoomToSelection": "Zooma till markering",
"preventBinding": "Förhindra pilbindning"
},
"encrypted": {
"tooltip": "Dina skisser är krypterade från ände till ände så Excalidraws servrar kommer aldrig att se dem."
},
"charts": {
"noNumericColumn": "Du klistrade in ett kalkylblad utan en numerisk kolumn.",
"tooManyColumns": "Du klistrade in ett kalkylblad med mer än två kolumner."
"stats": {
"angle": "Vinkel",
"element": "Element",
"elements": "Element",
"height": "Höjd",
"scene": "Skiss",
"selected": "Valda",
"storage": "Lagring",
"title": "Statistik för nördar",
"total": "Totalt",
"width": "Bredd"
}
}
+20 -3
View File
@@ -4,6 +4,7 @@
"selectAll": "Tümünü seç",
"multiSelect": "Seçime öge ekle",
"moveCanvas": "Tuvali taşı",
"cut": "",
"copy": "Kopyala",
"copyAsPng": "Panoya PNG olarak kopyala",
"copyAsSvg": "Panoya SVG olarak kopyala",
@@ -28,6 +29,11 @@
"edges": "Kenarlar",
"sharp": "Keskin",
"round": "Yuvarlak",
"arrowheads": "",
"arrowhead_none": "",
"arrowhead_arrow": "",
"arrowhead_bar": "",
"arrowhead_dot": "",
"fontSize": "Yazı tipi boyutu",
"fontFamily": "Yazı tipi ailesi",
"onlySelected": "Sadece seçilen",
@@ -71,9 +77,11 @@
"ungroup": "Seçilen grubu dağıt",
"collaborators": "Ortaklar",
"toggleGridMode": "Izgara modunu aç",
"toggleStats": "",
"addToLibrary": "Kütüphaneye ekle",
"removeFromLibrary": "Kütüphaneden kaldır",
"libraryLoadingMessage": "Kütüphane yükleniyor...",
"libraries": "",
"loadingScene": "Çalışma alanı yükleniyor...",
"align": "",
"alignTop": "",
@@ -205,13 +213,22 @@
"textNewLine": "Yeni satır ekle (yazı)",
"textFinish": "(Yazıyı) düzenlemeyi bitir",
"zoomToFit": "Tüm öğeleri sığdırmak için yakınlaştır",
"zoomToSelection": "",
"preventBinding": "Ok bağlamayı önleyin"
},
"encrypted": {
"tooltip": "Çizimleriniz uçtan-uca şifrelenmiştir, Excalidraw'ın sunucuları bile onları göremez."
},
"charts": {
"noNumericColumn": "Sayısal sütunu olmayan bir tablo yapıştırdın.",
"tooManyColumns": "İkiden daha fazla sütuna sahip bir tablo yapıştırdın."
"stats": {
"angle": "",
"element": "",
"elements": "",
"height": "",
"scene": "",
"selected": "",
"storage": "",
"title": "",
"total": "",
"width": ""
}
}
+20 -3
View File
@@ -4,6 +4,7 @@
"selectAll": "Вибрати все",
"multiSelect": "Додати елемент до вибраного",
"moveCanvas": "Перемістити полотно",
"cut": "Вирізати",
"copy": "Копіювати",
"copyAsPng": "Копіювати як PNG",
"copyAsSvg": "Копіювати як SVG",
@@ -28,6 +29,11 @@
"edges": "Краї",
"sharp": "Гострі",
"round": "Круглі",
"arrowheads": "",
"arrowhead_none": "Жоден",
"arrowhead_arrow": "Стрілка",
"arrowhead_bar": "Колона",
"arrowhead_dot": "Точка",
"fontSize": "Розмір шрифту",
"fontFamily": "Шрифт",
"onlySelected": "Тільки вибране",
@@ -71,9 +77,11 @@
"ungroup": "Розгрупувати виділене",
"collaborators": "Співавтори",
"toggleGridMode": "Режим сітки",
"toggleStats": "",
"addToLibrary": "Додати до бібліотеки",
"removeFromLibrary": "Видалити з бібліотеки",
"libraryLoadingMessage": "Завантажити бібліотеку...",
"libraries": "Огляд бібліотек",
"loadingScene": "Завантаження сцени...",
"align": "Вирівнювання",
"alignTop": "Вирівняти по верхньому краю",
@@ -205,13 +213,22 @@
"textNewLine": "Додати новий рядок (текст)",
"textFinish": "Завершити редагування (текст)",
"zoomToFit": "Збільшити щоб умістити все",
"zoomToSelection": "Перейти до виділеного",
"preventBinding": "Запобігти зв'язування зі стрілками"
},
"encrypted": {
"tooltip": "Ваші креслення захищені наскрізним шифруванням — сервери Excalidraw ніколи їх не побачать."
},
"charts": {
"noNumericColumn": "Ви вставили таблицю без числової колонки.",
"tooManyColumns": "Ви вставляли таблицю з більш ніж двома колонками."
"stats": {
"angle": "Кут",
"element": "Елемент",
"elements": "Елементи",
"height": "Висота",
"scene": "Сцена",
"selected": "Вибраний",
"storage": "Сховище",
"title": "",
"total": "Всього",
"width": "Ширина"
}
}
+20 -3
View File
@@ -4,6 +4,7 @@
"selectAll": "全部选中",
"multiSelect": "添加元素到选区",
"moveCanvas": "移动画布",
"cut": "",
"copy": "复制",
"copyAsPng": "复制为 PNG 到剪贴板",
"copyAsSvg": "复制为 SVG 到剪贴板",
@@ -28,6 +29,11 @@
"edges": "边角",
"sharp": "尖锐",
"round": "圆润",
"arrowheads": "",
"arrowhead_none": "",
"arrowhead_arrow": "",
"arrowhead_bar": "",
"arrowhead_dot": "",
"fontSize": "字体大小",
"fontFamily": "字体",
"onlySelected": "仅被选中",
@@ -71,9 +77,11 @@
"ungroup": "取消组选",
"collaborators": "协作者",
"toggleGridMode": "切换网格模式",
"toggleStats": "",
"addToLibrary": "添加到库中",
"removeFromLibrary": "从库中移除",
"libraryLoadingMessage": "正在加载库...",
"libraries": "",
"loadingScene": "正在加载绘图...",
"align": "对齐",
"alignTop": "顶部对齐",
@@ -205,13 +213,22 @@
"textNewLine": "文本换行",
"textFinish": "完成编辑文本",
"zoomToFit": "缩放以适应所有元素",
"zoomToSelection": "缩放至选择部分",
"preventBinding": "防止箭头吸附"
},
"encrypted": {
"tooltip": "您的绘图采用的端到端加密,因此Excalidraw服务器永远不会收集。"
},
"charts": {
"noNumericColumn": "您粘贴了一个没有数字列的表格。",
"tooManyColumns": "您粘贴了两列以上的表格。"
"stats": {
"angle": "角度",
"element": "元素",
"elements": "元素",
"height": "高度",
"scene": "场景",
"selected": "选中",
"storage": "存储",
"title": "",
"total": "总计",
"width": "宽度"
}
}
+20 -3
View File
@@ -4,6 +4,7 @@
"selectAll": "全選",
"multiSelect": "將物件加入選取範圍",
"moveCanvas": "移動畫布",
"cut": "剪下",
"copy": "複製",
"copyAsPng": "複製 PNG 至剪貼簿",
"copyAsSvg": "複製 SVG 至剪貼簿",
@@ -28,6 +29,11 @@
"edges": "邊緣",
"sharp": "尖銳",
"round": "平滑",
"arrowheads": "箭頭",
"arrowhead_none": "無",
"arrowhead_arrow": "箭頭",
"arrowhead_bar": "塊",
"arrowhead_dot": "點",
"fontSize": "字型大小",
"fontFamily": "字體",
"onlySelected": "僅選取物件",
@@ -71,9 +77,11 @@
"ungroup": "取消群組",
"collaborators": "協作者",
"toggleGridMode": "切換格線模式",
"toggleStats": "切換詳細統計",
"addToLibrary": "加入資料庫",
"removeFromLibrary": "從資料庫中移除",
"libraryLoadingMessage": "資料庫讀取中…",
"libraries": "",
"loadingScene": "場景讀取中…",
"align": "對齊",
"alignTop": "對齊頂部",
@@ -205,13 +213,22 @@
"textNewLine": "換行(文字)",
"textFinish": "完成編輯(文字)",
"zoomToFit": "放大至填滿畫面",
"zoomToSelection": "縮放至選取區",
"preventBinding": "防止箭頭綁定"
},
"encrypted": {
"tooltip": "你的作品已使用 end-to-end 方式加密,Excalidraw 的伺服器也無法取得其內容。"
},
"charts": {
"noNumericColumn": "你貼上的 spreadsheet 沒有數字欄。",
"tooManyColumns": "你貼上的 spreadsheet 超過兩欄。"
"stats": {
"angle": "角度",
"element": "元素",
"elements": "元素",
"height": "高度",
"scene": "場景",
"selected": "已選",
"storage": "儲存",
"title": "詳細統計",
"total": "合計",
"width": "寬度"
}
}
+5 -5
View File
@@ -1,5 +1,5 @@
import { Point } from "./types";
import { LINE_CONFIRM_THRESHOLD } from "./constants";
import { GRID_SIZE, LINE_CONFIRM_THRESHOLD } from "./constants";
import { ExcalidrawLinearElement } from "./element/types";
export const rotate = (
@@ -307,12 +307,12 @@ const doSegmentsIntersect = (p1: Point, q1: Point, p2: Point, q2: Point) => {
export const getGridPoint = (
x: number,
y: number,
gridSize: number | null,
isGridOn: boolean,
): [number, number] => {
if (gridSize) {
if (isGridOn) {
return [
Math.round(x / gridSize) * gridSize,
Math.round(y / gridSize) * gridSize,
Math.round(x / GRID_SIZE) * GRID_SIZE,
Math.round(y / GRID_SIZE) * GRID_SIZE,
];
}
return [x, y];
-10
View File
@@ -1,10 +0,0 @@
# Changelog
## 0.1.0
First release of `@excalidraw/excalidraw`
## 0.1.1
#### Fix
- Update the homepage URL so it redirects to correct readme [#2498](https://github.com/excalidraw/excalidraw/pull/2498)
+62
View File
@@ -0,0 +1,62 @@
# Changelog
<!--
Guidelines for changelog:
The change should be grouped under one of the below section and must contain PR link.
- Features: For new features.
- Fixes: For bug fixes.
- Chore: Changes for non src files example package.json.
- Improvements: For any improvements.
- Refactor: For any refactoring.
Please add the latest change on the top under the correct section.
-->
## [Unreleased]
### Features
- Add support for `exportToBackend` prop to allow host apps to implement shareable links [#2612](https://github.com/excalidraw/excalidraw/pull/2612/files)
- Add zoom to selection [#2522](https://github.com/excalidraw/excalidraw/pull/2522)
- Insert Library items in the middle of the screen [#2527](https://github.com/excalidraw/excalidraw/pull/2527)
- Show shortcut context menu [#2501](https://github.com/excalidraw/excalidraw/pull/2501)
- Aligns arrowhead schemas [#2517](https://github.com/excalidraw/excalidraw/pull/2517)
- Add Cut to menus [#2511](https://github.com/excalidraw/excalidraw/pull/2511)
- More Arrowheads: dot, bar [#2486](https://github.com/excalidraw/excalidraw/pull/2486)
- Support CSV graphs and improve the look and feel [#2495](https://github.com/excalidraw/excalidraw/pull/2495)
### Fixes
- Consistent case for export locale strings [#2622](https://github.com/excalidraw/excalidraw/pull/2622)
- Remove unnecessary console.error as it was polluting Sentry [#2637](https://github.com/excalidraw/excalidraw/pull/2637)
- Fix scroll-to-center on init for non-zero canvas offsets [#2445](https://github.com/excalidraw/excalidraw/pull/2445)
- Hide collab button when onCollabButtonClick not supplied [#2598](https://github.com/excalidraw/excalidraw/pull/2598)
- Fix resizing the pasted charts [#2586](https://github.com/excalidraw/excalidraw/pull/2586)
- Fix element visibility and zoom on cursor when canvas offset isn't 0. [#2534](https://github.com/excalidraw/excalidraw/pull/2534)
- Fix Library Menu Layout [#2502](https://github.com/excalidraw/excalidraw/pull/2502)
- Support number with commas in charts [#2636](https://github.com/excalidraw/excalidraw/pull/2636)
- Don't break zoom when zooming in on UI [#2638](https://github.com/excalidraw/excalidraw/pull/2638)
### Improvements
- Display proper tooltip for 2-point lines during resize, and normalize modifier key labels in hints [#2655](https://github.com/excalidraw/excalidraw/pull/2655)
- Improve error message around importing images [#2619](https://github.com/excalidraw/excalidraw/pull/2619)
- Add tooltip with icon for embedding scenes [#2532](https://github.com/excalidraw/excalidraw/pull/2532)
- RTL support for the stats dialog [#2530](https://github.com/excalidraw/excalidraw/pull/2530)
- Expand canvas padding based on zoom. [#2515](https://github.com/excalidraw/excalidraw/pull/2515)
- Hide shortcuts on pickers for mobile [#2508](https://github.com/excalidraw/excalidraw/pull/2508)
- Hide stats and scrollToContent-button when mobile menus open [#2509](https://github.com/excalidraw/excalidraw/pull/2509)
### Chore
- Bump ini from 1.3.5 to 1.3.7 in /src/packages/excalidraw [#2500](https://github.com/excalidraw/excalidraw/pull/2500)
## 0.1.1
#### Fix
- Update the homepage URL so it redirects to correct readme [#2498](https://github.com/excalidraw/excalidraw/pull/2498)
## 0.1.0
First release of `@excalidraw/excalidraw`
+33 -23
View File
@@ -39,26 +39,19 @@ import "./styles.css";
export default function App() {
const excalidrawRef = createRef();
const onChange = (elements, state) => {
console.log(excalidrawRef.current);
console.log("Elements :", elements, "State : ", state);
};
const [dimensions, setDimensions] = useState({
width: window.innerWidth,
height: window.innerHeight,
});
const onResize = () => {
setDimensions({
width: window.innerWidth,
height: window.innerHeight,
});
};
useEffect(() => {
const onResize = () => {
setDimensions({
width: window.innerWidth,
height: window.innerHeight,
});
};
window.addEventListener("resize", onResize);
return () => window.removeEventListener("resize", onResize);
}, []);
@@ -94,7 +87,6 @@ export default function App() {
excalidrawRef.current.updateScene(sceneData);
};
const { width, height } = dimensions;
return (
<div className="App">
<button className="update-scene" onClick={updateScene}>
@@ -111,12 +103,17 @@ export default function App() {
<div className="excalidraw-wrapper">
<Excalidraw
ref={excalidrawRef}
width={width}
height={height}
width={dimensions.width}
height={dimensions.height}
initialData={InitialData}
onChange={onChange}
onChange={(elements, state) => {
console.log("Latest elements:", elements, "Latest state:", state);
}}
user={{ name: "Excalidraw User" }}
onPointerUpdate={(payload) => console.log(payload)}
onPointerUpdate={(pointerData) => console.log(pointerData)}
onCollabButtonClick={() => {
window.alert("You clicked on collab button");
}}
/>
</div>
</div>
@@ -141,6 +138,7 @@ export default function App() {
| [`onCollabButtonClick`](#onCollabButtonClick) | Function | | Callback to be triggered when the collab button is clicked |
| [`isCollaborating`](#isCollaborating) | `boolean` | | This implies if the app is in collaboration mode |
| [`onPointerUpdate`](#onPointerUpdate) | Function | | Callback triggered when mouse pointer is updated. |
| [`onExportToBackend`](#onExportToBackend) | Function | | Callback triggered when link button is clicked on export dialog |
#### `width`
@@ -219,8 +217,8 @@ This is the user name which shows during collaboration. Defaults to `{name: ''}`
#### `excalidrawRef`
You can pass a ref when you want to access some excalidraw API's.
We expose the below API's
You can pass a `ref` when you want to access some excalidraw APIs.
We expose the below APIs:
| API | signature | Usage |
| -------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
@@ -241,15 +239,15 @@ const excalidrawRef = { current: { readyPromise: <a href="https://github.com/exc
#### `onCollabButtonClick`
This callback is triggered when clicked on the collab button in excalidraw
This callback is triggered when clicked on the collab button in excalidraw. If not supplied, the collab dialog button is not rendered.
#### `isCollaborating`
This props implies if the app is in collaboration mode
This prop indicates if the app is in collaboration mode.
#### `onPointerUpdate`
This callback is triggered when mouse pointer is updated
This callback is triggered when mouse pointer is updated.
```js
({ x, y }, button, pointersMap}) => void;
@@ -260,3 +258,15 @@ This callback is triggered when mouse pointer is updated
2.`button`: The position of the button. This will be one of `["down", "up"]`
3.`pointersMap`: [`pointers map`](https://github.com/excalidraw/excalidraw/blob/182a3e39e1362d73d2a565c870eb2fb72071fdcc/src/types.ts#L122) of the scene
#### `onExportToBackend`
This callback is triggered when the shareable-link button is clicked in the export dialog. The link button will only be shown if this callback is passed.
```js
(exportedElements, appState, canvas) => void
```
1. `exportedElements`: An array of [non deleted elements](https://github.com/excalidraw/excalidraw/blob/6e45cb95dbd7a8be1859c7055b06957298e3097c/src/element/types.ts#L76) which needs to be exported.
2. `appState`: [AppState](https://github.com/excalidraw/excalidraw/blob/4c90ea5667d29effe8ec4a115e49efc7c340cdb3/src/types.ts#L33) of the scene.
3. `canvas`: The `HTMLCanvasElement` of the scene.
+3 -2
View File
@@ -8,7 +8,6 @@ import "../../css/styles.scss";
import { ExcalidrawAPIRefValue, ExcalidrawProps } from "../../types";
import { IsMobileProvider } from "../../is-mobile";
import { noop } from "../../utils";
const Excalidraw = (props: ExcalidrawProps) => {
const {
@@ -20,9 +19,10 @@ const Excalidraw = (props: ExcalidrawProps) => {
initialData,
user,
excalidrawRef,
onCollabButtonClick = noop,
onCollabButtonClick,
isCollaborating,
onPointerUpdate,
onExportToBackend,
} = props;
useEffect(() => {
@@ -58,6 +58,7 @@ const Excalidraw = (props: ExcalidrawProps) => {
onCollabButtonClick={onCollabButtonClick}
isCollaborating={isCollaborating}
onPointerUpdate={onPointerUpdate}
onExportToBackend={onExportToBackend}
/>
</IsMobileProvider>
</InitializeApp>
+1414 -4809
View File
File diff suppressed because it is too large Load Diff
+21 -21
View File
@@ -37,30 +37,30 @@
]
},
"peerDependencies": {
"react": "^16.13.1",
"react-dom": "^16.13.1"
"react": "^17.0.1",
"react-dom": "^17.0.1"
},
"devDependencies": {
"@babel/core": "7.9.0",
"@babel/plugin-transform-arrow-functions": "7.8.3",
"@babel/plugin-transform-async-to-generator": "7.8.3",
"@babel/plugin-transform-runtime": "7.12.1",
"@babel/plugin-transform-typescript": "7.9.4",
"@babel/preset-env": "7.9.5",
"@babel/preset-react": "7.9.4",
"@babel/preset-typescript": "7.9.0",
"babel-loader": "8.1.0",
"@babel/core": "7.12.10",
"@babel/plugin-transform-arrow-functions": "7.12.1",
"@babel/plugin-transform-async-to-generator": "7.12.1",
"@babel/plugin-transform-runtime": "7.12.10",
"@babel/plugin-transform-typescript": "7.12.1",
"@babel/preset-env": "7.12.11",
"@babel/preset-react": "7.12.10",
"@babel/preset-typescript": "7.12.7",
"babel-loader": "8.2.2",
"babel-plugin-transform-class-properties": "6.24.1",
"cross-env": "7.0.2",
"css-loader": "3.5.2",
"file-loader": "6.0.0",
"mini-css-extract-plugin": "0.8.0",
"sass-loader": "8.0.2",
"terser-webpack-plugin": "2.3.5",
"ts-loader": "7.0.0",
"webpack": "4.42.0",
"webpack-bundle-analyzer": "3.9.0",
"webpack-cli": "3.3.11"
"cross-env": "7.0.3",
"css-loader": "5.0.1",
"file-loader": "6.2.0",
"mini-css-extract-plugin": "1.3.3",
"sass-loader": "10.1.0",
"terser-webpack-plugin": "5.0.3",
"ts-loader": "8.0.12",
"webpack": "5.11.0",
"webpack-bundle-analyzer": "4.3.0",
"webpack-cli": "4.2.0"
},
"bugs": "https://github.com/excalidraw/excalidraw/issues",
"repository": "https://github.com/excalidraw/excalidraw",
+1 -1
View File
@@ -16,7 +16,7 @@ type ExportOpts = {
) => { width: number; height: number; scale: number };
};
const exportToCanvas = ({
export const exportToCanvas = ({
elements,
appState = getDefaultAppState(),
getDimensions = (width, height) => ({ width, height, scale: 1 }),
-1
View File
@@ -75,7 +75,6 @@ const excalidrawDiagram = {
],
appState: {
viewBackgroundColor: "#ffffff",
gridSize: null,
},
};
+1 -1
View File
@@ -1 +1 @@
export { exportToBlob, exportToSvg } from "../utils.ts";
export { exportToBlob, exportToSvg, exportToCanvas } from "../utils.ts";
+968 -4264
View File
File diff suppressed because it is too large Load Diff
+14 -14
View File
@@ -34,21 +34,21 @@
]
},
"devDependencies": {
"@babel/core": "7.9.0",
"@babel/plugin-transform-arrow-functions": "7.8.3",
"@babel/plugin-transform-async-to-generator": "7.8.3",
"@babel/plugin-transform-runtime": "^7.12.1",
"@babel/plugin-transform-typescript": "7.9.4",
"@babel/preset-env": "7.9.5",
"@babel/preset-typescript": "7.9.0",
"babel-loader": "8.1.0",
"@babel/core": "7.12.10",
"@babel/plugin-transform-arrow-functions": "7.12.1",
"@babel/plugin-transform-async-to-generator": "7.12.1",
"@babel/plugin-transform-runtime": "^7.12.10",
"@babel/plugin-transform-typescript": "7.12.1",
"@babel/preset-env": "7.12.11",
"@babel/preset-typescript": "7.12.7",
"babel-loader": "8.2.2",
"babel-plugin-transform-class-properties": "6.24.1",
"cross-env": "7.0.2",
"file-loader": "6.0.0",
"ts-loader": "7.0.0",
"webpack": "4.42.0",
"webpack-bundle-analyzer": "3.9.0",
"webpack-cli": "3.3.11"
"cross-env": "7.0.3",
"file-loader": "6.2.0",
"ts-loader": "8.0.12",
"webpack": "5.11.0",
"webpack-bundle-analyzer": "4.3.0",
"webpack-cli": "4.2.0"
},
"bugs": "https://github.com/excalidraw/excalidraw/issues",
"repository": "https://github.com/excalidraw/excalidraw",
+23 -17
View File
@@ -48,6 +48,7 @@ import {
TransformHandleType,
} from "../element/transformHandles";
import { viewportCoordsToSceneCoords } from "../utils";
import { GRID_SIZE } from "../constants";
const strokeRectWithRotation = (
context: CanvasRenderingContext2D,
@@ -118,7 +119,6 @@ const fillCircle = (
const strokeGrid = (
context: CanvasRenderingContext2D,
gridSize: number,
offsetX: number,
offsetY: number,
width: number,
@@ -127,13 +127,13 @@ const strokeGrid = (
const origStrokeStyle = context.strokeStyle;
context.strokeStyle = "rgba(0,0,0,0.1)";
context.beginPath();
for (let x = offsetX; x < offsetX + width + gridSize * 2; x += gridSize) {
context.moveTo(x, offsetY - gridSize);
context.lineTo(x, offsetY + height + gridSize * 2);
for (let x = offsetX; x < offsetX + width + GRID_SIZE * 2; x += GRID_SIZE) {
context.moveTo(x, offsetY - GRID_SIZE);
context.lineTo(x, offsetY + height + GRID_SIZE * 2);
}
for (let y = offsetY; y < offsetY + height + gridSize * 2; y += gridSize) {
context.moveTo(offsetX - gridSize, y);
context.lineTo(offsetX + width + gridSize * 2, y);
for (let y = offsetY; y < offsetY + height + GRID_SIZE * 2; y += GRID_SIZE) {
context.moveTo(offsetX - GRID_SIZE, y);
context.lineTo(offsetX + width + GRID_SIZE * 2, y);
}
context.stroke();
context.strokeStyle = origStrokeStyle;
@@ -233,16 +233,15 @@ export const renderScene = (
context.scale(sceneState.zoom.value, sceneState.zoom.value);
// Grid
if (renderGrid && appState.gridSize) {
if (renderGrid && appState.showGrid) {
strokeGrid(
context,
appState.gridSize,
-Math.ceil(zoomTranslationX / sceneState.zoom.value / appState.gridSize) *
appState.gridSize +
(sceneState.scrollX % appState.gridSize),
-Math.ceil(zoomTranslationY / sceneState.zoom.value / appState.gridSize) *
appState.gridSize +
(sceneState.scrollY % appState.gridSize),
-Math.ceil(zoomTranslationX / sceneState.zoom.value / GRID_SIZE) *
GRID_SIZE +
(sceneState.scrollX % GRID_SIZE),
-Math.ceil(zoomTranslationY / sceneState.zoom.value / GRID_SIZE) *
GRID_SIZE +
(sceneState.scrollY % GRID_SIZE),
normalizedCanvasWidth / sceneState.zoom.value,
normalizedCanvasHeight / sceneState.zoom.value,
);
@@ -763,13 +762,20 @@ const isVisibleElement = (
) => {
const [x1, y1, x2, y2] = getElementBounds(element); // scene coordinates
const topLeftSceneCoords = viewportCoordsToSceneCoords(
{ clientX: 0, clientY: 0 },
{
clientX: viewTransformations.offsetLeft,
clientY: viewTransformations.offsetTop,
},
viewTransformations,
);
const bottomRightSceneCoords = viewportCoordsToSceneCoords(
{ clientX: canvasWidth, clientY: canvasHeight },
{
clientX: viewTransformations.offsetLeft + canvasWidth,
clientY: viewTransformations.offsetTop + canvasHeight,
},
viewTransformations,
);
return (
topLeftSceneCoords.x <= x2 &&
topLeftSceneCoords.y <= y2 &&
+5 -2
View File
@@ -3,6 +3,7 @@ import { NormalizedZoomValue, PointerCoords, Zoom } from "../types";
export const getNewZoom = (
newZoomValue: NormalizedZoomValue,
prevZoom: Zoom,
canvasOffset: { left: number; top: number },
zoomOnViewportPoint: PointerCoords = { x: 0, y: 0 },
): Zoom => {
return {
@@ -10,11 +11,13 @@ export const getNewZoom = (
translation: {
x:
zoomOnViewportPoint.x -
(zoomOnViewportPoint.x - prevZoom.translation.x) *
canvasOffset.left -
(zoomOnViewportPoint.x - canvasOffset.left - prevZoom.translation.x) *
(newZoomValue / prevZoom.value),
y:
zoomOnViewportPoint.y -
(zoomOnViewportPoint.y - prevZoom.translation.y) *
canvasOffset.top -
(zoomOnViewportPoint.y - canvasOffset.top - prevZoom.translation.y) *
(newZoomValue / prevZoom.value),
},
};
@@ -0,0 +1,20 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`tryParseSpreadsheet works for numbers with comma in them 1`] = `
Object {
"spreadsheet": Object {
"labels": Array [
"Week 1",
"Week 2",
"Week 3",
],
"title": "Users",
"values": Array [
814,
10301,
4264,
],
},
"type": "VALID_SPREADSHEET",
}
`;
@@ -31,7 +31,6 @@ Object {
"exportBackground": true,
"exportEmbedScene": false,
"fileHandle": null,
"gridSize": null,
"height": 768,
"isBindingEnabled": true,
"isLibraryOpen": false,
@@ -67,6 +66,7 @@ Object {
"selectionElement": null,
"shouldAddWatermark": false,
"shouldCacheIgnoreZoom": false,
"showGrid": false,
"showShortcutsDialog": false,
"showStats": false,
"startBoundElement": null,
@@ -486,7 +486,6 @@ Object {
"exportBackground": true,
"exportEmbedScene": false,
"fileHandle": null,
"gridSize": null,
"height": 768,
"isBindingEnabled": true,
"isLibraryOpen": false,
@@ -529,6 +528,7 @@ Object {
"selectionElement": null,
"shouldAddWatermark": false,
"shouldCacheIgnoreZoom": false,
"showGrid": false,
"showShortcutsDialog": false,
"showStats": false,
"startBoundElement": null,
@@ -947,7 +947,6 @@ Object {
"exportBackground": true,
"exportEmbedScene": false,
"fileHandle": null,
"gridSize": null,
"height": 768,
"isBindingEnabled": false,
"isLibraryOpen": false,
@@ -973,6 +972,7 @@ Object {
"selectionElement": null,
"shouldAddWatermark": false,
"shouldCacheIgnoreZoom": false,
"showGrid": false,
"showShortcutsDialog": false,
"showStats": false,
"startBoundElement": null,
@@ -1717,7 +1717,6 @@ Object {
"exportBackground": true,
"exportEmbedScene": false,
"fileHandle": null,
"gridSize": null,
"height": 768,
"isBindingEnabled": true,
"isLibraryOpen": false,
@@ -1745,6 +1744,7 @@ Object {
"selectionElement": null,
"shouldAddWatermark": false,
"shouldCacheIgnoreZoom": false,
"showGrid": false,
"showShortcutsDialog": false,
"showStats": false,
"startBoundElement": null,
@@ -1915,7 +1915,6 @@ Object {
"exportBackground": true,
"exportEmbedScene": false,
"fileHandle": null,
"gridSize": null,
"height": 768,
"isBindingEnabled": true,
"isLibraryOpen": false,
@@ -1948,6 +1947,7 @@ Object {
"selectionElement": null,
"shouldAddWatermark": false,
"shouldCacheIgnoreZoom": false,
"showGrid": false,
"showShortcutsDialog": false,
"showStats": false,
"startBoundElement": null,
@@ -2367,7 +2367,6 @@ Object {
"exportBackground": true,
"exportEmbedScene": false,
"fileHandle": null,
"gridSize": null,
"height": 768,
"isBindingEnabled": true,
"isLibraryOpen": false,
@@ -2395,6 +2394,7 @@ Object {
"selectionElement": null,
"shouldAddWatermark": false,
"shouldCacheIgnoreZoom": false,
"showGrid": false,
"showShortcutsDialog": false,
"showStats": false,
"startBoundElement": null,
@@ -2614,7 +2614,6 @@ Object {
"exportBackground": true,
"exportEmbedScene": false,
"fileHandle": null,
"gridSize": null,
"height": 768,
"isBindingEnabled": true,
"isLibraryOpen": false,
@@ -2639,6 +2638,7 @@ Object {
"selectionElement": null,
"shouldAddWatermark": false,
"shouldCacheIgnoreZoom": false,
"showGrid": false,
"showShortcutsDialog": false,
"showStats": false,
"startBoundElement": null,
@@ -2772,7 +2772,6 @@ Object {
"exportBackground": true,
"exportEmbedScene": false,
"fileHandle": null,
"gridSize": null,
"height": 768,
"isBindingEnabled": true,
"isLibraryOpen": false,
@@ -2800,6 +2799,7 @@ Object {
"selectionElement": null,
"shouldAddWatermark": false,
"shouldCacheIgnoreZoom": false,
"showGrid": false,
"showShortcutsDialog": false,
"showStats": false,
"startBoundElement": null,
@@ -3243,7 +3243,6 @@ Object {
"exportBackground": true,
"exportEmbedScene": false,
"fileHandle": null,
"gridSize": null,
"height": 768,
"isBindingEnabled": true,
"isLibraryOpen": false,
@@ -3268,6 +3267,7 @@ Object {
"selectionElement": null,
"shouldAddWatermark": false,
"shouldCacheIgnoreZoom": false,
"showGrid": false,
"showShortcutsDialog": false,
"showStats": false,
"startBoundElement": null,
@@ -3545,7 +3545,6 @@ Object {
"exportBackground": true,
"exportEmbedScene": false,
"fileHandle": null,
"gridSize": null,
"height": 768,
"isBindingEnabled": true,
"isLibraryOpen": false,
@@ -3573,6 +3572,7 @@ Object {
"selectionElement": null,
"shouldAddWatermark": false,
"shouldCacheIgnoreZoom": false,
"showGrid": false,
"showShortcutsDialog": false,
"showStats": false,
"startBoundElement": null,
@@ -3743,7 +3743,6 @@ Object {
"exportBackground": true,
"exportEmbedScene": false,
"fileHandle": null,
"gridSize": null,
"height": 768,
"isBindingEnabled": true,
"isLibraryOpen": false,
@@ -3773,6 +3772,7 @@ Object {
"selectionElement": null,
"shouldAddWatermark": false,
"shouldCacheIgnoreZoom": false,
"showGrid": false,
"showShortcutsDialog": false,
"showStats": false,
"startBoundElement": null,
@@ -3981,7 +3981,6 @@ Object {
"exportBackground": true,
"exportEmbedScene": false,
"fileHandle": null,
"gridSize": null,
"height": 768,
"isBindingEnabled": true,
"isLibraryOpen": false,
@@ -4009,6 +4008,7 @@ Object {
"selectionElement": null,
"shouldAddWatermark": false,
"shouldCacheIgnoreZoom": false,
"showGrid": false,
"showShortcutsDialog": false,
"showStats": false,
"startBoundElement": null,
@@ -4227,7 +4227,6 @@ Object {
"exportBackground": true,
"exportEmbedScene": false,
"fileHandle": null,
"gridSize": null,
"height": 768,
"isBindingEnabled": true,
"isLibraryOpen": false,
@@ -4256,6 +4255,7 @@ Object {
"selectionElement": null,
"shouldAddWatermark": false,
"shouldCacheIgnoreZoom": false,
"showGrid": false,
"showShortcutsDialog": false,
"showStats": false,
"startBoundElement": null,
@@ -4604,7 +4604,6 @@ Object {
"exportBackground": true,
"exportEmbedScene": false,
"fileHandle": null,
"gridSize": null,
"height": 768,
"isBindingEnabled": true,
"isLibraryOpen": false,
@@ -4653,6 +4652,7 @@ Object {
},
"shouldAddWatermark": false,
"shouldCacheIgnoreZoom": false,
"showGrid": false,
"showShortcutsDialog": false,
"showStats": false,
"startBoundElement": null,
@@ -4893,7 +4893,6 @@ Object {
"exportBackground": true,
"exportEmbedScene": false,
"fileHandle": null,
"gridSize": null,
"height": 768,
"isBindingEnabled": true,
"isLibraryOpen": false,
@@ -4920,6 +4919,7 @@ Object {
"selectionElement": null,
"shouldAddWatermark": false,
"shouldCacheIgnoreZoom": false,
"showGrid": false,
"showShortcutsDialog": false,
"showStats": false,
"startBoundElement": null,
@@ -5194,7 +5194,6 @@ Object {
"exportBackground": true,
"exportEmbedScene": false,
"fileHandle": null,
"gridSize": null,
"height": 768,
"isBindingEnabled": true,
"isLibraryOpen": false,
@@ -5241,6 +5240,7 @@ Object {
},
"shouldAddWatermark": false,
"shouldCacheIgnoreZoom": false,
"showGrid": false,
"showShortcutsDialog": false,
"showStats": false,
"startBoundElement": null,
@@ -5396,7 +5396,6 @@ Object {
"exportBackground": true,
"exportEmbedScene": false,
"fileHandle": null,
"gridSize": null,
"height": 768,
"isBindingEnabled": true,
"isLibraryOpen": false,
@@ -5421,6 +5420,7 @@ Object {
"selectionElement": null,
"shouldAddWatermark": false,
"shouldCacheIgnoreZoom": false,
"showGrid": false,
"showShortcutsDialog": false,
"showStats": false,
"startBoundElement": null,
@@ -5554,7 +5554,6 @@ Object {
"exportBackground": true,
"exportEmbedScene": false,
"fileHandle": null,
"gridSize": null,
"height": 768,
"isBindingEnabled": true,
"isLibraryOpen": false,
@@ -5579,6 +5578,7 @@ Object {
"selectionElement": null,
"shouldAddWatermark": false,
"shouldCacheIgnoreZoom": false,
"showGrid": false,
"showShortcutsDialog": false,
"showStats": false,
"startBoundElement": null,
@@ -6001,7 +6001,6 @@ Object {
"exportBackground": true,
"exportEmbedScene": false,
"fileHandle": null,
"gridSize": null,
"height": 768,
"isBindingEnabled": true,
"isLibraryOpen": false,
@@ -6033,6 +6032,7 @@ Object {
"selectionElement": null,
"shouldAddWatermark": false,
"shouldCacheIgnoreZoom": false,
"showGrid": false,
"showShortcutsDialog": false,
"showStats": false,
"startBoundElement": null,
@@ -6313,7 +6313,6 @@ Object {
"exportBackground": true,
"exportEmbedScene": false,
"fileHandle": null,
"gridSize": null,
"height": 768,
"isBindingEnabled": true,
"isLibraryOpen": false,
@@ -6338,6 +6337,7 @@ Object {
"selectionElement": null,
"shouldAddWatermark": false,
"shouldCacheIgnoreZoom": false,
"showGrid": false,
"showShortcutsDialog": false,
"showStats": false,
"startBoundElement": null,
@@ -8341,7 +8341,6 @@ Object {
"exportBackground": true,
"exportEmbedScene": false,
"fileHandle": null,
"gridSize": null,
"height": 768,
"isBindingEnabled": true,
"isLibraryOpen": false,
@@ -8371,6 +8370,7 @@ Object {
"selectionElement": null,
"shouldAddWatermark": false,
"shouldCacheIgnoreZoom": false,
"showGrid": false,
"showShortcutsDialog": false,
"showStats": false,
"startBoundElement": null,
@@ -8697,7 +8697,6 @@ Object {
"exportBackground": true,
"exportEmbedScene": false,
"fileHandle": null,
"gridSize": null,
"height": 768,
"isBindingEnabled": true,
"isLibraryOpen": false,
@@ -8728,6 +8727,7 @@ Object {
"selectionElement": null,
"shouldAddWatermark": false,
"shouldCacheIgnoreZoom": false,
"showGrid": false,
"showShortcutsDialog": false,
"showStats": false,
"startBoundElement": null,
@@ -8946,7 +8946,6 @@ Object {
"exportBackground": true,
"exportEmbedScene": false,
"fileHandle": null,
"gridSize": null,
"height": 768,
"isBindingEnabled": true,
"isLibraryOpen": false,
@@ -8975,6 +8974,7 @@ Object {
"selectionElement": null,
"shouldAddWatermark": false,
"shouldCacheIgnoreZoom": false,
"showGrid": false,
"showShortcutsDialog": false,
"showStats": false,
"startBoundElement": null,
@@ -9193,7 +9193,6 @@ Object {
"exportBackground": true,
"exportEmbedScene": false,
"fileHandle": null,
"gridSize": null,
"height": 768,
"isBindingEnabled": true,
"isLibraryOpen": false,
@@ -9223,6 +9222,7 @@ Object {
"selectionElement": null,
"shouldAddWatermark": false,
"shouldCacheIgnoreZoom": false,
"showGrid": false,
"showShortcutsDialog": false,
"showStats": false,
"startBoundElement": null,
@@ -9502,7 +9502,6 @@ Object {
"exportBackground": true,
"exportEmbedScene": false,
"fileHandle": null,
"gridSize": null,
"height": 768,
"isBindingEnabled": true,
"isLibraryOpen": false,
@@ -9527,6 +9526,7 @@ Object {
"selectionElement": null,
"shouldAddWatermark": false,
"shouldCacheIgnoreZoom": false,
"showGrid": false,
"showShortcutsDialog": false,
"showStats": false,
"startBoundElement": null,
@@ -9660,7 +9660,6 @@ Object {
"exportBackground": true,
"exportEmbedScene": false,
"fileHandle": null,
"gridSize": null,
"height": 768,
"isBindingEnabled": true,
"isLibraryOpen": false,
@@ -9685,6 +9684,7 @@ Object {
"selectionElement": null,
"shouldAddWatermark": false,
"shouldCacheIgnoreZoom": false,
"showGrid": false,
"showShortcutsDialog": false,
"showStats": false,
"startBoundElement": null,
@@ -9818,7 +9818,6 @@ Object {
"exportBackground": true,
"exportEmbedScene": false,
"fileHandle": null,
"gridSize": null,
"height": 768,
"isBindingEnabled": true,
"isLibraryOpen": false,
@@ -9843,6 +9842,7 @@ Object {
"selectionElement": null,
"shouldAddWatermark": false,
"shouldCacheIgnoreZoom": false,
"showGrid": false,
"showShortcutsDialog": false,
"showStats": false,
"startBoundElement": null,
@@ -9976,7 +9976,6 @@ Object {
"exportBackground": true,
"exportEmbedScene": false,
"fileHandle": null,
"gridSize": null,
"height": 768,
"isBindingEnabled": true,
"isLibraryOpen": false,
@@ -10001,6 +10000,7 @@ Object {
"selectionElement": null,
"shouldAddWatermark": false,
"shouldCacheIgnoreZoom": false,
"showGrid": false,
"showShortcutsDialog": false,
"showStats": false,
"startBoundElement": null,
@@ -10164,7 +10164,6 @@ Object {
"exportBackground": true,
"exportEmbedScene": false,
"fileHandle": null,
"gridSize": null,
"height": 768,
"isBindingEnabled": true,
"isLibraryOpen": false,
@@ -10189,6 +10188,7 @@ Object {
"selectionElement": null,
"shouldAddWatermark": false,
"shouldCacheIgnoreZoom": false,
"showGrid": false,
"showShortcutsDialog": false,
"showStats": false,
"startBoundElement": null,
@@ -10352,7 +10352,6 @@ Object {
"exportBackground": true,
"exportEmbedScene": false,
"fileHandle": null,
"gridSize": null,
"height": 768,
"isBindingEnabled": true,
"isLibraryOpen": false,
@@ -10377,6 +10376,7 @@ Object {
"selectionElement": null,
"shouldAddWatermark": false,
"shouldCacheIgnoreZoom": false,
"showGrid": false,
"showShortcutsDialog": false,
"showStats": false,
"startBoundElement": null,
@@ -10540,7 +10540,6 @@ Object {
"exportBackground": true,
"exportEmbedScene": false,
"fileHandle": null,
"gridSize": null,
"height": 768,
"isBindingEnabled": true,
"isLibraryOpen": false,
@@ -10565,6 +10564,7 @@ Object {
"selectionElement": null,
"shouldAddWatermark": false,
"shouldCacheIgnoreZoom": false,
"showGrid": false,
"showShortcutsDialog": false,
"showStats": false,
"startBoundElement": null,
@@ -10728,7 +10728,6 @@ Object {
"exportBackground": true,
"exportEmbedScene": false,
"fileHandle": null,
"gridSize": null,
"height": 768,
"isBindingEnabled": true,
"isLibraryOpen": false,
@@ -10753,6 +10752,7 @@ Object {
"selectionElement": null,
"shouldAddWatermark": false,
"shouldCacheIgnoreZoom": false,
"showGrid": false,
"showShortcutsDialog": false,
"showStats": false,
"startBoundElement": null,
@@ -10886,7 +10886,6 @@ Object {
"exportBackground": true,
"exportEmbedScene": false,
"fileHandle": null,
"gridSize": null,
"height": 768,
"isBindingEnabled": true,
"isLibraryOpen": false,
@@ -10911,6 +10910,7 @@ Object {
"selectionElement": null,
"shouldAddWatermark": false,
"shouldCacheIgnoreZoom": false,
"showGrid": false,
"showShortcutsDialog": false,
"showStats": false,
"startBoundElement": null,
@@ -11044,7 +11044,6 @@ Object {
"exportBackground": true,
"exportEmbedScene": false,
"fileHandle": null,
"gridSize": null,
"height": 768,
"isBindingEnabled": true,
"isLibraryOpen": false,
@@ -11069,6 +11068,7 @@ Object {
"selectionElement": null,
"shouldAddWatermark": false,
"shouldCacheIgnoreZoom": false,
"showGrid": false,
"showShortcutsDialog": false,
"showStats": false,
"startBoundElement": null,
@@ -11232,7 +11232,6 @@ Object {
"exportBackground": true,
"exportEmbedScene": false,
"fileHandle": null,
"gridSize": null,
"height": 768,
"isBindingEnabled": true,
"isLibraryOpen": false,
@@ -11257,6 +11256,7 @@ Object {
"selectionElement": null,
"shouldAddWatermark": false,
"shouldCacheIgnoreZoom": false,
"showGrid": false,
"showShortcutsDialog": false,
"showStats": false,
"startBoundElement": null,
@@ -11390,7 +11390,6 @@ Object {
"exportBackground": true,
"exportEmbedScene": false,
"fileHandle": null,
"gridSize": null,
"height": 768,
"isBindingEnabled": true,
"isLibraryOpen": false,
@@ -11415,6 +11414,7 @@ Object {
"selectionElement": null,
"shouldAddWatermark": false,
"shouldCacheIgnoreZoom": false,
"showGrid": false,
"showShortcutsDialog": false,
"showStats": false,
"startBoundElement": null,
@@ -11578,7 +11578,6 @@ Object {
"exportBackground": true,
"exportEmbedScene": false,
"fileHandle": null,
"gridSize": null,
"height": 768,
"isBindingEnabled": true,
"isLibraryOpen": false,
@@ -11614,6 +11613,7 @@ Object {
"selectionElement": null,
"shouldAddWatermark": false,
"shouldCacheIgnoreZoom": false,
"showGrid": false,
"showShortcutsDialog": false,
"showStats": false,
"startBoundElement": null,
@@ -12288,7 +12288,6 @@ Object {
"exportBackground": true,
"exportEmbedScene": false,
"fileHandle": null,
"gridSize": null,
"height": 768,
"isBindingEnabled": true,
"isLibraryOpen": false,
@@ -12317,6 +12316,7 @@ Object {
"selectionElement": null,
"shouldAddWatermark": false,
"shouldCacheIgnoreZoom": false,
"showGrid": false,
"showShortcutsDialog": false,
"showStats": false,
"startBoundElement": null,
@@ -12535,7 +12535,6 @@ Object {
"exportBackground": true,
"exportEmbedScene": false,
"fileHandle": null,
"gridSize": null,
"height": 768,
"isBindingEnabled": true,
"isLibraryOpen": false,
@@ -12560,6 +12559,7 @@ Object {
"selectionElement": null,
"shouldAddWatermark": false,
"shouldCacheIgnoreZoom": true,
"showGrid": false,
"showShortcutsDialog": false,
"showStats": false,
"startBoundElement": null,
@@ -12631,7 +12631,6 @@ Object {
"exportBackground": true,
"exportEmbedScene": false,
"fileHandle": null,
"gridSize": null,
"height": 768,
"isBindingEnabled": true,
"isLibraryOpen": false,
@@ -12654,6 +12653,7 @@ Object {
"selectionElement": null,
"shouldAddWatermark": false,
"shouldCacheIgnoreZoom": false,
"showGrid": false,
"showShortcutsDialog": false,
"showStats": false,
"startBoundElement": null,
@@ -12725,7 +12725,6 @@ Object {
"exportBackground": true,
"exportEmbedScene": false,
"fileHandle": null,
"gridSize": null,
"height": 768,
"isBindingEnabled": true,
"isLibraryOpen": false,
@@ -12750,6 +12749,7 @@ Object {
"selectionElement": null,
"shouldAddWatermark": false,
"shouldCacheIgnoreZoom": false,
"showGrid": false,
"showShortcutsDialog": false,
"showStats": false,
"startBoundElement": null,
@@ -12883,7 +12883,6 @@ Object {
"exportBackground": true,
"exportEmbedScene": false,
"fileHandle": null,
"gridSize": null,
"height": 768,
"isBindingEnabled": true,
"isLibraryOpen": false,
@@ -12908,6 +12907,7 @@ Object {
"selectionElement": null,
"shouldAddWatermark": false,
"shouldCacheIgnoreZoom": false,
"showGrid": false,
"showShortcutsDialog": false,
"showStats": false,
"startBoundElement": null,
@@ -13185,7 +13185,6 @@ Object {
"exportBackground": true,
"exportEmbedScene": false,
"fileHandle": null,
"gridSize": null,
"height": 768,
"isBindingEnabled": true,
"isLibraryOpen": false,
@@ -13210,6 +13209,7 @@ Object {
"selectionElement": null,
"shouldAddWatermark": false,
"shouldCacheIgnoreZoom": false,
"showGrid": false,
"showShortcutsDialog": false,
"showStats": false,
"startBoundElement": null,
@@ -13487,7 +13487,6 @@ Object {
"exportBackground": true,
"exportEmbedScene": false,
"fileHandle": null,
"gridSize": null,
"height": 768,
"isBindingEnabled": true,
"isLibraryOpen": false,
@@ -13512,6 +13511,7 @@ Object {
"selectionElement": null,
"shouldAddWatermark": false,
"shouldCacheIgnoreZoom": false,
"showGrid": false,
"showShortcutsDialog": false,
"showStats": false,
"startBoundElement": null,
@@ -13645,7 +13645,6 @@ Object {
"exportBackground": true,
"exportEmbedScene": false,
"fileHandle": null,
"gridSize": null,
"height": 768,
"isBindingEnabled": true,
"isLibraryOpen": false,
@@ -13668,6 +13667,7 @@ Object {
"selectionElement": null,
"shouldAddWatermark": false,
"shouldCacheIgnoreZoom": false,
"showGrid": false,
"showShortcutsDialog": false,
"showStats": false,
"startBoundElement": null,
@@ -13835,7 +13835,6 @@ Object {
"exportBackground": true,
"exportEmbedScene": false,
"fileHandle": null,
"gridSize": null,
"height": 768,
"isBindingEnabled": true,
"isLibraryOpen": false,
@@ -13860,6 +13859,7 @@ Object {
"selectionElement": null,
"shouldAddWatermark": false,
"shouldCacheIgnoreZoom": false,
"showGrid": false,
"showShortcutsDialog": false,
"showStats": false,
"startBoundElement": null,
@@ -14078,7 +14078,6 @@ Object {
"exportBackground": true,
"exportEmbedScene": false,
"fileHandle": null,
"gridSize": null,
"height": 768,
"isBindingEnabled": true,
"isLibraryOpen": false,
@@ -14109,6 +14108,7 @@ Object {
"selectionElement": null,
"shouldAddWatermark": false,
"shouldCacheIgnoreZoom": false,
"showGrid": false,
"showShortcutsDialog": false,
"showStats": false,
"startBoundElement": null,
@@ -14396,7 +14396,6 @@ Object {
"exportBackground": true,
"exportEmbedScene": false,
"fileHandle": null,
"gridSize": null,
"height": 768,
"isBindingEnabled": true,
"isLibraryOpen": false,
@@ -14421,6 +14420,7 @@ Object {
"selectionElement": null,
"shouldAddWatermark": false,
"shouldCacheIgnoreZoom": false,
"showGrid": false,
"showShortcutsDialog": false,
"showStats": false,
"startBoundElement": null,
@@ -15229,7 +15229,6 @@ Object {
"exportBackground": true,
"exportEmbedScene": false,
"fileHandle": null,
"gridSize": null,
"height": 768,
"isBindingEnabled": true,
"isLibraryOpen": false,
@@ -15254,6 +15253,7 @@ Object {
"selectionElement": null,
"shouldAddWatermark": false,
"shouldCacheIgnoreZoom": false,
"showGrid": false,
"showShortcutsDialog": false,
"showStats": false,
"startBoundElement": null,
@@ -15531,7 +15531,6 @@ Object {
"exportBackground": true,
"exportEmbedScene": false,
"fileHandle": null,
"gridSize": null,
"height": 768,
"isBindingEnabled": true,
"isLibraryOpen": false,
@@ -15556,6 +15555,7 @@ Object {
"selectionElement": null,
"shouldAddWatermark": false,
"shouldCacheIgnoreZoom": false,
"showGrid": false,
"showShortcutsDialog": false,
"showStats": false,
"startBoundElement": null,
@@ -15833,7 +15833,6 @@ Object {
"exportBackground": true,
"exportEmbedScene": false,
"fileHandle": null,
"gridSize": null,
"height": 768,
"isBindingEnabled": true,
"isLibraryOpen": false,
@@ -15862,6 +15861,7 @@ Object {
"selectionElement": null,
"shouldAddWatermark": false,
"shouldCacheIgnoreZoom": false,
"showGrid": false,
"showShortcutsDialog": false,
"showStats": false,
"startBoundElement": null,
@@ -16206,7 +16206,6 @@ Object {
"exportBackground": true,
"exportEmbedScene": false,
"fileHandle": null,
"gridSize": null,
"height": 768,
"isBindingEnabled": true,
"isLibraryOpen": false,
@@ -16234,6 +16233,7 @@ Object {
"selectionElement": null,
"shouldAddWatermark": false,
"shouldCacheIgnoreZoom": false,
"showGrid": false,
"showShortcutsDialog": false,
"showStats": false,
"startBoundElement": null,
@@ -16367,7 +16367,6 @@ Object {
"exportBackground": true,
"exportEmbedScene": false,
"fileHandle": null,
"gridSize": null,
"height": 768,
"isBindingEnabled": true,
"isLibraryOpen": false,
@@ -16401,6 +16400,7 @@ Object {
"selectionElement": null,
"shouldAddWatermark": false,
"shouldCacheIgnoreZoom": false,
"showGrid": false,
"showShortcutsDialog": false,
"showStats": false,
"startBoundElement": null,
@@ -16682,7 +16682,6 @@ Object {
"exportBackground": true,
"exportEmbedScene": false,
"fileHandle": null,
"gridSize": null,
"height": 768,
"isBindingEnabled": true,
"isLibraryOpen": false,
@@ -16710,6 +16709,7 @@ Object {
"selectionElement": null,
"shouldAddWatermark": false,
"shouldCacheIgnoreZoom": false,
"showGrid": false,
"showShortcutsDialog": false,
"showStats": false,
"startBoundElement": null,
@@ -16915,7 +16915,6 @@ Object {
"exportBackground": true,
"exportEmbedScene": false,
"fileHandle": null,
"gridSize": null,
"height": 768,
"isBindingEnabled": true,
"isLibraryOpen": false,
@@ -16946,6 +16945,7 @@ Object {
"selectionElement": null,
"shouldAddWatermark": false,
"shouldCacheIgnoreZoom": false,
"showGrid": false,
"showShortcutsDialog": false,
"showStats": false,
"startBoundElement": null,
@@ -17164,7 +17164,6 @@ Object {
"exportBackground": true,
"exportEmbedScene": false,
"fileHandle": null,
"gridSize": null,
"height": 768,
"isBindingEnabled": true,
"isLibraryOpen": false,
@@ -17197,6 +17196,7 @@ Object {
"selectionElement": null,
"shouldAddWatermark": false,
"shouldCacheIgnoreZoom": false,
"showGrid": false,
"showShortcutsDialog": false,
"showStats": false,
"startBoundElement": null,
@@ -17485,7 +17485,6 @@ Object {
"exportBackground": true,
"exportEmbedScene": false,
"fileHandle": null,
"gridSize": null,
"height": 768,
"isBindingEnabled": true,
"isLibraryOpen": false,
@@ -17508,6 +17507,7 @@ Object {
"selectionElement": null,
"shouldAddWatermark": false,
"shouldCacheIgnoreZoom": false,
"showGrid": false,
"showShortcutsDialog": false,
"showStats": false,
"startBoundElement": null,
@@ -17579,7 +17579,6 @@ Object {
"exportBackground": true,
"exportEmbedScene": false,
"fileHandle": null,
"gridSize": null,
"height": 768,
"isBindingEnabled": true,
"isLibraryOpen": false,
@@ -17604,6 +17603,7 @@ Object {
"selectionElement": null,
"shouldAddWatermark": false,
"shouldCacheIgnoreZoom": false,
"showGrid": false,
"showShortcutsDialog": false,
"showStats": false,
"startBoundElement": null,
@@ -17737,7 +17737,6 @@ Object {
"exportBackground": true,
"exportEmbedScene": false,
"fileHandle": null,
"gridSize": null,
"height": 768,
"isBindingEnabled": true,
"isLibraryOpen": false,
@@ -17773,6 +17772,7 @@ Object {
"selectionElement": null,
"shouldAddWatermark": false,
"shouldCacheIgnoreZoom": false,
"showGrid": false,
"showShortcutsDialog": false,
"showStats": false,
"startBoundElement": null,
@@ -18552,7 +18552,6 @@ Object {
"exportBackground": true,
"exportEmbedScene": false,
"fileHandle": null,
"gridSize": null,
"height": 768,
"isBindingEnabled": true,
"isLibraryOpen": false,
@@ -18575,6 +18574,7 @@ Object {
"selectionElement": null,
"shouldAddWatermark": false,
"shouldCacheIgnoreZoom": false,
"showGrid": false,
"showShortcutsDialog": false,
"showStats": false,
"startBoundElement": null,
@@ -18646,7 +18646,6 @@ Object {
"exportBackground": true,
"exportEmbedScene": false,
"fileHandle": null,
"gridSize": null,
"height": 768,
"isBindingEnabled": true,
"isLibraryOpen": false,
@@ -18671,6 +18670,7 @@ Object {
"selectionElement": null,
"shouldAddWatermark": false,
"shouldCacheIgnoreZoom": false,
"showGrid": false,
"showShortcutsDialog": false,
"showStats": false,
"startBoundElement": null,
@@ -19392,7 +19392,6 @@ Object {
"exportBackground": true,
"exportEmbedScene": false,
"fileHandle": null,
"gridSize": null,
"height": 768,
"isBindingEnabled": true,
"isLibraryOpen": false,
@@ -19443,6 +19442,7 @@ Object {
},
"shouldAddWatermark": false,
"shouldCacheIgnoreZoom": false,
"showGrid": false,
"showShortcutsDialog": false,
"showStats": false,
"startBoundElement": null,
@@ -19791,7 +19791,6 @@ Object {
"exportBackground": true,
"exportEmbedScene": false,
"fileHandle": null,
"gridSize": null,
"height": 768,
"isBindingEnabled": true,
"isLibraryOpen": false,
@@ -19840,6 +19839,7 @@ Object {
},
"shouldAddWatermark": false,
"shouldCacheIgnoreZoom": false,
"showGrid": false,
"showShortcutsDialog": false,
"showStats": false,
"startBoundElement": null,
@@ -20058,7 +20058,6 @@ Object {
"exportBackground": true,
"exportEmbedScene": false,
"fileHandle": null,
"gridSize": null,
"height": 768,
"isBindingEnabled": true,
"isLibraryOpen": false,
@@ -20083,6 +20082,7 @@ Object {
"selectionElement": null,
"shouldAddWatermark": false,
"shouldCacheIgnoreZoom": true,
"showGrid": false,
"showShortcutsDialog": false,
"showStats": false,
"startBoundElement": null,
@@ -20154,7 +20154,6 @@ Object {
"exportBackground": true,
"exportEmbedScene": false,
"fileHandle": null,
"gridSize": null,
"height": 768,
"isBindingEnabled": true,
"isLibraryOpen": false,
@@ -20179,6 +20178,7 @@ Object {
"selectionElement": null,
"shouldAddWatermark": false,
"shouldCacheIgnoreZoom": false,
"showGrid": false,
"showShortcutsDialog": false,
"showStats": false,
"startBoundElement": null,
@@ -20646,7 +20646,6 @@ Object {
"exportBackground": true,
"exportEmbedScene": false,
"fileHandle": null,
"gridSize": null,
"height": 768,
"isBindingEnabled": true,
"isLibraryOpen": false,
@@ -20669,6 +20668,7 @@ Object {
"selectionElement": null,
"shouldAddWatermark": false,
"shouldCacheIgnoreZoom": false,
"showGrid": false,
"showShortcutsDialog": false,
"showStats": false,
"startBoundElement": null,
@@ -20740,7 +20740,6 @@ Object {
"exportBackground": true,
"exportEmbedScene": false,
"fileHandle": null,
"gridSize": null,
"height": 768,
"isBindingEnabled": true,
"isLibraryOpen": false,
@@ -20763,6 +20762,7 @@ Object {
"selectionElement": null,
"shouldAddWatermark": false,
"shouldCacheIgnoreZoom": false,
"showGrid": false,
"showShortcutsDialog": false,
"showStats": false,
"startBoundElement": null,
+13
View File
@@ -0,0 +1,13 @@
import { tryParseSpreadsheet } from "../charts";
describe("tryParseSpreadsheet", () => {
it("works for numbers with comma in them", () => {
const result = tryParseSpreadsheet(
`Week Index${"\t"}Users
Week 1${"\t"}814
Week 2${"\t"}10,301
Week 3${"\t"}4,264`,
);
expect(result).toMatchSnapshot();
});
});
+4 -4
View File
@@ -1,4 +1,4 @@
import { queryByText } from "@testing-library/react";
import { queryAllByText, queryByText } from "@testing-library/react";
import React from "react";
import ReactDOM from "react-dom";
import { copiedStyles } from "../actions/actionStyles";
@@ -635,8 +635,8 @@ describe("regression tests", () => {
const contextMenu = document.querySelector(".context-menu");
const expectedShortcutNames: ShortcutName[] = [
"selectAll",
"toggleGridMode",
"toggleStats",
"gridMode",
"stats",
];
expect(contextMenu).not.toBeNull();
@@ -864,7 +864,7 @@ describe("regression tests", () => {
clientY: 1,
});
const contextMenu = document.querySelector(".context-menu");
fireEvent.click(queryByText(contextMenu as HTMLElement, "Delete")!);
fireEvent.click(queryAllByText(contextMenu as HTMLElement, "Delete")[0]);
expect(API.getSelectedElements()).toHaveLength(0);
expect(h.elements[0].isDeleted).toBe(true);
});
+48
View File
@@ -0,0 +1,48 @@
import React from "react";
import { render, waitFor } from "./test-utils";
import Excalidraw from "../packages/excalidraw/index";
import { API } from "./helpers/api";
const { h } = window;
describe("appState", () => {
it("scroll-to-center on init works with non-zero offsets", async () => {
const WIDTH = 600;
const HEIGHT = 700;
const OFFSET_LEFT = 200;
const OFFSET_TOP = 100;
const ELEM_WIDTH = 100;
const ELEM_HEIGHT = 60;
await render(
<Excalidraw
width={WIDTH}
height={HEIGHT}
offsetLeft={OFFSET_LEFT}
offsetTop={OFFSET_TOP}
initialData={{
elements: [
API.createElement({
type: "rectangle",
id: "A",
width: ELEM_WIDTH,
height: ELEM_HEIGHT,
}),
],
}}
/>,
);
await waitFor(() => {
expect(h.state.width).toBe(WIDTH);
expect(h.state.height).toBe(HEIGHT);
expect(h.state.offsetLeft).toBe(OFFSET_LEFT);
expect(h.state.offsetTop).toBe(OFFSET_TOP);
// assert scroll is in center
expect(h.state.scrollX).toBe(WIDTH / 2 - ELEM_WIDTH / 2);
expect(h.state.scrollY).toBe(HEIGHT / 2 - ELEM_HEIGHT / 2);
});
});
});
+6 -1
View File
@@ -83,7 +83,7 @@ export type AppState = {
showShortcutsDialog: boolean;
zenModeEnabled: boolean;
appearance: "light" | "dark";
gridSize: number | null;
showGrid: boolean;
/** top-most selected groups (i.e. does not include nested groups) */
selectedGroupIds: { [groupId: string]: boolean };
@@ -166,6 +166,11 @@ export interface ExcalidrawProps {
button: "down" | "up";
pointersMap: Gesture["pointers"];
}) => void;
onExportToBackend?: (
exportedElements: readonly NonDeletedExcalidrawElement[],
appState: AppState,
canvas: HTMLCanvasElement | null,
) => void;
}
export type SceneData = {
+11 -9
View File
@@ -7,6 +7,7 @@ import {
import { FontFamily, FontString } from "./element/types";
import { Zoom } from "./types";
import { unstable_batchedUpdates } from "react-dom";
import { isDarwin } from "./keys";
export const SVG_NS = "http://www.w3.org/2000/svg";
@@ -179,15 +180,18 @@ export const allowFullScreen = () =>
export const exitFullScreen = () => document.exitFullscreen();
export const getShortcutKey = (shortcut: string): string => {
const isMac = /Mac|iPod|iPhone|iPad/.test(window.navigator.platform);
if (isMac) {
return `${shortcut
shortcut = shortcut
.replace(/\bAlt\b/i, "Alt")
.replace(/\bShift\b/i, "Shift")
.replace(/\b(Enter|Return)\b/i, "Enter")
.replace(/\bDel\b/i, "Delete");
if (isDarwin) {
return shortcut
.replace(/\bCtrlOrCmd\b/i, "Cmd")
.replace(/\bAlt\b/i, "Option")
.replace(/\bDel\b/i, "Delete")
.replace(/\b(Enter|Return)\b/i, "Enter")}`;
.replace(/\bAlt\b/i, "Option");
}
return `${shortcut.replace(/\bCtrlOrCmd\b/i, "Ctrl")}`;
return shortcut.replace(/\bCtrlOrCmd\b/i, "Ctrl");
};
export const viewportCoordsToSceneCoords = (
@@ -310,8 +314,6 @@ export const isTransparent = (color: string) => {
);
};
export const noop = () => ({});
export type ResolvablePromise<T> = Promise<T> & {
resolve: [T] extends [undefined] ? (value?: T) => void : (value: T) => void;
reject: (error: Error) => void;
+1 -1
View File
@@ -14,7 +14,7 @@
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react"
"jsx": "react-jsx"
},
"include": ["src"]
}