Compare commits
85 Commits
grid
...
aakansha-sm
| Author | SHA1 | Date | |
|---|---|---|---|
| 09daff487a | |||
| 49bd683401 | |||
| 17330c4c03 | |||
| 2a4ad6fc41 | |||
| ec6999554a | |||
| 3922ee8c11 | |||
| 8c2bc94336 | |||
| fb4d97ef78 | |||
| a8e28afbad | |||
| 68347ba476 | |||
| ce2c341910 | |||
| 07cc858926 | |||
| a7a2936f7c | |||
| e72ff6be66 | |||
| 0ea29675df | |||
| da2ad4f37c | |||
| 33a7cf0d3f | |||
| f8e890df7b | |||
| 12337a54a3 | |||
| 7a9ed2cfa1 | |||
| 553bf2956f | |||
| 1e16a6e5bd | |||
| e26f374ca6 | |||
| c799b28a0e | |||
| ee703206b0 | |||
| 543c624405 | |||
| 0bf6830373 | |||
| af79461f41 | |||
| fd699c0447 | |||
| 6a16caf13c | |||
| 511eb62228 | |||
| 04c46fc01a | |||
| 49e792649d | |||
| 4e1caf2417 | |||
| 034f2e470b | |||
| adcd28f348 | |||
| 62f1ed74f1 | |||
| 8fa4273969 | |||
| 672068ce7e | |||
| f1fc308a5d | |||
| 001880ba88 | |||
| 3a130cb102 | |||
| e682cf9bf6 | |||
| 2a169924d0 | |||
| eb6e75b806 | |||
| 38857b9e9d | |||
| 342289f261 | |||
| 095d7de618 | |||
| f57d52028a | |||
| 60557df23a | |||
| bafbe9bbc8 | |||
| eb71e571e0 | |||
| b608ab74cc | |||
| a13c4f72f5 | |||
| 629341da4d | |||
| 778e4b08af | |||
| e16266ce5d | |||
| b6708fb73f | |||
| cdffed285d | |||
| 3aa01ad272 | |||
| 4acdc47ef0 | |||
| ade2565f49 | |||
| c35d983fef | |||
| 69878167c2 | |||
| eb1f717d35 | |||
| 8e9af5c51b | |||
| afe0c760f6 | |||
| a231cefac0 | |||
| cb4c9d16fc | |||
| 7366f089ba | |||
| 7c3513b9df | |||
| aef3644c93 | |||
| 0cf58adb4c | |||
| 3aab81bc35 | |||
| 3b0fb1562d | |||
| 0488b7b5c6 | |||
| b8d13c98b5 | |||
| 6f82a88b79 | |||
| 022f349dc6 | |||
| c1e2146d78 | |||
| 8091ac6c08 | |||
| bc414ccaaf | |||
| 0cf5f1ac1f | |||
| e9cb7ee77c | |||
| 86c036505b |
@@ -1,5 +1,5 @@
|
|||||||
REACT_APP_BACKEND_V1_GET_URL=https://json.excalidraw.com/api/v1/
|
REACT_APP_BACKEND_V1_GET_URL=https://json.excalidraw.com/api/v1/
|
||||||
REACT_APP_BACKEND_V2_GET_URL=https://json.excalidraw.com/api/v2/
|
REACT_APP_BACKEND_V2_GET_URL=https://json.excalidraw.com/api/v2/
|
||||||
REACT_APP_BACKEND_V2_POST_URL=https://json.excalidraw.com/api/v2/post/
|
REACT_APP_BACKEND_V2_POST_URL=https://json.excalidraw.com/api/v2/post/
|
||||||
REACT_APP_SOCKET_SERVER_URL=https://excalidraw-socket.herokuapp.com
|
REACT_APP_SOCKET_SERVER_URL=https://portal.excalidraw.com
|
||||||
REACT_APP_FIREBASE_CONFIG='{"apiKey":"AIzaSyAd15pYlMci_xIp9ko6wkEsDzAAA0Dn0RU","authDomain":"excalidraw-room-persistence.firebaseapp.com","databaseURL":"https://excalidraw-room-persistence.firebaseio.com","projectId":"excalidraw-room-persistence","storageBucket":"excalidraw-room-persistence.appspot.com","messagingSenderId":"654800341332","appId":"1:654800341332:web:4a692de832b55bd57ce0c1"}'
|
REACT_APP_FIREBASE_CONFIG='{"apiKey":"AIzaSyAd15pYlMci_xIp9ko6wkEsDzAAA0Dn0RU","authDomain":"excalidraw-room-persistence.firebaseapp.com","databaseURL":"https://excalidraw-room-persistence.firebaseio.com","projectId":"excalidraw-room-persistence","storageBucket":"excalidraw-room-persistence.appspot.com","messagingSenderId":"654800341332","appId":"1:654800341332:web:4a692de832b55bd57ce0c1"}'
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
REACT_APP_INCLUDE_GTAG=true
|
REACT_APP_GOOGLE_ANALYTICS_ID=UA-387204-13
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 39 KiB After Width: | Height: | Size: 20 KiB |
@@ -0,0 +1,12 @@
|
|||||||
|
name: Cancel
|
||||||
|
on: [push]
|
||||||
|
jobs:
|
||||||
|
cancel:
|
||||||
|
name: "Cancel Previous Runs"
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
timeout-minutes: 3
|
||||||
|
steps:
|
||||||
|
- uses: styfle/cancel-workflow-action@0.6.0
|
||||||
|
with:
|
||||||
|
workflow_id: 400555, 400556, 905313, 1451724, 1710116, 3185001, 3438604
|
||||||
|
access_token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
@@ -43,5 +43,5 @@ jobs:
|
|||||||
uses: kt3k/update-pr-description@v1.0.1
|
uses: kt3k/update-pr-description@v1.0.1
|
||||||
with:
|
with:
|
||||||
pr_body: ${{ steps.getCommentBody.outputs.body }}
|
pr_body: ${{ steps.getCommentBody.outputs.body }}
|
||||||
pr_title: "chore: New Crowdin updates"
|
pr_title: "chore: Update translations from Crowdin"
|
||||||
github_token: ${{ secrets.PUSH_TRANSLATIONS_COVERAGE_PAT }}
|
github_token: ${{ secrets.PUSH_TRANSLATIONS_COVERAGE_PAT }}
|
||||||
|
|||||||
@@ -11,6 +11,6 @@ jobs:
|
|||||||
main:
|
main:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: amannn/action-semantic-pull-request@v2.1.0
|
- uses: amannn/action-semantic-pull-request@v3.0.0
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
{
|
{
|
||||||
|
"proseWrap": "never",
|
||||||
"trailingComma": "all"
|
"trailingComma": "all"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,8 +8,7 @@
|
|||||||
1. Run `npm install` to install dependencies
|
1. Run `npm install` to install dependencies
|
||||||
1. Create a branch for your PR with `git checkout -b your-branch-name`
|
1. Create a branch for your PR with `git checkout -b your-branch-name`
|
||||||
|
|
||||||
> To keep `master` branch pointing to remote repository and make
|
> To keep `master` branch pointing to remote repository and make pull requests from branches on your fork. To do this, run:
|
||||||
> pull requests from branches on your fork. To do this, run:
|
|
||||||
>
|
>
|
||||||
> ```sh
|
> ```sh
|
||||||
> git remote add upstream https://github.com/excalidraw/excalidraw.git
|
> git remote add upstream https://github.com/excalidraw/excalidraw.git
|
||||||
@@ -25,3 +24,41 @@
|
|||||||
1. Tap on `Fork Sandbox`
|
1. Tap on `Fork Sandbox`
|
||||||
1. Write your code
|
1. Write your code
|
||||||
1. Commit and PR automatically
|
1. Commit and PR automatically
|
||||||
|
|
||||||
|
## Pull Request Guidelines
|
||||||
|
|
||||||
|
Don't worry if you get any of the below wrong, or if you don't know how. We'll gladly help out.
|
||||||
|
|
||||||
|
### Title
|
||||||
|
|
||||||
|
Make sure the title starts with a semantic prefix:
|
||||||
|
|
||||||
|
- **feat**: A new feature
|
||||||
|
- **fix**: A bug fix
|
||||||
|
- **improvement**: An improvement to a current feature
|
||||||
|
- **docs**: Documentation only changes
|
||||||
|
- **style**: Changes that do not affect the meaning of the code (white-space, formatting, missing semi-colons, etc)
|
||||||
|
- **refactor**: A code change that neither fixes a bug nor adds a feature
|
||||||
|
- **perf**: A code change that improves performance
|
||||||
|
- **test**: Adding missing tests or correcting existing tests
|
||||||
|
- **build**: Changes that affect the build system or external dependencies (example scopes: gulp, broccoli, npm)
|
||||||
|
- **ci**: Changes to our CI configuration files and scripts (example scopes: Travis, Circle, BrowserStack, SauceLabs)
|
||||||
|
- **chore**: Other changes that don't modify src or test files
|
||||||
|
- **revert**: Reverts a previous commit
|
||||||
|
|
||||||
|
### Changelog
|
||||||
|
|
||||||
|
Add a brief description of your pull request to the changelog located here: [`src/packages/excalidraw/CHANGELOG.md`](src/packages/excalidraw/CHANGELOG.md)
|
||||||
|
|
||||||
|
Notes:
|
||||||
|
|
||||||
|
- Make sure to prepend to the section corresponding with the semantic prefix you selected in the title
|
||||||
|
- Link to your pull request - this will require updating the CHANGELOG _after_ creating the pull request
|
||||||
|
|
||||||
|
### Testing
|
||||||
|
|
||||||
|
Once you submit your pull request it will automatically be tested. Be sure to check the results of the test and fix any issues that arise.
|
||||||
|
|
||||||
|
It's also a good idea to consider if your change should include additional tests. This is highly recommended for new features or bug-fixes. For example, it's good practice to create a test for each bug you fix which ensures that we don't regress the code in the future.
|
||||||
|
|
||||||
|
Finally - always manually test your changes using the convenient staging environment deployed for each pull request. As much as local development attempts to replicate production, there can still be subtle differences in behavior. For larger features consider testing your change in multiple browsers as well.
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ WORKDIR /opt/node_app
|
|||||||
COPY package.json package-lock.json ./
|
COPY package.json package-lock.json ./
|
||||||
RUN npm i --no-optional
|
RUN npm i --no-optional
|
||||||
|
|
||||||
ARG REACT_APP_INCLUDE_GTAG=false
|
|
||||||
ARG NODE_ENV=production
|
ARG NODE_ENV=production
|
||||||
|
|
||||||
COPY . .
|
COPY . .
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
<div align="center" style="display:flex;flex-direction:column;">
|
<div align="center" style="display:flex;flex-direction:column;">
|
||||||
<a href="https://excalidraw.com">
|
<a href="https://excalidraw.com">
|
||||||
<img src="./public/og-image.png" alt="Excalidraw logo: Sketch handrawn like diagrams." />
|
<img width="540" src="./public/og-image-sm.png" alt="Excalidraw logo: Sketch handrawn like diagrams." />
|
||||||
</a>
|
</a>
|
||||||
<h3>Virtual whiteboard for sketching hand-drawn like diagrams.</h3>
|
<h3>Virtual whiteboard for sketching hand-drawn like diagrams.<br>Collaborative and end to end encrypted.</h3>
|
||||||
<p>
|
<p>
|
||||||
<a href="https://twitter.com/Excalidraw">
|
<a href="https://twitter.com/Excalidraw">
|
||||||
<img alt="Follow Excalidraw on Twitter" src="https://img.shields.io/twitter/follow/excalidraw.svg?label=follow+excalidraw&style=social&logo=twitter">
|
<img alt="Follow Excalidraw on Twitter" src="https://img.shields.io/twitter/follow/excalidraw.svg?label=follow+excalidraw&style=social&logo=twitter">
|
||||||
@@ -10,9 +10,6 @@
|
|||||||
<a target="_blank" href="https://crowdin.com/project/excalidraw">
|
<a target="_blank" href="https://crowdin.com/project/excalidraw">
|
||||||
<img src="https://badges.crowdin.net/excalidraw/localized.svg">
|
<img src="https://badges.crowdin.net/excalidraw/localized.svg">
|
||||||
</a>
|
</a>
|
||||||
<a target="_blank" href="https://hub.docker.com/r/excalidraw/excalidraw">
|
|
||||||
<img src="https://img.shields.io/docker/pulls/excalidraw/excalidraw">
|
|
||||||
</a>
|
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -20,13 +17,51 @@
|
|||||||
|
|
||||||
Go to [excalidraw.com](https://excalidraw.com) to start sketching.
|
Go to [excalidraw.com](https://excalidraw.com) to start sketching.
|
||||||
|
|
||||||
Read our [blog](https://blog.excalidraw.com) and follow the [guides](https://howto.excalidraw.com) to learn more about Excalidraw and how to use it effectively.
|
Read the latest news and updates on our [blog](https://blog.excalidraw.com). A good start is to see all the updates of [One Year of Excalidraw](https://blog.excalidraw.com/one-year-of-excalidraw/).
|
||||||
|
|
||||||
|
## Documentation
|
||||||
|
|
||||||
|
### Shortcuts
|
||||||
|
|
||||||
|
You can almost do anything with shortcuts. Click on the help icon on the bottom right corner to see them all.
|
||||||
|
|
||||||
|
### Curved lines and arrows
|
||||||
|
|
||||||
|
Choose line or arrow and click click click instead of drag.
|
||||||
|
|
||||||
|
### Charts
|
||||||
|
|
||||||
|
You can easily create charts by copy pasting data from Excel or just plain comma separated text.
|
||||||
|
|
||||||
|
### Translating
|
||||||
|
|
||||||
|
To translate Excalidraw into other languages, please visit [our Crowdin page](https://crowdin.com/project/excalidraw). To add a new language, [open an issue](https://github.com/excalidraw/excalidraw/issues/new) so we can get things set up on our end first.
|
||||||
|
|
||||||
|
Translations will be available on the app if they exceed a certain threshold of completion (currently 85%).
|
||||||
|
|
||||||
|
### Create a collaboration session manually
|
||||||
|
|
||||||
|
In order to create a session manually you just need to generate a link of this form:
|
||||||
|
|
||||||
|
```
|
||||||
|
https://excalidraw.com/#room=[0-9a-f]{20},[a-zA-Z0-9_-]{22}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Example
|
||||||
|
|
||||||
|
```
|
||||||
|
https://excalidraw.com/#room=91bd46ae3aa84dff9d20,pfLqgEoY1c2ioq8LmGwsFA
|
||||||
|
```
|
||||||
|
|
||||||
|
The first set of digits is the room. This is visible from the server that’s going to dispatch messages to everyone that knows this number.
|
||||||
|
|
||||||
|
The second set of digits is the encryption key. The Excalidraw server doesn’t know about it. This is what all the participants use to encrypt/decrypt the messages.
|
||||||
|
|
||||||
## Shape libraries
|
## Shape libraries
|
||||||
|
|
||||||
Find a growing list of libraries containing assets for your drawings at [libraries.excalidraw.com](https://libraries.excalidraw.com).
|
Find a growing list of libraries containing assets for your drawings at [libraries.excalidraw.com](https://libraries.excalidraw.com).
|
||||||
|
|
||||||
## Run the code
|
## Developement
|
||||||
|
|
||||||
### Code Sandbox
|
### Code Sandbox
|
||||||
|
|
||||||
@@ -63,7 +98,7 @@ You can use docker-compose to work on excalidraw locally if you don't want to se
|
|||||||
docker-compose up --build -d
|
docker-compose up --build -d
|
||||||
```
|
```
|
||||||
|
|
||||||
## Self hosting
|
### Self hosting
|
||||||
|
|
||||||
We publish a Docker image with the Excalidraw client at [excalidraw/excalidraw](https://hub.docker.com/r/excalidraw/excalidraw). You can use it to self host your own client under your own domain, on Kubernetes, AWS ECS, etc.
|
We publish a Docker image with the Excalidraw client at [excalidraw/excalidraw](https://hub.docker.com/r/excalidraw/excalidraw). You can use it to self host your own client under your own domain, on Kubernetes, AWS ECS, etc.
|
||||||
|
|
||||||
@@ -82,57 +117,11 @@ We are working towards providing a full-fledged solution for self hosting your o
|
|||||||
|
|
||||||
Pull requests are welcome. For major changes, please [open an issue](https://github.com/excalidraw/excalidraw/issues/new) first to discuss what you would like to change.
|
Pull requests are welcome. For major changes, please [open an issue](https://github.com/excalidraw/excalidraw/issues/new) first to discuss what you would like to change.
|
||||||
|
|
||||||
## Translating
|
## Notable used tools
|
||||||
|
|
||||||
To translate Excalidraw into other languages, please visit [our Crowdin page](https://crowdin.com/project/excalidraw). To add a new language, [open an issue](https://github.com/excalidraw/excalidraw/issues/new) so we can get things set up on our end first.
|
- [Create React App](https://github.com/facebook/create-react-app)
|
||||||
|
|
||||||
Translations will be available on the app if they exceed a certain threshold of completion (currently 85%).
|
|
||||||
|
|
||||||
## Excalidraw is built using these awesome tools
|
|
||||||
|
|
||||||
- [React](https://reactjs.org)
|
|
||||||
- [Rough.js](https://roughjs.com)
|
- [Rough.js](https://roughjs.com)
|
||||||
- [TypeScript](https://www.typescriptlang.org)
|
- [TypeScript](https://www.typescriptlang.org)
|
||||||
- [Vercel](https://vercel.com)
|
- [Vercel](https://vercel.com)
|
||||||
|
|
||||||
And the main source of inspiration for starting the project is the awesome [Zwibbler](https://zwibbler.com/demo/) app.
|
And the main source of inspiration for starting the project is the awesome [Zwibbler](https://zwibbler.com/demo/) app.
|
||||||
|
|
||||||
## Testimonials
|
|
||||||
|
|
||||||
<a href="https://twitter.com/Lissy_Sykes/status/1213813117177729026"><img width="398" src="https://user-images.githubusercontent.com/197597/71783813-dbf8a600-2fa0-11ea-9c0d-bb3cc45969e6.png"></a>
|
|
||||||
<a href="https://twitter.com/dan_abramov/status/1213762494428262400"><img width="398" src="https://user-images.githubusercontent.com/197597/71783990-4d395880-2fa3-11ea-9ad7-186138db5003.png"></a>
|
|
||||||
|
|
||||||
<a href="https://twitter.com/kyehohenberger/status/1214288572037025792"><img width="423" src="https://user-images.githubusercontent.com/197597/71851802-34f13880-308c-11ea-9416-191099e6349c.png"></a>
|
|
||||||
<a href="https://twitter.com/lucasazzola/status/1215126440330416128"><img width="429" src="https://user-images.githubusercontent.com/197597/72039003-48e99580-3258-11ea-8daa-85dd055f2a82.png">
|
|
||||||
|
|
||||||
<a href="https://twitter.com/jordwalke/status/1214858186789806080"><img width="434" src="https://user-images.githubusercontent.com/197597/72036874-07a1b780-3251-11ea-99e8-6bafd93483a0.png"></a>
|
|
||||||
|
|
||||||
## Contributors
|
|
||||||
|
|
||||||
### Code Contributors
|
|
||||||
|
|
||||||
This project exists thanks to all the people who contribute. [[Contribute](CONTRIBUTING.md)].
|
|
||||||
<a href="https://github.com/excalidraw/excalidraw/graphs/contributors"><img src="https://opencollective.com/excalidraw/contributors.svg?width=890&button=false" /></a>
|
|
||||||
|
|
||||||
### Financial Contributors
|
|
||||||
|
|
||||||
Become a financial contributor and help us sustain our community. [[Contribute](https://opencollective.com/excalidraw/contribute)]
|
|
||||||
|
|
||||||
#### Individuals
|
|
||||||
|
|
||||||
<a href="https://opencollective.com/excalidraw"><img src="https://opencollective.com/excalidraw/individuals.svg?width=890"></a>
|
|
||||||
|
|
||||||
#### Organizations
|
|
||||||
|
|
||||||
Support this project with your organization. Your logo will show up here with a link to your website. [[Contribute](https://opencollective.com/excalidraw/contribute)]
|
|
||||||
|
|
||||||
<a href="https://opencollective.com/excalidraw/organization/0/website"><img src="https://opencollective.com/excalidraw/organization/0/avatar.svg"></a>
|
|
||||||
<a href="https://opencollective.com/excalidraw/organization/1/website"><img src="https://opencollective.com/excalidraw/organization/1/avatar.svg"></a>
|
|
||||||
<a href="https://opencollective.com/excalidraw/organization/2/website"><img src="https://opencollective.com/excalidraw/organization/2/avatar.svg"></a>
|
|
||||||
<a href="https://opencollective.com/excalidraw/organization/3/website"><img src="https://opencollective.com/excalidraw/organization/3/avatar.svg"></a>
|
|
||||||
<a href="https://opencollective.com/excalidraw/organization/4/website"><img src="https://opencollective.com/excalidraw/organization/4/avatar.svg"></a>
|
|
||||||
<a href="https://opencollective.com/excalidraw/organization/5/website"><img src="https://opencollective.com/excalidraw/organization/5/avatar.svg"></a>
|
|
||||||
<a href="https://opencollective.com/excalidraw/organization/6/website"><img src="https://opencollective.com/excalidraw/organization/6/avatar.svg"></a>
|
|
||||||
<a href="https://opencollective.com/excalidraw/organization/7/website"><img src="https://opencollective.com/excalidraw/organization/7/avatar.svg"></a>
|
|
||||||
<a href="https://opencollective.com/excalidraw/organization/8/website"><img src="https://opencollective.com/excalidraw/organization/8/avatar.svg"></a>
|
|
||||||
<a href="https://opencollective.com/excalidraw/organization/9/website"><img src="https://opencollective.com/excalidraw/organization/9/avatar.svg"></a>
|
|
||||||
|
|||||||
@@ -1,64 +0,0 @@
|
|||||||
| Excalidraw | Category | Name | Label | Value |
|
|
||||||
| ----------------------- | -------- | ---------------------------------- | ------------------------------- | --------- |
|
|
||||||
| Shape / Selection | shape | selection, rectangle, diamond, etc | `toolbar` or `shortcut` |
|
|
||||||
| Text on double click | shape | text | `double-click` |
|
|
||||||
| Lock selection | shape | lock | `on` or `off` |
|
|
||||||
| Clear canvas | action | clear canvas |
|
|
||||||
| Zoom in | action | zoom | in | `zoom` |
|
|
||||||
| Zoom out | action | zoom | out | `zoom` |
|
|
||||||
| Zoom fit | action | zoom | fit | `zoom` |
|
|
||||||
| Zoom reset | action | zoom | reset | `zoom` |
|
|
||||||
| Scroll back to content | action | scroll to content |
|
|
||||||
| Load file | io | load | `MIME type` |
|
|
||||||
| Import from URL | io | import |
|
|
||||||
| Save | io | save |
|
|
||||||
| Save as | io | save as |
|
|
||||||
| Export to backend | io | export | backend |
|
|
||||||
| Export as SVG | io | export | `svg` or `clipboard-svg` |
|
|
||||||
| Export to PNG | io | export | `png` or `clipboard-png` |
|
|
||||||
| Canvas color | change | canvas color | `color` |
|
|
||||||
| Background color | change | background color | `color` |
|
|
||||||
| Stroke color | change | stroke color | `color` |
|
|
||||||
| Stroke width | change | stroke | width | `width` |
|
|
||||||
| Stroke style | change | style | `solid` or `dashed` or `dotted` |
|
|
||||||
| Stroke sloppiness | change | stroke | sloppiness | `value` |
|
|
||||||
| Fill | change | fill | `value` |
|
|
||||||
| Edge | change | edge | `value` |
|
|
||||||
| Opacity | change | opacity | value | `opacity` |
|
|
||||||
| Project name | change | title |
|
|
||||||
| Theme | change | theme | `light` or `dark` |
|
|
||||||
| Change language | change | language | `language` |
|
|
||||||
| Send to back | layer | move | `back` |
|
|
||||||
| Send backward | layer | move | `down` |
|
|
||||||
| Bring to front | layer | move | `front` |
|
|
||||||
| Bring forward | layer | move | `up` |
|
|
||||||
| Align left | align | align | `left` |
|
|
||||||
| Align right | align | align | `right` |
|
|
||||||
| Align top | align | align | `top` |
|
|
||||||
| Align bottom | align | align | `bottom` |
|
|
||||||
| Center horizontally | align | horizontally | `center` |
|
|
||||||
| Center vertically | align | vertically | `center` |
|
|
||||||
| Distribute horizontally | align | distribute | `horizontally` |
|
|
||||||
| Distribute vertically | align | distribute | `vertically` |
|
|
||||||
| Start session | share | session start |
|
|
||||||
| Join session | share | session join |
|
|
||||||
| Start end | share | session end |
|
|
||||||
| Copy room link | share | copy link |
|
|
||||||
| Go to collaborator | share | go to collaborator |
|
|
||||||
| Change name | share | name |
|
|
||||||
| Add to library | library | add |
|
|
||||||
| Remove from library | library | remove |
|
|
||||||
| Load library | library | load |
|
|
||||||
| Save library | library | save |
|
|
||||||
| Import library | library | import |
|
|
||||||
| Shortcuts dialog | dialog | shortcuts |
|
|
||||||
| Collaboration dialog | dialog | collaboration |
|
|
||||||
| Export dialog | dialog | export |
|
|
||||||
| Library dialog | dialog | library |
|
|
||||||
| E2EE shield | exit | e2ee shield |
|
|
||||||
| GitHub corner | exit | github |
|
|
||||||
| Excalidraw blog | exit | blog |
|
|
||||||
| Excalidraw guides | exit | guides |
|
|
||||||
| File issues | exit | issues |
|
|
||||||
| First load | load | first load |
|
|
||||||
| Load from stroage | load | storage | size | `bytes` |
|
|
||||||
@@ -19,23 +19,22 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@sentry/browser": "5.29.0",
|
"@sentry/browser": "5.30.0",
|
||||||
"@sentry/integrations": "5.29.0",
|
"@sentry/integrations": "5.30.0",
|
||||||
"@testing-library/jest-dom": "5.11.6",
|
"@testing-library/jest-dom": "5.11.9",
|
||||||
"@testing-library/react": "11.2.2",
|
"@testing-library/react": "11.2.3",
|
||||||
"@types/jest": "26.0.19",
|
"@types/jest": "26.0.20",
|
||||||
"@types/nanoid": "2.1.0",
|
|
||||||
"@types/react": "17.0.0",
|
"@types/react": "17.0.0",
|
||||||
"@types/react-dom": "17.0.0",
|
"@types/react-dom": "17.0.0",
|
||||||
"@types/socket.io-client": "1.4.34",
|
"@types/socket.io-client": "1.4.35",
|
||||||
"browser-nativefs": "0.12.0",
|
"browser-nativefs": "0.12.0",
|
||||||
"clsx": "1.1.1",
|
"clsx": "1.1.1",
|
||||||
"firebase": "8.2.1",
|
"firebase": "8.2.3",
|
||||||
"i18next-browser-languagedetector": "6.0.1",
|
"i18next-browser-languagedetector": "6.0.1",
|
||||||
"lodash.throttle": "4.1.1",
|
"lodash.throttle": "4.1.1",
|
||||||
"nanoid": "2.1.11",
|
"nanoid": "3.1.20",
|
||||||
"node-sass": "4.14.1",
|
"node-sass": "4.14.1",
|
||||||
"open-color": "1.7.0",
|
"open-color": "1.8.0",
|
||||||
"pako": "1.0.11",
|
"pako": "1.0.11",
|
||||||
"png-chunk-text": "1.0.0",
|
"png-chunk-text": "1.0.0",
|
||||||
"png-chunks-encode": "1.0.0",
|
"png-chunks-encode": "1.0.0",
|
||||||
@@ -53,9 +52,9 @@
|
|||||||
"@types/lodash.throttle": "4.1.6",
|
"@types/lodash.throttle": "4.1.6",
|
||||||
"@types/pako": "1.0.1",
|
"@types/pako": "1.0.1",
|
||||||
"eslint-config-prettier": "7.1.0",
|
"eslint-config-prettier": "7.1.0",
|
||||||
"eslint-plugin-prettier": "3.3.0",
|
"eslint-plugin-prettier": "3.3.1",
|
||||||
"firebase-tools": "9.0.1",
|
"firebase-tools": "9.2.1",
|
||||||
"husky": "4.3.6",
|
"husky": "4.3.8",
|
||||||
"jest-canvas-mock": "2.3.0",
|
"jest-canvas-mock": "2.3.0",
|
||||||
"lint-staged": "10.5.3",
|
"lint-staged": "10.5.3",
|
||||||
"pepjs": "0.5.3",
|
"pepjs": "0.5.3",
|
||||||
@@ -81,8 +80,8 @@
|
|||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build-node": "node ./scripts/build-node.js",
|
"build-node": "node ./scripts/build-node.js",
|
||||||
"build:app:docker": "REACT_APP_INCLUDE_GTAG=false REACT_APP_DISABLE_SENTRY=true react-scripts build",
|
"build:app:docker": "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:app": "REACT_APP_GIT_SHA=$VERCEL_GIT_COMMIT_SHA react-scripts build",
|
||||||
"build:version": "node ./scripts/build-version.js",
|
"build:version": "node ./scripts/build-version.js",
|
||||||
"build": "npm run build:app && npm run build:version",
|
"build": "npm run build:app && npm run build:version",
|
||||||
"eject": "react-scripts eject",
|
"eject": "react-scripts eject",
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 9.0 KiB After Width: | Height: | Size: 4.3 KiB |
@@ -86,10 +86,10 @@
|
|||||||
|
|
||||||
<link rel="stylesheet" href="fonts.css" type="text/css" />
|
<link rel="stylesheet" href="fonts.css" type="text/css" />
|
||||||
|
|
||||||
<% if (process.env.REACT_APP_INCLUDE_GTAG === 'true') { %>
|
<% if (process.env.REACT_APP_GOOGLE_ANALYTICS_ID) { %>
|
||||||
<script
|
<script
|
||||||
async
|
async
|
||||||
src="https://www.googletagmanager.com/gtag/js?id=UA-387204-13"
|
src="https://www.googletagmanager.com/gtag/js?id=%REACT_APP_GOOGLE_ANALYTICS_ID%"
|
||||||
></script>
|
></script>
|
||||||
<script>
|
<script>
|
||||||
window.dataLayer = window.dataLayer || [];
|
window.dataLayer = window.dataLayer || [];
|
||||||
@@ -97,7 +97,7 @@
|
|||||||
dataLayer.push(arguments);
|
dataLayer.push(arguments);
|
||||||
}
|
}
|
||||||
gtag("js", new Date());
|
gtag("js", new Date());
|
||||||
gtag("config", "UA-387204-13");
|
gtag("config", "%REACT_APP_GOOGLE_ANALYTICS_ID%");
|
||||||
</script>
|
</script>
|
||||||
<% } %>
|
<% } %>
|
||||||
|
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 6.4 KiB After Width: | Height: | Size: 2.6 KiB |
|
After Width: | Height: | Size: 76 KiB |
|
Before Width: | Height: | Size: 83 KiB After Width: | Height: | Size: 70 KiB |
@@ -5,23 +5,40 @@ const path = require("path");
|
|||||||
const versionFile = path.join("build", "version.json");
|
const versionFile = path.join("build", "version.json");
|
||||||
const indexFile = path.join("build", "index.html");
|
const indexFile = path.join("build", "index.html");
|
||||||
|
|
||||||
const zero = (digit) => `0${digit}`.slice(-2);
|
const versionDate = (date) => date.toISOString().replace(".000", "");
|
||||||
|
|
||||||
const versionDate = (date) => {
|
const commitHash = () => {
|
||||||
const date_ = `${date.getFullYear()}-${zero(date.getMonth() + 1)}-${zero(
|
try {
|
||||||
date.getDate(),
|
return require("child_process")
|
||||||
)}`;
|
.execSync("git rev-parse --short HEAD")
|
||||||
const time = `${zero(date.getHours())}-${zero(date.getMinutes())}-${zero(
|
.toString()
|
||||||
date.getSeconds(),
|
.trim();
|
||||||
)}`;
|
} catch {
|
||||||
return `${date_}-${time}`;
|
return "none";
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const now = new Date();
|
const commitDate = (hash) => {
|
||||||
|
try {
|
||||||
|
const unix = require("child_process")
|
||||||
|
.execSync(`git show -s --format=%ct ${hash}`)
|
||||||
|
.toString()
|
||||||
|
.trim();
|
||||||
|
const date = new Date(parseInt(unix) * 1000);
|
||||||
|
return versionDate(date);
|
||||||
|
} catch {
|
||||||
|
return versionDate(new Date());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const getFullVersion = () => {
|
||||||
|
const hash = commitHash();
|
||||||
|
return `${commitDate(hash)}-${hash}`;
|
||||||
|
};
|
||||||
|
|
||||||
const data = JSON.stringify(
|
const data = JSON.stringify(
|
||||||
{
|
{
|
||||||
version: versionDate(now),
|
version: getFullVersion(),
|
||||||
},
|
},
|
||||||
undefined,
|
undefined,
|
||||||
2,
|
2,
|
||||||
@@ -34,7 +51,7 @@ fs.readFile(indexFile, "utf8", (error, data) => {
|
|||||||
if (error) {
|
if (error) {
|
||||||
return console.error(error);
|
return console.error(error);
|
||||||
}
|
}
|
||||||
const result = data.replace(/{version}/g, versionDate(now));
|
const result = data.replace(/{version}/g, getFullVersion());
|
||||||
|
|
||||||
fs.writeFile(indexFile, result, "utf8", (error) => {
|
fs.writeFile(indexFile, result, "utf8", (error) => {
|
||||||
if (error) {
|
if (error) {
|
||||||
|
|||||||
@@ -4,26 +4,28 @@ const THRESSHOLD = 85;
|
|||||||
|
|
||||||
const crowdinMap = {
|
const crowdinMap = {
|
||||||
"ar-SA": "en-ar",
|
"ar-SA": "en-ar",
|
||||||
"el-GR": "en-el",
|
|
||||||
"fi-FI": "en-fi",
|
|
||||||
"ja-JP": "en-ja",
|
|
||||||
"bg-BG": "en-bg",
|
"bg-BG": "en-bg",
|
||||||
"ca-ES": "en-ca",
|
"ca-ES": "en-ca",
|
||||||
"de-DE": "en-de",
|
"de-DE": "en-de",
|
||||||
|
"el-GR": "en-el",
|
||||||
"es-ES": "en-es",
|
"es-ES": "en-es",
|
||||||
"fa-IR": "en-fa",
|
"fa-IR": "en-fa",
|
||||||
|
"fi-FI": "en-fi",
|
||||||
"fr-FR": "en-fr",
|
"fr-FR": "en-fr",
|
||||||
"he-IL": "en-he",
|
"he-IL": "en-he",
|
||||||
"hi-IN": "en-hi",
|
"hi-IN": "en-hi",
|
||||||
"hu-HU": "en-hu",
|
"hu-HU": "en-hu",
|
||||||
"id-ID": "en-id",
|
"id-ID": "en-id",
|
||||||
"it-IT": "en-it",
|
"it-IT": "en-it",
|
||||||
|
"ja-JP": "en-ja",
|
||||||
"ko-KR": "en-ko",
|
"ko-KR": "en-ko",
|
||||||
"my-MM": "en-my",
|
"my-MM": "en-my",
|
||||||
"nb-NO": "en-nb",
|
"nb-NO": "en-nb",
|
||||||
"nl-NL": "en-nl",
|
"nl-NL": "en-nl",
|
||||||
"nn-NO": "en-nnno",
|
"nn-NO": "en-nnno",
|
||||||
|
"pa-IN": "en-pain",
|
||||||
"pl-PL": "en-pl",
|
"pl-PL": "en-pl",
|
||||||
|
"pt-BR": "en-ptbr",
|
||||||
"pt-PT": "en-pt",
|
"pt-PT": "en-pt",
|
||||||
"ro-RO": "en-ro",
|
"ro-RO": "en-ro",
|
||||||
"ru-RU": "en-ru",
|
"ru-RU": "en-ru",
|
||||||
@@ -56,7 +58,9 @@ const flags = {
|
|||||||
"nb-NO": "🇳🇴",
|
"nb-NO": "🇳🇴",
|
||||||
"nl-NL": "🇳🇱",
|
"nl-NL": "🇳🇱",
|
||||||
"nn-NO": "🇳🇴",
|
"nn-NO": "🇳🇴",
|
||||||
|
"pa-IN": "🇮🇳",
|
||||||
"pl-PL": "🇵🇱",
|
"pl-PL": "🇵🇱",
|
||||||
|
"pt-BR": "🇧🇷",
|
||||||
"pt-PT": "🇵🇹",
|
"pt-PT": "🇵🇹",
|
||||||
"ro-RO": "🇷🇴",
|
"ro-RO": "🇷🇴",
|
||||||
"ru-RU": "🇷🇺",
|
"ru-RU": "🇷🇺",
|
||||||
@@ -71,7 +75,7 @@ const flags = {
|
|||||||
const languages = {
|
const languages = {
|
||||||
"ar-SA": "العربية",
|
"ar-SA": "العربية",
|
||||||
"bg-BG": "Български",
|
"bg-BG": "Български",
|
||||||
"ca-ES": "Catalan",
|
"ca-ES": "Català",
|
||||||
"de-DE": "Deutsch",
|
"de-DE": "Deutsch",
|
||||||
"el-GR": "Ελληνικά",
|
"el-GR": "Ελληνικά",
|
||||||
"es-ES": "Español",
|
"es-ES": "Español",
|
||||||
@@ -89,7 +93,9 @@ const languages = {
|
|||||||
"nb-NO": "Norsk bokmål",
|
"nb-NO": "Norsk bokmål",
|
||||||
"nl-NL": "Nederlands",
|
"nl-NL": "Nederlands",
|
||||||
"nn-NO": "Norsk nynorsk",
|
"nn-NO": "Norsk nynorsk",
|
||||||
|
"pa-IN": "ਪੰਜਾਬੀ",
|
||||||
"pl-PL": "Polski",
|
"pl-PL": "Polski",
|
||||||
|
"pt-BR": "Português Brasileiro",
|
||||||
"pt-PT": "Português",
|
"pt-PT": "Português",
|
||||||
"ro-RO": "Română",
|
"ro-RO": "Română",
|
||||||
"ru-RU": "Русский",
|
"ru-RU": "Русский",
|
||||||
@@ -114,16 +120,14 @@ const boldIf = (text, condition) => (condition ? `**${text}**` : text);
|
|||||||
|
|
||||||
const printHeader = () => {
|
const printHeader = () => {
|
||||||
let result = "| | Flag | Locale | % |\n";
|
let result = "| | Flag | Locale | % |\n";
|
||||||
result += "| --: | :--: | -- | --: |";
|
result += "| :--: | :--: | -- | :--: |";
|
||||||
return result;
|
return result;
|
||||||
};
|
};
|
||||||
|
|
||||||
const printRow = (id, locale, coverage) => {
|
const printRow = (id, locale, coverage) => {
|
||||||
const isOver = coverage > THRESSHOLD;
|
const isOver = coverage >= THRESSHOLD;
|
||||||
let result = `| ${boldIf(id, isOver)} | `;
|
let result = `| ${isOver ? id : "..."} | `;
|
||||||
|
|
||||||
result += `${locale in flags ? flags[locale] : ""} | `;
|
result += `${locale in flags ? flags[locale] : ""} | `;
|
||||||
|
|
||||||
const language = locale in languages ? languages[locale] : locale;
|
const language = locale in languages ? languages[locale] : locale;
|
||||||
if (locale in crowdinMap && crowdinMap[locale]) {
|
if (locale in crowdinMap && crowdinMap[locale]) {
|
||||||
result += `[${boldIf(
|
result += `[${boldIf(
|
||||||
@@ -133,14 +137,12 @@ const printRow = (id, locale, coverage) => {
|
|||||||
} else {
|
} else {
|
||||||
result += `${boldIf(language, isOver)} | `;
|
result += `${boldIf(language, isOver)} | `;
|
||||||
}
|
}
|
||||||
result += `${boldIf(coverage, isOver)} |`;
|
result += `${coverage === 100 ? "💯" : boldIf(coverage, isOver)} |`;
|
||||||
return result;
|
return result;
|
||||||
};
|
};
|
||||||
|
|
||||||
console.info("## Languages check");
|
|
||||||
console.info("\n\r");
|
|
||||||
console.info(
|
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.`,
|
`Each language must be at least **${THRESSHOLD}%** translated in order to appear on Excalidraw. Join us on [Crowdin](https://crowdin.com/project/excalidraw) and help us translate your own language. **Can't find yours yet?** Open an [issue](https://github.com/excalidraw/excalidraw/issues/new) and we'll add it to the list.`,
|
||||||
);
|
);
|
||||||
console.info("\n\r");
|
console.info("\n\r");
|
||||||
console.info(printHeader());
|
console.info(printHeader());
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ import { getSelectedElements } from "../scene";
|
|||||||
import { getNonDeletedElements } from "../element";
|
import { getNonDeletedElements } from "../element";
|
||||||
import { deepCopyElement } from "../element/newElement";
|
import { deepCopyElement } from "../element/newElement";
|
||||||
import { Library } from "../data/library";
|
import { Library } from "../data/library";
|
||||||
import { EVENT_LIBRARY, trackEvent } from "../analytics";
|
|
||||||
|
|
||||||
export const actionAddToLibrary = register({
|
export const actionAddToLibrary = register({
|
||||||
name: "addToLibrary",
|
name: "addToLibrary",
|
||||||
@@ -16,7 +15,6 @@ export const actionAddToLibrary = register({
|
|||||||
Library.loadLibrary().then((items) => {
|
Library.loadLibrary().then((items) => {
|
||||||
Library.saveLibrary([...items, selectedElements.map(deepCopyElement)]);
|
Library.saveLibrary([...items, selectedElements.map(deepCopyElement)]);
|
||||||
});
|
});
|
||||||
trackEvent(EVENT_LIBRARY, "add");
|
|
||||||
return false;
|
return false;
|
||||||
},
|
},
|
||||||
contextMenuOrder: 6,
|
contextMenuOrder: 6,
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { KEYS } from "../keys";
|
import { alignElements, Alignment } from "../align";
|
||||||
import { t } from "../i18n";
|
|
||||||
import { register } from "./register";
|
|
||||||
import {
|
import {
|
||||||
AlignBottomIcon,
|
AlignBottomIcon,
|
||||||
AlignLeftIcon,
|
AlignLeftIcon,
|
||||||
@@ -10,14 +8,15 @@ import {
|
|||||||
CenterHorizontallyIcon,
|
CenterHorizontallyIcon,
|
||||||
CenterVerticallyIcon,
|
CenterVerticallyIcon,
|
||||||
} from "../components/icons";
|
} from "../components/icons";
|
||||||
import { getSelectedElements, isSomeElementSelected } from "../scene";
|
|
||||||
import { getElementMap, getNonDeletedElements } from "../element";
|
|
||||||
import { ToolButton } from "../components/ToolButton";
|
import { ToolButton } from "../components/ToolButton";
|
||||||
|
import { getElementMap, getNonDeletedElements } from "../element";
|
||||||
import { ExcalidrawElement } from "../element/types";
|
import { ExcalidrawElement } from "../element/types";
|
||||||
|
import { t } from "../i18n";
|
||||||
|
import { KEYS } from "../keys";
|
||||||
|
import { getSelectedElements, isSomeElementSelected } from "../scene";
|
||||||
import { AppState } from "../types";
|
import { AppState } from "../types";
|
||||||
import { alignElements, Alignment } from "../align";
|
|
||||||
import { getShortcutKey } from "../utils";
|
import { getShortcutKey } from "../utils";
|
||||||
import { trackEvent, EVENT_ALIGN } from "../analytics";
|
import { register } from "./register";
|
||||||
|
|
||||||
const enableActionGroup = (
|
const enableActionGroup = (
|
||||||
elements: readonly ExcalidrawElement[],
|
elements: readonly ExcalidrawElement[],
|
||||||
@@ -44,7 +43,6 @@ const alignSelectedElements = (
|
|||||||
export const actionAlignTop = register({
|
export const actionAlignTop = register({
|
||||||
name: "alignTop",
|
name: "alignTop",
|
||||||
perform: (elements, appState) => {
|
perform: (elements, appState) => {
|
||||||
trackEvent(EVENT_ALIGN, "align", "top");
|
|
||||||
return {
|
return {
|
||||||
appState,
|
appState,
|
||||||
elements: alignSelectedElements(elements, appState, {
|
elements: alignSelectedElements(elements, appState, {
|
||||||
@@ -74,7 +72,6 @@ export const actionAlignTop = register({
|
|||||||
export const actionAlignBottom = register({
|
export const actionAlignBottom = register({
|
||||||
name: "alignBottom",
|
name: "alignBottom",
|
||||||
perform: (elements, appState) => {
|
perform: (elements, appState) => {
|
||||||
trackEvent(EVENT_ALIGN, "align", "bottom");
|
|
||||||
return {
|
return {
|
||||||
appState,
|
appState,
|
||||||
elements: alignSelectedElements(elements, appState, {
|
elements: alignSelectedElements(elements, appState, {
|
||||||
@@ -104,7 +101,6 @@ export const actionAlignBottom = register({
|
|||||||
export const actionAlignLeft = register({
|
export const actionAlignLeft = register({
|
||||||
name: "alignLeft",
|
name: "alignLeft",
|
||||||
perform: (elements, appState) => {
|
perform: (elements, appState) => {
|
||||||
trackEvent(EVENT_ALIGN, "align", "left");
|
|
||||||
return {
|
return {
|
||||||
appState,
|
appState,
|
||||||
elements: alignSelectedElements(elements, appState, {
|
elements: alignSelectedElements(elements, appState, {
|
||||||
@@ -134,7 +130,6 @@ export const actionAlignLeft = register({
|
|||||||
export const actionAlignRight = register({
|
export const actionAlignRight = register({
|
||||||
name: "alignRight",
|
name: "alignRight",
|
||||||
perform: (elements, appState) => {
|
perform: (elements, appState) => {
|
||||||
trackEvent(EVENT_ALIGN, "align", "right");
|
|
||||||
return {
|
return {
|
||||||
appState,
|
appState,
|
||||||
elements: alignSelectedElements(elements, appState, {
|
elements: alignSelectedElements(elements, appState, {
|
||||||
@@ -164,7 +159,6 @@ export const actionAlignRight = register({
|
|||||||
export const actionAlignVerticallyCentered = register({
|
export const actionAlignVerticallyCentered = register({
|
||||||
name: "alignVerticallyCentered",
|
name: "alignVerticallyCentered",
|
||||||
perform: (elements, appState) => {
|
perform: (elements, appState) => {
|
||||||
trackEvent(EVENT_ALIGN, "vertically", "center");
|
|
||||||
return {
|
return {
|
||||||
appState,
|
appState,
|
||||||
elements: alignSelectedElements(elements, appState, {
|
elements: alignSelectedElements(elements, appState, {
|
||||||
@@ -190,7 +184,6 @@ export const actionAlignVerticallyCentered = register({
|
|||||||
export const actionAlignHorizontallyCentered = register({
|
export const actionAlignHorizontallyCentered = register({
|
||||||
name: "alignHorizontallyCentered",
|
name: "alignHorizontallyCentered",
|
||||||
perform: (elements, appState) => {
|
perform: (elements, appState) => {
|
||||||
trackEvent(EVENT_ALIGN, "horizontally", "center");
|
|
||||||
return {
|
return {
|
||||||
appState,
|
appState,
|
||||||
elements: alignSelectedElements(elements, appState, {
|
elements: alignSelectedElements(elements, appState, {
|
||||||
|
|||||||
@@ -1,36 +1,24 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { ColorPicker } from "../components/ColorPicker";
|
|
||||||
import { getDefaultAppState } from "../appState";
|
import { getDefaultAppState } from "../appState";
|
||||||
import { trash, zoomIn, zoomOut, resetZoom } from "../components/icons";
|
import { ColorPicker } from "../components/ColorPicker";
|
||||||
|
import { resetZoom, trash, zoomIn, zoomOut } from "../components/icons";
|
||||||
import { ToolButton } from "../components/ToolButton";
|
import { ToolButton } from "../components/ToolButton";
|
||||||
import { t } from "../i18n";
|
import { getCommonBounds, getNonDeletedElements } from "../element";
|
||||||
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 { newElementWith } from "../element/mutateElement";
|
||||||
import { ExcalidrawElement } from "../element/types";
|
import { ExcalidrawElement } from "../element/types";
|
||||||
import { AppState, NormalizedZoomValue } from "../types";
|
import { t } from "../i18n";
|
||||||
import { getCommonBounds } from "../element";
|
import useIsMobile from "../is-mobile";
|
||||||
import { getNewZoom } from "../scene/zoom";
|
import { CODES, KEYS } from "../keys";
|
||||||
|
import { getNormalizedZoom, getSelectedElements } from "../scene";
|
||||||
import { centerScrollOn } from "../scene/scroll";
|
import { centerScrollOn } from "../scene/scroll";
|
||||||
import { EVENT_ACTION, EVENT_CHANGE, trackEvent } from "../analytics";
|
import { getNewZoom } from "../scene/zoom";
|
||||||
import colors from "../colors";
|
import { AppState, NormalizedZoomValue } from "../types";
|
||||||
|
import { getShortcutKey } from "../utils";
|
||||||
|
import { register } from "./register";
|
||||||
|
|
||||||
export const actionChangeViewBackgroundColor = register({
|
export const actionChangeViewBackgroundColor = register({
|
||||||
name: "changeViewBackgroundColor",
|
name: "changeViewBackgroundColor",
|
||||||
perform: (_, appState, value) => {
|
perform: (_, appState, value) => {
|
||||||
if (value !== appState.viewBackgroundColor) {
|
|
||||||
trackEvent(
|
|
||||||
EVENT_CHANGE,
|
|
||||||
"canvas color",
|
|
||||||
colors.canvasBackground.includes(value)
|
|
||||||
? `${value} (picker ${colors.canvasBackground.indexOf(value)})`
|
|
||||||
: value,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return {
|
return {
|
||||||
appState: { ...appState, viewBackgroundColor: value },
|
appState: { ...appState, viewBackgroundColor: value },
|
||||||
commitToHistory: true,
|
commitToHistory: true,
|
||||||
@@ -53,7 +41,6 @@ export const actionChangeViewBackgroundColor = register({
|
|||||||
export const actionClearCanvas = register({
|
export const actionClearCanvas = register({
|
||||||
name: "clearCanvas",
|
name: "clearCanvas",
|
||||||
perform: (elements, appState: AppState) => {
|
perform: (elements, appState: AppState) => {
|
||||||
trackEvent(EVENT_ACTION, "clear canvas");
|
|
||||||
return {
|
return {
|
||||||
elements: elements.map((element) =>
|
elements: elements.map((element) =>
|
||||||
newElementWith(element, { isDeleted: true }),
|
newElementWith(element, { isDeleted: true }),
|
||||||
@@ -67,6 +54,7 @@ export const actionClearCanvas = register({
|
|||||||
gridSize: appState.gridSize,
|
gridSize: appState.gridSize,
|
||||||
shouldAddWatermark: appState.shouldAddWatermark,
|
shouldAddWatermark: appState.shouldAddWatermark,
|
||||||
showStats: appState.showStats,
|
showStats: appState.showStats,
|
||||||
|
pasteDialog: appState.pasteDialog,
|
||||||
},
|
},
|
||||||
commitToHistory: true,
|
commitToHistory: true,
|
||||||
};
|
};
|
||||||
@@ -98,7 +86,6 @@ export const actionZoomIn = register({
|
|||||||
{ left: appState.offsetLeft, top: appState.offsetTop },
|
{ left: appState.offsetLeft, top: appState.offsetTop },
|
||||||
{ x: appState.width / 2, y: appState.height / 2 },
|
{ x: appState.width / 2, y: appState.height / 2 },
|
||||||
);
|
);
|
||||||
trackEvent(EVENT_ACTION, "zoom", "in", zoom.value * 100);
|
|
||||||
return {
|
return {
|
||||||
appState: {
|
appState: {
|
||||||
...appState,
|
...appState,
|
||||||
@@ -133,7 +120,6 @@ export const actionZoomOut = register({
|
|||||||
{ x: appState.width / 2, y: appState.height / 2 },
|
{ x: appState.width / 2, y: appState.height / 2 },
|
||||||
);
|
);
|
||||||
|
|
||||||
trackEvent(EVENT_ACTION, "zoom", "out", zoom.value * 100);
|
|
||||||
return {
|
return {
|
||||||
appState: {
|
appState: {
|
||||||
...appState,
|
...appState,
|
||||||
@@ -161,7 +147,6 @@ export const actionZoomOut = register({
|
|||||||
export const actionResetZoom = register({
|
export const actionResetZoom = register({
|
||||||
name: "resetZoom",
|
name: "resetZoom",
|
||||||
perform: (_elements, appState) => {
|
perform: (_elements, appState) => {
|
||||||
trackEvent(EVENT_ACTION, "zoom", "reset", 100);
|
|
||||||
return {
|
return {
|
||||||
appState: {
|
appState: {
|
||||||
...appState,
|
...appState,
|
||||||
@@ -234,12 +219,10 @@ const zoomToFitElements = (
|
|||||||
left: appState.offsetLeft,
|
left: appState.offsetLeft,
|
||||||
top: appState.offsetTop,
|
top: appState.offsetTop,
|
||||||
});
|
});
|
||||||
const action = zoomToSelection ? "selection" : "fit";
|
|
||||||
|
|
||||||
const [x1, y1, x2, y2] = commonBounds;
|
const [x1, y1, x2, y2] = commonBounds;
|
||||||
const centerX = (x1 + x2) / 2;
|
const centerX = (x1 + x2) / 2;
|
||||||
const centerY = (y1 + y2) / 2;
|
const centerY = (y1 + y2) / 2;
|
||||||
trackEvent(EVENT_ACTION, "zoom", action, newZoom.value * 100);
|
|
||||||
return {
|
return {
|
||||||
appState: {
|
appState: {
|
||||||
...appState,
|
...appState,
|
||||||
|
|||||||
@@ -1,19 +1,18 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { CODES } from "../keys";
|
|
||||||
import { t } from "../i18n";
|
|
||||||
import { register } from "./register";
|
|
||||||
import {
|
import {
|
||||||
DistributeHorizontallyIcon,
|
DistributeHorizontallyIcon,
|
||||||
DistributeVerticallyIcon,
|
DistributeVerticallyIcon,
|
||||||
} from "../components/icons";
|
} from "../components/icons";
|
||||||
import { getSelectedElements, isSomeElementSelected } from "../scene";
|
|
||||||
import { getElementMap, getNonDeletedElements } from "../element";
|
|
||||||
import { ToolButton } from "../components/ToolButton";
|
import { ToolButton } from "../components/ToolButton";
|
||||||
import { ExcalidrawElement } from "../element/types";
|
|
||||||
import { AppState } from "../types";
|
|
||||||
import { distributeElements, Distribution } from "../disitrubte";
|
import { distributeElements, Distribution } from "../disitrubte";
|
||||||
|
import { getElementMap, getNonDeletedElements } from "../element";
|
||||||
|
import { ExcalidrawElement } from "../element/types";
|
||||||
|
import { t } from "../i18n";
|
||||||
|
import { CODES } from "../keys";
|
||||||
|
import { getSelectedElements, isSomeElementSelected } from "../scene";
|
||||||
|
import { AppState } from "../types";
|
||||||
import { getShortcutKey } from "../utils";
|
import { getShortcutKey } from "../utils";
|
||||||
import { EVENT_ALIGN, trackEvent } from "../analytics";
|
import { register } from "./register";
|
||||||
|
|
||||||
const enableActionGroup = (
|
const enableActionGroup = (
|
||||||
elements: readonly ExcalidrawElement[],
|
elements: readonly ExcalidrawElement[],
|
||||||
@@ -40,7 +39,6 @@ const distributeSelectedElements = (
|
|||||||
export const distributeHorizontally = register({
|
export const distributeHorizontally = register({
|
||||||
name: "distributeHorizontally",
|
name: "distributeHorizontally",
|
||||||
perform: (elements, appState) => {
|
perform: (elements, appState) => {
|
||||||
trackEvent(EVENT_ALIGN, "distribute", "horizontally");
|
|
||||||
return {
|
return {
|
||||||
appState,
|
appState,
|
||||||
elements: distributeSelectedElements(elements, appState, {
|
elements: distributeSelectedElements(elements, appState, {
|
||||||
@@ -69,7 +67,6 @@ export const distributeHorizontally = register({
|
|||||||
export const distributeVertically = register({
|
export const distributeVertically = register({
|
||||||
name: "distributeVertically",
|
name: "distributeVertically",
|
||||||
perform: (elements, appState) => {
|
perform: (elements, appState) => {
|
||||||
trackEvent(EVENT_ALIGN, "distribute", "vertically");
|
|
||||||
return {
|
return {
|
||||||
appState,
|
appState,
|
||||||
elements: distributeSelectedElements(elements, appState, {
|
elements: distributeSelectedElements(elements, appState, {
|
||||||
|
|||||||
@@ -1,22 +1,20 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { EVENT_CHANGE, EVENT_IO, trackEvent } from "../analytics";
|
import { trackEvent } from "../analytics";
|
||||||
import { load, save, saveAs } from "../components/icons";
|
import { load, questionCircle, save, saveAs } from "../components/icons";
|
||||||
import { ProjectName } from "../components/ProjectName";
|
import { ProjectName } from "../components/ProjectName";
|
||||||
import { ToolButton } from "../components/ToolButton";
|
import { ToolButton } from "../components/ToolButton";
|
||||||
|
import "../components/ToolIcon.scss";
|
||||||
import { Tooltip } from "../components/Tooltip";
|
import { Tooltip } from "../components/Tooltip";
|
||||||
import { questionCircle } from "../components/icons";
|
|
||||||
import { loadFromJSON, saveAsJSON } from "../data";
|
import { loadFromJSON, saveAsJSON } from "../data";
|
||||||
import { t } from "../i18n";
|
import { t } from "../i18n";
|
||||||
import useIsMobile from "../is-mobile";
|
import useIsMobile from "../is-mobile";
|
||||||
import { KEYS } from "../keys";
|
import { KEYS } from "../keys";
|
||||||
import { muteFSAbortError } from "../utils";
|
|
||||||
import { register } from "./register";
|
import { register } from "./register";
|
||||||
import "../components/ToolIcon.scss";
|
|
||||||
|
|
||||||
export const actionChangeProjectName = register({
|
export const actionChangeProjectName = register({
|
||||||
name: "changeProjectName",
|
name: "changeProjectName",
|
||||||
perform: (_elements, appState, value) => {
|
perform: (_elements, appState, value) => {
|
||||||
trackEvent(EVENT_CHANGE, "title");
|
trackEvent("change", "title");
|
||||||
return { appState: { ...appState, name: value }, commitToHistory: false };
|
return { appState: { ...appState, name: value }, commitToHistory: false };
|
||||||
},
|
},
|
||||||
PanelComponent: ({ appState, updateData }) => (
|
PanelComponent: ({ appState, updateData }) => (
|
||||||
@@ -100,7 +98,6 @@ export const actionSaveScene = register({
|
|||||||
perform: async (elements, appState, value) => {
|
perform: async (elements, appState, value) => {
|
||||||
try {
|
try {
|
||||||
const { fileHandle } = await saveAsJSON(elements, appState);
|
const { fileHandle } = await saveAsJSON(elements, appState);
|
||||||
trackEvent(EVENT_IO, "save");
|
|
||||||
return { commitToHistory: false, appState: { ...appState, fileHandle } };
|
return { commitToHistory: false, appState: { ...appState, fileHandle } };
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error?.name !== "AbortError") {
|
if (error?.name !== "AbortError") {
|
||||||
@@ -131,7 +128,6 @@ export const actionSaveAsScene = register({
|
|||||||
...appState,
|
...appState,
|
||||||
fileHandle: null,
|
fileHandle: null,
|
||||||
});
|
});
|
||||||
trackEvent(EVENT_IO, "save as");
|
|
||||||
return { commitToHistory: false, appState: { ...appState, fileHandle } };
|
return { commitToHistory: false, appState: { ...appState, fileHandle } };
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error?.name !== "AbortError") {
|
if (error?.name !== "AbortError") {
|
||||||
@@ -159,18 +155,29 @@ export const actionSaveAsScene = register({
|
|||||||
|
|
||||||
export const actionLoadScene = register({
|
export const actionLoadScene = register({
|
||||||
name: "loadScene",
|
name: "loadScene",
|
||||||
perform: (
|
perform: async (elements, appState) => {
|
||||||
elements,
|
try {
|
||||||
appState,
|
const {
|
||||||
{ elements: loadedElements, appState: loadedAppState, error },
|
elements: loadedElements,
|
||||||
) => ({
|
appState: loadedAppState,
|
||||||
elements: loadedElements,
|
} = await loadFromJSON(appState);
|
||||||
appState: {
|
return {
|
||||||
...loadedAppState,
|
elements: loadedElements,
|
||||||
errorMessage: error,
|
appState: loadedAppState,
|
||||||
},
|
commitToHistory: true,
|
||||||
commitToHistory: true,
|
};
|
||||||
}),
|
} catch (error) {
|
||||||
|
if (error?.name === "AbortError") {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
elements,
|
||||||
|
appState: { ...appState, errorMessage: error.message },
|
||||||
|
commitToHistory: false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
},
|
||||||
|
keyTest: (event) => event[KEYS.CTRL_OR_CMD] && event.key === KEYS.O,
|
||||||
PanelComponent: ({ updateData, appState }) => (
|
PanelComponent: ({ updateData, appState }) => (
|
||||||
<ToolButton
|
<ToolButton
|
||||||
type="button"
|
type="button"
|
||||||
@@ -178,16 +185,7 @@ export const actionLoadScene = register({
|
|||||||
title={t("buttons.load")}
|
title={t("buttons.load")}
|
||||||
aria-label={t("buttons.load")}
|
aria-label={t("buttons.load")}
|
||||||
showAriaLabel={useIsMobile()}
|
showAriaLabel={useIsMobile()}
|
||||||
onClick={() => {
|
onClick={updateData}
|
||||||
loadFromJSON(appState)
|
|
||||||
.then(({ elements, appState }) => {
|
|
||||||
updateData({ elements, appState });
|
|
||||||
})
|
|
||||||
.catch(muteFSAbortError)
|
|
||||||
.catch((error) => {
|
|
||||||
updateData({ error: error.message });
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -118,11 +118,14 @@ export const actionFinalize = register({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!appState.elementLocked) {
|
if (!appState.elementLocked && appState.elementType !== "draw") {
|
||||||
appState.selectedElementIds[multiPointElement.id] = true;
|
appState.selectedElementIds[multiPointElement.id] = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!appState.elementLocked || !multiPointElement) {
|
if (
|
||||||
|
(!appState.elementLocked && appState.elementType !== "draw") ||
|
||||||
|
!multiPointElement
|
||||||
|
) {
|
||||||
resetCursor();
|
resetCursor();
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
@@ -130,7 +133,8 @@ export const actionFinalize = register({
|
|||||||
appState: {
|
appState: {
|
||||||
...appState,
|
...appState,
|
||||||
elementType:
|
elementType:
|
||||||
appState.elementLocked && multiPointElement
|
(appState.elementLocked || appState.elementType === "draw") &&
|
||||||
|
multiPointElement
|
||||||
? appState.elementType
|
? appState.elementType
|
||||||
: "selection",
|
: "selection",
|
||||||
draggingElement: null,
|
draggingElement: null,
|
||||||
@@ -139,7 +143,9 @@ export const actionFinalize = register({
|
|||||||
startBoundElement: null,
|
startBoundElement: null,
|
||||||
suggestedBindings: [],
|
suggestedBindings: [],
|
||||||
selectedElementIds:
|
selectedElementIds:
|
||||||
multiPointElement && !appState.elementLocked
|
multiPointElement &&
|
||||||
|
!appState.elementLocked &&
|
||||||
|
appState.elementType !== "draw"
|
||||||
? {
|
? {
|
||||||
...appState.selectedElementIds,
|
...appState.selectedElementIds,
|
||||||
[multiPointElement.id]: true,
|
[multiPointElement.id]: true,
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ import { register } from "./register";
|
|||||||
import { allowFullScreen, exitFullScreen, isFullScreen } from "../utils";
|
import { allowFullScreen, exitFullScreen, isFullScreen } from "../utils";
|
||||||
import { CODES, KEYS } from "../keys";
|
import { CODES, KEYS } from "../keys";
|
||||||
import { HelpIcon } from "../components/HelpIcon";
|
import { HelpIcon } from "../components/HelpIcon";
|
||||||
import { EVENT_DIALOG, trackEvent } from "../analytics";
|
|
||||||
|
|
||||||
export const actionToggleCanvasMenu = register({
|
export const actionToggleCanvasMenu = register({
|
||||||
name: "toggleCanvasMenu",
|
name: "toggleCanvasMenu",
|
||||||
@@ -72,17 +71,16 @@ export const actionFullScreen = register({
|
|||||||
export const actionShortcuts = register({
|
export const actionShortcuts = register({
|
||||||
name: "toggleShortcuts",
|
name: "toggleShortcuts",
|
||||||
perform: (_elements, appState) => {
|
perform: (_elements, appState) => {
|
||||||
trackEvent(EVENT_DIALOG, "shortcuts");
|
|
||||||
return {
|
return {
|
||||||
appState: {
|
appState: {
|
||||||
...appState,
|
...appState,
|
||||||
showShortcutsDialog: true,
|
showHelpDialog: true,
|
||||||
},
|
},
|
||||||
commitToHistory: false,
|
commitToHistory: false,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
PanelComponent: ({ updateData }) => (
|
PanelComponent: ({ updateData }) => (
|
||||||
<HelpIcon title={t("shortcutsDialog.title")} onClick={updateData} />
|
<HelpIcon title={t("helpDialog.title")} onClick={updateData} />
|
||||||
),
|
),
|
||||||
keyTest: (event) => event.key === KEYS.QUESTION_MARK,
|
keyTest: (event) => event.key === KEYS.QUESTION_MARK,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,16 +1,14 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { Avatar } from "../components/Avatar";
|
|
||||||
import { register } from "./register";
|
|
||||||
import { getClientColors, getClientInitials } from "../clients";
|
import { getClientColors, getClientInitials } from "../clients";
|
||||||
import { Collaborator } from "../types";
|
import { Avatar } from "../components/Avatar";
|
||||||
import { centerScrollOn } from "../scene/scroll";
|
import { centerScrollOn } from "../scene/scroll";
|
||||||
import { EVENT_SHARE, trackEvent } from "../analytics";
|
import { Collaborator } from "../types";
|
||||||
|
import { register } from "./register";
|
||||||
|
|
||||||
export const actionGoToCollaborator = register({
|
export const actionGoToCollaborator = register({
|
||||||
name: "goToCollaborator",
|
name: "goToCollaborator",
|
||||||
perform: (_elements, appState, value) => {
|
perform: (_elements, appState, value) => {
|
||||||
const point = value as Collaborator["pointer"];
|
const point = value as Collaborator["pointer"];
|
||||||
trackEvent(EVENT_SHARE, "go to collaborator");
|
|
||||||
if (!point) {
|
if (!point) {
|
||||||
return { appState, commitToHistory: false };
|
return { appState, commitToHistory: false };
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,56 +1,53 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { getLanguage } from "../i18n";
|
import { AppState } from "../../src/types";
|
||||||
import {
|
|
||||||
ExcalidrawElement,
|
|
||||||
ExcalidrawTextElement,
|
|
||||||
TextAlign,
|
|
||||||
FontFamily,
|
|
||||||
ExcalidrawLinearElement,
|
|
||||||
Arrowhead,
|
|
||||||
} from "../element/types";
|
|
||||||
import {
|
|
||||||
getCommonAttributeOfSelectedElements,
|
|
||||||
isSomeElementSelected,
|
|
||||||
getTargetElements,
|
|
||||||
canChangeSharpness,
|
|
||||||
canHaveArrowheads,
|
|
||||||
} from "../scene";
|
|
||||||
import { ButtonSelect } from "../components/ButtonSelect";
|
|
||||||
import { ButtonIconSelect } from "../components/ButtonIconSelect";
|
import { ButtonIconSelect } from "../components/ButtonIconSelect";
|
||||||
|
import { ButtonSelect } from "../components/ButtonSelect";
|
||||||
|
import { ColorPicker } from "../components/ColorPicker";
|
||||||
import { IconPicker } from "../components/IconPicker";
|
import { IconPicker } from "../components/IconPicker";
|
||||||
import {
|
import {
|
||||||
isTextElement,
|
|
||||||
redrawTextBoundingBox,
|
|
||||||
getNonDeletedElements,
|
|
||||||
} from "../element";
|
|
||||||
import { isLinearElement, isLinearElementType } from "../element/typeChecks";
|
|
||||||
import { ColorPicker } from "../components/ColorPicker";
|
|
||||||
import { AppState } from "../../src/types";
|
|
||||||
import { t } from "../i18n";
|
|
||||||
import { register } from "./register";
|
|
||||||
import { newElementWith } from "../element/mutateElement";
|
|
||||||
import { DEFAULT_FONT_SIZE, DEFAULT_FONT_FAMILY } from "../constants";
|
|
||||||
import { randomInteger } from "../random";
|
|
||||||
import {
|
|
||||||
FillHachureIcon,
|
|
||||||
FillCrossHatchIcon,
|
|
||||||
FillSolidIcon,
|
|
||||||
StrokeWidthIcon,
|
|
||||||
StrokeStyleSolidIcon,
|
|
||||||
StrokeStyleDashedIcon,
|
|
||||||
StrokeStyleDottedIcon,
|
|
||||||
EdgeSharpIcon,
|
|
||||||
EdgeRoundIcon,
|
|
||||||
SloppinessArchitectIcon,
|
|
||||||
SloppinessArtistIcon,
|
|
||||||
SloppinessCartoonistIcon,
|
|
||||||
ArrowheadArrowIcon,
|
ArrowheadArrowIcon,
|
||||||
ArrowheadBarIcon,
|
ArrowheadBarIcon,
|
||||||
ArrowheadDotIcon,
|
ArrowheadDotIcon,
|
||||||
ArrowheadNoneIcon,
|
ArrowheadNoneIcon,
|
||||||
|
EdgeRoundIcon,
|
||||||
|
EdgeSharpIcon,
|
||||||
|
FillCrossHatchIcon,
|
||||||
|
FillHachureIcon,
|
||||||
|
FillSolidIcon,
|
||||||
|
SloppinessArchitectIcon,
|
||||||
|
SloppinessArtistIcon,
|
||||||
|
SloppinessCartoonistIcon,
|
||||||
|
StrokeStyleDashedIcon,
|
||||||
|
StrokeStyleDottedIcon,
|
||||||
|
StrokeStyleSolidIcon,
|
||||||
|
StrokeWidthIcon,
|
||||||
} from "../components/icons";
|
} from "../components/icons";
|
||||||
import { EVENT_CHANGE, trackEvent } from "../analytics";
|
import { DEFAULT_FONT_FAMILY, DEFAULT_FONT_SIZE } from "../constants";
|
||||||
import colors from "../colors";
|
import {
|
||||||
|
getNonDeletedElements,
|
||||||
|
isTextElement,
|
||||||
|
redrawTextBoundingBox,
|
||||||
|
} from "../element";
|
||||||
|
import { newElementWith } from "../element/mutateElement";
|
||||||
|
import { isLinearElement, isLinearElementType } from "../element/typeChecks";
|
||||||
|
import {
|
||||||
|
Arrowhead,
|
||||||
|
ExcalidrawElement,
|
||||||
|
ExcalidrawLinearElement,
|
||||||
|
ExcalidrawTextElement,
|
||||||
|
FontFamily,
|
||||||
|
TextAlign,
|
||||||
|
} from "../element/types";
|
||||||
|
import { getLanguage, t } from "../i18n";
|
||||||
|
import { randomInteger } from "../random";
|
||||||
|
import {
|
||||||
|
canChangeSharpness,
|
||||||
|
canHaveArrowheads,
|
||||||
|
getCommonAttributeOfSelectedElements,
|
||||||
|
getTargetElements,
|
||||||
|
isSomeElementSelected,
|
||||||
|
} from "../scene";
|
||||||
|
import { register } from "./register";
|
||||||
|
|
||||||
const changeProperty = (
|
const changeProperty = (
|
||||||
elements: readonly ExcalidrawElement[],
|
elements: readonly ExcalidrawElement[],
|
||||||
@@ -92,15 +89,6 @@ const getFormValue = function <T>(
|
|||||||
export const actionChangeStrokeColor = register({
|
export const actionChangeStrokeColor = register({
|
||||||
name: "changeStrokeColor",
|
name: "changeStrokeColor",
|
||||||
perform: (elements, appState, value) => {
|
perform: (elements, appState, value) => {
|
||||||
if (value !== appState.currentItemStrokeColor) {
|
|
||||||
trackEvent(
|
|
||||||
EVENT_CHANGE,
|
|
||||||
"stroke color",
|
|
||||||
colors.elementStroke.includes(value)
|
|
||||||
? `${value} (picker ${colors.elementStroke.indexOf(value)})`
|
|
||||||
: value,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return {
|
return {
|
||||||
elements: changeProperty(elements, appState, (el) =>
|
elements: changeProperty(elements, appState, (el) =>
|
||||||
newElementWith(el, {
|
newElementWith(el, {
|
||||||
@@ -132,16 +120,6 @@ export const actionChangeStrokeColor = register({
|
|||||||
export const actionChangeBackgroundColor = register({
|
export const actionChangeBackgroundColor = register({
|
||||||
name: "changeBackgroundColor",
|
name: "changeBackgroundColor",
|
||||||
perform: (elements, appState, value) => {
|
perform: (elements, appState, value) => {
|
||||||
if (value !== appState.currentItemBackgroundColor) {
|
|
||||||
trackEvent(
|
|
||||||
EVENT_CHANGE,
|
|
||||||
"background color",
|
|
||||||
colors.elementBackground.includes(value)
|
|
||||||
? `${value} (picker ${colors.elementBackground.indexOf(value)})`
|
|
||||||
: value,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
elements: changeProperty(elements, appState, (el) =>
|
elements: changeProperty(elements, appState, (el) =>
|
||||||
newElementWith(el, {
|
newElementWith(el, {
|
||||||
@@ -173,7 +151,6 @@ export const actionChangeBackgroundColor = register({
|
|||||||
export const actionChangeFillStyle = register({
|
export const actionChangeFillStyle = register({
|
||||||
name: "changeFillStyle",
|
name: "changeFillStyle",
|
||||||
perform: (elements, appState, value) => {
|
perform: (elements, appState, value) => {
|
||||||
trackEvent(EVENT_CHANGE, "fill", value);
|
|
||||||
return {
|
return {
|
||||||
elements: changeProperty(elements, appState, (el) =>
|
elements: changeProperty(elements, appState, (el) =>
|
||||||
newElementWith(el, {
|
newElementWith(el, {
|
||||||
@@ -223,7 +200,6 @@ export const actionChangeFillStyle = register({
|
|||||||
export const actionChangeStrokeWidth = register({
|
export const actionChangeStrokeWidth = register({
|
||||||
name: "changeStrokeWidth",
|
name: "changeStrokeWidth",
|
||||||
perform: (elements, appState, value) => {
|
perform: (elements, appState, value) => {
|
||||||
trackEvent(EVENT_CHANGE, "stroke", "width", value);
|
|
||||||
return {
|
return {
|
||||||
elements: changeProperty(elements, appState, (el) =>
|
elements: changeProperty(elements, appState, (el) =>
|
||||||
newElementWith(el, {
|
newElementWith(el, {
|
||||||
@@ -286,7 +262,6 @@ export const actionChangeStrokeWidth = register({
|
|||||||
export const actionChangeSloppiness = register({
|
export const actionChangeSloppiness = register({
|
||||||
name: "changeSloppiness",
|
name: "changeSloppiness",
|
||||||
perform: (elements, appState, value) => {
|
perform: (elements, appState, value) => {
|
||||||
trackEvent(EVENT_CHANGE, "stroke", "sloppiness", value);
|
|
||||||
return {
|
return {
|
||||||
elements: changeProperty(elements, appState, (el) =>
|
elements: changeProperty(elements, appState, (el) =>
|
||||||
newElementWith(el, {
|
newElementWith(el, {
|
||||||
@@ -335,7 +310,6 @@ export const actionChangeSloppiness = register({
|
|||||||
export const actionChangeStrokeStyle = register({
|
export const actionChangeStrokeStyle = register({
|
||||||
name: "changeStrokeStyle",
|
name: "changeStrokeStyle",
|
||||||
perform: (elements, appState, value) => {
|
perform: (elements, appState, value) => {
|
||||||
trackEvent(EVENT_CHANGE, "style", value);
|
|
||||||
return {
|
return {
|
||||||
elements: changeProperty(elements, appState, (el) =>
|
elements: changeProperty(elements, appState, (el) =>
|
||||||
newElementWith(el, {
|
newElementWith(el, {
|
||||||
@@ -383,7 +357,6 @@ export const actionChangeStrokeStyle = register({
|
|||||||
export const actionChangeOpacity = register({
|
export const actionChangeOpacity = register({
|
||||||
name: "changeOpacity",
|
name: "changeOpacity",
|
||||||
perform: (elements, appState, value) => {
|
perform: (elements, appState, value) => {
|
||||||
trackEvent(EVENT_CHANGE, "opacity", "value", value);
|
|
||||||
return {
|
return {
|
||||||
elements: changeProperty(elements, appState, (el) =>
|
elements: changeProperty(elements, appState, (el) =>
|
||||||
newElementWith(el, {
|
newElementWith(el, {
|
||||||
@@ -580,7 +553,6 @@ export const actionChangeSharpness = register({
|
|||||||
const shouldUpdateForLinearElements = targetElements.length
|
const shouldUpdateForLinearElements = targetElements.length
|
||||||
? targetElements.every(isLinearElement)
|
? targetElements.every(isLinearElement)
|
||||||
: isLinearElementType(appState.elementType);
|
: isLinearElementType(appState.elementType);
|
||||||
trackEvent(EVENT_CHANGE, "edge", value);
|
|
||||||
return {
|
return {
|
||||||
elements: changeProperty(elements, appState, (el) =>
|
elements: changeProperty(elements, appState, (el) =>
|
||||||
newElementWith(el, {
|
newElementWith(el, {
|
||||||
@@ -642,12 +614,6 @@ export const actionChangeArrowhead = register({
|
|||||||
return {
|
return {
|
||||||
elements: changeProperty(elements, appState, (el) => {
|
elements: changeProperty(elements, appState, (el) => {
|
||||||
if (isLinearElement(el)) {
|
if (isLinearElement(el)) {
|
||||||
trackEvent(
|
|
||||||
EVENT_CHANGE,
|
|
||||||
`arrowhead ${value.position}`,
|
|
||||||
value.type || "none",
|
|
||||||
);
|
|
||||||
|
|
||||||
const { position, type } = value;
|
const { position, type } = value;
|
||||||
|
|
||||||
if (position === "start") {
|
if (position === "start") {
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import {
|
|||||||
redrawTextBoundingBox,
|
redrawTextBoundingBox,
|
||||||
} from "../element";
|
} from "../element";
|
||||||
import { CODES, KEYS } from "../keys";
|
import { CODES, KEYS } from "../keys";
|
||||||
|
import { t } from "../i18n";
|
||||||
import { register } from "./register";
|
import { register } from "./register";
|
||||||
import { mutateElement, newElementWith } from "../element/mutateElement";
|
import { mutateElement, newElementWith } from "../element/mutateElement";
|
||||||
import {
|
import {
|
||||||
@@ -23,6 +24,10 @@ export const actionCopyStyles = register({
|
|||||||
copiedStyles = JSON.stringify(element);
|
copiedStyles = JSON.stringify(element);
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
|
appState: {
|
||||||
|
...appState,
|
||||||
|
toastMessage: t("toast.copyStyles"),
|
||||||
|
},
|
||||||
commitToHistory: false,
|
commitToHistory: false,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ export type ShortcutName =
|
|||||||
| "group"
|
| "group"
|
||||||
| "ungroup"
|
| "ungroup"
|
||||||
| "gridMode"
|
| "gridMode"
|
||||||
|
| "zenMode"
|
||||||
| "stats"
|
| "stats"
|
||||||
| "addToLibrary";
|
| "addToLibrary";
|
||||||
|
|
||||||
@@ -33,7 +34,7 @@ const shortcutMap: Record<ShortcutName, string[]> = {
|
|||||||
delete: [getShortcutKey("Del")],
|
delete: [getShortcutKey("Del")],
|
||||||
duplicateSelection: [
|
duplicateSelection: [
|
||||||
getShortcutKey("CtrlOrCmd+D"),
|
getShortcutKey("CtrlOrCmd+D"),
|
||||||
getShortcutKey(`Alt+${t("shortcutsDialog.drag")}`),
|
getShortcutKey(`Alt+${t("helpDialog.drag")}`),
|
||||||
],
|
],
|
||||||
sendBackward: [getShortcutKey("CtrlOrCmd+[")],
|
sendBackward: [getShortcutKey("CtrlOrCmd+[")],
|
||||||
bringForward: [getShortcutKey("CtrlOrCmd+]")],
|
bringForward: [getShortcutKey("CtrlOrCmd+]")],
|
||||||
@@ -52,6 +53,7 @@ const shortcutMap: Record<ShortcutName, string[]> = {
|
|||||||
group: [getShortcutKey("CtrlOrCmd+G")],
|
group: [getShortcutKey("CtrlOrCmd+G")],
|
||||||
ungroup: [getShortcutKey("CtrlOrCmd+Shift+G")],
|
ungroup: [getShortcutKey("CtrlOrCmd+Shift+G")],
|
||||||
gridMode: [getShortcutKey("CtrlOrCmd+'")],
|
gridMode: [getShortcutKey("CtrlOrCmd+'")],
|
||||||
|
zenMode: [getShortcutKey("Alt+Z")],
|
||||||
stats: [],
|
stats: [],
|
||||||
addToLibrary: [],
|
addToLibrary: [],
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,18 +1,8 @@
|
|||||||
export const EVENT_ACTION = "action";
|
|
||||||
export const EVENT_ALIGN = "align";
|
|
||||||
export const EVENT_CHANGE = "change";
|
|
||||||
export const EVENT_DIALOG = "dialog";
|
|
||||||
export const EVENT_EXIT = "exit";
|
|
||||||
export const EVENT_IO = "io";
|
|
||||||
export const EVENT_LAYER = "layer";
|
|
||||||
export const EVENT_LIBRARY = "library";
|
|
||||||
export const EVENT_LOAD = "load";
|
|
||||||
export const EVENT_SHAPE = "shape";
|
|
||||||
export const EVENT_SHARE = "share";
|
|
||||||
export const EVENT_MAGIC = "magic";
|
|
||||||
|
|
||||||
export const trackEvent =
|
export const trackEvent =
|
||||||
typeof window !== "undefined" && window.gtag
|
typeof process !== "undefined" &&
|
||||||
|
process.env?.REACT_APP_GOOGLE_ANALYTICS_ID &&
|
||||||
|
typeof window !== "undefined" &&
|
||||||
|
window.gtag
|
||||||
? (category: string, name: string, label?: string, value?: number) => {
|
? (category: string, name: string, label?: string, value?: number) => {
|
||||||
window.gtag("event", name, {
|
window.gtag("event", name, {
|
||||||
event_category: category,
|
event_category: category,
|
||||||
@@ -20,8 +10,9 @@ export const trackEvent =
|
|||||||
value,
|
value,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
: typeof process !== "undefined" && process?.env?.JEST_WORKER_ID
|
: typeof process !== "undefined" && process.env?.JEST_WORKER_ID
|
||||||
? (category: string, name: string, label?: string, value?: number) => {}
|
? (category: string, name: string, label?: string, value?: number) => {}
|
||||||
: (category: string, name: string, label?: string, value?: number) => {
|
: (category: string, name: string, label?: string, value?: number) => {
|
||||||
console.info("Track Event", category, name, label, value);
|
// Uncomment the next line to track locally
|
||||||
|
// console.info("Track Event", category, name, label, value);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
import oc from "open-color";
|
import oc from "open-color";
|
||||||
import { AppState, FlooredNumber, NormalizedZoomValue } from "./types";
|
|
||||||
import { getDateTime } from "./utils";
|
|
||||||
import { t } from "./i18n";
|
|
||||||
import {
|
import {
|
||||||
DEFAULT_FONT_SIZE,
|
|
||||||
DEFAULT_FONT_FAMILY,
|
DEFAULT_FONT_FAMILY,
|
||||||
|
DEFAULT_FONT_SIZE,
|
||||||
DEFAULT_TEXT_ALIGN,
|
DEFAULT_TEXT_ALIGN,
|
||||||
} from "./constants";
|
} from "./constants";
|
||||||
|
import { t } from "./i18n";
|
||||||
|
import { AppState, FlooredNumber, NormalizedZoomValue } from "./types";
|
||||||
|
import { getDateTime } from "./utils";
|
||||||
|
|
||||||
export const getDefaultAppState = (): Omit<
|
export const getDefaultAppState = (): Omit<
|
||||||
AppState,
|
AppState,
|
||||||
@@ -14,66 +14,64 @@ export const getDefaultAppState = (): Omit<
|
|||||||
> => {
|
> => {
|
||||||
return {
|
return {
|
||||||
appearance: "light",
|
appearance: "light",
|
||||||
isLoading: false,
|
collaborators: new Map(),
|
||||||
errorMessage: null,
|
currentChartType: "bar",
|
||||||
|
currentItemBackgroundColor: "transparent",
|
||||||
|
currentItemEndArrowhead: "arrow",
|
||||||
|
currentItemFillStyle: "hachure",
|
||||||
|
currentItemFontFamily: DEFAULT_FONT_FAMILY,
|
||||||
|
currentItemFontSize: DEFAULT_FONT_SIZE,
|
||||||
|
currentItemLinearStrokeSharpness: "round",
|
||||||
|
currentItemOpacity: 100,
|
||||||
|
currentItemRoughness: 1,
|
||||||
|
currentItemStartArrowhead: null,
|
||||||
|
currentItemStrokeColor: oc.black,
|
||||||
|
currentItemStrokeSharpness: "sharp",
|
||||||
|
currentItemStrokeStyle: "solid",
|
||||||
|
currentItemStrokeWidth: 1,
|
||||||
|
currentItemTextAlign: DEFAULT_TEXT_ALIGN,
|
||||||
|
cursorButton: "up",
|
||||||
draggingElement: null,
|
draggingElement: null,
|
||||||
resizingElement: null,
|
|
||||||
multiElement: null,
|
|
||||||
editingElement: null,
|
editingElement: null,
|
||||||
startBoundElement: null,
|
editingGroupId: null,
|
||||||
editingLinearElement: null,
|
editingLinearElement: null,
|
||||||
elementType: "selection",
|
|
||||||
elementLocked: false,
|
elementLocked: false,
|
||||||
|
elementType: "selection",
|
||||||
|
errorMessage: null,
|
||||||
exportBackground: true,
|
exportBackground: true,
|
||||||
exportEmbedScene: false,
|
exportEmbedScene: false,
|
||||||
shouldAddWatermark: false,
|
fileHandle: null,
|
||||||
currentItemStrokeColor: oc.black,
|
gridSize: null,
|
||||||
currentItemBackgroundColor: "transparent",
|
height: window.innerHeight,
|
||||||
currentItemFillStyle: "hachure",
|
|
||||||
currentItemStrokeWidth: 1,
|
|
||||||
currentItemStrokeStyle: "solid",
|
|
||||||
currentItemRoughness: 1,
|
|
||||||
currentItemOpacity: 100,
|
|
||||||
currentItemFontSize: DEFAULT_FONT_SIZE,
|
|
||||||
currentItemFontFamily: DEFAULT_FONT_FAMILY,
|
|
||||||
currentItemTextAlign: DEFAULT_TEXT_ALIGN,
|
|
||||||
currentItemStrokeSharpness: "sharp",
|
|
||||||
currentItemLinearStrokeSharpness: "round",
|
|
||||||
currentItemStartArrowhead: null,
|
|
||||||
currentItemEndArrowhead: "arrow",
|
|
||||||
viewBackgroundColor: oc.white,
|
|
||||||
scrollX: 0 as FlooredNumber,
|
|
||||||
scrollY: 0 as FlooredNumber,
|
|
||||||
cursorX: 0,
|
|
||||||
cursorY: 0,
|
|
||||||
cursorButton: "up",
|
|
||||||
scrolledOutside: false,
|
|
||||||
name: `${t("labels.untitled")}-${getDateTime()}`,
|
|
||||||
isBindingEnabled: true,
|
isBindingEnabled: true,
|
||||||
|
isLibraryOpen: false,
|
||||||
|
isLoading: false,
|
||||||
isResizing: false,
|
isResizing: false,
|
||||||
isRotating: false,
|
isRotating: false,
|
||||||
selectionElement: null,
|
|
||||||
zoom: {
|
|
||||||
value: 1 as NormalizedZoomValue,
|
|
||||||
translation: { x: 0, y: 0 },
|
|
||||||
},
|
|
||||||
openMenu: null,
|
|
||||||
lastPointerDownWith: "mouse",
|
lastPointerDownWith: "mouse",
|
||||||
selectedElementIds: {},
|
multiElement: null,
|
||||||
|
name: `${t("labels.untitled")}-${getDateTime()}`,
|
||||||
|
openMenu: null,
|
||||||
|
pasteDialog: { shown: false, data: null },
|
||||||
previousSelectedElementIds: {},
|
previousSelectedElementIds: {},
|
||||||
shouldCacheIgnoreZoom: false,
|
resizingElement: null,
|
||||||
showShortcutsDialog: false,
|
scrolledOutside: false,
|
||||||
suggestedBindings: [],
|
scrollX: 0 as FlooredNumber,
|
||||||
zenModeEnabled: false,
|
scrollY: 0 as FlooredNumber,
|
||||||
gridSize: null,
|
selectedElementIds: {},
|
||||||
editingGroupId: null,
|
|
||||||
selectedGroupIds: {},
|
selectedGroupIds: {},
|
||||||
width: window.innerWidth,
|
selectionElement: null,
|
||||||
height: window.innerHeight,
|
shouldAddWatermark: false,
|
||||||
isLibraryOpen: false,
|
shouldCacheIgnoreZoom: false,
|
||||||
fileHandle: null,
|
showHelpDialog: false,
|
||||||
collaborators: new Map(),
|
|
||||||
showStats: false,
|
showStats: false,
|
||||||
|
startBoundElement: null,
|
||||||
|
suggestedBindings: [],
|
||||||
|
toastMessage: null,
|
||||||
|
viewBackgroundColor: oc.white,
|
||||||
|
width: window.innerWidth,
|
||||||
|
zenModeEnabled: false,
|
||||||
|
zoom: { value: 1 as NormalizedZoomValue, translation: { x: 0, y: 0 } },
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -93,26 +91,25 @@ const APP_STATE_STORAGE_CONF = (<
|
|||||||
config: { [K in keyof T]: K extends keyof AppState ? T[K] : never },
|
config: { [K in keyof T]: K extends keyof AppState ? T[K] : never },
|
||||||
) => config)({
|
) => config)({
|
||||||
appearance: { browser: true, export: false },
|
appearance: { browser: true, export: false },
|
||||||
|
collaborators: { browser: false, export: false },
|
||||||
|
currentChartType: { browser: true, export: false },
|
||||||
currentItemBackgroundColor: { browser: true, export: false },
|
currentItemBackgroundColor: { browser: true, export: false },
|
||||||
|
currentItemEndArrowhead: { browser: true, export: false },
|
||||||
currentItemFillStyle: { browser: true, export: false },
|
currentItemFillStyle: { browser: true, export: false },
|
||||||
currentItemFontFamily: { browser: true, export: false },
|
currentItemFontFamily: { browser: true, export: false },
|
||||||
currentItemFontSize: { browser: true, export: false },
|
currentItemFontSize: { browser: true, export: false },
|
||||||
|
currentItemLinearStrokeSharpness: { browser: true, export: false },
|
||||||
currentItemOpacity: { browser: true, export: false },
|
currentItemOpacity: { browser: true, export: false },
|
||||||
currentItemRoughness: { browser: true, export: false },
|
currentItemRoughness: { browser: true, export: false },
|
||||||
|
currentItemStartArrowhead: { browser: true, export: false },
|
||||||
currentItemStrokeColor: { browser: true, export: false },
|
currentItemStrokeColor: { browser: true, export: false },
|
||||||
|
currentItemStrokeSharpness: { browser: true, export: false },
|
||||||
currentItemStrokeStyle: { browser: true, export: false },
|
currentItemStrokeStyle: { browser: true, export: false },
|
||||||
currentItemStrokeWidth: { browser: true, export: false },
|
currentItemStrokeWidth: { browser: true, export: false },
|
||||||
currentItemTextAlign: { browser: true, export: false },
|
currentItemTextAlign: { browser: true, export: false },
|
||||||
currentItemStrokeSharpness: { browser: true, export: false },
|
|
||||||
currentItemLinearStrokeSharpness: { browser: true, export: false },
|
|
||||||
currentItemStartArrowhead: { browser: true, export: false },
|
|
||||||
currentItemEndArrowhead: { browser: true, export: false },
|
|
||||||
cursorButton: { browser: true, export: false },
|
cursorButton: { browser: true, export: false },
|
||||||
cursorX: { browser: true, export: false },
|
|
||||||
cursorY: { browser: true, export: false },
|
|
||||||
draggingElement: { browser: false, export: false },
|
draggingElement: { browser: false, export: false },
|
||||||
editingElement: { browser: false, export: false },
|
editingElement: { browser: false, export: false },
|
||||||
startBoundElement: { browser: false, export: false },
|
|
||||||
editingGroupId: { browser: true, export: false },
|
editingGroupId: { browser: true, export: false },
|
||||||
editingLinearElement: { browser: false, export: false },
|
editingLinearElement: { browser: false, export: false },
|
||||||
elementLocked: { browser: true, export: false },
|
elementLocked: { browser: true, export: false },
|
||||||
@@ -120,6 +117,7 @@ const APP_STATE_STORAGE_CONF = (<
|
|||||||
errorMessage: { browser: false, export: false },
|
errorMessage: { browser: false, export: false },
|
||||||
exportBackground: { browser: true, export: false },
|
exportBackground: { browser: true, export: false },
|
||||||
exportEmbedScene: { browser: true, export: false },
|
exportEmbedScene: { browser: true, export: false },
|
||||||
|
fileHandle: { browser: false, export: false },
|
||||||
gridSize: { browser: true, export: true },
|
gridSize: { browser: true, export: true },
|
||||||
height: { browser: false, export: false },
|
height: { browser: false, export: false },
|
||||||
isBindingEnabled: { browser: false, export: false },
|
isBindingEnabled: { browser: false, export: false },
|
||||||
@@ -130,7 +128,10 @@ const APP_STATE_STORAGE_CONF = (<
|
|||||||
lastPointerDownWith: { browser: true, export: false },
|
lastPointerDownWith: { browser: true, export: false },
|
||||||
multiElement: { browser: false, export: false },
|
multiElement: { browser: false, export: false },
|
||||||
name: { browser: true, export: false },
|
name: { browser: true, export: false },
|
||||||
|
offsetLeft: { browser: false, export: false },
|
||||||
|
offsetTop: { browser: false, export: false },
|
||||||
openMenu: { browser: true, export: false },
|
openMenu: { browser: true, export: false },
|
||||||
|
pasteDialog: { browser: false, export: false },
|
||||||
previousSelectedElementIds: { browser: true, export: false },
|
previousSelectedElementIds: { browser: true, export: false },
|
||||||
resizingElement: { browser: false, export: false },
|
resizingElement: { browser: false, export: false },
|
||||||
scrolledOutside: { browser: true, export: false },
|
scrolledOutside: { browser: true, export: false },
|
||||||
@@ -141,17 +142,15 @@ const APP_STATE_STORAGE_CONF = (<
|
|||||||
selectionElement: { browser: false, export: false },
|
selectionElement: { browser: false, export: false },
|
||||||
shouldAddWatermark: { browser: true, export: false },
|
shouldAddWatermark: { browser: true, export: false },
|
||||||
shouldCacheIgnoreZoom: { browser: true, export: false },
|
shouldCacheIgnoreZoom: { browser: true, export: false },
|
||||||
showShortcutsDialog: { browser: false, export: false },
|
showHelpDialog: { browser: false, export: false },
|
||||||
|
showStats: { browser: true, export: false },
|
||||||
|
startBoundElement: { browser: false, export: false },
|
||||||
suggestedBindings: { browser: false, export: false },
|
suggestedBindings: { browser: false, export: false },
|
||||||
|
toastMessage: { browser: false, export: false },
|
||||||
viewBackgroundColor: { browser: true, export: true },
|
viewBackgroundColor: { browser: true, export: true },
|
||||||
width: { browser: false, export: false },
|
width: { browser: false, export: false },
|
||||||
zenModeEnabled: { browser: true, export: false },
|
zenModeEnabled: { browser: true, export: false },
|
||||||
zoom: { browser: true, export: false },
|
zoom: { browser: true, export: false },
|
||||||
offsetTop: { browser: false, export: false },
|
|
||||||
offsetLeft: { browser: false, export: false },
|
|
||||||
fileHandle: { browser: false, export: false },
|
|
||||||
collaborators: { browser: false, export: false },
|
|
||||||
showStats: { browser: true, export: false },
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const _clearAppStateForStorage = <ExportType extends "export" | "browser">(
|
const _clearAppStateForStorage = <ExportType extends "export" | "browser">(
|
||||||
|
|||||||
@@ -1,13 +1,15 @@
|
|||||||
import { EVENT_MAGIC, trackEvent } from "./analytics";
|
|
||||||
import colors from "./colors";
|
import colors from "./colors";
|
||||||
import { DEFAULT_FONT_FAMILY, DEFAULT_FONT_SIZE } from "./constants";
|
import { DEFAULT_FONT_FAMILY, DEFAULT_FONT_SIZE, ENV } from "./constants";
|
||||||
import { newElement, newTextElement, newLinearElement } from "./element";
|
import { newElement, newLinearElement, newTextElement } from "./element";
|
||||||
import { ExcalidrawElement } from "./element/types";
|
import { NonDeletedExcalidrawElement } from "./element/types";
|
||||||
import { randomId } from "./random";
|
import { randomId } from "./random";
|
||||||
|
|
||||||
|
export type ChartElements = readonly NonDeletedExcalidrawElement[];
|
||||||
|
|
||||||
const BAR_WIDTH = 32;
|
const BAR_WIDTH = 32;
|
||||||
const BAR_GAP = 12;
|
const BAR_GAP = 12;
|
||||||
const BAR_HEIGHT = 256;
|
const BAR_HEIGHT = 256;
|
||||||
|
const GRID_OPACITY = 50;
|
||||||
|
|
||||||
export interface Spreadsheet {
|
export interface Spreadsheet {
|
||||||
title: string | null;
|
title: string | null;
|
||||||
@@ -139,114 +141,48 @@ export const tryParseSpreadsheet = (text: string): ParseSpreadsheetResult => {
|
|||||||
return transposedResults;
|
return transposedResults;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
};
|
};
|
||||||
|
|
||||||
// For the maths behind it https://excalidraw.com/#json=6320864370884608,O_5xfD-Agh32tytHpRJx1g
|
const bgColors = colors.elementBackground.slice(
|
||||||
export const renderSpreadsheet = (
|
2,
|
||||||
|
colors.elementBackground.length,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Put all the common properties here so when the whole chart is selected
|
||||||
|
// the properties dialog shows the correct selected values
|
||||||
|
const commonProps = {
|
||||||
|
fillStyle: "hachure",
|
||||||
|
fontFamily: DEFAULT_FONT_FAMILY,
|
||||||
|
fontSize: DEFAULT_FONT_SIZE,
|
||||||
|
opacity: 100,
|
||||||
|
roughness: 1,
|
||||||
|
strokeColor: colors.elementStroke[0],
|
||||||
|
strokeSharpness: "sharp",
|
||||||
|
strokeStyle: "solid",
|
||||||
|
strokeWidth: 1,
|
||||||
|
verticalAlign: "middle",
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
const getChartDimentions = (spreadsheet: Spreadsheet) => {
|
||||||
|
const chartWidth =
|
||||||
|
(BAR_WIDTH + BAR_GAP) * spreadsheet.values.length + BAR_GAP;
|
||||||
|
const chartHeight = BAR_HEIGHT + BAR_GAP * 2;
|
||||||
|
return { chartWidth, chartHeight };
|
||||||
|
};
|
||||||
|
|
||||||
|
const chartXLabels = (
|
||||||
spreadsheet: Spreadsheet,
|
spreadsheet: Spreadsheet,
|
||||||
x: number,
|
x: number,
|
||||||
y: number,
|
y: number,
|
||||||
): ExcalidrawElement[] => {
|
groupId: string,
|
||||||
const values = spreadsheet.values;
|
backgroundColor: string,
|
||||||
const max = Math.max(...values);
|
): ChartElements => {
|
||||||
const chartHeight = BAR_HEIGHT + BAR_GAP * 2;
|
return (
|
||||||
const chartWidth = (BAR_WIDTH + BAR_GAP) * values.length + BAR_GAP;
|
|
||||||
const maxColors = colors.elementBackground.length;
|
|
||||||
const bgColors = colors.elementBackground.slice(2, maxColors);
|
|
||||||
|
|
||||||
// Put all the common properties here so when the whole chart is selected
|
|
||||||
// the properties dialog shows the correct selected values
|
|
||||||
const commonProps = {
|
|
||||||
backgroundColor: bgColors[Math.floor(Math.random() * bgColors.length)],
|
|
||||||
fillStyle: "hachure",
|
|
||||||
fontFamily: DEFAULT_FONT_FAMILY,
|
|
||||||
fontSize: DEFAULT_FONT_SIZE,
|
|
||||||
groupIds: [randomId()],
|
|
||||||
opacity: 100,
|
|
||||||
roughness: 1,
|
|
||||||
strokeColor: colors.elementStroke[0],
|
|
||||||
strokeSharpness: "sharp",
|
|
||||||
strokeStyle: "solid",
|
|
||||||
strokeWidth: 1,
|
|
||||||
verticalAlign: "middle",
|
|
||||||
} as const;
|
|
||||||
|
|
||||||
const minYLabel = newTextElement({
|
|
||||||
...commonProps,
|
|
||||||
x: x - BAR_GAP,
|
|
||||||
y: y - BAR_GAP,
|
|
||||||
text: "0",
|
|
||||||
textAlign: "right",
|
|
||||||
});
|
|
||||||
|
|
||||||
const maxYLabel = newTextElement({
|
|
||||||
...commonProps,
|
|
||||||
x: x - BAR_GAP,
|
|
||||||
y: y - BAR_HEIGHT - minYLabel.height / 2,
|
|
||||||
text: max.toLocaleString(),
|
|
||||||
textAlign: "right",
|
|
||||||
});
|
|
||||||
|
|
||||||
const xAxisLine = newLinearElement({
|
|
||||||
type: "line",
|
|
||||||
x,
|
|
||||||
y,
|
|
||||||
startArrowhead: null,
|
|
||||||
endArrowhead: null,
|
|
||||||
width: chartWidth,
|
|
||||||
points: [
|
|
||||||
[0, 0],
|
|
||||||
[chartWidth, 0],
|
|
||||||
],
|
|
||||||
...commonProps,
|
|
||||||
});
|
|
||||||
|
|
||||||
const yAxisLine = newLinearElement({
|
|
||||||
type: "line",
|
|
||||||
x,
|
|
||||||
y,
|
|
||||||
startArrowhead: null,
|
|
||||||
endArrowhead: null,
|
|
||||||
height: chartHeight,
|
|
||||||
points: [
|
|
||||||
[0, 0],
|
|
||||||
[0, -chartHeight],
|
|
||||||
],
|
|
||||||
...commonProps,
|
|
||||||
});
|
|
||||||
|
|
||||||
const maxValueLine = newLinearElement({
|
|
||||||
type: "line",
|
|
||||||
x,
|
|
||||||
y: y - BAR_HEIGHT - BAR_GAP,
|
|
||||||
startArrowhead: null,
|
|
||||||
endArrowhead: null,
|
|
||||||
...commonProps,
|
|
||||||
strokeStyle: "dotted",
|
|
||||||
width: chartWidth,
|
|
||||||
points: [
|
|
||||||
[0, 0],
|
|
||||||
[chartWidth, 0],
|
|
||||||
],
|
|
||||||
});
|
|
||||||
|
|
||||||
const bars = values.map((value, index) => {
|
|
||||||
const barHeight = (value / max) * BAR_HEIGHT;
|
|
||||||
return newElement({
|
|
||||||
...commonProps,
|
|
||||||
type: "rectangle",
|
|
||||||
x: x + index * (BAR_WIDTH + BAR_GAP) + BAR_GAP,
|
|
||||||
y: y - barHeight - BAR_GAP,
|
|
||||||
width: BAR_WIDTH,
|
|
||||||
height: barHeight,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
const xLabels =
|
|
||||||
spreadsheet.labels?.map((label, index) => {
|
spreadsheet.labels?.map((label, index) => {
|
||||||
return newTextElement({
|
return newTextElement({
|
||||||
|
groupIds: [groupId],
|
||||||
|
backgroundColor,
|
||||||
...commonProps,
|
...commonProps,
|
||||||
text: label.length > 8 ? `${label.slice(0, 5)}...` : label,
|
text: label.length > 8 ? `${label.slice(0, 5)}...` : label,
|
||||||
x: x + index * (BAR_WIDTH + BAR_GAP) + BAR_GAP * 2,
|
x: x + index * (BAR_WIDTH + BAR_GAP) + BAR_GAP * 2,
|
||||||
@@ -257,29 +193,287 @@ export const renderSpreadsheet = (
|
|||||||
textAlign: "center",
|
textAlign: "center",
|
||||||
verticalAlign: "top",
|
verticalAlign: "top",
|
||||||
});
|
});
|
||||||
}) || [];
|
}) || []
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const chartYLabels = (
|
||||||
|
spreadsheet: Spreadsheet,
|
||||||
|
x: number,
|
||||||
|
y: number,
|
||||||
|
groupId: string,
|
||||||
|
backgroundColor: string,
|
||||||
|
): ChartElements => {
|
||||||
|
const minYLabel = newTextElement({
|
||||||
|
groupIds: [groupId],
|
||||||
|
backgroundColor,
|
||||||
|
...commonProps,
|
||||||
|
x: x - BAR_GAP,
|
||||||
|
y: y - BAR_GAP,
|
||||||
|
text: "0",
|
||||||
|
textAlign: "right",
|
||||||
|
});
|
||||||
|
|
||||||
|
const maxYLabel = newTextElement({
|
||||||
|
groupIds: [groupId],
|
||||||
|
backgroundColor,
|
||||||
|
...commonProps,
|
||||||
|
x: x - BAR_GAP,
|
||||||
|
y: y - BAR_HEIGHT - minYLabel.height / 2,
|
||||||
|
text: Math.max(...spreadsheet.values).toLocaleString(),
|
||||||
|
textAlign: "right",
|
||||||
|
});
|
||||||
|
|
||||||
|
return [minYLabel, maxYLabel];
|
||||||
|
};
|
||||||
|
|
||||||
|
const chartLines = (
|
||||||
|
spreadsheet: Spreadsheet,
|
||||||
|
x: number,
|
||||||
|
y: number,
|
||||||
|
groupId: string,
|
||||||
|
backgroundColor: string,
|
||||||
|
): ChartElements => {
|
||||||
|
const { chartWidth, chartHeight } = getChartDimentions(spreadsheet);
|
||||||
|
const xLine = newLinearElement({
|
||||||
|
backgroundColor,
|
||||||
|
groupIds: [groupId],
|
||||||
|
...commonProps,
|
||||||
|
type: "line",
|
||||||
|
x,
|
||||||
|
y,
|
||||||
|
startArrowhead: null,
|
||||||
|
endArrowhead: null,
|
||||||
|
width: chartWidth,
|
||||||
|
points: [
|
||||||
|
[0, 0],
|
||||||
|
[chartWidth, 0],
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
const yLine = newLinearElement({
|
||||||
|
backgroundColor,
|
||||||
|
groupIds: [groupId],
|
||||||
|
...commonProps,
|
||||||
|
type: "line",
|
||||||
|
x,
|
||||||
|
y,
|
||||||
|
startArrowhead: null,
|
||||||
|
endArrowhead: null,
|
||||||
|
height: chartHeight,
|
||||||
|
points: [
|
||||||
|
[0, 0],
|
||||||
|
[0, -chartHeight],
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
const maxLine = newLinearElement({
|
||||||
|
backgroundColor,
|
||||||
|
groupIds: [groupId],
|
||||||
|
...commonProps,
|
||||||
|
type: "line",
|
||||||
|
x,
|
||||||
|
y: y - BAR_HEIGHT - BAR_GAP,
|
||||||
|
startArrowhead: null,
|
||||||
|
endArrowhead: null,
|
||||||
|
strokeStyle: "dotted",
|
||||||
|
width: chartWidth,
|
||||||
|
opacity: GRID_OPACITY,
|
||||||
|
points: [
|
||||||
|
[0, 0],
|
||||||
|
[chartWidth, 0],
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
return [xLine, yLine, maxLine];
|
||||||
|
};
|
||||||
|
|
||||||
|
// For the maths behind it https://excalidraw.com/#json=6320864370884608,O_5xfD-Agh32tytHpRJx1g
|
||||||
|
const chartBaseElements = (
|
||||||
|
spreadsheet: Spreadsheet,
|
||||||
|
x: number,
|
||||||
|
y: number,
|
||||||
|
groupId: string,
|
||||||
|
backgroundColor: string,
|
||||||
|
debug?: boolean,
|
||||||
|
): ChartElements => {
|
||||||
|
const { chartWidth, chartHeight } = getChartDimentions(spreadsheet);
|
||||||
|
|
||||||
const title = spreadsheet.title
|
const title = spreadsheet.title
|
||||||
? newTextElement({
|
? newTextElement({
|
||||||
|
backgroundColor,
|
||||||
|
groupIds: [groupId],
|
||||||
...commonProps,
|
...commonProps,
|
||||||
text: spreadsheet.title,
|
text: spreadsheet.title,
|
||||||
x: x + chartWidth / 2,
|
x: x + chartWidth / 2,
|
||||||
y: y - BAR_HEIGHT - BAR_GAP * 2 - maxYLabel.height,
|
y: y - BAR_HEIGHT - BAR_GAP * 2 - DEFAULT_FONT_SIZE,
|
||||||
strokeSharpness: "sharp",
|
strokeSharpness: "sharp",
|
||||||
strokeStyle: "solid",
|
strokeStyle: "solid",
|
||||||
textAlign: "center",
|
textAlign: "center",
|
||||||
})
|
})
|
||||||
: null;
|
: null;
|
||||||
|
|
||||||
trackEvent(EVENT_MAGIC, "chart", "bars", bars.length);
|
const debugRect = debug
|
||||||
|
? newElement({
|
||||||
|
backgroundColor,
|
||||||
|
groupIds: [groupId],
|
||||||
|
...commonProps,
|
||||||
|
type: "rectangle",
|
||||||
|
x,
|
||||||
|
y: y - chartHeight,
|
||||||
|
width: chartWidth,
|
||||||
|
height: chartHeight,
|
||||||
|
strokeColor: colors.elementStroke[0],
|
||||||
|
fillStyle: "solid",
|
||||||
|
opacity: 6,
|
||||||
|
})
|
||||||
|
: null;
|
||||||
|
|
||||||
return [
|
return [
|
||||||
title,
|
...(debugRect ? [debugRect] : []),
|
||||||
...bars,
|
...(title ? [title] : []),
|
||||||
...xLabels,
|
...chartXLabels(spreadsheet, x, y, groupId, backgroundColor),
|
||||||
xAxisLine,
|
...chartYLabels(spreadsheet, x, y, groupId, backgroundColor),
|
||||||
yAxisLine,
|
...chartLines(spreadsheet, x, y, groupId, backgroundColor),
|
||||||
maxValueLine,
|
];
|
||||||
minYLabel,
|
};
|
||||||
maxYLabel,
|
|
||||||
].filter((element) => element !== null) as ExcalidrawElement[];
|
const chartTypeBar = (
|
||||||
|
spreadsheet: Spreadsheet,
|
||||||
|
x: number,
|
||||||
|
y: number,
|
||||||
|
): ChartElements => {
|
||||||
|
const max = Math.max(...spreadsheet.values);
|
||||||
|
const groupId = randomId();
|
||||||
|
const backgroundColor = bgColors[Math.floor(Math.random() * bgColors.length)];
|
||||||
|
|
||||||
|
const bars = spreadsheet.values.map((value, index) => {
|
||||||
|
const barHeight = (value / max) * BAR_HEIGHT;
|
||||||
|
return newElement({
|
||||||
|
backgroundColor,
|
||||||
|
groupIds: [groupId],
|
||||||
|
...commonProps,
|
||||||
|
type: "rectangle",
|
||||||
|
x: x + index * (BAR_WIDTH + BAR_GAP) + BAR_GAP,
|
||||||
|
y: y - barHeight - BAR_GAP,
|
||||||
|
width: BAR_WIDTH,
|
||||||
|
height: barHeight,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return [
|
||||||
|
...bars,
|
||||||
|
...chartBaseElements(
|
||||||
|
spreadsheet,
|
||||||
|
x,
|
||||||
|
y,
|
||||||
|
groupId,
|
||||||
|
backgroundColor,
|
||||||
|
process.env.NODE_ENV === ENV.DEVELOPMENT,
|
||||||
|
),
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
|
const chartTypeLine = (
|
||||||
|
spreadsheet: Spreadsheet,
|
||||||
|
x: number,
|
||||||
|
y: number,
|
||||||
|
): ChartElements => {
|
||||||
|
const max = Math.max(...spreadsheet.values);
|
||||||
|
const groupId = randomId();
|
||||||
|
const backgroundColor = bgColors[Math.floor(Math.random() * bgColors.length)];
|
||||||
|
|
||||||
|
let index = 0;
|
||||||
|
const points = [];
|
||||||
|
for (const value of spreadsheet.values) {
|
||||||
|
const cx = index * (BAR_WIDTH + BAR_GAP);
|
||||||
|
const cy = -(value / max) * BAR_HEIGHT;
|
||||||
|
points.push([cx, cy]);
|
||||||
|
index++;
|
||||||
|
}
|
||||||
|
|
||||||
|
const maxX = Math.max(...points.map((element) => element[0]));
|
||||||
|
const maxY = Math.max(...points.map((element) => element[1]));
|
||||||
|
const minX = Math.min(...points.map((element) => element[0]));
|
||||||
|
const minY = Math.min(...points.map((element) => element[1]));
|
||||||
|
|
||||||
|
const line = newLinearElement({
|
||||||
|
backgroundColor,
|
||||||
|
groupIds: [groupId],
|
||||||
|
...commonProps,
|
||||||
|
type: "line",
|
||||||
|
x: x + BAR_GAP + BAR_WIDTH / 2,
|
||||||
|
y: y - BAR_GAP,
|
||||||
|
startArrowhead: null,
|
||||||
|
endArrowhead: null,
|
||||||
|
height: maxY - minY,
|
||||||
|
width: maxX - minX,
|
||||||
|
strokeWidth: 2,
|
||||||
|
points: points as any,
|
||||||
|
});
|
||||||
|
|
||||||
|
const dots = spreadsheet.values.map((value, index) => {
|
||||||
|
const cx = index * (BAR_WIDTH + BAR_GAP) + BAR_GAP / 2;
|
||||||
|
const cy = -(value / max) * BAR_HEIGHT + BAR_GAP / 2;
|
||||||
|
return newElement({
|
||||||
|
backgroundColor,
|
||||||
|
groupIds: [groupId],
|
||||||
|
...commonProps,
|
||||||
|
fillStyle: "solid",
|
||||||
|
strokeWidth: 2,
|
||||||
|
type: "ellipse",
|
||||||
|
x: x + cx + BAR_WIDTH / 2,
|
||||||
|
y: y + cy - BAR_GAP * 2,
|
||||||
|
width: BAR_GAP,
|
||||||
|
height: BAR_GAP,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
const lines = spreadsheet.values.map((value, index) => {
|
||||||
|
const cx = index * (BAR_WIDTH + BAR_GAP) + BAR_GAP / 2;
|
||||||
|
const cy = (value / max) * BAR_HEIGHT + BAR_GAP / 2 + BAR_GAP;
|
||||||
|
return newLinearElement({
|
||||||
|
backgroundColor,
|
||||||
|
groupIds: [groupId],
|
||||||
|
...commonProps,
|
||||||
|
type: "line",
|
||||||
|
x: x + cx + BAR_WIDTH / 2 + BAR_GAP / 2,
|
||||||
|
y: y - cy,
|
||||||
|
startArrowhead: null,
|
||||||
|
endArrowhead: null,
|
||||||
|
height: cy,
|
||||||
|
strokeStyle: "dotted",
|
||||||
|
opacity: GRID_OPACITY,
|
||||||
|
points: [
|
||||||
|
[0, 0],
|
||||||
|
[0, cy],
|
||||||
|
],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return [
|
||||||
|
...chartBaseElements(
|
||||||
|
spreadsheet,
|
||||||
|
x,
|
||||||
|
y,
|
||||||
|
groupId,
|
||||||
|
backgroundColor,
|
||||||
|
process.env.NODE_ENV === ENV.DEVELOPMENT,
|
||||||
|
),
|
||||||
|
line,
|
||||||
|
...lines,
|
||||||
|
...dots,
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
|
export const renderSpreadsheet = (
|
||||||
|
chartType: string,
|
||||||
|
spreadsheet: Spreadsheet,
|
||||||
|
x: number,
|
||||||
|
y: number,
|
||||||
|
): ChartElements => {
|
||||||
|
if (chartType === "line") {
|
||||||
|
return chartTypeLine(spreadsheet, x, y);
|
||||||
|
}
|
||||||
|
return chartTypeBar(spreadsheet, x, y);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,23 +1,22 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { AppState, Zoom } from "../types";
|
|
||||||
import { ExcalidrawElement } from "../element/types";
|
|
||||||
import { ActionManager } from "../actions/manager";
|
import { ActionManager } from "../actions/manager";
|
||||||
|
import { getNonDeletedElements } from "../element";
|
||||||
|
import { ExcalidrawElement } from "../element/types";
|
||||||
|
import { t } from "../i18n";
|
||||||
|
import useIsMobile from "../is-mobile";
|
||||||
import {
|
import {
|
||||||
hasBackground,
|
|
||||||
hasStroke,
|
|
||||||
canChangeSharpness,
|
canChangeSharpness,
|
||||||
hasText,
|
|
||||||
canHaveArrowheads,
|
canHaveArrowheads,
|
||||||
getTargetElements,
|
getTargetElements,
|
||||||
|
hasBackground,
|
||||||
|
hasStroke,
|
||||||
|
hasText,
|
||||||
} from "../scene";
|
} from "../scene";
|
||||||
import { t } from "../i18n";
|
|
||||||
import { SHAPES } from "../shapes";
|
import { SHAPES } from "../shapes";
|
||||||
import { ToolButton } from "./ToolButton";
|
import { AppState, Zoom } from "../types";
|
||||||
import { capitalizeString, isTransparent, setCursorForShape } from "../utils";
|
import { capitalizeString, isTransparent, setCursorForShape } from "../utils";
|
||||||
import Stack from "./Stack";
|
import Stack from "./Stack";
|
||||||
import useIsMobile from "../is-mobile";
|
import { ToolButton } from "./ToolButton";
|
||||||
import { getNonDeletedElements } from "../element";
|
|
||||||
import { trackEvent, EVENT_SHAPE, EVENT_DIALOG } from "../analytics";
|
|
||||||
|
|
||||||
export const SelectedShapeActions = ({
|
export const SelectedShapeActions = ({
|
||||||
appState,
|
appState,
|
||||||
@@ -164,9 +163,9 @@ export const ShapesSwitcher = ({
|
|||||||
{SHAPES.map(({ value, icon, key }, index) => {
|
{SHAPES.map(({ value, icon, key }, index) => {
|
||||||
const label = t(`toolBar.${value}`);
|
const label = t(`toolBar.${value}`);
|
||||||
const letter = typeof key === "string" ? key : key[0];
|
const letter = typeof key === "string" ? key : key[0];
|
||||||
const shortcut = `${capitalizeString(letter)} ${t(
|
const shortcut = `${capitalizeString(letter)} ${t("helpDialog.or")} ${
|
||||||
"shortcutsDialog.or",
|
index + 1
|
||||||
)} ${index + 1}`;
|
}`;
|
||||||
return (
|
return (
|
||||||
<ToolButton
|
<ToolButton
|
||||||
className="Shape"
|
className="Shape"
|
||||||
@@ -181,7 +180,6 @@ export const ShapesSwitcher = ({
|
|||||||
aria-keyshortcuts={shortcut}
|
aria-keyshortcuts={shortcut}
|
||||||
data-testid={value}
|
data-testid={value}
|
||||||
onChange={() => {
|
onChange={() => {
|
||||||
trackEvent(EVENT_SHAPE, value, "toolbar");
|
|
||||||
setAppState({
|
setAppState({
|
||||||
elementType: value,
|
elementType: value,
|
||||||
multiElement: null,
|
multiElement: null,
|
||||||
@@ -203,9 +201,6 @@ export const ShapesSwitcher = ({
|
|||||||
title={`${capitalizeString(t("toolBar.library"))} — 9`}
|
title={`${capitalizeString(t("toolBar.library"))} — 9`}
|
||||||
aria-label={capitalizeString(t("toolBar.library"))}
|
aria-label={capitalizeString(t("toolBar.library"))}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (!isLibraryOpen) {
|
|
||||||
trackEvent(EVENT_DIALOG, "library");
|
|
||||||
}
|
|
||||||
setAppState({ isLibraryOpen: !isLibraryOpen });
|
setAppState({ isLibraryOpen: !isLibraryOpen });
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -1,182 +1,164 @@
|
|||||||
|
import { Point, simplify } from "points-on-curve";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
|
|
||||||
import rough from "roughjs/bin/rough";
|
|
||||||
import { RoughCanvas } from "roughjs/bin/canvas";
|
import { RoughCanvas } from "roughjs/bin/canvas";
|
||||||
import { simplify, Point } from "points-on-curve";
|
import rough from "roughjs/bin/rough";
|
||||||
|
|
||||||
import {
|
|
||||||
newElement,
|
|
||||||
newTextElement,
|
|
||||||
duplicateElement,
|
|
||||||
isInvisiblySmallElement,
|
|
||||||
isTextElement,
|
|
||||||
textWysiwyg,
|
|
||||||
getCommonBounds,
|
|
||||||
getCursorForResizingElement,
|
|
||||||
getPerfectElementSize,
|
|
||||||
getNormalizedDimensions,
|
|
||||||
newLinearElement,
|
|
||||||
transformElements,
|
|
||||||
getElementWithTransformHandleType,
|
|
||||||
getResizeOffsetXY,
|
|
||||||
getResizeArrowDirection,
|
|
||||||
getTransformHandleTypeFromCoords,
|
|
||||||
isNonDeletedElement,
|
|
||||||
updateTextElement,
|
|
||||||
dragSelectedElements,
|
|
||||||
getDragOffsetXY,
|
|
||||||
dragNewElement,
|
|
||||||
hitTest,
|
|
||||||
isHittingElementBoundingBoxWithoutHittingElement,
|
|
||||||
getNonDeletedElements,
|
|
||||||
} from "../element";
|
|
||||||
import {
|
|
||||||
getElementsWithinSelection,
|
|
||||||
isOverScrollBars,
|
|
||||||
getElementsAtPosition,
|
|
||||||
getElementContainingPosition,
|
|
||||||
getNormalizedZoom,
|
|
||||||
getSelectedElements,
|
|
||||||
isSomeElementSelected,
|
|
||||||
calculateScrollCenter,
|
|
||||||
} from "../scene";
|
|
||||||
import { loadFromBlob, exportCanvas } from "../data";
|
|
||||||
|
|
||||||
import { renderScene } from "../renderer";
|
|
||||||
import {
|
|
||||||
AppState,
|
|
||||||
GestureEvent,
|
|
||||||
Gesture,
|
|
||||||
ExcalidrawProps,
|
|
||||||
SceneData,
|
|
||||||
} from "../types";
|
|
||||||
import {
|
|
||||||
ExcalidrawElement,
|
|
||||||
ExcalidrawTextElement,
|
|
||||||
NonDeleted,
|
|
||||||
ExcalidrawGenericElement,
|
|
||||||
ExcalidrawLinearElement,
|
|
||||||
ExcalidrawBindableElement,
|
|
||||||
} from "../element/types";
|
|
||||||
|
|
||||||
import { distance2d, isPathALoop, getGridPoint } from "../math";
|
|
||||||
|
|
||||||
import {
|
|
||||||
isWritableElement,
|
|
||||||
isInputLike,
|
|
||||||
isToolIcon,
|
|
||||||
debounce,
|
|
||||||
distance,
|
|
||||||
resetCursor,
|
|
||||||
viewportCoordsToSceneCoords,
|
|
||||||
sceneCoordsToViewportCoords,
|
|
||||||
setCursorForShape,
|
|
||||||
tupleToCoors,
|
|
||||||
ResolvablePromise,
|
|
||||||
resolvablePromise,
|
|
||||||
withBatchedUpdates,
|
|
||||||
} from "../utils";
|
|
||||||
import {
|
|
||||||
KEYS,
|
|
||||||
isArrowKey,
|
|
||||||
getResizeCenterPointKey,
|
|
||||||
getResizeWithSidesSameLengthKey,
|
|
||||||
getRotateWithDiscreteAngleKey,
|
|
||||||
CODES,
|
|
||||||
} from "../keys";
|
|
||||||
|
|
||||||
import { findShapeByKey } from "../shapes";
|
|
||||||
import { createHistory, SceneHistory } from "../history";
|
|
||||||
|
|
||||||
import ContextMenu from "./ContextMenu";
|
|
||||||
|
|
||||||
import { ActionManager } from "../actions/manager";
|
|
||||||
import "../actions";
|
import "../actions";
|
||||||
|
import { actionDeleteSelected, actionFinalize } from "../actions";
|
||||||
|
import { createRedoAction, createUndoAction } from "../actions/actionHistory";
|
||||||
|
import { ActionManager } from "../actions/manager";
|
||||||
import { actions } from "../actions/register";
|
import { actions } from "../actions/register";
|
||||||
|
|
||||||
import { ActionResult } from "../actions/types";
|
import { ActionResult } from "../actions/types";
|
||||||
|
import { trackEvent } from "../analytics";
|
||||||
import { getDefaultAppState } from "../appState";
|
import { getDefaultAppState } from "../appState";
|
||||||
import { t, getLanguage } from "../i18n";
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
copyToClipboard,
|
copyToClipboard,
|
||||||
parseClipboard,
|
parseClipboard,
|
||||||
probablySupportsClipboardBlob,
|
probablySupportsClipboardBlob,
|
||||||
probablySupportsClipboardWriteText,
|
probablySupportsClipboardWriteText,
|
||||||
} from "../clipboard";
|
} from "../clipboard";
|
||||||
import { normalizeScroll } from "../scene";
|
|
||||||
import { getCenter, getDistance } from "../gesture";
|
|
||||||
import { createUndoAction, createRedoAction } from "../actions/actionHistory";
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
APP_NAME,
|
||||||
|
CANVAS_ONLY_ACTIONS,
|
||||||
CURSOR_TYPE,
|
CURSOR_TYPE,
|
||||||
|
DEFAULT_VERTICAL_ALIGN,
|
||||||
|
DRAGGING_THRESHOLD,
|
||||||
ELEMENT_SHIFT_TRANSLATE_AMOUNT,
|
ELEMENT_SHIFT_TRANSLATE_AMOUNT,
|
||||||
ELEMENT_TRANSLATE_AMOUNT,
|
ELEMENT_TRANSLATE_AMOUNT,
|
||||||
POINTER_BUTTON,
|
|
||||||
DRAGGING_THRESHOLD,
|
|
||||||
TEXT_TO_CENTER_SNAP_THRESHOLD,
|
|
||||||
LINE_CONFIRM_THRESHOLD,
|
|
||||||
EVENT,
|
|
||||||
ENV,
|
ENV,
|
||||||
CANVAS_ONLY_ACTIONS,
|
EVENT,
|
||||||
DEFAULT_VERTICAL_ALIGN,
|
|
||||||
GRID_SIZE,
|
GRID_SIZE,
|
||||||
|
LINE_CONFIRM_THRESHOLD,
|
||||||
MIME_TYPES,
|
MIME_TYPES,
|
||||||
|
POINTER_BUTTON,
|
||||||
TAP_TWICE_TIMEOUT,
|
TAP_TWICE_TIMEOUT,
|
||||||
|
TEXT_TO_CENTER_SNAP_THRESHOLD,
|
||||||
TOUCH_CTX_MENU_TIMEOUT,
|
TOUCH_CTX_MENU_TIMEOUT,
|
||||||
APP_NAME,
|
|
||||||
} from "../constants";
|
} from "../constants";
|
||||||
|
import { exportCanvas, loadFromBlob } from "../data";
|
||||||
import LayerUI from "./LayerUI";
|
|
||||||
import { ScrollBars, SceneState } from "../scene/types";
|
|
||||||
import { mutateElement } from "../element/mutateElement";
|
|
||||||
import { invalidateShapeForElement } from "../renderer/renderElement";
|
|
||||||
import {
|
|
||||||
isLinearElement,
|
|
||||||
isLinearElementType,
|
|
||||||
isBindingElement,
|
|
||||||
isBindingElementType,
|
|
||||||
} from "../element/typeChecks";
|
|
||||||
import { actionFinalize, actionDeleteSelected } from "../actions";
|
|
||||||
|
|
||||||
import { LinearElementEditor } from "../element/linearElementEditor";
|
|
||||||
import {
|
|
||||||
getSelectedGroupIds,
|
|
||||||
isSelectedViaGroup,
|
|
||||||
selectGroupsForSelectedElements,
|
|
||||||
isElementInGroup,
|
|
||||||
getSelectedGroupIdForElement,
|
|
||||||
getElementsInGroup,
|
|
||||||
editGroupForSelectedElement,
|
|
||||||
} from "../groups";
|
|
||||||
import { Library } from "../data/library";
|
|
||||||
import Scene from "../scene/Scene";
|
|
||||||
import {
|
|
||||||
getHoveredElementForBinding,
|
|
||||||
maybeBindLinearElement,
|
|
||||||
getEligibleElementsForBinding,
|
|
||||||
bindOrUnbindSelectedElements,
|
|
||||||
unbindLinearElements,
|
|
||||||
fixBindingsAfterDuplication,
|
|
||||||
fixBindingsAfterDeletion,
|
|
||||||
isLinearElementSimpleAndAlreadyBound,
|
|
||||||
isBindingEnabled,
|
|
||||||
updateBoundElements,
|
|
||||||
shouldEnableBindingForPointerEvent,
|
|
||||||
} from "../element/binding";
|
|
||||||
import { MaybeTransformHandleType } from "../element/transformHandles";
|
|
||||||
import { deepCopyElement } from "../element/newElement";
|
|
||||||
import { renderSpreadsheet } from "../charts";
|
|
||||||
import { isValidLibrary } from "../data/json";
|
import { isValidLibrary } from "../data/json";
|
||||||
import { getNewZoom } from "../scene/zoom";
|
import { Library } from "../data/library";
|
||||||
import { restore } from "../data/restore";
|
import { restore } from "../data/restore";
|
||||||
import {
|
import {
|
||||||
EVENT_DIALOG,
|
dragNewElement,
|
||||||
EVENT_LIBRARY,
|
dragSelectedElements,
|
||||||
EVENT_SHAPE,
|
duplicateElement,
|
||||||
trackEvent,
|
getCommonBounds,
|
||||||
} from "../analytics";
|
getCursorForResizingElement,
|
||||||
|
getDragOffsetXY,
|
||||||
|
getElementWithTransformHandleType,
|
||||||
|
getNonDeletedElements,
|
||||||
|
getNormalizedDimensions,
|
||||||
|
getPerfectElementSize,
|
||||||
|
getResizeArrowDirection,
|
||||||
|
getResizeOffsetXY,
|
||||||
|
getTransformHandleTypeFromCoords,
|
||||||
|
hitTest,
|
||||||
|
isHittingElementBoundingBoxWithoutHittingElement,
|
||||||
|
isInvisiblySmallElement,
|
||||||
|
isNonDeletedElement,
|
||||||
|
isTextElement,
|
||||||
|
newElement,
|
||||||
|
newLinearElement,
|
||||||
|
newTextElement,
|
||||||
|
textWysiwyg,
|
||||||
|
transformElements,
|
||||||
|
updateTextElement,
|
||||||
|
} from "../element";
|
||||||
|
import {
|
||||||
|
bindOrUnbindSelectedElements,
|
||||||
|
fixBindingsAfterDeletion,
|
||||||
|
fixBindingsAfterDuplication,
|
||||||
|
getEligibleElementsForBinding,
|
||||||
|
getHoveredElementForBinding,
|
||||||
|
isBindingEnabled,
|
||||||
|
isLinearElementSimpleAndAlreadyBound,
|
||||||
|
maybeBindLinearElement,
|
||||||
|
shouldEnableBindingForPointerEvent,
|
||||||
|
unbindLinearElements,
|
||||||
|
updateBoundElements,
|
||||||
|
} from "../element/binding";
|
||||||
|
import { LinearElementEditor } from "../element/linearElementEditor";
|
||||||
|
import { mutateElement } from "../element/mutateElement";
|
||||||
|
import { deepCopyElement } from "../element/newElement";
|
||||||
|
import { MaybeTransformHandleType } from "../element/transformHandles";
|
||||||
|
import {
|
||||||
|
isBindingElement,
|
||||||
|
isBindingElementType,
|
||||||
|
isLinearElement,
|
||||||
|
isLinearElementType,
|
||||||
|
} from "../element/typeChecks";
|
||||||
|
import {
|
||||||
|
ExcalidrawBindableElement,
|
||||||
|
ExcalidrawElement,
|
||||||
|
ExcalidrawGenericElement,
|
||||||
|
ExcalidrawLinearElement,
|
||||||
|
ExcalidrawTextElement,
|
||||||
|
NonDeleted,
|
||||||
|
} from "../element/types";
|
||||||
|
import { getCenter, getDistance } from "../gesture";
|
||||||
|
import {
|
||||||
|
editGroupForSelectedElement,
|
||||||
|
getElementsInGroup,
|
||||||
|
getSelectedGroupIdForElement,
|
||||||
|
getSelectedGroupIds,
|
||||||
|
isElementInGroup,
|
||||||
|
isSelectedViaGroup,
|
||||||
|
selectGroupsForSelectedElements,
|
||||||
|
} from "../groups";
|
||||||
|
import { createHistory, SceneHistory } from "../history";
|
||||||
|
import { defaultLang, getLanguage, languages, setLanguage, t } from "../i18n";
|
||||||
|
import {
|
||||||
|
CODES,
|
||||||
|
getResizeCenterPointKey,
|
||||||
|
getResizeWithSidesSameLengthKey,
|
||||||
|
getRotateWithDiscreteAngleKey,
|
||||||
|
isArrowKey,
|
||||||
|
KEYS,
|
||||||
|
} from "../keys";
|
||||||
|
import { distance2d, getGridPoint, isPathALoop } from "../math";
|
||||||
|
import { renderScene } from "../renderer";
|
||||||
|
import { invalidateShapeForElement } from "../renderer/renderElement";
|
||||||
|
import {
|
||||||
|
calculateScrollCenter,
|
||||||
|
getElementContainingPosition,
|
||||||
|
getElementsAtPosition,
|
||||||
|
getElementsWithinSelection,
|
||||||
|
getNormalizedZoom,
|
||||||
|
getSelectedElements,
|
||||||
|
isOverScrollBars,
|
||||||
|
isSomeElementSelected,
|
||||||
|
normalizeScroll,
|
||||||
|
} from "../scene";
|
||||||
|
import Scene from "../scene/Scene";
|
||||||
|
import { SceneState, ScrollBars } from "../scene/types";
|
||||||
|
import { getNewZoom } from "../scene/zoom";
|
||||||
|
import { findShapeByKey } from "../shapes";
|
||||||
|
import {
|
||||||
|
AppState,
|
||||||
|
ExcalidrawProps,
|
||||||
|
Gesture,
|
||||||
|
GestureEvent,
|
||||||
|
SceneData,
|
||||||
|
} from "../types";
|
||||||
|
import {
|
||||||
|
debounce,
|
||||||
|
distance,
|
||||||
|
isInputLike,
|
||||||
|
isToolIcon,
|
||||||
|
isWritableElement,
|
||||||
|
resetCursor,
|
||||||
|
ResolvablePromise,
|
||||||
|
resolvablePromise,
|
||||||
|
sceneCoordsToViewportCoords,
|
||||||
|
setCursorForShape,
|
||||||
|
tupleToCoors,
|
||||||
|
viewportCoordsToSceneCoords,
|
||||||
|
withBatchedUpdates,
|
||||||
|
} from "../utils";
|
||||||
|
import ContextMenu from "./ContextMenu";
|
||||||
|
import LayerUI from "./LayerUI";
|
||||||
import { Stats } from "./Stats";
|
import { Stats } from "./Stats";
|
||||||
|
import { Toast } from "./Toast";
|
||||||
|
|
||||||
const { history } = createHistory();
|
const { history } = createHistory();
|
||||||
|
|
||||||
@@ -346,7 +328,7 @@ class App extends React.Component<ExcalidrawProps, AppState> {
|
|||||||
offsetLeft,
|
offsetLeft,
|
||||||
} = this.state;
|
} = this.state;
|
||||||
|
|
||||||
const { onCollabButtonClick, onExportToBackend } = this.props;
|
const { onCollabButtonClick, onExportToBackend, renderFooter } = this.props;
|
||||||
const canvasScale = window.devicePixelRatio;
|
const canvasScale = window.devicePixelRatio;
|
||||||
|
|
||||||
const canvasWidth = canvasDOMWidth * canvasScale;
|
const canvasWidth = canvasDOMWidth * canvasScale;
|
||||||
@@ -374,7 +356,7 @@ class App extends React.Component<ExcalidrawProps, AppState> {
|
|||||||
elements={this.scene.getElements()}
|
elements={this.scene.getElements()}
|
||||||
onCollabButtonClick={onCollabButtonClick}
|
onCollabButtonClick={onCollabButtonClick}
|
||||||
onLockToggle={this.toggleLock}
|
onLockToggle={this.toggleLock}
|
||||||
onInsertShape={(elements) =>
|
onInsertElements={(elements) =>
|
||||||
this.addElementsFromPasteOrLibrary(
|
this.addElementsFromPasteOrLibrary(
|
||||||
elements,
|
elements,
|
||||||
DEFAULT_PASTE_X,
|
DEFAULT_PASTE_X,
|
||||||
@@ -383,9 +365,10 @@ class App extends React.Component<ExcalidrawProps, AppState> {
|
|||||||
}
|
}
|
||||||
zenModeEnabled={zenModeEnabled}
|
zenModeEnabled={zenModeEnabled}
|
||||||
toggleZenMode={this.toggleZenMode}
|
toggleZenMode={this.toggleZenMode}
|
||||||
lng={getLanguage().lng}
|
langCode={getLanguage().code}
|
||||||
isCollaborating={this.props.isCollaborating || false}
|
isCollaborating={this.props.isCollaborating || false}
|
||||||
onExportToBackend={onExportToBackend}
|
onExportToBackend={onExportToBackend}
|
||||||
|
renderCustomFooter={renderFooter}
|
||||||
/>
|
/>
|
||||||
{this.state.showStats && (
|
{this.state.showStats && (
|
||||||
<Stats
|
<Stats
|
||||||
@@ -394,6 +377,12 @@ class App extends React.Component<ExcalidrawProps, AppState> {
|
|||||||
onClose={this.toggleStats}
|
onClose={this.toggleStats}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
{this.state.toastMessage !== null && (
|
||||||
|
<Toast
|
||||||
|
message={this.state.toastMessage}
|
||||||
|
clearToast={this.clearToast}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
<main>
|
<main>
|
||||||
<canvas
|
<canvas
|
||||||
id="canvas"
|
id="canvas"
|
||||||
@@ -517,7 +506,6 @@ class App extends React.Component<ExcalidrawProps, AppState> {
|
|||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
await Library.importLibrary(blob);
|
await Library.importLibrary(blob);
|
||||||
trackEvent(EVENT_LIBRARY, "import");
|
|
||||||
this.setState({
|
this.setState({
|
||||||
isLibraryOpen: true,
|
isLibraryOpen: true,
|
||||||
});
|
});
|
||||||
@@ -752,6 +740,10 @@ class App extends React.Component<ExcalidrawProps, AppState> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
componentDidUpdate(prevProps: ExcalidrawProps, prevState: AppState) {
|
componentDidUpdate(prevProps: ExcalidrawProps, prevState: AppState) {
|
||||||
|
if (prevProps.langCode !== this.props.langCode) {
|
||||||
|
this.updateLanguage();
|
||||||
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
prevProps.width !== this.props.width ||
|
prevProps.width !== this.props.width ||
|
||||||
prevProps.height !== this.props.height ||
|
prevProps.height !== this.props.height ||
|
||||||
@@ -875,7 +867,16 @@ class App extends React.Component<ExcalidrawProps, AppState> {
|
|||||||
|
|
||||||
history.record(this.state, this.scene.getElementsIncludingDeleted());
|
history.record(this.state, this.scene.getElementsIncludingDeleted());
|
||||||
|
|
||||||
this.props.onChange?.(this.scene.getElementsIncludingDeleted(), this.state);
|
// Do not notify consumers if we're still loading the scene. Among other
|
||||||
|
// potential issues, this fixes a case where the tab isn't focused during
|
||||||
|
// init, which would trigger onChange with empty elements, which would then
|
||||||
|
// override whatever is in localStorage currently.
|
||||||
|
if (!this.state.isLoading) {
|
||||||
|
this.props.onChange?.(
|
||||||
|
this.scene.getElementsIncludingDeleted(),
|
||||||
|
this.state,
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Copy/paste
|
// Copy/paste
|
||||||
@@ -917,6 +918,7 @@ class App extends React.Component<ExcalidrawProps, AppState> {
|
|||||||
this.canvas!,
|
this.canvas!,
|
||||||
this.state,
|
this.state,
|
||||||
);
|
);
|
||||||
|
this.setState({ toastMessage: t("toast.copyToClipboardAsPng") });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
this.setState({ errorMessage: error.message });
|
this.setState({ errorMessage: error.message });
|
||||||
@@ -1004,9 +1006,12 @@ class App extends React.Component<ExcalidrawProps, AppState> {
|
|||||||
if (data.errorMessage) {
|
if (data.errorMessage) {
|
||||||
this.setState({ errorMessage: data.errorMessage });
|
this.setState({ errorMessage: data.errorMessage });
|
||||||
} else if (data.spreadsheet) {
|
} else if (data.spreadsheet) {
|
||||||
this.addElementsFromPasteOrLibrary(
|
this.setState({
|
||||||
renderSpreadsheet(data.spreadsheet, cursorX, cursorY),
|
pasteDialog: {
|
||||||
);
|
data: data.spreadsheet,
|
||||||
|
shown: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
} else if (data.elements) {
|
} else if (data.elements) {
|
||||||
this.addElementsFromPasteOrLibrary(data.elements);
|
this.addElementsFromPasteOrLibrary(data.elements);
|
||||||
} else if (data.text) {
|
} else if (data.text) {
|
||||||
@@ -1131,7 +1136,6 @@ class App extends React.Component<ExcalidrawProps, AppState> {
|
|||||||
|
|
||||||
toggleLock = () => {
|
toggleLock = () => {
|
||||||
this.setState((prevState) => {
|
this.setState((prevState) => {
|
||||||
trackEvent(EVENT_SHAPE, "lock", !prevState.elementLocked ? "on" : "off");
|
|
||||||
return {
|
return {
|
||||||
elementLocked: !prevState.elementLocked,
|
elementLocked: !prevState.elementLocked,
|
||||||
elementType: prevState.elementLocked
|
elementType: prevState.elementLocked
|
||||||
@@ -1155,7 +1159,7 @@ class App extends React.Component<ExcalidrawProps, AppState> {
|
|||||||
|
|
||||||
toggleStats = () => {
|
toggleStats = () => {
|
||||||
if (!this.state.showStats) {
|
if (!this.state.showStats) {
|
||||||
trackEvent(EVENT_DIALOG, "stats");
|
trackEvent("dialog", "stats");
|
||||||
}
|
}
|
||||||
this.setState({
|
this.setState({
|
||||||
showStats: !this.state.showStats,
|
showStats: !this.state.showStats,
|
||||||
@@ -1172,6 +1176,10 @@ class App extends React.Component<ExcalidrawProps, AppState> {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
clearToast = () => {
|
||||||
|
this.setState({ toastMessage: null });
|
||||||
|
};
|
||||||
|
|
||||||
public updateScene = withBatchedUpdates((sceneData: SceneData) => {
|
public updateScene = withBatchedUpdates((sceneData: SceneData) => {
|
||||||
if (sceneData.commitToHistory) {
|
if (sceneData.commitToHistory) {
|
||||||
history.resumeRecording();
|
history.resumeRecording();
|
||||||
@@ -1241,7 +1249,7 @@ class App extends React.Component<ExcalidrawProps, AppState> {
|
|||||||
|
|
||||||
if (event.key === KEYS.QUESTION_MARK) {
|
if (event.key === KEYS.QUESTION_MARK) {
|
||||||
this.setState({
|
this.setState({
|
||||||
showShortcutsDialog: true,
|
showHelpDialog: true,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1267,9 +1275,6 @@ class App extends React.Component<ExcalidrawProps, AppState> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (event.code === CODES.NINE) {
|
if (event.code === CODES.NINE) {
|
||||||
if (!this.state.isLibraryOpen) {
|
|
||||||
trackEvent(EVENT_DIALOG, "library");
|
|
||||||
}
|
|
||||||
this.setState({ isLibraryOpen: !this.state.isLibraryOpen });
|
this.setState({ isLibraryOpen: !this.state.isLibraryOpen });
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1354,7 +1359,6 @@ class App extends React.Component<ExcalidrawProps, AppState> {
|
|||||||
) {
|
) {
|
||||||
const shape = findShapeByKey(event.key);
|
const shape = findShapeByKey(event.key);
|
||||||
if (shape) {
|
if (shape) {
|
||||||
trackEvent(EVENT_SHAPE, shape, "shortcut");
|
|
||||||
this.selectShapeTool(shape);
|
this.selectShapeTool(shape);
|
||||||
} else if (event.key === KEYS.Q) {
|
} else if (event.key === KEYS.Q) {
|
||||||
this.toggleLock();
|
this.toggleLock();
|
||||||
@@ -1738,7 +1742,6 @@ class App extends React.Component<ExcalidrawProps, AppState> {
|
|||||||
resetCursor();
|
resetCursor();
|
||||||
|
|
||||||
if (!event[KEYS.CTRL_OR_CMD]) {
|
if (!event[KEYS.CTRL_OR_CMD]) {
|
||||||
trackEvent(EVENT_SHAPE, "text", "double-click");
|
|
||||||
this.startTextEditing({
|
this.startTextEditing({
|
||||||
sceneX,
|
sceneX,
|
||||||
sceneY,
|
sceneY,
|
||||||
@@ -2462,8 +2465,7 @@ class App extends React.Component<ExcalidrawProps, AppState> {
|
|||||||
// otherwise, it will trigger selection based on current
|
// otherwise, it will trigger selection based on current
|
||||||
// state of the box
|
// state of the box
|
||||||
if (!this.state.selectedElementIds[hitElement.id]) {
|
if (!this.state.selectedElementIds[hitElement.id]) {
|
||||||
// if we are currently editing a group, treat all selections outside of the group
|
// if we are currently editing a group, exiting editing mode and deselect the group.
|
||||||
// as exiting editing mode.
|
|
||||||
if (
|
if (
|
||||||
this.state.editingGroupId &&
|
this.state.editingGroupId &&
|
||||||
!isElementInGroup(hitElement, this.state.editingGroupId)
|
!isElementInGroup(hitElement, this.state.editingGroupId)
|
||||||
@@ -2473,7 +2475,6 @@ class App extends React.Component<ExcalidrawProps, AppState> {
|
|||||||
selectedGroupIds: {},
|
selectedGroupIds: {},
|
||||||
editingGroupId: null,
|
editingGroupId: null,
|
||||||
});
|
});
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add hit element to selection. At this point if we're not holding
|
// Add hit element to selection. At this point if we're not holding
|
||||||
@@ -3140,7 +3141,7 @@ class App extends React.Component<ExcalidrawProps, AppState> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
this.setState({ suggestedBindings: [], startBoundElement: null });
|
this.setState({ suggestedBindings: [], startBoundElement: null });
|
||||||
if (!elementLocked) {
|
if (!elementLocked && elementType !== "draw") {
|
||||||
resetCursor();
|
resetCursor();
|
||||||
this.setState((prevState) => ({
|
this.setState((prevState) => ({
|
||||||
draggingElement: null,
|
draggingElement: null,
|
||||||
@@ -3287,7 +3288,7 @@ class App extends React.Component<ExcalidrawProps, AppState> {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!elementLocked && draggingElement) {
|
if (!elementLocked && elementType !== "draw" && draggingElement) {
|
||||||
this.setState((prevState) => ({
|
this.setState((prevState) => ({
|
||||||
selectedElementIds: {
|
selectedElementIds: {
|
||||||
...prevState.selectedElementIds,
|
...prevState.selectedElementIds,
|
||||||
@@ -3311,7 +3312,7 @@ class App extends React.Component<ExcalidrawProps, AppState> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!elementLocked) {
|
if (!elementLocked && elementType !== "draw") {
|
||||||
resetCursor();
|
resetCursor();
|
||||||
this.setState({
|
this.setState({
|
||||||
draggingElement: null,
|
draggingElement: null,
|
||||||
@@ -3586,9 +3587,6 @@ class App extends React.Component<ExcalidrawProps, AppState> {
|
|||||||
transformElements(
|
transformElements(
|
||||||
pointerDownState,
|
pointerDownState,
|
||||||
transformHandleType,
|
transformHandleType,
|
||||||
(newTransformHandle) => {
|
|
||||||
pointerDownState.resize.handleType = newTransformHandle;
|
|
||||||
},
|
|
||||||
selectedElements,
|
selectedElements,
|
||||||
pointerDownState.resize.arrowDirection,
|
pointerDownState.resize.arrowDirection,
|
||||||
getRotateWithDiscreteAngleKey(event),
|
getRotateWithDiscreteAngleKey(event),
|
||||||
@@ -3649,6 +3647,12 @@ class App extends React.Component<ExcalidrawProps, AppState> {
|
|||||||
label: t("labels.gridMode"),
|
label: t("labels.gridMode"),
|
||||||
action: this.toggleGridMode,
|
action: this.toggleGridMode,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
checked: this.state.zenModeEnabled,
|
||||||
|
shortcutName: "zenMode",
|
||||||
|
label: t("buttons.zenMode"),
|
||||||
|
action: this.toggleZenMode,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
checked: this.state.showStats,
|
checked: this.state.showStats,
|
||||||
shortcutName: "stats",
|
shortcutName: "stats",
|
||||||
@@ -3821,7 +3825,9 @@ class App extends React.Component<ExcalidrawProps, AppState> {
|
|||||||
};
|
};
|
||||||
|
|
||||||
private resetShouldCacheIgnoreZoomDebounced = debounce(() => {
|
private resetShouldCacheIgnoreZoomDebounced = debounce(() => {
|
||||||
this.setState({ shouldCacheIgnoreZoom: false });
|
if (!this.unmounted) {
|
||||||
|
this.setState({ shouldCacheIgnoreZoom: false });
|
||||||
|
}
|
||||||
}, 300);
|
}, 300);
|
||||||
|
|
||||||
private getCanvasOffsets(offsets?: {
|
private getCanvasOffsets(offsets?: {
|
||||||
@@ -3853,6 +3859,14 @@ class App extends React.Component<ExcalidrawProps, AppState> {
|
|||||||
offsetTop: typeof offsets?.offsetTop === "number" ? offsets.offsetTop : 0,
|
offsetTop: typeof offsets?.offsetTop === "number" ? offsets.offsetTop : 0,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async updateLanguage() {
|
||||||
|
const currentLang =
|
||||||
|
languages.find((lang) => lang.code === this.props.langCode) ||
|
||||||
|
defaultLang;
|
||||||
|
await setLanguage(currentLang);
|
||||||
|
this.setAppState({});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// -----------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { ActionManager } from "../actions/manager";
|
import { ActionManager } from "../actions/manager";
|
||||||
import { EVENT_CHANGE, trackEvent } from "../analytics";
|
|
||||||
import { AppState } from "../types";
|
import { AppState } from "../types";
|
||||||
import { DarkModeToggle } from "./DarkModeToggle";
|
import { DarkModeToggle } from "./DarkModeToggle";
|
||||||
|
|
||||||
@@ -19,8 +18,6 @@ export const BackgroundPickerAndDarkModeToggle = ({
|
|||||||
<DarkModeToggle
|
<DarkModeToggle
|
||||||
value={appState.appearance}
|
value={appState.appearance}
|
||||||
onChange={(appearance) => {
|
onChange={(appearance) => {
|
||||||
// TODO: track the theme on the first load too
|
|
||||||
trackEvent(EVENT_CHANGE, "theme", appearance);
|
|
||||||
setAppState({ appearance });
|
setAppState({ appearance });
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import useIsMobile from "../is-mobile";
|
|||||||
import { users } from "./icons";
|
import { users } from "./icons";
|
||||||
|
|
||||||
import "./CollabButton.scss";
|
import "./CollabButton.scss";
|
||||||
import { EVENT_DIALOG, trackEvent } from "../analytics";
|
|
||||||
|
|
||||||
const CollabButton = ({
|
const CollabButton = ({
|
||||||
isCollaborating,
|
isCollaborating,
|
||||||
@@ -23,10 +22,7 @@ const CollabButton = ({
|
|||||||
className={clsx("CollabButton", {
|
className={clsx("CollabButton", {
|
||||||
"is-collaborating": isCollaborating,
|
"is-collaborating": isCollaborating,
|
||||||
})}
|
})}
|
||||||
onClick={() => {
|
onClick={onClick}
|
||||||
trackEvent(EVENT_DIALOG, "collaboration");
|
|
||||||
onClick();
|
|
||||||
}}
|
|
||||||
icon={users}
|
icon={users}
|
||||||
type="button"
|
type="button"
|
||||||
title={t("buttons.roomDialog")}
|
title={t("buttons.roomDialog")}
|
||||||
|
|||||||
@@ -218,7 +218,7 @@
|
|||||||
left: 2px;
|
left: 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media #{$media-query} {
|
@media #{$is-mobile-query} {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
@import "open-color/open-color.scss";
|
@import "../css/_variables";
|
||||||
|
|
||||||
.excalidraw {
|
.excalidraw {
|
||||||
.context-menu {
|
.context-menu {
|
||||||
@@ -42,18 +42,19 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
&.dangerous {
|
&.dangerous {
|
||||||
div:nth-child(1) {
|
.context-menu-option__label {
|
||||||
color: $oc-red-7;
|
color: $oc-red-7;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
div:nth-child(1) {
|
.context-menu-option__label {
|
||||||
justify-self: start;
|
justify-self: start;
|
||||||
margin-inline-end: 20px;
|
margin-inline-end: 20px;
|
||||||
}
|
}
|
||||||
div:nth-child(2) {
|
.context-menu-option__shortcut {
|
||||||
justify-self: end;
|
justify-self: end;
|
||||||
opacity: 0.6;
|
opacity: 0.6;
|
||||||
|
font-family: inherit;
|
||||||
font-size: 0.7rem;
|
font-size: 0.7rem;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -63,7 +64,7 @@
|
|||||||
background-color: var(--select-highlight-color);
|
background-color: var(--select-highlight-color);
|
||||||
|
|
||||||
&.dangerous {
|
&.dangerous {
|
||||||
div:nth-child(1) {
|
.context-menu-option__label {
|
||||||
color: var(--popup-background-color);
|
color: var(--popup-background-color);
|
||||||
}
|
}
|
||||||
background-color: $oc-red-6;
|
background-color: $oc-red-6;
|
||||||
@@ -73,4 +74,18 @@
|
|||||||
.context-menu-option:focus {
|
.context-menu-option:focus {
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@media #{$is-mobile-query} {
|
||||||
|
.context-menu-option {
|
||||||
|
display: block;
|
||||||
|
|
||||||
|
.context-menu-option__label {
|
||||||
|
margin-inline-end: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.context-menu-option__shortcut {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -51,12 +51,12 @@ const ContextMenu = ({ options, onCloseRequest, top, left }: Props) => {
|
|||||||
${checked ? "checkmark" : ""}`}
|
${checked ? "checkmark" : ""}`}
|
||||||
onClick={action}
|
onClick={action}
|
||||||
>
|
>
|
||||||
<div>{label}</div>
|
<div className="context-menu-option__label">{label}</div>
|
||||||
<div>
|
<kbd className="context-menu-option__shortcut">
|
||||||
{shortcutName
|
{shortcutName
|
||||||
? getShortcutFromShortcutName(shortcutName)
|
? getShortcutFromShortcutName(shortcutName)
|
||||||
: ""}
|
: ""}
|
||||||
</div>
|
</kbd>
|
||||||
</button>
|
</button>
|
||||||
</li>
|
</li>
|
||||||
))}
|
))}
|
||||||
|
|||||||
@@ -1,12 +1,21 @@
|
|||||||
@import "../css/_variables";
|
@import "../css/_variables";
|
||||||
|
|
||||||
.excalidraw {
|
.excalidraw {
|
||||||
|
.Dialog {
|
||||||
|
user-select: text;
|
||||||
|
cursor: auto;
|
||||||
|
}
|
||||||
|
|
||||||
.Dialog__title {
|
.Dialog__title {
|
||||||
display: grid;
|
display: grid;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
margin-top: 0;
|
margin-top: 0;
|
||||||
grid-template-columns: 1fr calc(var(--space-factor) * 7);
|
grid-template-columns: 1fr calc(var(--space-factor) * 7);
|
||||||
grid-gap: var(--metric);
|
grid-gap: var(--metric);
|
||||||
|
padding: calc(var(--space-factor) * 2);
|
||||||
|
text-align: center;
|
||||||
|
font-variant: small-caps;
|
||||||
|
font-size: 1.2em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.Dialog__titleContent {
|
.Dialog__titleContent {
|
||||||
@@ -18,7 +27,11 @@
|
|||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media #{$media-query} {
|
.Dialog__content {
|
||||||
|
padding: 0 16px 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media #{$is-mobile-query} {
|
||||||
.Dialog {
|
.Dialog {
|
||||||
--metric: calc(var(--space-factor) * 4);
|
--metric: calc(var(--space-factor) * 4);
|
||||||
--inset-left: #{"max(var(--metric), var(--sal))"};
|
--inset-left: #{"max(var(--metric), var(--sal))"};
|
||||||
@@ -30,13 +43,8 @@
|
|||||||
var(--space-factor) * 7
|
var(--space-factor) * 7
|
||||||
);
|
);
|
||||||
position: sticky;
|
position: sticky;
|
||||||
top: calc(-1 * var(--metric));
|
top: 0;
|
||||||
margin: calc(-1 * var(--inset-right));
|
|
||||||
margin-top: calc(-1 * var(--metric));
|
|
||||||
margin-bottom: var(--metric);
|
|
||||||
padding: calc(var(--space-factor) * 2);
|
padding: calc(var(--space-factor) * 2);
|
||||||
padding-left: var(--inset-left);
|
|
||||||
padding-right: var(--inset-right);
|
|
||||||
background: var(--bg-color-island);
|
background: var(--bg-color-island);
|
||||||
font-size: 1.25em;
|
font-size: 1.25em;
|
||||||
|
|
||||||
|
|||||||
@@ -1,13 +1,12 @@
|
|||||||
import React, { useCallback, useEffect, useState } from "react";
|
|
||||||
import clsx from "clsx";
|
import clsx from "clsx";
|
||||||
import { Modal } from "./Modal";
|
import React, { useCallback, useEffect, useState } from "react";
|
||||||
import { Island } from "./Island";
|
|
||||||
import { t } from "../i18n";
|
import { t } from "../i18n";
|
||||||
import useIsMobile from "../is-mobile";
|
import useIsMobile from "../is-mobile";
|
||||||
import { back, close } from "./icons";
|
|
||||||
import { KEYS } from "../keys";
|
import { KEYS } from "../keys";
|
||||||
|
|
||||||
import "./Dialog.scss";
|
import "./Dialog.scss";
|
||||||
|
import { back, close } from "./icons";
|
||||||
|
import { Island } from "./Island";
|
||||||
|
import { Modal } from "./Modal";
|
||||||
|
|
||||||
const useRefState = <T,>() => {
|
const useRefState = <T,>() => {
|
||||||
const [refValue, setRefValue] = useState<T | null>(null);
|
const [refValue, setRefValue] = useState<T | null>(null);
|
||||||
@@ -20,9 +19,10 @@ const useRefState = <T,>() => {
|
|||||||
export const Dialog = (props: {
|
export const Dialog = (props: {
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
className?: string;
|
className?: string;
|
||||||
maxWidth?: number;
|
small?: boolean;
|
||||||
onCloseRequest(): void;
|
onCloseRequest(): void;
|
||||||
title: React.ReactNode;
|
title: React.ReactNode;
|
||||||
|
autofocus?: boolean;
|
||||||
}) => {
|
}) => {
|
||||||
const [islandNode, setIslandNode] = useRefState<HTMLDivElement>();
|
const [islandNode, setIslandNode] = useRefState<HTMLDivElement>();
|
||||||
|
|
||||||
@@ -33,7 +33,7 @@ export const Dialog = (props: {
|
|||||||
|
|
||||||
const focusableElements = queryFocusableElements(islandNode);
|
const focusableElements = queryFocusableElements(islandNode);
|
||||||
|
|
||||||
if (focusableElements.length > 0) {
|
if (focusableElements.length > 0 && props.autofocus !== false) {
|
||||||
// If there's an element other than close, focus it.
|
// If there's an element other than close, focus it.
|
||||||
(focusableElements[1] || focusableElements[0]).focus();
|
(focusableElements[1] || focusableElements[0]).focus();
|
||||||
}
|
}
|
||||||
@@ -62,7 +62,7 @@ export const Dialog = (props: {
|
|||||||
islandNode.addEventListener("keydown", handleKeyDown);
|
islandNode.addEventListener("keydown", handleKeyDown);
|
||||||
|
|
||||||
return () => islandNode.removeEventListener("keydown", handleKeyDown);
|
return () => islandNode.removeEventListener("keydown", handleKeyDown);
|
||||||
}, [islandNode]);
|
}, [islandNode, props.autofocus]);
|
||||||
|
|
||||||
const queryFocusableElements = (node: HTMLElement) => {
|
const queryFocusableElements = (node: HTMLElement) => {
|
||||||
const focusableElements = node.querySelectorAll<HTMLElement>(
|
const focusableElements = node.querySelectorAll<HTMLElement>(
|
||||||
@@ -76,10 +76,10 @@ export const Dialog = (props: {
|
|||||||
<Modal
|
<Modal
|
||||||
className={clsx("Dialog", props.className)}
|
className={clsx("Dialog", props.className)}
|
||||||
labelledBy="dialog-title"
|
labelledBy="dialog-title"
|
||||||
maxWidth={props.maxWidth}
|
maxWidth={props.small ? 550 : 800}
|
||||||
onCloseRequest={props.onCloseRequest}
|
onCloseRequest={props.onCloseRequest}
|
||||||
>
|
>
|
||||||
<Island padding={4} ref={setIslandNode}>
|
<Island ref={setIslandNode}>
|
||||||
<h2 id="dialog-title" className="Dialog__title">
|
<h2 id="dialog-title" className="Dialog__title">
|
||||||
<span className="Dialog__titleContent">{props.title}</span>
|
<span className="Dialog__titleContent">{props.title}</span>
|
||||||
<button
|
<button
|
||||||
@@ -90,7 +90,7 @@ export const Dialog = (props: {
|
|||||||
{useIsMobile() ? back : close}
|
{useIsMobile() ? back : close}
|
||||||
</button>
|
</button>
|
||||||
</h2>
|
</h2>
|
||||||
{props.children}
|
<div className="Dialog__content">{props.children}</div>
|
||||||
</Island>
|
</Island>
|
||||||
</Modal>
|
</Modal>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ export const ErrorDialog = ({
|
|||||||
<>
|
<>
|
||||||
{modalIsShown && (
|
{modalIsShown && (
|
||||||
<Dialog
|
<Dialog
|
||||||
maxWidth={500}
|
small
|
||||||
onCloseRequest={handleClose}
|
onCloseRequest={handleClose}
|
||||||
title={t("errorDialog.title")}
|
title={t("errorDialog.title")}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -37,7 +37,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 550px) {
|
@media #{$is-mobile-query} {
|
||||||
.ExportDialog {
|
.ExportDialog {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
@@ -51,9 +51,7 @@
|
|||||||
.ExportDialog__actions > * {
|
.ExportDialog__actions > * {
|
||||||
margin-bottom: calc(var(--space-factor) * 3);
|
margin-bottom: calc(var(--space-factor) * 3);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
@media #{$media-query} {
|
|
||||||
.ExportDialog__preview canvas {
|
.ExportDialog__preview canvas {
|
||||||
max-height: 30vh;
|
max-height: 30vh;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import React, { useEffect, useRef, useState } from "react";
|
import React, { useEffect, useRef, useState } from "react";
|
||||||
import { render, unmountComponentAtNode } from "react-dom";
|
import { render, unmountComponentAtNode } from "react-dom";
|
||||||
import { ActionsManagerInterface } from "../actions/types";
|
import { ActionsManagerInterface } from "../actions/types";
|
||||||
import { EVENT_DIALOG, trackEvent } from "../analytics";
|
|
||||||
import { probablySupportsClipboardBlob } from "../clipboard";
|
import { probablySupportsClipboardBlob } from "../clipboard";
|
||||||
import { canvasToBlob } from "../data/blob";
|
import { canvasToBlob } from "../data/blob";
|
||||||
import { NonDeletedExcalidrawElement } from "../element/types";
|
import { NonDeletedExcalidrawElement } from "../element/types";
|
||||||
@@ -251,7 +250,6 @@ export const ExportDialog = ({
|
|||||||
<>
|
<>
|
||||||
<ToolButton
|
<ToolButton
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
trackEvent(EVENT_DIALOG, "export");
|
|
||||||
setModalIsShown(true);
|
setModalIsShown(true);
|
||||||
}}
|
}}
|
||||||
icon={exportFile}
|
icon={exportFile}
|
||||||
@@ -262,11 +260,7 @@ export const ExportDialog = ({
|
|||||||
ref={triggerButton}
|
ref={triggerButton}
|
||||||
/>
|
/>
|
||||||
{modalIsShown && (
|
{modalIsShown && (
|
||||||
<Dialog
|
<Dialog onCloseRequest={handleClose} title={t("buttons.export")}>
|
||||||
maxWidth={800}
|
|
||||||
onCloseRequest={handleClose}
|
|
||||||
title={t("buttons.export")}
|
|
||||||
>
|
|
||||||
<ExportModal
|
<ExportModal
|
||||||
elements={elements}
|
elements={elements}
|
||||||
appState={appState}
|
appState={appState}
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import React from "react";
|
|
||||||
import oc from "open-color";
|
import oc from "open-color";
|
||||||
import { EVENT_EXIT, trackEvent } from "../analytics";
|
import React from "react";
|
||||||
|
|
||||||
// https://github.com/tholman/github-corners
|
// https://github.com/tholman/github-corners
|
||||||
export const GitHubCorner = React.memo(
|
export const GitHubCorner = React.memo(
|
||||||
@@ -17,9 +16,6 @@ export const GitHubCorner = React.memo(
|
|||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
aria-label="GitHub repository"
|
aria-label="GitHub repository"
|
||||||
onClick={() => {
|
|
||||||
trackEvent(EVENT_EXIT, "github");
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
<path
|
<path
|
||||||
d="M0 0l115 115h15l12 27 108 108V0z"
|
d="M0 0l115 115h15l12 27 108 108V0z"
|
||||||
|
|||||||
@@ -1,23 +1,28 @@
|
|||||||
@import "../css/_variables";
|
@import "../css/_variables";
|
||||||
|
|
||||||
.excalidraw {
|
.excalidraw {
|
||||||
.ShortcutsDialog-island {
|
.HelpDialog h3 {
|
||||||
|
border-bottom: 1px solid var(--button-gray-2);
|
||||||
|
padding-bottom: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.HelpDialog--island {
|
||||||
border: 1px solid var(--button-gray-2);
|
border: 1px solid var(--button-gray-2);
|
||||||
margin-bottom: 16px;
|
margin-bottom: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ShortcutsDialog-island-title {
|
.HelpDialog--island-title {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 4px;
|
padding: 4px;
|
||||||
background-color: var(--button-gray-1);
|
background-color: var(--button-gray-1);
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ShorcutsDialog-shortcut {
|
.HelpDialog--shortcut {
|
||||||
border-top: 1px solid var(--button-gray-2);
|
border-top: 1px solid var(--button-gray-2);
|
||||||
}
|
}
|
||||||
|
|
||||||
.ShorcutsDialog-key {
|
.HelpDialog--key {
|
||||||
word-break: keep-all;
|
word-break: keep-all;
|
||||||
border: 1px solid var(--button-gray-2);
|
border: 1px solid var(--button-gray-2);
|
||||||
padding: 2px 8px;
|
padding: 2px 8px;
|
||||||
@@ -29,14 +34,23 @@
|
|||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
font-family: inherit;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ShortcutsDialog-footer {
|
.HelpDialog--header {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
justify-content: space-evenly;
|
justify-content: space-evenly;
|
||||||
border-top: 1px solid var(--button-gray-2);
|
margin-bottom: 32px;
|
||||||
margin-top: 8px;
|
padding-bottom: 16px;
|
||||||
padding-top: 16px;
|
}
|
||||||
|
|
||||||
|
.HelpDialog--btn {
|
||||||
|
border: 1px solid var(--link-color);
|
||||||
|
padding: 8px 32px;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
.HelpDialog--btn:hover {
|
||||||
|
text-decoration: none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,348 @@
|
|||||||
|
import React from "react";
|
||||||
|
import { t } from "../i18n";
|
||||||
|
import { isDarwin } from "../keys";
|
||||||
|
import { Dialog } from "./Dialog";
|
||||||
|
import { getShortcutKey } from "../utils";
|
||||||
|
import "./HelpDialog.scss";
|
||||||
|
|
||||||
|
const Header = () => (
|
||||||
|
<div className="HelpDialog--header">
|
||||||
|
<a
|
||||||
|
className="HelpDialog--btn"
|
||||||
|
href="https://github.com/excalidraw/excalidraw#documentation"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
>
|
||||||
|
{t("helpDialog.documentation")}
|
||||||
|
</a>
|
||||||
|
<a
|
||||||
|
className="HelpDialog--btn"
|
||||||
|
href="https://blog.excalidraw.com"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
>
|
||||||
|
{t("helpDialog.blog")}
|
||||||
|
</a>
|
||||||
|
<a
|
||||||
|
className="HelpDialog--btn"
|
||||||
|
href="https://github.com/excalidraw/excalidraw/issues"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
>
|
||||||
|
{t("helpDialog.github")}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
const Section = (props: { title: string; children: React.ReactNode }) => (
|
||||||
|
<>
|
||||||
|
<h3>{props.title}</h3>
|
||||||
|
{props.children}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
|
||||||
|
const Columns = (props: { children: React.ReactNode }) => (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: "flex",
|
||||||
|
flexDirection: "row",
|
||||||
|
flexWrap: "wrap",
|
||||||
|
justifyContent: "space-between",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{props.children}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
const Column = (props: { children: React.ReactNode }) => (
|
||||||
|
<div style={{ width: "49%" }}>{props.children}</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
const ShortcutIsland = (props: {
|
||||||
|
caption: string;
|
||||||
|
children: React.ReactNode;
|
||||||
|
}) => (
|
||||||
|
<div className="HelpDialog--island">
|
||||||
|
<h3 className="HelpDialog--island-title">{props.caption}</h3>
|
||||||
|
{props.children}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
const Shortcut = (props: {
|
||||||
|
label: string;
|
||||||
|
shortcuts: string[];
|
||||||
|
isOr: boolean;
|
||||||
|
}) => {
|
||||||
|
return (
|
||||||
|
<div className="HelpDialog--shortcut">
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: "flex",
|
||||||
|
margin: "0",
|
||||||
|
padding: "4px 8px",
|
||||||
|
alignItems: "center",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
lineHeight: 1.4,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{props.label}
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: "flex",
|
||||||
|
flex: "0 0 auto",
|
||||||
|
justifyContent: "flex-end",
|
||||||
|
marginInlineStart: "auto",
|
||||||
|
minWidth: "30%",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{props.shortcuts.map((shortcut, index) => (
|
||||||
|
<React.Fragment key={index}>
|
||||||
|
<ShortcutKey>{shortcut}</ShortcutKey>
|
||||||
|
{props.isOr &&
|
||||||
|
index !== props.shortcuts.length - 1 &&
|
||||||
|
t("helpDialog.or")}
|
||||||
|
</React.Fragment>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
Shortcut.defaultProps = {
|
||||||
|
isOr: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
const ShortcutKey = (props: { children: React.ReactNode }) => (
|
||||||
|
<kbd className="HelpDialog--key" {...props} />
|
||||||
|
);
|
||||||
|
|
||||||
|
export const HelpDialog = ({ onClose }: { onClose?: () => void }) => {
|
||||||
|
const handleClose = React.useCallback(() => {
|
||||||
|
if (onClose) {
|
||||||
|
onClose();
|
||||||
|
}
|
||||||
|
}, [onClose]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Dialog
|
||||||
|
onCloseRequest={handleClose}
|
||||||
|
title={t("helpDialog.title")}
|
||||||
|
className={"HelpDialog"}
|
||||||
|
>
|
||||||
|
<Header />
|
||||||
|
<Section title={t("helpDialog.shortcuts")}>
|
||||||
|
<Columns>
|
||||||
|
<Column>
|
||||||
|
<ShortcutIsland caption={t("helpDialog.shapes")}>
|
||||||
|
<Shortcut
|
||||||
|
label={t("toolBar.selection")}
|
||||||
|
shortcuts={["V", "1"]}
|
||||||
|
/>
|
||||||
|
<Shortcut
|
||||||
|
label={t("toolBar.rectangle")}
|
||||||
|
shortcuts={["R", "2"]}
|
||||||
|
/>
|
||||||
|
<Shortcut label={t("toolBar.diamond")} shortcuts={["D", "3"]} />
|
||||||
|
<Shortcut label={t("toolBar.ellipse")} shortcuts={["E", "4"]} />
|
||||||
|
<Shortcut label={t("toolBar.arrow")} shortcuts={["A", "5"]} />
|
||||||
|
<Shortcut label={t("toolBar.line")} shortcuts={["P", "6"]} />
|
||||||
|
<Shortcut
|
||||||
|
label={t("toolBar.draw")}
|
||||||
|
shortcuts={["Shift+P", "7"]}
|
||||||
|
/>
|
||||||
|
<Shortcut label={t("toolBar.text")} shortcuts={["T", "8"]} />
|
||||||
|
<Shortcut
|
||||||
|
label={t("helpDialog.textNewLine")}
|
||||||
|
shortcuts={[
|
||||||
|
getShortcutKey("Enter"),
|
||||||
|
getShortcutKey("Shift+Enter"),
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
<Shortcut
|
||||||
|
label={t("helpDialog.textFinish")}
|
||||||
|
shortcuts={[
|
||||||
|
getShortcutKey("Esc"),
|
||||||
|
getShortcutKey("CtrlOrCmd+Enter"),
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
<Shortcut
|
||||||
|
label={t("helpDialog.curvedArrow")}
|
||||||
|
shortcuts={[
|
||||||
|
"A",
|
||||||
|
t("helpDialog.click"),
|
||||||
|
t("helpDialog.click"),
|
||||||
|
t("helpDialog.click"),
|
||||||
|
]}
|
||||||
|
isOr={false}
|
||||||
|
/>
|
||||||
|
<Shortcut
|
||||||
|
label={t("helpDialog.curvedLine")}
|
||||||
|
shortcuts={[
|
||||||
|
"L",
|
||||||
|
t("helpDialog.click"),
|
||||||
|
t("helpDialog.click"),
|
||||||
|
t("helpDialog.click"),
|
||||||
|
]}
|
||||||
|
isOr={false}
|
||||||
|
/>
|
||||||
|
<Shortcut label={t("toolBar.lock")} shortcuts={["Q"]} />
|
||||||
|
<Shortcut
|
||||||
|
label={t("helpDialog.preventBinding")}
|
||||||
|
shortcuts={[getShortcutKey("CtrlOrCmd")]}
|
||||||
|
/>
|
||||||
|
</ShortcutIsland>
|
||||||
|
<ShortcutIsland caption={t("helpDialog.view")}>
|
||||||
|
<Shortcut
|
||||||
|
label={t("buttons.zoomIn")}
|
||||||
|
shortcuts={[getShortcutKey("CtrlOrCmd++")]}
|
||||||
|
/>
|
||||||
|
<Shortcut
|
||||||
|
label={t("buttons.zoomOut")}
|
||||||
|
shortcuts={[getShortcutKey("CtrlOrCmd+-")]}
|
||||||
|
/>
|
||||||
|
<Shortcut
|
||||||
|
label={t("buttons.resetZoom")}
|
||||||
|
shortcuts={[getShortcutKey("CtrlOrCmd+0")]}
|
||||||
|
/>
|
||||||
|
<Shortcut
|
||||||
|
label={t("helpDialog.zoomToFit")}
|
||||||
|
shortcuts={["Shift+1"]}
|
||||||
|
/>
|
||||||
|
<Shortcut
|
||||||
|
label={t("helpDialog.zoomToSelection")}
|
||||||
|
shortcuts={["Shift+2"]}
|
||||||
|
/>
|
||||||
|
<Shortcut label={t("buttons.fullScreen")} shortcuts={["F"]} />
|
||||||
|
<Shortcut
|
||||||
|
label={t("buttons.zenMode")}
|
||||||
|
shortcuts={[getShortcutKey("Alt+Z")]}
|
||||||
|
/>
|
||||||
|
<Shortcut
|
||||||
|
label={t("labels.gridMode")}
|
||||||
|
shortcuts={[getShortcutKey("CtrlOrCmd+'")]}
|
||||||
|
/>
|
||||||
|
</ShortcutIsland>
|
||||||
|
</Column>
|
||||||
|
<Column>
|
||||||
|
<ShortcutIsland caption={t("helpDialog.editor")}>
|
||||||
|
<Shortcut
|
||||||
|
label={t("labels.selectAll")}
|
||||||
|
shortcuts={[getShortcutKey("CtrlOrCmd+A")]}
|
||||||
|
/>
|
||||||
|
<Shortcut
|
||||||
|
label={t("labels.multiSelect")}
|
||||||
|
shortcuts={[getShortcutKey(`Shift+${t("helpDialog.click")}`)]}
|
||||||
|
/>
|
||||||
|
<Shortcut
|
||||||
|
label={t("labels.moveCanvas")}
|
||||||
|
shortcuts={[
|
||||||
|
getShortcutKey(`Space+${t("helpDialog.drag")}`),
|
||||||
|
getShortcutKey(`Wheel+${t("helpDialog.drag")}`),
|
||||||
|
]}
|
||||||
|
isOr={true}
|
||||||
|
/>
|
||||||
|
<Shortcut
|
||||||
|
label={t("labels.cut")}
|
||||||
|
shortcuts={[getShortcutKey("CtrlOrCmd+X")]}
|
||||||
|
/>
|
||||||
|
<Shortcut
|
||||||
|
label={t("labels.copy")}
|
||||||
|
shortcuts={[getShortcutKey("CtrlOrCmd+C")]}
|
||||||
|
/>
|
||||||
|
<Shortcut
|
||||||
|
label={t("labels.paste")}
|
||||||
|
shortcuts={[getShortcutKey("CtrlOrCmd+V")]}
|
||||||
|
/>
|
||||||
|
<Shortcut
|
||||||
|
label={t("labels.copyAsPng")}
|
||||||
|
shortcuts={[getShortcutKey("Shift+Alt+C")]}
|
||||||
|
/>
|
||||||
|
<Shortcut
|
||||||
|
label={t("labels.copyStyles")}
|
||||||
|
shortcuts={[getShortcutKey("CtrlOrCmd+Alt+C")]}
|
||||||
|
/>
|
||||||
|
<Shortcut
|
||||||
|
label={t("labels.pasteStyles")}
|
||||||
|
shortcuts={[getShortcutKey("CtrlOrCmd+Alt+V")]}
|
||||||
|
/>
|
||||||
|
<Shortcut
|
||||||
|
label={t("labels.delete")}
|
||||||
|
shortcuts={[getShortcutKey("Del")]}
|
||||||
|
/>
|
||||||
|
<Shortcut
|
||||||
|
label={t("labels.sendToBack")}
|
||||||
|
shortcuts={[
|
||||||
|
isDarwin
|
||||||
|
? getShortcutKey("CtrlOrCmd+Alt+[")
|
||||||
|
: getShortcutKey("CtrlOrCmd+Shift+["),
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
<Shortcut
|
||||||
|
label={t("labels.bringToFront")}
|
||||||
|
shortcuts={[
|
||||||
|
isDarwin
|
||||||
|
? getShortcutKey("CtrlOrCmd+Alt+]")
|
||||||
|
: getShortcutKey("CtrlOrCmd+Shift+]"),
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
<Shortcut
|
||||||
|
label={t("labels.sendBackward")}
|
||||||
|
shortcuts={[getShortcutKey("CtrlOrCmd+[")]}
|
||||||
|
/>
|
||||||
|
<Shortcut
|
||||||
|
label={t("labels.bringForward")}
|
||||||
|
shortcuts={[getShortcutKey("CtrlOrCmd+]")]}
|
||||||
|
/>
|
||||||
|
<Shortcut
|
||||||
|
label={t("labels.alignTop")}
|
||||||
|
shortcuts={[getShortcutKey("CtrlOrCmd+Shift+Up")]}
|
||||||
|
/>
|
||||||
|
<Shortcut
|
||||||
|
label={t("labels.alignBottom")}
|
||||||
|
shortcuts={[getShortcutKey("CtrlOrCmd+Shift+Down")]}
|
||||||
|
/>
|
||||||
|
<Shortcut
|
||||||
|
label={t("labels.alignLeft")}
|
||||||
|
shortcuts={[getShortcutKey("CtrlOrCmd+Shift+Left")]}
|
||||||
|
/>
|
||||||
|
<Shortcut
|
||||||
|
label={t("labels.alignRight")}
|
||||||
|
shortcuts={[getShortcutKey("CtrlOrCmd+Shift+Right")]}
|
||||||
|
/>
|
||||||
|
<Shortcut
|
||||||
|
label={t("labels.duplicateSelection")}
|
||||||
|
shortcuts={[
|
||||||
|
getShortcutKey("CtrlOrCmd+D"),
|
||||||
|
getShortcutKey(`Alt+${t("helpDialog.drag")}`),
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
<Shortcut
|
||||||
|
label={t("buttons.undo")}
|
||||||
|
shortcuts={[getShortcutKey("CtrlOrCmd+Z")]}
|
||||||
|
/>
|
||||||
|
<Shortcut
|
||||||
|
label={t("buttons.redo")}
|
||||||
|
shortcuts={[getShortcutKey("CtrlOrCmd+Shift+Z")]}
|
||||||
|
/>
|
||||||
|
<Shortcut
|
||||||
|
label={t("labels.group")}
|
||||||
|
shortcuts={[getShortcutKey("CtrlOrCmd+G")]}
|
||||||
|
/>
|
||||||
|
<Shortcut
|
||||||
|
label={t("labels.ungroup")}
|
||||||
|
shortcuts={[getShortcutKey("CtrlOrCmd+Shift+G")]}
|
||||||
|
/>
|
||||||
|
</ShortcutIsland>
|
||||||
|
</Column>
|
||||||
|
</Columns>
|
||||||
|
</Section>
|
||||||
|
</Dialog>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
|
import { questionCircle } from "../components/icons";
|
||||||
|
|
||||||
type HelpIconProps = {
|
type HelpIconProps = {
|
||||||
title?: string;
|
title?: string;
|
||||||
@@ -7,19 +8,8 @@ type HelpIconProps = {
|
|||||||
onClick?(): void;
|
onClick?(): void;
|
||||||
};
|
};
|
||||||
|
|
||||||
const ICON = (
|
|
||||||
<svg
|
|
||||||
width="30"
|
|
||||||
height="22"
|
|
||||||
viewBox="0 0 512 512"
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
>
|
|
||||||
<path d="M528 448H48c-26.51 0-48-21.49-48-48V112c0-26.51 21.49-48 48-48h480c26.51 0 48 21.49 48 48v288c0 26.51-21.49 48-48 48zM128 180v-40c0-6.627-5.373-12-12-12H76c-6.627 0-12 5.373-12 12v40c0 6.627 5.373 12 12 12h40c6.627 0 12-5.373 12-12zm96 0v-40c0-6.627-5.373-12-12-12h-40c-6.627 0-12 5.373-12 12v40c0 6.627 5.373 12 12 12h40c6.627 0 12-5.373 12-12zm96 0v-40c0-6.627-5.373-12-12-12h-40c-6.627 0-12 5.373-12 12v40c0 6.627 5.373 12 12 12h40c6.627 0 12-5.373 12-12zm96 0v-40c0-6.627-5.373-12-12-12h-40c-6.627 0-12 5.373-12 12v40c0 6.627 5.373 12 12 12h40c6.627 0 12-5.373 12-12zm96 0v-40c0-6.627-5.373-12-12-12h-40c-6.627 0-12 5.373-12 12v40c0 6.627 5.373 12 12 12h40c6.627 0 12-5.373 12-12zm-336 96v-40c0-6.627-5.373-12-12-12h-40c-6.627 0-12 5.373-12 12v40c0 6.627 5.373 12 12 12h40c6.627 0 12-5.373 12-12zm96 0v-40c0-6.627-5.373-12-12-12h-40c-6.627 0-12 5.373-12 12v40c0 6.627 5.373 12 12 12h40c6.627 0 12-5.373 12-12zm96 0v-40c0-6.627-5.373-12-12-12h-40c-6.627 0-12 5.373-12 12v40c0 6.627 5.373 12 12 12h40c6.627 0 12-5.373 12-12zm96 0v-40c0-6.627-5.373-12-12-12h-40c-6.627 0-12 5.373-12 12v40c0 6.627 5.373 12 12 12h40c6.627 0 12-5.373 12-12zm-336 96v-40c0-6.627-5.373-12-12-12H76c-6.627 0-12 5.373-12 12v40c0 6.627 5.373 12 12 12h40c6.627 0 12-5.373 12-12zm288 0v-40c0-6.627-5.373-12-12-12H172c-6.627 0-12 5.373-12 12v40c0 6.627 5.373 12 12 12h232c6.627 0 12-5.373 12-12zm96 0v-40c0-6.627-5.373-12-12-12h-40c-6.627 0-12 5.373-12 12v40c0 6.627 5.373 12 12 12h40c6.627 0 12-5.373 12-12z" />
|
|
||||||
</svg>
|
|
||||||
);
|
|
||||||
|
|
||||||
export const HelpIcon = (props: HelpIconProps) => (
|
export const HelpIcon = (props: HelpIconProps) => (
|
||||||
<label title={`${props.title} — ?`} className="help-icon">
|
<label title={`${props.title} — ?`} className="help-icon">
|
||||||
<div onClick={props.onClick}>{ICON}</div>
|
<div onClick={props.onClick}>{questionCircle}</div>
|
||||||
</label>
|
</label>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
@import "../css/_variables";
|
@import "../css/_variables";
|
||||||
|
|
||||||
|
// this is loosely based on the longest hint text
|
||||||
|
$wide-viewport-width: 1000px;
|
||||||
|
|
||||||
.excalidraw {
|
.excalidraw {
|
||||||
.HintViewer {
|
.HintViewer {
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
@@ -16,12 +19,9 @@
|
|||||||
color: $oc-gray-6;
|
color: $oc-gray-6;
|
||||||
font-size: 0.8rem;
|
font-size: 0.8rem;
|
||||||
|
|
||||||
@media (min-width: 1200px) {
|
@media #{$is-mobile-query} {
|
||||||
white-space: pre;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media #{$media-query} {
|
|
||||||
position: static;
|
position: static;
|
||||||
|
padding-right: 2em;
|
||||||
}
|
}
|
||||||
|
|
||||||
> span {
|
> span {
|
||||||
|
|||||||
@@ -102,6 +102,7 @@
|
|||||||
position: absolute;
|
position: absolute;
|
||||||
bottom: 2px;
|
bottom: 2px;
|
||||||
font-size: 0.7em;
|
font-size: 0.7em;
|
||||||
|
color: var(--keybinding-color);
|
||||||
|
|
||||||
:root[dir="ltr"] & {
|
:root[dir="ltr"] & {
|
||||||
right: 2px;
|
right: 2px;
|
||||||
@@ -110,7 +111,7 @@
|
|||||||
:root[dir="rtl"] & {
|
:root[dir="rtl"] & {
|
||||||
left: 2px;
|
left: 2px;
|
||||||
}
|
}
|
||||||
@media #{$media-query} {
|
@media #{$is-mobile-query} {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,18 +1,29 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
|
|
||||||
import { LoadingMessage } from "./LoadingMessage";
|
import { LoadingMessage } from "./LoadingMessage";
|
||||||
import { setLanguageFirstTime } from "../i18n";
|
import {
|
||||||
|
defaultLang,
|
||||||
|
Language,
|
||||||
|
languages,
|
||||||
|
setLanguageFirstTime,
|
||||||
|
} from "../i18n";
|
||||||
|
|
||||||
export class InitializeApp extends React.Component<
|
interface Props {
|
||||||
any,
|
langCode: Language["code"];
|
||||||
{ isLoading: boolean }
|
}
|
||||||
> {
|
interface State {
|
||||||
|
isLoading: boolean;
|
||||||
|
}
|
||||||
|
export class InitializeApp extends React.Component<Props, State> {
|
||||||
public state: { isLoading: boolean } = {
|
public state: { isLoading: boolean } = {
|
||||||
isLoading: true,
|
isLoading: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
async componentDidMount() {
|
async componentDidMount() {
|
||||||
await setLanguageFirstTime();
|
const currentLang =
|
||||||
|
languages.find((lang) => lang.code === this.props.langCode) ||
|
||||||
|
defaultLang;
|
||||||
|
await setLanguageFirstTime(currentLang);
|
||||||
this.setState({
|
this.setState({
|
||||||
isLoading: false,
|
isLoading: false,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
background-color: var(--bg-color-island);
|
background-color: var(--bg-color-island);
|
||||||
backdrop-filter: saturate(100%) blur(10px);
|
backdrop-filter: saturate(100%) blur(10px);
|
||||||
box-shadow: var(--shadow-island);
|
box-shadow: var(--shadow-island);
|
||||||
border-radius: var(--border-radius-m);
|
border-radius: 4px;
|
||||||
padding: calc(var(--padding) * var(--space-factor));
|
padding: calc(var(--padding) * var(--space-factor));
|
||||||
position: relative;
|
position: relative;
|
||||||
transition: box-shadow 0.5s ease-in-out;
|
transition: box-shadow 0.5s ease-in-out;
|
||||||
|
|||||||
@@ -7,11 +7,23 @@
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
|
||||||
.browse-libraries {
|
.layer-ui__library-header {
|
||||||
position: absolute;
|
display: flex;
|
||||||
right: 12px;
|
align-items: center;
|
||||||
top: 16px;
|
width: 100%;
|
||||||
white-space: nowrap;
|
margin: 2px 0;
|
||||||
|
|
||||||
|
button {
|
||||||
|
// 2px from the left to account for focus border of left-most button
|
||||||
|
margin: 0 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
margin-left: auto;
|
||||||
|
// 17px for scrollbar (needed for overlay scrollbars on Big Sur?) + 1px extra
|
||||||
|
padding-right: 18px;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,56 +1,46 @@
|
|||||||
|
import clsx from "clsx";
|
||||||
import React, {
|
import React, {
|
||||||
|
RefObject,
|
||||||
|
useCallback,
|
||||||
|
useEffect,
|
||||||
useRef,
|
useRef,
|
||||||
useState,
|
useState,
|
||||||
RefObject,
|
|
||||||
useEffect,
|
|
||||||
useCallback,
|
|
||||||
} from "react";
|
} from "react";
|
||||||
import { showSelectedShapeActions } from "../element";
|
|
||||||
import { calculateScrollCenter, getSelectedElements } from "../scene";
|
|
||||||
import { exportCanvas } from "../data";
|
|
||||||
|
|
||||||
import { AppState, LibraryItems, LibraryItem } from "../types";
|
|
||||||
import { NonDeletedExcalidrawElement } from "../element/types";
|
|
||||||
|
|
||||||
import { ActionManager } from "../actions/manager";
|
import { ActionManager } from "../actions/manager";
|
||||||
import { Island } from "./Island";
|
import { CLASSES } from "../constants";
|
||||||
import Stack from "./Stack";
|
import { exportCanvas } from "../data";
|
||||||
import { FixedSideContainer } from "./FixedSideContainer";
|
import { importLibraryFromJSON, saveLibraryAsJSON } from "../data/json";
|
||||||
import { UserList } from "./UserList";
|
import { Library } from "../data/library";
|
||||||
import { LockIcon } from "./LockIcon";
|
import { showSelectedShapeActions } from "../element";
|
||||||
import { ExportDialog, ExportCB } from "./ExportDialog";
|
import { NonDeletedExcalidrawElement } from "../element/types";
|
||||||
import { LanguageList } from "./LanguageList";
|
import { Language, t } from "../i18n";
|
||||||
import { t, languages, setLanguage } from "../i18n";
|
|
||||||
import { HintViewer } from "./HintViewer";
|
|
||||||
import useIsMobile from "../is-mobile";
|
import useIsMobile from "../is-mobile";
|
||||||
|
import { calculateScrollCenter, getSelectedElements } from "../scene";
|
||||||
import { ExportType } from "../scene/types";
|
import { ExportType } from "../scene/types";
|
||||||
import { MobileMenu } from "./MobileMenu";
|
import { AppState, LibraryItem, LibraryItems } from "../types";
|
||||||
import { ZoomActions, SelectedShapeActions, ShapesSwitcher } from "./Actions";
|
import { muteFSAbortError } from "../utils";
|
||||||
import { Section } from "./Section";
|
import { SelectedShapeActions, ShapesSwitcher, ZoomActions } from "./Actions";
|
||||||
|
import { BackgroundPickerAndDarkModeToggle } from "./BackgroundPickerAndDarkModeToggle";
|
||||||
import CollabButton from "./CollabButton";
|
import CollabButton from "./CollabButton";
|
||||||
import { ErrorDialog } from "./ErrorDialog";
|
import { ErrorDialog } from "./ErrorDialog";
|
||||||
import { ShortcutsDialog } from "./ShortcutsDialog";
|
import { ExportCB, ExportDialog } from "./ExportDialog";
|
||||||
import { LoadingMessage } from "./LoadingMessage";
|
import { FixedSideContainer } from "./FixedSideContainer";
|
||||||
import { CLASSES } from "../constants";
|
|
||||||
import { shield, exportFile, load } from "./icons";
|
|
||||||
import { GitHubCorner } from "./GitHubCorner";
|
import { GitHubCorner } from "./GitHubCorner";
|
||||||
import { Tooltip } from "./Tooltip";
|
import { HintViewer } from "./HintViewer";
|
||||||
|
import { exportFile, load, shield } from "./icons";
|
||||||
|
import { Island } from "./Island";
|
||||||
import "./LayerUI.scss";
|
import "./LayerUI.scss";
|
||||||
import { LibraryUnit } from "./LibraryUnit";
|
import { LibraryUnit } from "./LibraryUnit";
|
||||||
|
import { LoadingMessage } from "./LoadingMessage";
|
||||||
|
import { LockIcon } from "./LockIcon";
|
||||||
|
import { MobileMenu } from "./MobileMenu";
|
||||||
|
import { PasteChartDialog } from "./PasteChartDialog";
|
||||||
|
import { Section } from "./Section";
|
||||||
|
import { HelpDialog } from "./HelpDialog";
|
||||||
|
import Stack from "./Stack";
|
||||||
import { ToolButton } from "./ToolButton";
|
import { ToolButton } from "./ToolButton";
|
||||||
import { saveLibraryAsJSON, importLibraryFromJSON } from "../data/json";
|
import { Tooltip } from "./Tooltip";
|
||||||
import { muteFSAbortError } from "../utils";
|
import { UserList } from "./UserList";
|
||||||
import { BackgroundPickerAndDarkModeToggle } from "./BackgroundPickerAndDarkModeToggle";
|
|
||||||
import clsx from "clsx";
|
|
||||||
import { Library } from "../data/library";
|
|
||||||
import {
|
|
||||||
EVENT_ACTION,
|
|
||||||
EVENT_EXIT,
|
|
||||||
EVENT_LIBRARY,
|
|
||||||
trackEvent,
|
|
||||||
} from "../analytics";
|
|
||||||
|
|
||||||
interface LayerUIProps {
|
interface LayerUIProps {
|
||||||
actionManager: ActionManager;
|
actionManager: ActionManager;
|
||||||
@@ -60,16 +50,17 @@ interface LayerUIProps {
|
|||||||
elements: readonly NonDeletedExcalidrawElement[];
|
elements: readonly NonDeletedExcalidrawElement[];
|
||||||
onCollabButtonClick?: () => void;
|
onCollabButtonClick?: () => void;
|
||||||
onLockToggle: () => void;
|
onLockToggle: () => void;
|
||||||
onInsertShape: (elements: LibraryItem) => void;
|
onInsertElements: (elements: readonly NonDeletedExcalidrawElement[]) => void;
|
||||||
zenModeEnabled: boolean;
|
zenModeEnabled: boolean;
|
||||||
toggleZenMode: () => void;
|
toggleZenMode: () => void;
|
||||||
lng: string;
|
langCode: Language["code"];
|
||||||
isCollaborating: boolean;
|
isCollaborating: boolean;
|
||||||
onExportToBackend?: (
|
onExportToBackend?: (
|
||||||
exportedElements: readonly NonDeletedExcalidrawElement[],
|
exportedElements: readonly NonDeletedExcalidrawElement[],
|
||||||
appState: AppState,
|
appState: AppState,
|
||||||
canvas: HTMLCanvasElement | null,
|
canvas: HTMLCanvasElement | null,
|
||||||
) => void;
|
) => void;
|
||||||
|
renderCustomFooter?: (isMobile: boolean) => JSX.Element;
|
||||||
}
|
}
|
||||||
|
|
||||||
const useOnClickOutside = (
|
const useOnClickOutside = (
|
||||||
@@ -123,59 +114,45 @@ const LibraryMenuItems = ({
|
|||||||
let addedPendingElements = false;
|
let addedPendingElements = false;
|
||||||
|
|
||||||
rows.push(
|
rows.push(
|
||||||
<>
|
<div className="layer-ui__library-header">
|
||||||
<a
|
<ToolButton
|
||||||
className="browse-libraries"
|
key="import"
|
||||||
href="https://libraries.excalidraw.com"
|
type="button"
|
||||||
target="_excalidraw_libraries"
|
title={t("buttons.load")}
|
||||||
|
aria-label={t("buttons.load")}
|
||||||
|
icon={load}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
trackEvent(EVENT_EXIT, "libraries");
|
importLibraryFromJSON()
|
||||||
|
.then(() => {
|
||||||
|
// Maybe we should close and open the menu so that the items get updated.
|
||||||
|
// But for now we just close the menu.
|
||||||
|
setAppState({ isLibraryOpen: false });
|
||||||
|
})
|
||||||
|
.catch(muteFSAbortError)
|
||||||
|
.catch((error) => {
|
||||||
|
setAppState({ errorMessage: error.message });
|
||||||
|
});
|
||||||
}}
|
}}
|
||||||
>
|
/>
|
||||||
|
<ToolButton
|
||||||
|
key="export"
|
||||||
|
type="button"
|
||||||
|
title={t("buttons.export")}
|
||||||
|
aria-label={t("buttons.export")}
|
||||||
|
icon={exportFile}
|
||||||
|
onClick={() => {
|
||||||
|
saveLibraryAsJSON()
|
||||||
|
.catch(muteFSAbortError)
|
||||||
|
.catch((error) => {
|
||||||
|
setAppState({ errorMessage: error.message });
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<a href="https://libraries.excalidraw.com" target="_excalidraw_libraries">
|
||||||
{t("labels.libraries")}
|
{t("labels.libraries")}
|
||||||
</a>
|
</a>
|
||||||
|
</div>,
|
||||||
<Stack.Row
|
|
||||||
align="center"
|
|
||||||
gap={1}
|
|
||||||
key={"actions"}
|
|
||||||
style={{ padding: "2px" }}
|
|
||||||
>
|
|
||||||
<ToolButton
|
|
||||||
key="import"
|
|
||||||
type="button"
|
|
||||||
title={t("buttons.load")}
|
|
||||||
aria-label={t("buttons.load")}
|
|
||||||
icon={load}
|
|
||||||
onClick={() => {
|
|
||||||
importLibraryFromJSON()
|
|
||||||
.then(() => {
|
|
||||||
// Maybe we should close and open the menu so that the items get updated.
|
|
||||||
// But for now we just close the menu.
|
|
||||||
setAppState({ isLibraryOpen: false });
|
|
||||||
})
|
|
||||||
.catch(muteFSAbortError)
|
|
||||||
.catch((error) => {
|
|
||||||
setAppState({ errorMessage: error.message });
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<ToolButton
|
|
||||||
key="export"
|
|
||||||
type="button"
|
|
||||||
title={t("buttons.export")}
|
|
||||||
aria-label={t("buttons.export")}
|
|
||||||
icon={exportFile}
|
|
||||||
onClick={() => {
|
|
||||||
saveLibraryAsJSON()
|
|
||||||
.catch(muteFSAbortError)
|
|
||||||
.catch((error) => {
|
|
||||||
setAppState({ errorMessage: error.message });
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</Stack.Row>
|
|
||||||
</>,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
for (let row = 0; row < numRows; row++) {
|
for (let row = 0; row < numRows; row++) {
|
||||||
@@ -274,7 +251,6 @@ const LibraryMenu = ({
|
|||||||
const items = await Library.loadLibrary();
|
const items = await Library.loadLibrary();
|
||||||
const nextItems = items.filter((_, index) => index !== indexToRemove);
|
const nextItems = items.filter((_, index) => index !== indexToRemove);
|
||||||
Library.saveLibrary(nextItems);
|
Library.saveLibrary(nextItems);
|
||||||
trackEvent(EVENT_LIBRARY, "remove");
|
|
||||||
setLibraryItems(nextItems);
|
setLibraryItems(nextItems);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
@@ -283,7 +259,6 @@ const LibraryMenu = ({
|
|||||||
const items = await Library.loadLibrary();
|
const items = await Library.loadLibrary();
|
||||||
const nextItems = [...items, elements];
|
const nextItems = [...items, elements];
|
||||||
onAddToLibrary();
|
onAddToLibrary();
|
||||||
trackEvent(EVENT_LIBRARY, "add");
|
|
||||||
Library.saveLibrary(nextItems);
|
Library.saveLibrary(nextItems);
|
||||||
setLibraryItems(nextItems);
|
setLibraryItems(nextItems);
|
||||||
},
|
},
|
||||||
@@ -318,11 +293,12 @@ const LayerUI = ({
|
|||||||
elements,
|
elements,
|
||||||
onCollabButtonClick,
|
onCollabButtonClick,
|
||||||
onLockToggle,
|
onLockToggle,
|
||||||
onInsertShape,
|
onInsertElements,
|
||||||
zenModeEnabled,
|
zenModeEnabled,
|
||||||
toggleZenMode,
|
toggleZenMode,
|
||||||
isCollaborating,
|
isCollaborating,
|
||||||
onExportToBackend,
|
onExportToBackend,
|
||||||
|
renderCustomFooter,
|
||||||
}: LayerUIProps) => {
|
}: LayerUIProps) => {
|
||||||
const isMobile = useIsMobile();
|
const isMobile = useIsMobile();
|
||||||
|
|
||||||
@@ -334,9 +310,6 @@ const LayerUI = ({
|
|||||||
href="https://blog.excalidraw.com/end-to-end-encryption/"
|
href="https://blog.excalidraw.com/end-to-end-encryption/"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
onClick={() => {
|
|
||||||
trackEvent(EVENT_EXIT, "e2ee shield");
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
<Tooltip label={t("encrypted.tooltip")} position="above" long={true}>
|
<Tooltip label={t("encrypted.tooltip")} position="above" long={true}>
|
||||||
{shield}
|
{shield}
|
||||||
@@ -456,7 +429,7 @@ const LayerUI = ({
|
|||||||
<LibraryMenu
|
<LibraryMenu
|
||||||
pendingElements={getSelectedElements(elements, appState)}
|
pendingElements={getSelectedElements(elements, appState)}
|
||||||
onClickOutside={closeLibrary}
|
onClickOutside={closeLibrary}
|
||||||
onInsertShape={onInsertShape}
|
onInsertShape={onInsertElements}
|
||||||
onAddToLibrary={deselectItems}
|
onAddToLibrary={deselectItems}
|
||||||
setAppState={setAppState}
|
setAppState={setAppState}
|
||||||
/>
|
/>
|
||||||
@@ -558,14 +531,7 @@ const LayerUI = ({
|
|||||||
"transition-right disable-pointerEvents": zenModeEnabled,
|
"transition-right disable-pointerEvents": zenModeEnabled,
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
<LanguageList
|
{renderCustomFooter?.(false)}
|
||||||
onChange={async (lng) => {
|
|
||||||
await setLanguage(lng);
|
|
||||||
setAppState({});
|
|
||||||
}}
|
|
||||||
languages={languages}
|
|
||||||
floating
|
|
||||||
/>
|
|
||||||
{actionManager.renderAction("toggleShortcuts")}
|
{actionManager.renderAction("toggleShortcuts")}
|
||||||
</div>
|
</div>
|
||||||
<button
|
<button
|
||||||
@@ -580,7 +546,6 @@ const LayerUI = ({
|
|||||||
<button
|
<button
|
||||||
className="scroll-back-to-content"
|
className="scroll-back-to-content"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
trackEvent(EVENT_ACTION, "scroll to content");
|
|
||||||
setAppState({
|
setAppState({
|
||||||
...calculateScrollCenter(elements, appState, canvas),
|
...calculateScrollCenter(elements, appState, canvas),
|
||||||
});
|
});
|
||||||
@@ -592,21 +557,8 @@ const LayerUI = ({
|
|||||||
</footer>
|
</footer>
|
||||||
);
|
);
|
||||||
|
|
||||||
return isMobile ? (
|
const dialogs = (
|
||||||
<MobileMenu
|
<>
|
||||||
appState={appState}
|
|
||||||
elements={elements}
|
|
||||||
actionManager={actionManager}
|
|
||||||
libraryMenu={libraryMenu}
|
|
||||||
exportButton={renderExportDialog()}
|
|
||||||
setAppState={setAppState}
|
|
||||||
onCollabButtonClick={onCollabButtonClick}
|
|
||||||
onLockToggle={onLockToggle}
|
|
||||||
canvas={canvas}
|
|
||||||
isCollaborating={isCollaborating}
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<div className="layer-ui__wrapper">
|
|
||||||
{appState.isLoading && <LoadingMessage />}
|
{appState.isLoading && <LoadingMessage />}
|
||||||
{appState.errorMessage && (
|
{appState.errorMessage && (
|
||||||
<ErrorDialog
|
<ErrorDialog
|
||||||
@@ -614,11 +566,44 @@ const LayerUI = ({
|
|||||||
onClose={() => setAppState({ errorMessage: null })}
|
onClose={() => setAppState({ errorMessage: null })}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{appState.showShortcutsDialog && (
|
{appState.showHelpDialog && (
|
||||||
<ShortcutsDialog
|
<HelpDialog onClose={() => setAppState({ showHelpDialog: false })} />
|
||||||
onClose={() => setAppState({ showShortcutsDialog: false })}
|
)}
|
||||||
|
{appState.pasteDialog.shown && (
|
||||||
|
<PasteChartDialog
|
||||||
|
setAppState={setAppState}
|
||||||
|
appState={appState}
|
||||||
|
onInsertChart={onInsertElements}
|
||||||
|
onClose={() =>
|
||||||
|
setAppState({
|
||||||
|
pasteDialog: { shown: false, data: null },
|
||||||
|
})
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
|
||||||
|
return isMobile ? (
|
||||||
|
<>
|
||||||
|
{dialogs}
|
||||||
|
<MobileMenu
|
||||||
|
appState={appState}
|
||||||
|
elements={elements}
|
||||||
|
actionManager={actionManager}
|
||||||
|
libraryMenu={libraryMenu}
|
||||||
|
exportButton={renderExportDialog()}
|
||||||
|
setAppState={setAppState}
|
||||||
|
onCollabButtonClick={onCollabButtonClick}
|
||||||
|
onLockToggle={onLockToggle}
|
||||||
|
canvas={canvas}
|
||||||
|
isCollaborating={isCollaborating}
|
||||||
|
renderCustomFooter={renderCustomFooter}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<div className="layer-ui__wrapper">
|
||||||
|
{dialogs}
|
||||||
{renderFixedSideContainer()}
|
{renderFixedSideContainer()}
|
||||||
{renderBottomAppMenu()}
|
{renderBottomAppMenu()}
|
||||||
{
|
{
|
||||||
@@ -641,8 +626,6 @@ const LayerUI = ({
|
|||||||
const areEqual = (prev: LayerUIProps, next: LayerUIProps) => {
|
const areEqual = (prev: LayerUIProps, next: LayerUIProps) => {
|
||||||
const getNecessaryObj = (appState: AppState): Partial<AppState> => {
|
const getNecessaryObj = (appState: AppState): Partial<AppState> => {
|
||||||
const {
|
const {
|
||||||
cursorX,
|
|
||||||
cursorY,
|
|
||||||
suggestedBindings,
|
suggestedBindings,
|
||||||
startBoundElement: boundElement,
|
startBoundElement: boundElement,
|
||||||
...ret
|
...ret
|
||||||
@@ -653,9 +636,8 @@ const areEqual = (prev: LayerUIProps, next: LayerUIProps) => {
|
|||||||
const nextAppState = getNecessaryObj(next.appState);
|
const nextAppState = getNecessaryObj(next.appState);
|
||||||
|
|
||||||
const keys = Object.keys(prevAppState) as (keyof Partial<AppState>)[];
|
const keys = Object.keys(prevAppState) as (keyof Partial<AppState>)[];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
prev.lng === next.lng &&
|
prev.langCode === next.langCode &&
|
||||||
prev.elements === next.elements &&
|
prev.elements === next.elements &&
|
||||||
keys.every((key) => prevAppState[key] === nextAppState[key])
|
keys.every((key) => prevAppState[key] === nextAppState[key])
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
import React, { useRef, useEffect, useState } from "react";
|
|
||||||
import clsx from "clsx";
|
import clsx from "clsx";
|
||||||
import { exportToSvg } from "../scene/export";
|
import oc from "open-color";
|
||||||
|
import React, { useEffect, useRef, useState } from "react";
|
||||||
import { close } from "../components/icons";
|
import { close } from "../components/icons";
|
||||||
|
import { MIME_TYPES } from "../constants";
|
||||||
import "./LibraryUnit.scss";
|
|
||||||
import { t } from "../i18n";
|
import { t } from "../i18n";
|
||||||
import useIsMobile from "../is-mobile";
|
import useIsMobile from "../is-mobile";
|
||||||
|
import { exportToSvg } from "../scene/export";
|
||||||
import { LibraryItem } from "../types";
|
import { LibraryItem } from "../types";
|
||||||
import { MIME_TYPES } from "../constants";
|
import "./LibraryUnit.scss";
|
||||||
|
|
||||||
// fa-plus
|
// fa-plus
|
||||||
const PLUS_ICON = (
|
const PLUS_ICON = (
|
||||||
@@ -38,7 +38,7 @@ export const LibraryUnit = ({
|
|||||||
}
|
}
|
||||||
const svg = exportToSvg(elementsToRender, {
|
const svg = exportToSvg(elementsToRender, {
|
||||||
exportBackground: false,
|
exportBackground: false,
|
||||||
viewBackgroundColor: "#fff",
|
viewBackgroundColor: oc.white,
|
||||||
shouldAddWatermark: false,
|
shouldAddWatermark: false,
|
||||||
});
|
});
|
||||||
for (const child of ref.current!.children) {
|
for (const child of ref.current!.children) {
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { AppState } from "../types";
|
import { AppState } from "../types";
|
||||||
import { ActionManager } from "../actions/manager";
|
import { ActionManager } from "../actions/manager";
|
||||||
import { t, setLanguage } from "../i18n";
|
import { t } from "../i18n";
|
||||||
import Stack from "./Stack";
|
import Stack from "./Stack";
|
||||||
import { LanguageList } from "./LanguageList";
|
|
||||||
import { showSelectedShapeActions } from "../element";
|
import { showSelectedShapeActions } from "../element";
|
||||||
import { NonDeletedExcalidrawElement } from "../element/types";
|
import { NonDeletedExcalidrawElement } from "../element/types";
|
||||||
import { FixedSideContainer } from "./FixedSideContainer";
|
import { FixedSideContainer } from "./FixedSideContainer";
|
||||||
@@ -15,10 +14,8 @@ import { Section } from "./Section";
|
|||||||
import CollabButton from "./CollabButton";
|
import CollabButton from "./CollabButton";
|
||||||
import { SCROLLBAR_WIDTH, SCROLLBAR_MARGIN } from "../scene/scrollbars";
|
import { SCROLLBAR_WIDTH, SCROLLBAR_MARGIN } from "../scene/scrollbars";
|
||||||
import { LockIcon } from "./LockIcon";
|
import { LockIcon } from "./LockIcon";
|
||||||
import { LoadingMessage } from "./LoadingMessage";
|
|
||||||
import { UserList } from "./UserList";
|
import { UserList } from "./UserList";
|
||||||
import { BackgroundPickerAndDarkModeToggle } from "./BackgroundPickerAndDarkModeToggle";
|
import { BackgroundPickerAndDarkModeToggle } from "./BackgroundPickerAndDarkModeToggle";
|
||||||
import { EVENT_ACTION, trackEvent } from "../analytics";
|
|
||||||
|
|
||||||
type MobileMenuProps = {
|
type MobileMenuProps = {
|
||||||
appState: AppState;
|
appState: AppState;
|
||||||
@@ -31,6 +28,7 @@ type MobileMenuProps = {
|
|||||||
onLockToggle: () => void;
|
onLockToggle: () => void;
|
||||||
canvas: HTMLCanvasElement | null;
|
canvas: HTMLCanvasElement | null;
|
||||||
isCollaborating: boolean;
|
isCollaborating: boolean;
|
||||||
|
renderCustomFooter?: (isMobile: boolean) => JSX.Element;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const MobileMenu = ({
|
export const MobileMenu = ({
|
||||||
@@ -44,9 +42,9 @@ export const MobileMenu = ({
|
|||||||
onLockToggle,
|
onLockToggle,
|
||||||
canvas,
|
canvas,
|
||||||
isCollaborating,
|
isCollaborating,
|
||||||
|
renderCustomFooter,
|
||||||
}: MobileMenuProps) => (
|
}: MobileMenuProps) => (
|
||||||
<>
|
<>
|
||||||
{appState.isLoading && <LoadingMessage />}
|
|
||||||
<FixedSideContainer side="top">
|
<FixedSideContainer side="top">
|
||||||
<Section heading="shapes">
|
<Section heading="shapes">
|
||||||
{(heading) => (
|
{(heading) => (
|
||||||
@@ -104,15 +102,7 @@ export const MobileMenu = ({
|
|||||||
appState={appState}
|
appState={appState}
|
||||||
setAppState={setAppState}
|
setAppState={setAppState}
|
||||||
/>
|
/>
|
||||||
<fieldset>
|
{renderCustomFooter?.(true)}
|
||||||
<legend>{t("labels.language")}</legend>
|
|
||||||
<LanguageList
|
|
||||||
onChange={async (lng) => {
|
|
||||||
await setLanguage(lng);
|
|
||||||
setAppState({});
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</fieldset>
|
|
||||||
<fieldset>
|
<fieldset>
|
||||||
<legend>{t("labels.collaborators")}</legend>
|
<legend>{t("labels.collaborators")}</legend>
|
||||||
<UserList mobile>
|
<UserList mobile>
|
||||||
@@ -158,7 +148,6 @@ export const MobileMenu = ({
|
|||||||
<button
|
<button
|
||||||
className="scroll-back-to-content"
|
className="scroll-back-to-content"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
trackEvent(EVENT_ACTION, "scroll to content");
|
|
||||||
setAppState({
|
setAppState({
|
||||||
...calculateScrollCenter(elements, appState, canvas),
|
...calculateScrollCenter(elements, appState, canvas),
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -30,18 +30,26 @@
|
|||||||
z-index: 2;
|
z-index: 2;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
max-width: var(--max-width);
|
max-width: var(--max-width);
|
||||||
|
max-height: 100%;
|
||||||
|
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
transform: translateY(10px);
|
transform: translateY(10px);
|
||||||
animation: Modal__content_fade-in 0.1s ease-out 0.05s forwards;
|
animation: Modal__content_fade-in 0.1s ease-out 0.05s forwards;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
overflow-y: auto;
|
||||||
|
|
||||||
// for modals, reset blurry bg
|
// for modals, reset blurry bg
|
||||||
background: var(--bg-color-island);
|
background: var(--bg-color-island);
|
||||||
backdrop-filter: none;
|
backdrop-filter: none;
|
||||||
|
|
||||||
@media #{$media-query} {
|
border: 1px solid var(--dialog-border);
|
||||||
|
box-shadow: 0 2px 10px transparentize($oc-black, 0.75);
|
||||||
|
border-radius: 6px;
|
||||||
|
|
||||||
|
@media #{$is-mobile-query} {
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
|
border: 0;
|
||||||
|
border-radius: 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -68,13 +76,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.Modal__close--floating {
|
@media #{$is-mobile-query} {
|
||||||
position: absolute;
|
|
||||||
right: calc(var(--space-factor) * 5);
|
|
||||||
top: calc(var(--space-factor) * 5);
|
|
||||||
}
|
|
||||||
|
|
||||||
@media #{$media-query} {
|
|
||||||
.Modal {
|
.Modal {
|
||||||
padding: 0;
|
padding: 0;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,11 +36,7 @@ export const Modal = (props: {
|
|||||||
<div className="Modal__background" onClick={props.onCloseRequest}></div>
|
<div className="Modal__background" onClick={props.onCloseRequest}></div>
|
||||||
<div
|
<div
|
||||||
className="Modal__content"
|
className="Modal__content"
|
||||||
style={{
|
style={{ "--max-width": `${props.maxWidth}px` }}
|
||||||
"--max-width": `${props.maxWidth}px`,
|
|
||||||
maxHeight: "100%",
|
|
||||||
overflowY: "scroll",
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
{props.children}
|
{props.children}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -0,0 +1,46 @@
|
|||||||
|
@import "../css/_variables";
|
||||||
|
|
||||||
|
.excalidraw {
|
||||||
|
.PasteChartDialog {
|
||||||
|
@media #{$is-mobile-query} {
|
||||||
|
.Island {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.container {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-around;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
@media #{$is-mobile-query} {
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.ChartPreview {
|
||||||
|
margin: 8px;
|
||||||
|
text-align: center;
|
||||||
|
width: 192px;
|
||||||
|
height: 128px;
|
||||||
|
border-radius: 2px;
|
||||||
|
padding: 1px;
|
||||||
|
border: 1px solid $oc-gray-4;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
background: transparent;
|
||||||
|
div {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
svg {
|
||||||
|
max-height: 120px;
|
||||||
|
max-width: 186px;
|
||||||
|
}
|
||||||
|
&:hover {
|
||||||
|
padding: 0;
|
||||||
|
border: 2px solid $oc-blue-5;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,124 @@
|
|||||||
|
import oc from "open-color";
|
||||||
|
import React, { useLayoutEffect, useRef, useState } from "react";
|
||||||
|
import { trackEvent } from "../analytics";
|
||||||
|
import { ChartElements, renderSpreadsheet, Spreadsheet } from "../charts";
|
||||||
|
import { ChartType } from "../element/types";
|
||||||
|
import { t } from "../i18n";
|
||||||
|
import { exportToSvg } from "../scene/export";
|
||||||
|
import { AppState, LibraryItem } from "../types";
|
||||||
|
import { Dialog } from "./Dialog";
|
||||||
|
import "./PasteChartDialog.scss";
|
||||||
|
|
||||||
|
type OnInsertChart = (chartType: ChartType, elements: ChartElements) => void;
|
||||||
|
|
||||||
|
const ChartPreviewBtn = (props: {
|
||||||
|
spreadsheet: Spreadsheet | null;
|
||||||
|
chartType: ChartType;
|
||||||
|
selected: boolean;
|
||||||
|
onClick: OnInsertChart;
|
||||||
|
}) => {
|
||||||
|
const previewRef = useRef<HTMLDivElement | null>(null);
|
||||||
|
const [chartElements, setChartElements] = useState<ChartElements | null>(
|
||||||
|
null,
|
||||||
|
);
|
||||||
|
|
||||||
|
useLayoutEffect(() => {
|
||||||
|
if (!props.spreadsheet) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const elements = renderSpreadsheet(
|
||||||
|
props.chartType,
|
||||||
|
props.spreadsheet,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
);
|
||||||
|
setChartElements(elements);
|
||||||
|
|
||||||
|
const svg = exportToSvg(elements, {
|
||||||
|
exportBackground: false,
|
||||||
|
viewBackgroundColor: oc.white,
|
||||||
|
shouldAddWatermark: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
const previewNode = previewRef.current!;
|
||||||
|
|
||||||
|
previewNode.appendChild(svg);
|
||||||
|
|
||||||
|
if (props.selected) {
|
||||||
|
(previewNode.parentNode as HTMLDivElement).focus();
|
||||||
|
}
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
previewNode.removeChild(svg);
|
||||||
|
};
|
||||||
|
}, [props.spreadsheet, props.chartType, props.selected]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
className="ChartPreview"
|
||||||
|
onClick={() => {
|
||||||
|
if (chartElements) {
|
||||||
|
props.onClick(props.chartType, chartElements);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div ref={previewRef} />
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const PasteChartDialog = ({
|
||||||
|
setAppState,
|
||||||
|
appState,
|
||||||
|
onClose,
|
||||||
|
onInsertChart,
|
||||||
|
}: {
|
||||||
|
appState: AppState;
|
||||||
|
onClose: () => void;
|
||||||
|
setAppState: React.Component<any, AppState>["setState"];
|
||||||
|
onInsertChart: (elements: LibraryItem) => void;
|
||||||
|
}) => {
|
||||||
|
const handleClose = React.useCallback(() => {
|
||||||
|
if (onClose) {
|
||||||
|
onClose();
|
||||||
|
}
|
||||||
|
}, [onClose]);
|
||||||
|
|
||||||
|
const handleChartClick = (chartType: ChartType, elements: ChartElements) => {
|
||||||
|
onInsertChart(elements);
|
||||||
|
trackEvent("magic", "chart", chartType);
|
||||||
|
setAppState({
|
||||||
|
currentChartType: chartType,
|
||||||
|
pasteDialog: {
|
||||||
|
shown: false,
|
||||||
|
data: null,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Dialog
|
||||||
|
small
|
||||||
|
onCloseRequest={handleClose}
|
||||||
|
title={t("labels.pasteCharts")}
|
||||||
|
className={"PasteChartDialog"}
|
||||||
|
autofocus={false}
|
||||||
|
>
|
||||||
|
<div className={"container"}>
|
||||||
|
<ChartPreviewBtn
|
||||||
|
chartType="bar"
|
||||||
|
spreadsheet={appState.pasteDialog.data}
|
||||||
|
selected={appState.currentChartType === "bar"}
|
||||||
|
onClick={handleChartClick}
|
||||||
|
/>
|
||||||
|
<ChartPreviewBtn
|
||||||
|
chartType="line"
|
||||||
|
spreadsheet={appState.pasteDialog.data}
|
||||||
|
selected={appState.currentChartType === "line"}
|
||||||
|
onClick={handleChartClick}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</Dialog>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -1,342 +0,0 @@
|
|||||||
import React from "react";
|
|
||||||
import { t } from "../i18n";
|
|
||||||
import { isDarwin } from "../keys";
|
|
||||||
import { Dialog } from "./Dialog";
|
|
||||||
import { getShortcutKey } from "../utils";
|
|
||||||
import "./ShortcutsDialog.scss";
|
|
||||||
import { EVENT_EXIT, trackEvent } from "../analytics";
|
|
||||||
|
|
||||||
const Columns = (props: { children: React.ReactNode }) => (
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
display: "flex",
|
|
||||||
flexDirection: "row",
|
|
||||||
flexWrap: "wrap",
|
|
||||||
justifyContent: "space-between",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{props.children}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
|
|
||||||
const Column = (props: { children: React.ReactNode }) => (
|
|
||||||
<div style={{ width: "49%" }}>{props.children}</div>
|
|
||||||
);
|
|
||||||
|
|
||||||
const ShortcutIsland = (props: {
|
|
||||||
caption: string;
|
|
||||||
children: React.ReactNode;
|
|
||||||
}) => (
|
|
||||||
<div className="ShortcutsDialog-island">
|
|
||||||
<h3 className="ShortcutsDialog-island-title">{props.caption}</h3>
|
|
||||||
{props.children}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
|
|
||||||
const Shortcut = (props: {
|
|
||||||
label: string;
|
|
||||||
shortcuts: string[];
|
|
||||||
isOr: boolean;
|
|
||||||
}) => {
|
|
||||||
return (
|
|
||||||
<div className="ShorcutsDialog-shortcut">
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
display: "flex",
|
|
||||||
margin: "0",
|
|
||||||
padding: "4px 8px",
|
|
||||||
alignItems: "center",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
lineHeight: 1.4,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{props.label}
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
display: "flex",
|
|
||||||
flex: "0 0 auto",
|
|
||||||
justifyContent: "flex-end",
|
|
||||||
marginInlineStart: "auto",
|
|
||||||
minWidth: "30%",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{props.shortcuts.map((shortcut, index) => (
|
|
||||||
<React.Fragment key={index}>
|
|
||||||
<ShortcutKey>{shortcut}</ShortcutKey>
|
|
||||||
{props.isOr &&
|
|
||||||
index !== props.shortcuts.length - 1 &&
|
|
||||||
t("shortcutsDialog.or")}
|
|
||||||
</React.Fragment>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
Shortcut.defaultProps = {
|
|
||||||
isOr: true,
|
|
||||||
};
|
|
||||||
|
|
||||||
const ShortcutKey = (props: { children: React.ReactNode }) => (
|
|
||||||
<span className="ShorcutsDialog-key" {...props} />
|
|
||||||
);
|
|
||||||
|
|
||||||
const Footer = () => (
|
|
||||||
<div className="ShortcutsDialog-footer">
|
|
||||||
<a
|
|
||||||
href="https://blog.excalidraw.com"
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
onClick={() => {
|
|
||||||
trackEvent(EVENT_EXIT, "blog");
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{t("shortcutsDialog.blog")}
|
|
||||||
</a>
|
|
||||||
<a
|
|
||||||
href="https://howto.excalidraw.com"
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
onClick={() => {
|
|
||||||
trackEvent(EVENT_EXIT, "guides");
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{t("shortcutsDialog.howto")}
|
|
||||||
</a>
|
|
||||||
<a
|
|
||||||
href="https://github.com/excalidraw/excalidraw/issues"
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
onClick={() => {
|
|
||||||
trackEvent(EVENT_EXIT, "issues");
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{t("shortcutsDialog.github")}
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
|
|
||||||
export const ShortcutsDialog = ({ onClose }: { onClose?: () => void }) => {
|
|
||||||
const handleClose = React.useCallback(() => {
|
|
||||||
if (onClose) {
|
|
||||||
onClose();
|
|
||||||
}
|
|
||||||
}, [onClose]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<Dialog
|
|
||||||
maxWidth={900}
|
|
||||||
onCloseRequest={handleClose}
|
|
||||||
title={t("shortcutsDialog.title")}
|
|
||||||
>
|
|
||||||
<Columns>
|
|
||||||
<Column>
|
|
||||||
<ShortcutIsland caption={t("shortcutsDialog.shapes")}>
|
|
||||||
<Shortcut label={t("toolBar.selection")} shortcuts={["V", "1"]} />
|
|
||||||
<Shortcut label={t("toolBar.rectangle")} shortcuts={["R", "2"]} />
|
|
||||||
<Shortcut label={t("toolBar.diamond")} shortcuts={["D", "3"]} />
|
|
||||||
<Shortcut label={t("toolBar.ellipse")} shortcuts={["E", "4"]} />
|
|
||||||
<Shortcut label={t("toolBar.arrow")} shortcuts={["A", "5"]} />
|
|
||||||
<Shortcut label={t("toolBar.line")} shortcuts={["P", "6"]} />
|
|
||||||
<Shortcut
|
|
||||||
label={t("toolBar.draw")}
|
|
||||||
shortcuts={["Shift+P", "7"]}
|
|
||||||
/>
|
|
||||||
<Shortcut label={t("toolBar.text")} shortcuts={["T", "8"]} />
|
|
||||||
<Shortcut
|
|
||||||
label={t("shortcutsDialog.textNewLine")}
|
|
||||||
shortcuts={[
|
|
||||||
getShortcutKey("Enter"),
|
|
||||||
getShortcutKey("Shift+Enter"),
|
|
||||||
]}
|
|
||||||
/>
|
|
||||||
<Shortcut
|
|
||||||
label={t("shortcutsDialog.textFinish")}
|
|
||||||
shortcuts={[
|
|
||||||
getShortcutKey("Esc"),
|
|
||||||
getShortcutKey("CtrlOrCmd+Enter"),
|
|
||||||
]}
|
|
||||||
/>
|
|
||||||
<Shortcut
|
|
||||||
label={t("shortcutsDialog.curvedArrow")}
|
|
||||||
shortcuts={[
|
|
||||||
"A",
|
|
||||||
t("shortcutsDialog.click"),
|
|
||||||
t("shortcutsDialog.click"),
|
|
||||||
t("shortcutsDialog.click"),
|
|
||||||
]}
|
|
||||||
isOr={false}
|
|
||||||
/>
|
|
||||||
<Shortcut
|
|
||||||
label={t("shortcutsDialog.curvedLine")}
|
|
||||||
shortcuts={[
|
|
||||||
"L",
|
|
||||||
t("shortcutsDialog.click"),
|
|
||||||
t("shortcutsDialog.click"),
|
|
||||||
t("shortcutsDialog.click"),
|
|
||||||
]}
|
|
||||||
isOr={false}
|
|
||||||
/>
|
|
||||||
<Shortcut label={t("toolBar.lock")} shortcuts={["Q"]} />
|
|
||||||
<Shortcut
|
|
||||||
label={t("shortcutsDialog.preventBinding")}
|
|
||||||
shortcuts={[getShortcutKey("CtrlOrCmd")]}
|
|
||||||
/>
|
|
||||||
</ShortcutIsland>
|
|
||||||
<ShortcutIsland caption={t("shortcutsDialog.view")}>
|
|
||||||
<Shortcut
|
|
||||||
label={t("buttons.zoomIn")}
|
|
||||||
shortcuts={[getShortcutKey("CtrlOrCmd++")]}
|
|
||||||
/>
|
|
||||||
<Shortcut
|
|
||||||
label={t("buttons.zoomOut")}
|
|
||||||
shortcuts={[getShortcutKey("CtrlOrCmd+-")]}
|
|
||||||
/>
|
|
||||||
<Shortcut
|
|
||||||
label={t("buttons.resetZoom")}
|
|
||||||
shortcuts={[getShortcutKey("CtrlOrCmd+0")]}
|
|
||||||
/>
|
|
||||||
<Shortcut
|
|
||||||
label={t("shortcutsDialog.zoomToFit")}
|
|
||||||
shortcuts={["Shift+1"]}
|
|
||||||
/>
|
|
||||||
<Shortcut
|
|
||||||
label={t("shortcutsDialog.zoomToSelection")}
|
|
||||||
shortcuts={["Shift+2"]}
|
|
||||||
/>
|
|
||||||
<Shortcut label={t("buttons.fullScreen")} shortcuts={["F"]} />
|
|
||||||
<Shortcut
|
|
||||||
label={t("buttons.zenMode")}
|
|
||||||
shortcuts={[getShortcutKey("Alt+Z")]}
|
|
||||||
/>
|
|
||||||
<Shortcut
|
|
||||||
label={t("labels.gridMode")}
|
|
||||||
shortcuts={[getShortcutKey("CtrlOrCmd+'")]}
|
|
||||||
/>
|
|
||||||
</ShortcutIsland>
|
|
||||||
</Column>
|
|
||||||
<Column>
|
|
||||||
<ShortcutIsland caption={t("shortcutsDialog.editor")}>
|
|
||||||
<Shortcut
|
|
||||||
label={t("labels.selectAll")}
|
|
||||||
shortcuts={[getShortcutKey("CtrlOrCmd+A")]}
|
|
||||||
/>
|
|
||||||
<Shortcut
|
|
||||||
label={t("labels.multiSelect")}
|
|
||||||
shortcuts={[
|
|
||||||
getShortcutKey(`Shift+${t("shortcutsDialog.click")}`),
|
|
||||||
]}
|
|
||||||
/>
|
|
||||||
<Shortcut
|
|
||||||
label={t("labels.moveCanvas")}
|
|
||||||
shortcuts={[
|
|
||||||
getShortcutKey(`Space+${t("shortcutsDialog.drag")}`),
|
|
||||||
getShortcutKey(`Wheel+${t("shortcutsDialog.drag")}`),
|
|
||||||
]}
|
|
||||||
isOr={true}
|
|
||||||
/>
|
|
||||||
<Shortcut
|
|
||||||
label={t("labels.cut")}
|
|
||||||
shortcuts={[getShortcutKey("CtrlOrCmd+X")]}
|
|
||||||
/>
|
|
||||||
<Shortcut
|
|
||||||
label={t("labels.copy")}
|
|
||||||
shortcuts={[getShortcutKey("CtrlOrCmd+C")]}
|
|
||||||
/>
|
|
||||||
<Shortcut
|
|
||||||
label={t("labels.paste")}
|
|
||||||
shortcuts={[getShortcutKey("CtrlOrCmd+V")]}
|
|
||||||
/>
|
|
||||||
<Shortcut
|
|
||||||
label={t("labels.copyAsPng")}
|
|
||||||
shortcuts={[getShortcutKey("Shift+Alt+C")]}
|
|
||||||
/>
|
|
||||||
<Shortcut
|
|
||||||
label={t("labels.copyStyles")}
|
|
||||||
shortcuts={[getShortcutKey("CtrlOrCmd+Alt+C")]}
|
|
||||||
/>
|
|
||||||
<Shortcut
|
|
||||||
label={t("labels.pasteStyles")}
|
|
||||||
shortcuts={[getShortcutKey("CtrlOrCmd+Alt+V")]}
|
|
||||||
/>
|
|
||||||
<Shortcut
|
|
||||||
label={t("labels.delete")}
|
|
||||||
shortcuts={[getShortcutKey("Del")]}
|
|
||||||
/>
|
|
||||||
<Shortcut
|
|
||||||
label={t("labels.sendToBack")}
|
|
||||||
shortcuts={[
|
|
||||||
isDarwin
|
|
||||||
? getShortcutKey("CtrlOrCmd+Alt+[")
|
|
||||||
: getShortcutKey("CtrlOrCmd+Shift+["),
|
|
||||||
]}
|
|
||||||
/>
|
|
||||||
<Shortcut
|
|
||||||
label={t("labels.bringToFront")}
|
|
||||||
shortcuts={[
|
|
||||||
isDarwin
|
|
||||||
? getShortcutKey("CtrlOrCmd+Alt+]")
|
|
||||||
: getShortcutKey("CtrlOrCmd+Shift+]"),
|
|
||||||
]}
|
|
||||||
/>
|
|
||||||
<Shortcut
|
|
||||||
label={t("labels.sendBackward")}
|
|
||||||
shortcuts={[getShortcutKey("CtrlOrCmd+[")]}
|
|
||||||
/>
|
|
||||||
<Shortcut
|
|
||||||
label={t("labels.bringForward")}
|
|
||||||
shortcuts={[getShortcutKey("CtrlOrCmd+]")]}
|
|
||||||
/>
|
|
||||||
<Shortcut
|
|
||||||
label={t("labels.alignTop")}
|
|
||||||
shortcuts={[getShortcutKey("CtrlOrCmd+Shift+Up")]}
|
|
||||||
/>
|
|
||||||
<Shortcut
|
|
||||||
label={t("labels.alignBottom")}
|
|
||||||
shortcuts={[getShortcutKey("CtrlOrCmd+Shift+Down")]}
|
|
||||||
/>
|
|
||||||
<Shortcut
|
|
||||||
label={t("labels.alignLeft")}
|
|
||||||
shortcuts={[getShortcutKey("CtrlOrCmd+Shift+Left")]}
|
|
||||||
/>
|
|
||||||
<Shortcut
|
|
||||||
label={t("labels.alignRight")}
|
|
||||||
shortcuts={[getShortcutKey("CtrlOrCmd+Shift+Right")]}
|
|
||||||
/>
|
|
||||||
<Shortcut
|
|
||||||
label={t("labels.duplicateSelection")}
|
|
||||||
shortcuts={[
|
|
||||||
getShortcutKey("CtrlOrCmd+D"),
|
|
||||||
getShortcutKey(`Alt+${t("shortcutsDialog.drag")}`),
|
|
||||||
]}
|
|
||||||
/>
|
|
||||||
<Shortcut
|
|
||||||
label={t("buttons.undo")}
|
|
||||||
shortcuts={[getShortcutKey("CtrlOrCmd+Z")]}
|
|
||||||
/>
|
|
||||||
<Shortcut
|
|
||||||
label={t("buttons.redo")}
|
|
||||||
shortcuts={[getShortcutKey("CtrlOrCmd+Shift+Z")]}
|
|
||||||
/>
|
|
||||||
<Shortcut
|
|
||||||
label={t("labels.group")}
|
|
||||||
shortcuts={[getShortcutKey("CtrlOrCmd+G")]}
|
|
||||||
/>
|
|
||||||
<Shortcut
|
|
||||||
label={t("labels.ungroup")}
|
|
||||||
shortcuts={[getShortcutKey("CtrlOrCmd+Shift+G")]}
|
|
||||||
/>
|
|
||||||
</ShortcutIsland>
|
|
||||||
</Column>
|
|
||||||
</Columns>
|
|
||||||
<Footer />
|
|
||||||
</Dialog>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@@ -85,7 +85,6 @@ export const Stats = (props: {
|
|||||||
<td>{t("stats.total")}</td>
|
<td>{t("stats.total")}</td>
|
||||||
<td>{nFormatter(storageSizes.total, 1)}</td>
|
<td>{nFormatter(storageSizes.total, 1)}</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
||||||
{selectedElements.length === 1 && (
|
{selectedElements.length === 1 && (
|
||||||
<tr>
|
<tr>
|
||||||
<th colSpan={2}>{t("stats.element")}</th>
|
<th colSpan={2}>{t("stats.element")}</th>
|
||||||
|
|||||||
@@ -0,0 +1,32 @@
|
|||||||
|
@import "../css/_variables";
|
||||||
|
|
||||||
|
.excalidraw {
|
||||||
|
.Toast {
|
||||||
|
animation: fade-in 0.5s;
|
||||||
|
background-color: var(--button-gray-1);
|
||||||
|
border-radius: 4px;
|
||||||
|
bottom: 10px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
cursor: default;
|
||||||
|
left: 50%;
|
||||||
|
margin-left: -150px;
|
||||||
|
padding: 4px 0;
|
||||||
|
position: fixed;
|
||||||
|
text-align: center;
|
||||||
|
width: 300px;
|
||||||
|
z-index: 999999;
|
||||||
|
}
|
||||||
|
|
||||||
|
.Toast__message {
|
||||||
|
color: var(--popup-text-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes fade-in {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
import React, { useCallback, useEffect, useRef } from "react";
|
||||||
|
import { TOAST_TIMEOUT } from "../constants";
|
||||||
|
import "./Toast.scss";
|
||||||
|
|
||||||
|
export const Toast = ({
|
||||||
|
message,
|
||||||
|
clearToast,
|
||||||
|
}: {
|
||||||
|
message: string;
|
||||||
|
clearToast: () => void;
|
||||||
|
}) => {
|
||||||
|
const timerRef = useRef<number>(0);
|
||||||
|
|
||||||
|
const scheduleTimeout = useCallback(
|
||||||
|
() =>
|
||||||
|
(timerRef.current = window.setTimeout(() => clearToast(), TOAST_TIMEOUT)),
|
||||||
|
[clearToast],
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
scheduleTimeout();
|
||||||
|
return () => clearTimeout(timerRef.current);
|
||||||
|
}, [scheduleTimeout, message]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className="Toast"
|
||||||
|
onMouseEnter={() => clearTimeout(timerRef?.current)}
|
||||||
|
onMouseLeave={scheduleTimeout}
|
||||||
|
>
|
||||||
|
<p className="Toast__message">{message}</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -142,6 +142,7 @@
|
|||||||
user-select: none;
|
user-select: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// shrink shape icons on small viewports to make them fit
|
||||||
@media (max-width: 425px) {
|
@media (max-width: 425px) {
|
||||||
.Shape .ToolIcon__icon {
|
.Shape .ToolIcon__icon {
|
||||||
width: 2rem;
|
width: 2rem;
|
||||||
@@ -153,6 +154,8 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// move the lock button out of the way on small viewports
|
||||||
|
// it begins to collide with the GitHub icon before we switch to mobile mode
|
||||||
@media (max-width: 760px) {
|
@media (max-width: 760px) {
|
||||||
.ToolIcon.ToolIcon__lock {
|
.ToolIcon.ToolIcon__lock {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
@@ -162,6 +165,7 @@
|
|||||||
|
|
||||||
margin-left: 0;
|
margin-left: 0;
|
||||||
border-radius: 20px 0 0 20px;
|
border-radius: 20px 0 0 20px;
|
||||||
|
z-index: 1;
|
||||||
|
|
||||||
background-color: var(--button-gray-1);
|
background-color: var(--button-gray-1);
|
||||||
|
|
||||||
@@ -189,7 +193,7 @@
|
|||||||
margin-left: 5px;
|
margin-left: 5px;
|
||||||
margin-top: 1px;
|
margin-top: 1px;
|
||||||
|
|
||||||
@media #{$media-query} {
|
@media #{$is-mobile-query} {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -70,6 +70,7 @@ export const DEFAULT_FONT_SIZE = 20;
|
|||||||
export const DEFAULT_FONT_FAMILY: FontFamily = 1;
|
export const DEFAULT_FONT_FAMILY: FontFamily = 1;
|
||||||
export const DEFAULT_TEXT_ALIGN = "left";
|
export const DEFAULT_TEXT_ALIGN = "left";
|
||||||
export const DEFAULT_VERTICAL_ALIGN = "top";
|
export const DEFAULT_VERTICAL_ALIGN = "top";
|
||||||
|
export const DEFAULT_VERSION = "{version}";
|
||||||
|
|
||||||
export const CANVAS_ONLY_ACTIONS = ["selectAll"];
|
export const CANVAS_ONLY_ACTIONS = ["selectAll"];
|
||||||
|
|
||||||
@@ -88,3 +89,5 @@ export const STORAGE_KEYS = {
|
|||||||
export const TAP_TWICE_TIMEOUT = 300;
|
export const TAP_TWICE_TIMEOUT = 300;
|
||||||
export const TOUCH_CTX_MENU_TIMEOUT = 500;
|
export const TOUCH_CTX_MENU_TIMEOUT = 500;
|
||||||
export const TITLE_TIMEOUT = 10000;
|
export const TITLE_TIMEOUT = 10000;
|
||||||
|
export const TOAST_TIMEOUT = 5000;
|
||||||
|
export const VERSION_TIMEOUT = 15000;
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
@import "open-color/open-color.scss";
|
@import "open-color/open-color.scss";
|
||||||
|
|
||||||
$media-query: "(max-width: 600px), (max-height: 500px) and (max-width: 1000px)";
|
// keep up to date with is-mobile.tsx
|
||||||
|
$is-mobile-query: "(max-width: 600px), (max-height: 500px) and (max-width: 1000px)";
|
||||||
|
|||||||
@@ -13,7 +13,7 @@
|
|||||||
a {
|
a {
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
color: $oc-blue-7; /* OC Blue 7 */
|
color: var(--link-color);
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
text-decoration: underline;
|
text-decoration: underline;
|
||||||
@@ -431,6 +431,7 @@
|
|||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
fill: $oc-gray-6;
|
fill: $oc-gray-6;
|
||||||
bottom: 14px;
|
bottom: 14px;
|
||||||
|
width: 1.5rem;
|
||||||
|
|
||||||
:root[dir="ltr"] & {
|
:root[dir="ltr"] & {
|
||||||
right: 14px;
|
right: 14px;
|
||||||
@@ -441,7 +442,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@media #{$media-query} {
|
@media #{$is-mobile-query} {
|
||||||
aside {
|
aside {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,6 @@
|
|||||||
:root {
|
:root {
|
||||||
--bg-color-island: rgba(255, 255, 255, 0.9);
|
--bg-color-island: rgba(255, 255, 255, 0.9);
|
||||||
--popup-background-color: #{$oc-white};
|
--popup-background-color: #{$oc-white};
|
||||||
--border-radius-m: 4px;
|
|
||||||
--space-factor: 0.25rem;
|
--space-factor: 0.25rem;
|
||||||
--button-gray-1: #{$oc-gray-2};
|
--button-gray-1: #{$oc-gray-2};
|
||||||
--button-gray-2: #{$oc-gray-4};
|
--button-gray-2: #{$oc-gray-4};
|
||||||
@@ -15,7 +14,6 @@
|
|||||||
--icon-fill-color: #{$oc-black};
|
--icon-fill-color: #{$oc-black};
|
||||||
--icon-green-fill-color: #{$oc-green-9};
|
--icon-green-fill-color: #{$oc-green-9};
|
||||||
--keybinding-color: #{$oc-gray-5};
|
--keybinding-color: #{$oc-gray-5};
|
||||||
--color-overlay-text-color: #ccc;
|
|
||||||
--sat: env(safe-area-inset-top);
|
--sat: env(safe-area-inset-top);
|
||||||
--sab: env(safe-area-inset-bottom);
|
--sab: env(safe-area-inset-bottom);
|
||||||
--sal: env(safe-area-inset-left);
|
--sal: env(safe-area-inset-left);
|
||||||
@@ -23,8 +21,6 @@
|
|||||||
--text-color-primary: #{$oc-gray-8};
|
--text-color-primary: #{$oc-gray-8};
|
||||||
--shadow-island: 0 1px 5px #{transparentize($oc-black, 0.85)};
|
--shadow-island: 0 1px 5px #{transparentize($oc-black, 0.85)};
|
||||||
--overlay-background-color: #{transparentize($oc-white, 0.12)};
|
--overlay-background-color: #{transparentize($oc-white, 0.12)};
|
||||||
--border-radius-m: 4px;
|
|
||||||
--space-factor: 0.25rem;
|
|
||||||
--dropdown-icon: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" width="292.4" height="292.4" viewBox="0 0 292 292"><path d="M287 197L159 69c-4-3-8-5-13-5s-9 2-13 5L5 197c-3 4-5 8-5 13s2 9 5 13c4 4 8 5 13 5h256c5 0 9-1 13-5s5-8 5-13-1-9-5-13z"/></svg>');
|
--dropdown-icon: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" width="292.4" height="292.4" viewBox="0 0 292 292"><path d="M287 197L159 69c-4-3-8-5-13-5s-9 2-13 5L5 197c-3 4-5 8-5 13s2 9 5 13c4 4 8 5 13 5h256c5 0 9-1 13-5s5-8 5-13-1-9-5-13z"/></svg>');
|
||||||
--focus-highlight-color: #{$oc-blue-2};
|
--focus-highlight-color: #{$oc-blue-2};
|
||||||
--select-highlight-color: #{$oc-blue-5};
|
--select-highlight-color: #{$oc-blue-5};
|
||||||
@@ -35,6 +31,8 @@
|
|||||||
--popup-secondary-background-color: #{$oc-gray-1};
|
--popup-secondary-background-color: #{$oc-gray-1};
|
||||||
--popup-text-color: #{$oc-black};
|
--popup-text-color: #{$oc-black};
|
||||||
--popup-text-inverted-color: #{$oc-white};
|
--popup-text-inverted-color: #{$oc-white};
|
||||||
|
--dialog-border: #{$oc-gray-6};
|
||||||
|
--link-color: #{$oc-blue-7};
|
||||||
}
|
}
|
||||||
|
|
||||||
.excalidraw {
|
.excalidraw {
|
||||||
@@ -60,10 +58,8 @@
|
|||||||
--icon-fill-color: #{$oc-gray-4};
|
--icon-fill-color: #{$oc-gray-4};
|
||||||
--icon-green-fill-color: #{$oc-green-4};
|
--icon-green-fill-color: #{$oc-green-4};
|
||||||
--keybinding-color: #{$oc-gray-6};
|
--keybinding-color: #{$oc-gray-6};
|
||||||
--color-overlay-text-color: #bbb;
|
|
||||||
--shadow-island: 0 1px 5px #{transparentize($oc-black, 0.7)};
|
--shadow-island: 0 1px 5px #{transparentize($oc-black, 0.7)};
|
||||||
--overlay-background-color: rgba(30, 30, 30, 0.88);
|
--overlay-background-color: rgba(30, 30, 30, 0.88);
|
||||||
// #{$oc-gray-4}; inlined
|
|
||||||
--dropdown-icon: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" width="292.4" height="292.4" viewBox="0 0 292 292"><path fill="%23ced4da" d="M287 197L159 69c-4-3-8-5-13-5s-9 2-13 5L5 197c-3 4-5 8-5 13s2 9 5 13c4 4 8 5 13 5h256c5 0 9-1 13-5s5-8 5-13-1-9-5-13z"/></svg>');
|
--dropdown-icon: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" width="292.4" height="292.4" viewBox="0 0 292 292"><path fill="%23ced4da" d="M287 197L159 69c-4-3-8-5-13-5s-9 2-13 5L5 197c-3 4-5 8-5 13s2 9 5 13c4 4 8 5 13 5h256c5 0 9-1 13-5s5-8 5-13-1-9-5-13z"/></svg>');
|
||||||
--focus-highlight-color: #{$oc-blue-6};
|
--focus-highlight-color: #{$oc-blue-6};
|
||||||
--select-highlight-color: #{$oc-blue-4};
|
--select-highlight-color: #{$oc-blue-4};
|
||||||
@@ -74,5 +70,6 @@
|
|||||||
--popup-secondary-background-color: #222;
|
--popup-secondary-background-color: #222;
|
||||||
--popup-text-color: #{$oc-gray-4};
|
--popup-text-color: #{$oc-gray-4};
|
||||||
--popup-text-inverted-color: #2c2c2c;
|
--popup-text-inverted-color: #2c2c2c;
|
||||||
|
--dialog-border: #{$oc-gray-9};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import { EVENT_IO, trackEvent } from "../analytics";
|
|
||||||
import { cleanAppStateForExport } from "../appState";
|
import { cleanAppStateForExport } from "../appState";
|
||||||
import { MIME_TYPES } from "../constants";
|
import { MIME_TYPES } from "../constants";
|
||||||
import { clearElementsForExport } from "../element";
|
import { clearElementsForExport } from "../element";
|
||||||
@@ -111,7 +110,6 @@ export const loadFromBlob = async (
|
|||||||
localAppState,
|
localAppState,
|
||||||
);
|
);
|
||||||
|
|
||||||
trackEvent(EVENT_IO, "load", getMimeType(blob));
|
|
||||||
return result;
|
return result;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error.message);
|
console.error(error.message);
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import { fileSave } from "browser-nativefs";
|
import { fileSave } from "browser-nativefs";
|
||||||
import { EVENT_IO, trackEvent } from "../analytics";
|
|
||||||
import {
|
import {
|
||||||
copyCanvasToClipboardAsPng,
|
copyCanvasToClipboardAsPng,
|
||||||
copyTextToSystemClipboard,
|
copyTextToSystemClipboard,
|
||||||
@@ -8,8 +7,8 @@ import { NonDeletedExcalidrawElement } from "../element/types";
|
|||||||
import { t } from "../i18n";
|
import { t } from "../i18n";
|
||||||
import { exportToCanvas, exportToSvg } from "../scene/export";
|
import { exportToCanvas, exportToSvg } from "../scene/export";
|
||||||
import { ExportType } from "../scene/types";
|
import { ExportType } from "../scene/types";
|
||||||
import { canvasToBlob } from "./blob";
|
|
||||||
import { AppState } from "../types";
|
import { AppState } from "../types";
|
||||||
|
import { canvasToBlob } from "./blob";
|
||||||
import { serializeAsJSON } from "./json";
|
import { serializeAsJSON } from "./json";
|
||||||
|
|
||||||
export { loadFromBlob } from "./blob";
|
export { loadFromBlob } from "./blob";
|
||||||
@@ -37,7 +36,7 @@ export const exportCanvas = async (
|
|||||||
},
|
},
|
||||||
) => {
|
) => {
|
||||||
if (elements.length === 0) {
|
if (elements.length === 0) {
|
||||||
return window.alert(t("alerts.cannotExportEmptyCanvas"));
|
throw new Error(t("alerts.cannotExportEmptyCanvas"));
|
||||||
}
|
}
|
||||||
if (type === "svg" || type === "clipboard-svg") {
|
if (type === "svg" || type === "clipboard-svg") {
|
||||||
const tempSvg = exportToSvg(elements, {
|
const tempSvg = exportToSvg(elements, {
|
||||||
@@ -60,10 +59,8 @@ export const exportCanvas = async (
|
|||||||
fileName: `${name}.svg`,
|
fileName: `${name}.svg`,
|
||||||
extensions: [".svg"],
|
extensions: [".svg"],
|
||||||
});
|
});
|
||||||
trackEvent(EVENT_IO, "export", "svg");
|
|
||||||
return;
|
return;
|
||||||
} else if (type === "clipboard-svg") {
|
} else if (type === "clipboard-svg") {
|
||||||
trackEvent(EVENT_IO, "export", "clipboard-svg");
|
|
||||||
copyTextToSystemClipboard(tempSvg.outerHTML);
|
copyTextToSystemClipboard(tempSvg.outerHTML);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -95,11 +92,9 @@ export const exportCanvas = async (
|
|||||||
fileName,
|
fileName,
|
||||||
extensions: [".png"],
|
extensions: [".png"],
|
||||||
});
|
});
|
||||||
trackEvent(EVENT_IO, "export", "png");
|
|
||||||
} else if (type === "clipboard") {
|
} else if (type === "clipboard") {
|
||||||
try {
|
try {
|
||||||
await copyCanvasToClipboardAsPng(tempCanvas);
|
await copyCanvasToClipboardAsPng(tempCanvas);
|
||||||
trackEvent(EVENT_IO, "export", "clipboard-png");
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error.name === "CANVAS_POSSIBLY_TOO_BIG") {
|
if (error.name === "CANVAS_POSSIBLY_TOO_BIG") {
|
||||||
throw error;
|
throw error;
|
||||||
|
|||||||
@@ -1,13 +1,11 @@
|
|||||||
import { ExcalidrawElement } from "../element/types";
|
|
||||||
import { AppState } from "../types";
|
|
||||||
import { cleanAppStateForExport } from "../appState";
|
|
||||||
|
|
||||||
import { fileOpen, fileSave } from "browser-nativefs";
|
import { fileOpen, fileSave } from "browser-nativefs";
|
||||||
import { loadFromBlob } from "./blob";
|
import { cleanAppStateForExport } from "../appState";
|
||||||
import { Library } from "./library";
|
|
||||||
import { MIME_TYPES } from "../constants";
|
import { MIME_TYPES } from "../constants";
|
||||||
import { clearElementsForExport } from "../element";
|
import { clearElementsForExport } from "../element";
|
||||||
import { EVENT_LIBRARY, trackEvent } from "../analytics";
|
import { ExcalidrawElement } from "../element/types";
|
||||||
|
import { AppState } from "../types";
|
||||||
|
import { loadFromBlob } from "./blob";
|
||||||
|
import { Library } from "./library";
|
||||||
|
|
||||||
export const serializeAsJSON = (
|
export const serializeAsJSON = (
|
||||||
elements: readonly ExcalidrawElement[],
|
elements: readonly ExcalidrawElement[],
|
||||||
@@ -84,7 +82,6 @@ export const saveLibraryAsJSON = async () => {
|
|||||||
description: "Excalidraw library file",
|
description: "Excalidraw library file",
|
||||||
extensions: [".excalidrawlib"],
|
extensions: [".excalidrawlib"],
|
||||||
});
|
});
|
||||||
trackEvent(EVENT_LIBRARY, "save");
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const importLibraryFromJSON = async () => {
|
export const importLibraryFromJSON = async () => {
|
||||||
@@ -93,6 +90,5 @@ export const importLibraryFromJSON = async () => {
|
|||||||
extensions: [".json", ".excalidrawlib"],
|
extensions: [".json", ".excalidrawlib"],
|
||||||
mimeTypes: ["application/json"],
|
mimeTypes: ["application/json"],
|
||||||
});
|
});
|
||||||
trackEvent(EVENT_LIBRARY, "load");
|
|
||||||
Library.importLibrary(blob);
|
Library.importLibrary(blob);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -34,7 +34,6 @@ export {
|
|||||||
export {
|
export {
|
||||||
resizeTest,
|
resizeTest,
|
||||||
getCursorForResizingElement,
|
getCursorForResizingElement,
|
||||||
normalizeTransformHandleType,
|
|
||||||
getElementWithTransformHandleType,
|
getElementWithTransformHandleType,
|
||||||
getTransformHandleTypeFromCoords,
|
getTransformHandleTypeFromCoords,
|
||||||
} from "./resizeTest";
|
} from "./resizeTest";
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import { rescalePoints } from "../points";
|
|||||||
import {
|
import {
|
||||||
rotate,
|
rotate,
|
||||||
adjustXYWithRotation,
|
adjustXYWithRotation,
|
||||||
getFlipAdjustment,
|
|
||||||
centerPoint,
|
centerPoint,
|
||||||
rotatePoint,
|
rotatePoint,
|
||||||
} from "../math";
|
} from "../math";
|
||||||
@@ -13,21 +12,16 @@ import {
|
|||||||
ExcalidrawTextElement,
|
ExcalidrawTextElement,
|
||||||
NonDeletedExcalidrawElement,
|
NonDeletedExcalidrawElement,
|
||||||
NonDeleted,
|
NonDeleted,
|
||||||
ExcalidrawGenericElement,
|
|
||||||
ExcalidrawElement,
|
|
||||||
} from "./types";
|
} from "./types";
|
||||||
import {
|
import {
|
||||||
getElementAbsoluteCoords,
|
getElementAbsoluteCoords,
|
||||||
getCommonBounds,
|
getCommonBounds,
|
||||||
getResizedElementAbsoluteCoords,
|
getResizedElementAbsoluteCoords,
|
||||||
} from "./bounds";
|
} from "./bounds";
|
||||||
import { isGenericElement, isLinearElement, isTextElement } from "./typeChecks";
|
import { isLinearElement, isTextElement } from "./typeChecks";
|
||||||
import { mutateElement } from "./mutateElement";
|
import { mutateElement } from "./mutateElement";
|
||||||
import { getPerfectElementSize } from "./sizeHelpers";
|
import { getPerfectElementSize } from "./sizeHelpers";
|
||||||
import {
|
import { getCursorForResizingElement } from "./resizeTest";
|
||||||
getCursorForResizingElement,
|
|
||||||
normalizeTransformHandleType,
|
|
||||||
} from "./resizeTest";
|
|
||||||
import { measureText, getFontString } from "../utils";
|
import { measureText, getFontString } from "../utils";
|
||||||
import { updateBoundElements } from "./binding";
|
import { updateBoundElements } from "./binding";
|
||||||
import {
|
import {
|
||||||
@@ -49,7 +43,6 @@ const normalizeAngle = (angle: number): number => {
|
|||||||
export const transformElements = (
|
export const transformElements = (
|
||||||
pointerDownState: PointerDownState,
|
pointerDownState: PointerDownState,
|
||||||
transformHandleType: MaybeTransformHandleType,
|
transformHandleType: MaybeTransformHandleType,
|
||||||
setTransformHandle: (nextTransformHandle: MaybeTransformHandleType) => void,
|
|
||||||
selectedElements: readonly NonDeletedExcalidrawElement[],
|
selectedElements: readonly NonDeletedExcalidrawElement[],
|
||||||
resizeArrowDirection: "origin" | "end",
|
resizeArrowDirection: "origin" | "end",
|
||||||
isRotateWithDiscreteAngle: boolean,
|
isRotateWithDiscreteAngle: boolean,
|
||||||
@@ -101,36 +94,15 @@ export const transformElements = (
|
|||||||
);
|
);
|
||||||
updateBoundElements(element);
|
updateBoundElements(element);
|
||||||
} else if (transformHandleType) {
|
} else if (transformHandleType) {
|
||||||
if (isGenericElement(element)) {
|
resizeSingleElement(
|
||||||
resizeSingleGenericElement(
|
pointerDownState.originalElements.get(element.id) as typeof element,
|
||||||
pointerDownState.originalElements.get(element.id) as typeof element,
|
shouldKeepSidesRatio,
|
||||||
shouldKeepSidesRatio,
|
element,
|
||||||
element,
|
transformHandleType,
|
||||||
transformHandleType,
|
isResizeCenterPoint,
|
||||||
isResizeCenterPoint,
|
pointerX,
|
||||||
pointerX,
|
pointerY,
|
||||||
pointerY,
|
);
|
||||||
);
|
|
||||||
} else {
|
|
||||||
const keepSquareAspectRatio = shouldKeepSidesRatio;
|
|
||||||
resizeSingleNonGenericElement(
|
|
||||||
element,
|
|
||||||
transformHandleType,
|
|
||||||
isResizeCenterPoint,
|
|
||||||
keepSquareAspectRatio,
|
|
||||||
pointerX,
|
|
||||||
pointerY,
|
|
||||||
);
|
|
||||||
setTransformHandle(
|
|
||||||
normalizeTransformHandleType(element, transformHandleType),
|
|
||||||
);
|
|
||||||
if (element.width < 0) {
|
|
||||||
mutateElement(element, { width: -element.width });
|
|
||||||
}
|
|
||||||
if (element.height < 0) {
|
|
||||||
mutateElement(element, { height: -element.height });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// update cursor
|
// update cursor
|
||||||
@@ -414,8 +386,8 @@ const resizeSingleTextElement = (
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const resizeSingleGenericElement = (
|
const resizeSingleElement = (
|
||||||
stateAtResizeStart: NonDeleted<ExcalidrawGenericElement>,
|
stateAtResizeStart: NonDeletedExcalidrawElement,
|
||||||
shouldKeepSidesRatio: boolean,
|
shouldKeepSidesRatio: boolean,
|
||||||
element: NonDeletedExcalidrawElement,
|
element: NonDeletedExcalidrawElement,
|
||||||
transformHandleDirection: TransformHandleDirection,
|
transformHandleDirection: TransformHandleDirection,
|
||||||
@@ -423,251 +395,184 @@ const resizeSingleGenericElement = (
|
|||||||
pointerX: number,
|
pointerX: number,
|
||||||
pointerY: number,
|
pointerY: number,
|
||||||
) => {
|
) => {
|
||||||
const [x1, y1, x2, y2] = getElementAbsoluteCoords(stateAtResizeStart);
|
// Gets bounds corners
|
||||||
|
const [x1, y1, x2, y2] = getResizedElementAbsoluteCoords(
|
||||||
|
stateAtResizeStart,
|
||||||
|
stateAtResizeStart.width,
|
||||||
|
stateAtResizeStart.height,
|
||||||
|
);
|
||||||
const startTopLeft: Point = [x1, y1];
|
const startTopLeft: Point = [x1, y1];
|
||||||
const startBottomRight: Point = [x2, y2];
|
const startBottomRight: Point = [x2, y2];
|
||||||
const startCenter: Point = centerPoint(startTopLeft, startBottomRight);
|
const startCenter: Point = centerPoint(startTopLeft, startBottomRight);
|
||||||
|
|
||||||
// Calculate new dimensions based on cursor position
|
// Calculate new dimensions based on cursor position
|
||||||
let newWidth = stateAtResizeStart.width;
|
|
||||||
let newHeight = stateAtResizeStart.height;
|
|
||||||
const rotatedPointer = rotatePoint(
|
const rotatedPointer = rotatePoint(
|
||||||
[pointerX, pointerY],
|
[pointerX, pointerY],
|
||||||
startCenter,
|
startCenter,
|
||||||
-stateAtResizeStart.angle,
|
-stateAtResizeStart.angle,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
//Get bounds corners rendered on screen
|
||||||
|
const [esx1, esy1, esx2, esy2] = getResizedElementAbsoluteCoords(
|
||||||
|
element,
|
||||||
|
element.width,
|
||||||
|
element.height,
|
||||||
|
);
|
||||||
|
const boundsCurrentWidth = esx2 - esx1;
|
||||||
|
const boundsCurrentHeight = esy2 - esy1;
|
||||||
|
|
||||||
|
// It's important we set the initial scale value based on the width and height at resize start,
|
||||||
|
// otherwise previous dimensions affected by modifiers will be taken into account.
|
||||||
|
const atStartBoundsWidth = startBottomRight[0] - startTopLeft[0];
|
||||||
|
const atStartBoundsHeight = startBottomRight[1] - startTopLeft[1];
|
||||||
|
let scaleX = atStartBoundsWidth / boundsCurrentWidth;
|
||||||
|
let scaleY = atStartBoundsHeight / boundsCurrentHeight;
|
||||||
|
|
||||||
if (transformHandleDirection.includes("e")) {
|
if (transformHandleDirection.includes("e")) {
|
||||||
newWidth = rotatedPointer[0] - startTopLeft[0];
|
scaleX = (rotatedPointer[0] - startTopLeft[0]) / boundsCurrentWidth;
|
||||||
}
|
}
|
||||||
if (transformHandleDirection.includes("s")) {
|
if (transformHandleDirection.includes("s")) {
|
||||||
newHeight = rotatedPointer[1] - startTopLeft[1];
|
scaleY = (rotatedPointer[1] - startTopLeft[1]) / boundsCurrentHeight;
|
||||||
}
|
}
|
||||||
if (transformHandleDirection.includes("w")) {
|
if (transformHandleDirection.includes("w")) {
|
||||||
newWidth = startBottomRight[0] - rotatedPointer[0];
|
scaleX = (startBottomRight[0] - rotatedPointer[0]) / boundsCurrentWidth;
|
||||||
}
|
}
|
||||||
if (transformHandleDirection.includes("n")) {
|
if (transformHandleDirection.includes("n")) {
|
||||||
newHeight = startBottomRight[1] - rotatedPointer[1];
|
scaleY = (startBottomRight[1] - rotatedPointer[1]) / boundsCurrentHeight;
|
||||||
}
|
}
|
||||||
|
// Linear elements dimensions differ from bounds dimensions
|
||||||
|
const eleInitialWidth = stateAtResizeStart.width;
|
||||||
|
const eleInitialHeight = stateAtResizeStart.height;
|
||||||
|
// We have to use dimensions of element on screen, otherwise the scaling of the
|
||||||
|
// dimensions won't match the cursor for linear elements.
|
||||||
|
let eleNewWidth = element.width * scaleX;
|
||||||
|
let eleNewHeight = element.height * scaleY;
|
||||||
|
|
||||||
// adjust dimensions for resizing from center
|
// adjust dimensions for resizing from center
|
||||||
if (isResizeFromCenter) {
|
if (isResizeFromCenter) {
|
||||||
newWidth = 2 * newWidth - stateAtResizeStart.width;
|
eleNewWidth = 2 * eleNewWidth - eleInitialWidth;
|
||||||
newHeight = 2 * newHeight - stateAtResizeStart.height;
|
eleNewHeight = 2 * eleNewHeight - eleInitialHeight;
|
||||||
}
|
}
|
||||||
|
|
||||||
// adjust dimensions to keep sides ratio
|
// adjust dimensions to keep sides ratio
|
||||||
if (shouldKeepSidesRatio) {
|
if (shouldKeepSidesRatio) {
|
||||||
const widthRatio = Math.abs(newWidth) / stateAtResizeStart.width;
|
const widthRatio = Math.abs(eleNewWidth) / eleInitialWidth;
|
||||||
const heightRatio = Math.abs(newHeight) / stateAtResizeStart.height;
|
const heightRatio = Math.abs(eleNewHeight) / eleInitialHeight;
|
||||||
if (transformHandleDirection.length === 1) {
|
if (transformHandleDirection.length === 1) {
|
||||||
newHeight *= widthRatio;
|
eleNewHeight *= widthRatio;
|
||||||
newWidth *= heightRatio;
|
eleNewWidth *= heightRatio;
|
||||||
}
|
}
|
||||||
if (transformHandleDirection.length === 2) {
|
if (transformHandleDirection.length === 2) {
|
||||||
const ratio = Math.max(widthRatio, heightRatio);
|
const ratio = Math.max(widthRatio, heightRatio);
|
||||||
newWidth = stateAtResizeStart.width * ratio * Math.sign(newWidth);
|
eleNewWidth = eleInitialWidth * ratio * Math.sign(eleNewWidth);
|
||||||
newHeight = stateAtResizeStart.height * ratio * Math.sign(newHeight);
|
eleNewHeight = eleInitialHeight * ratio * Math.sign(eleNewHeight);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const [
|
||||||
|
newBoundsX1,
|
||||||
|
newBoundsY1,
|
||||||
|
newBoundsX2,
|
||||||
|
newBoundsY2,
|
||||||
|
] = getResizedElementAbsoluteCoords(
|
||||||
|
stateAtResizeStart,
|
||||||
|
eleNewWidth,
|
||||||
|
eleNewHeight,
|
||||||
|
);
|
||||||
|
const newBoundsWidth = newBoundsX2 - newBoundsX1;
|
||||||
|
const newBoundsHeight = newBoundsY2 - newBoundsY1;
|
||||||
|
|
||||||
// Calculate new topLeft based on fixed corner during resize
|
// Calculate new topLeft based on fixed corner during resize
|
||||||
let newTopLeft = startTopLeft as [number, number];
|
let newTopLeft = [...startTopLeft] as [number, number];
|
||||||
if (["n", "w", "nw"].includes(transformHandleDirection)) {
|
if (["n", "w", "nw"].includes(transformHandleDirection)) {
|
||||||
newTopLeft = [
|
newTopLeft = [
|
||||||
startBottomRight[0] - Math.abs(newWidth),
|
startBottomRight[0] - Math.abs(newBoundsWidth),
|
||||||
startBottomRight[1] - Math.abs(newHeight),
|
startBottomRight[1] - Math.abs(newBoundsHeight),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
if (transformHandleDirection === "ne") {
|
if (transformHandleDirection === "ne") {
|
||||||
const bottomLeft = [
|
const bottomLeft = [startTopLeft[0], startBottomRight[1]];
|
||||||
stateAtResizeStart.x,
|
newTopLeft = [bottomLeft[0], bottomLeft[1] - Math.abs(newBoundsHeight)];
|
||||||
stateAtResizeStart.y + stateAtResizeStart.height,
|
|
||||||
];
|
|
||||||
newTopLeft = [bottomLeft[0], bottomLeft[1] - Math.abs(newHeight)];
|
|
||||||
}
|
}
|
||||||
if (transformHandleDirection === "sw") {
|
if (transformHandleDirection === "sw") {
|
||||||
const topRight = [
|
const topRight = [startBottomRight[0], startTopLeft[1]];
|
||||||
stateAtResizeStart.x + stateAtResizeStart.width,
|
newTopLeft = [topRight[0] - Math.abs(newBoundsWidth), topRight[1]];
|
||||||
stateAtResizeStart.y,
|
|
||||||
];
|
|
||||||
newTopLeft = [topRight[0] - Math.abs(newWidth), topRight[1]];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Keeps opposite handle fixed during resize
|
// Keeps opposite handle fixed during resize
|
||||||
if (shouldKeepSidesRatio) {
|
if (shouldKeepSidesRatio) {
|
||||||
if (["s", "n"].includes(transformHandleDirection)) {
|
if (["s", "n"].includes(transformHandleDirection)) {
|
||||||
newTopLeft[0] = startCenter[0] - newWidth / 2;
|
newTopLeft[0] = startCenter[0] - newBoundsWidth / 2;
|
||||||
}
|
}
|
||||||
if (["e", "w"].includes(transformHandleDirection)) {
|
if (["e", "w"].includes(transformHandleDirection)) {
|
||||||
newTopLeft[1] = startCenter[1] - newHeight / 2;
|
newTopLeft[1] = startCenter[1] - newBoundsHeight / 2;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Flip horizontally
|
// Flip horizontally
|
||||||
if (newWidth < 0) {
|
if (eleNewWidth < 0) {
|
||||||
if (transformHandleDirection.includes("e")) {
|
if (transformHandleDirection.includes("e")) {
|
||||||
newTopLeft[0] -= Math.abs(newWidth);
|
newTopLeft[0] -= Math.abs(newBoundsWidth);
|
||||||
}
|
}
|
||||||
if (transformHandleDirection.includes("w")) {
|
if (transformHandleDirection.includes("w")) {
|
||||||
newTopLeft[0] += Math.abs(newWidth);
|
newTopLeft[0] += Math.abs(newBoundsWidth);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Flip vertically
|
// Flip vertically
|
||||||
if (newHeight < 0) {
|
if (eleNewHeight < 0) {
|
||||||
if (transformHandleDirection.includes("s")) {
|
if (transformHandleDirection.includes("s")) {
|
||||||
newTopLeft[1] -= Math.abs(newHeight);
|
newTopLeft[1] -= Math.abs(newBoundsHeight);
|
||||||
}
|
}
|
||||||
if (transformHandleDirection.includes("n")) {
|
if (transformHandleDirection.includes("n")) {
|
||||||
newTopLeft[1] += Math.abs(newHeight);
|
newTopLeft[1] += Math.abs(newBoundsHeight);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isResizeFromCenter) {
|
if (isResizeFromCenter) {
|
||||||
newTopLeft[0] = startCenter[0] - Math.abs(newWidth) / 2;
|
newTopLeft[0] = startCenter[0] - Math.abs(newBoundsWidth) / 2;
|
||||||
newTopLeft[1] = startCenter[1] - Math.abs(newHeight) / 2;
|
newTopLeft[1] = startCenter[1] - Math.abs(newBoundsHeight) / 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
// adjust topLeft to new rotation point
|
// adjust topLeft to new rotation point
|
||||||
const angle = stateAtResizeStart.angle;
|
const angle = stateAtResizeStart.angle;
|
||||||
const rotatedTopLeft = rotatePoint(newTopLeft, startCenter, angle);
|
const rotatedTopLeft = rotatePoint(newTopLeft, startCenter, angle);
|
||||||
const newCenter: Point = [
|
const newCenter: Point = [
|
||||||
newTopLeft[0] + Math.abs(newWidth) / 2,
|
newTopLeft[0] + Math.abs(newBoundsWidth) / 2,
|
||||||
newTopLeft[1] + Math.abs(newHeight) / 2,
|
newTopLeft[1] + Math.abs(newBoundsHeight) / 2,
|
||||||
];
|
];
|
||||||
const rotatedNewCenter = rotatePoint(newCenter, startCenter, angle);
|
const rotatedNewCenter = rotatePoint(newCenter, startCenter, angle);
|
||||||
newTopLeft = rotatePoint(rotatedTopLeft, rotatedNewCenter, -angle);
|
newTopLeft = rotatePoint(rotatedTopLeft, rotatedNewCenter, -angle);
|
||||||
|
|
||||||
|
// Readjust points for linear elements
|
||||||
|
const rescaledPoints = rescalePointsInElement(
|
||||||
|
stateAtResizeStart,
|
||||||
|
eleNewWidth,
|
||||||
|
eleNewHeight,
|
||||||
|
);
|
||||||
|
// For linear elements (x,y) are the coordinates of the first drawn point not the top-left corner
|
||||||
|
// So we need to readjust (x,y) to be where the first point should be
|
||||||
|
const newOrigin = [...newTopLeft];
|
||||||
|
newOrigin[0] += stateAtResizeStart.x - newBoundsX1;
|
||||||
|
newOrigin[1] += stateAtResizeStart.y - newBoundsY1;
|
||||||
|
|
||||||
const resizedElement = {
|
const resizedElement = {
|
||||||
width: Math.abs(newWidth),
|
width: Math.abs(eleNewWidth),
|
||||||
height: Math.abs(newHeight),
|
height: Math.abs(eleNewHeight),
|
||||||
x: newTopLeft[0],
|
x: newOrigin[0],
|
||||||
y: newTopLeft[1],
|
y: newOrigin[1],
|
||||||
|
...rescaledPoints,
|
||||||
};
|
};
|
||||||
updateBoundElements(element, {
|
|
||||||
newSize: { width: resizedElement.width, height: resizedElement.height },
|
|
||||||
});
|
|
||||||
mutateElement(element, resizedElement);
|
|
||||||
};
|
|
||||||
|
|
||||||
const resizeSingleNonGenericElement = (
|
|
||||||
element: NonDeleted<Exclude<ExcalidrawElement, ExcalidrawGenericElement>>,
|
|
||||||
transformHandleType: "n" | "s" | "w" | "e" | "nw" | "ne" | "sw" | "se",
|
|
||||||
isResizeFromCenter: boolean,
|
|
||||||
keepSquareAspectRatio: boolean,
|
|
||||||
pointerX: number,
|
|
||||||
pointerY: number,
|
|
||||||
) => {
|
|
||||||
const [x1, y1, x2, y2] = getElementAbsoluteCoords(element);
|
|
||||||
const cx = (x1 + x2) / 2;
|
|
||||||
const cy = (y1 + y2) / 2;
|
|
||||||
|
|
||||||
// rotation pointer with reverse angle
|
|
||||||
const [rotatedX, rotatedY] = rotate(
|
|
||||||
pointerX,
|
|
||||||
pointerY,
|
|
||||||
cx,
|
|
||||||
cy,
|
|
||||||
-element.angle,
|
|
||||||
);
|
|
||||||
|
|
||||||
let scaleX = 1;
|
|
||||||
let scaleY = 1;
|
|
||||||
if (
|
|
||||||
transformHandleType === "e" ||
|
|
||||||
transformHandleType === "ne" ||
|
|
||||||
transformHandleType === "se"
|
|
||||||
) {
|
|
||||||
scaleX = (rotatedX - x1) / (x2 - x1);
|
|
||||||
}
|
|
||||||
if (
|
|
||||||
transformHandleType === "s" ||
|
|
||||||
transformHandleType === "sw" ||
|
|
||||||
transformHandleType === "se"
|
|
||||||
) {
|
|
||||||
scaleY = (rotatedY - y1) / (y2 - y1);
|
|
||||||
}
|
|
||||||
if (
|
|
||||||
transformHandleType === "w" ||
|
|
||||||
transformHandleType === "nw" ||
|
|
||||||
transformHandleType === "sw"
|
|
||||||
) {
|
|
||||||
scaleX = (x2 - rotatedX) / (x2 - x1);
|
|
||||||
}
|
|
||||||
if (
|
|
||||||
transformHandleType === "n" ||
|
|
||||||
transformHandleType === "nw" ||
|
|
||||||
transformHandleType === "ne"
|
|
||||||
) {
|
|
||||||
scaleY = (y2 - rotatedY) / (y2 - y1);
|
|
||||||
}
|
|
||||||
let nextWidth = element.width * scaleX;
|
|
||||||
let nextHeight = element.height * scaleY;
|
|
||||||
if (keepSquareAspectRatio) {
|
|
||||||
nextWidth = nextHeight = Math.max(nextWidth, nextHeight);
|
|
||||||
}
|
|
||||||
|
|
||||||
const [nextX1, nextY1, nextX2, nextY2] = getResizedElementAbsoluteCoords(
|
|
||||||
element,
|
|
||||||
nextWidth,
|
|
||||||
nextHeight,
|
|
||||||
);
|
|
||||||
const deltaX1 = (x1 - nextX1) / 2;
|
|
||||||
const deltaY1 = (y1 - nextY1) / 2;
|
|
||||||
const deltaX2 = (x2 - nextX2) / 2;
|
|
||||||
const deltaY2 = (y2 - nextY2) / 2;
|
|
||||||
|
|
||||||
const rescaledPoints = rescalePointsInElement(element, nextWidth, nextHeight);
|
|
||||||
|
|
||||||
updateBoundElements(element, {
|
|
||||||
newSize: { width: nextWidth, height: nextHeight },
|
|
||||||
});
|
|
||||||
const [finalX1, finalY1, finalX2, finalY2] = getResizedElementAbsoluteCoords(
|
|
||||||
{
|
|
||||||
...element,
|
|
||||||
...rescaledPoints,
|
|
||||||
},
|
|
||||||
Math.abs(nextWidth),
|
|
||||||
Math.abs(nextHeight),
|
|
||||||
);
|
|
||||||
const [flipDiffX, flipDiffY] = getFlipAdjustment(
|
|
||||||
transformHandleType,
|
|
||||||
nextWidth,
|
|
||||||
nextHeight,
|
|
||||||
nextX1,
|
|
||||||
nextY1,
|
|
||||||
nextX2,
|
|
||||||
nextY2,
|
|
||||||
finalX1,
|
|
||||||
finalY1,
|
|
||||||
finalX2,
|
|
||||||
finalY2,
|
|
||||||
isLinearElement(element),
|
|
||||||
element.angle,
|
|
||||||
);
|
|
||||||
const [nextElementX, nextElementY] = adjustXYWithRotation(
|
|
||||||
getSidesForTransformHandle(transformHandleType, isResizeFromCenter),
|
|
||||||
element.x - flipDiffX,
|
|
||||||
element.y - flipDiffY,
|
|
||||||
element.angle,
|
|
||||||
deltaX1,
|
|
||||||
deltaY1,
|
|
||||||
deltaX2,
|
|
||||||
deltaY2,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (
|
if (
|
||||||
nextWidth !== 0 &&
|
resizedElement.width !== 0 &&
|
||||||
nextHeight !== 0 &&
|
resizedElement.height !== 0 &&
|
||||||
Number.isFinite(nextElementX) &&
|
Number.isFinite(resizedElement.x) &&
|
||||||
Number.isFinite(nextElementY)
|
Number.isFinite(resizedElement.y)
|
||||||
) {
|
) {
|
||||||
mutateElement(element, {
|
updateBoundElements(element, {
|
||||||
width: nextWidth,
|
newSize: { width: resizedElement.width, height: resizedElement.height },
|
||||||
height: nextHeight,
|
|
||||||
x: nextElementX,
|
|
||||||
y: nextElementY,
|
|
||||||
...rescaledPoints,
|
|
||||||
});
|
});
|
||||||
|
mutateElement(element, resizedElement);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -173,57 +173,3 @@ export const getCursorForResizingElement = (resizingElement: {
|
|||||||
|
|
||||||
return cursor ? `${cursor}-resize` : "";
|
return cursor ? `${cursor}-resize` : "";
|
||||||
};
|
};
|
||||||
|
|
||||||
export const normalizeTransformHandleType = (
|
|
||||||
element: ExcalidrawElement,
|
|
||||||
transformHandleType: TransformHandleType,
|
|
||||||
): TransformHandleType => {
|
|
||||||
if (element.width >= 0 && element.height >= 0) {
|
|
||||||
return transformHandleType;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (element.width < 0 && element.height < 0) {
|
|
||||||
switch (transformHandleType) {
|
|
||||||
case "nw":
|
|
||||||
return "se";
|
|
||||||
case "ne":
|
|
||||||
return "sw";
|
|
||||||
case "se":
|
|
||||||
return "nw";
|
|
||||||
case "sw":
|
|
||||||
return "ne";
|
|
||||||
}
|
|
||||||
} else if (element.width < 0) {
|
|
||||||
switch (transformHandleType) {
|
|
||||||
case "nw":
|
|
||||||
return "ne";
|
|
||||||
case "ne":
|
|
||||||
return "nw";
|
|
||||||
case "se":
|
|
||||||
return "sw";
|
|
||||||
case "sw":
|
|
||||||
return "se";
|
|
||||||
case "e":
|
|
||||||
return "w";
|
|
||||||
case "w":
|
|
||||||
return "e";
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
switch (transformHandleType) {
|
|
||||||
case "nw":
|
|
||||||
return "sw";
|
|
||||||
case "ne":
|
|
||||||
return "se";
|
|
||||||
case "se":
|
|
||||||
return "ne";
|
|
||||||
case "sw":
|
|
||||||
return "nw";
|
|
||||||
case "n":
|
|
||||||
return "s";
|
|
||||||
case "s":
|
|
||||||
return "n";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return transformHandleType;
|
|
||||||
};
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { Point } from "../types";
|
import { Point } from "../types";
|
||||||
import { FONT_FAMILY } from "../constants";
|
import { FONT_FAMILY } from "../constants";
|
||||||
|
|
||||||
|
export type ChartType = "bar" | "line";
|
||||||
export type FillStyle = "hachure" | "cross-hatch" | "solid";
|
export type FillStyle = "hachure" | "cross-hatch" | "solid";
|
||||||
export type FontFamily = keyof typeof FONT_FAMILY;
|
export type FontFamily = keyof typeof FONT_FAMILY;
|
||||||
export type FontString = string & { _brand: "fontString" };
|
export type FontString = string & { _brand: "fontString" };
|
||||||
@@ -26,11 +27,21 @@ type _ExcalidrawElementBase = Readonly<{
|
|||||||
width: number;
|
width: number;
|
||||||
height: number;
|
height: number;
|
||||||
angle: number;
|
angle: number;
|
||||||
|
/** Random integer used to seed shape generation so that the roughjs shape
|
||||||
|
doesn't differ across renders. */
|
||||||
seed: number;
|
seed: number;
|
||||||
|
/** Integer that is sequentially incremented on each change. Used to reconcile
|
||||||
|
elements during collaboration or when saving to server. */
|
||||||
version: number;
|
version: number;
|
||||||
|
/** Random integer that is regenerated on each change.
|
||||||
|
Used for deterministic reconciliation of updates during collaboration,
|
||||||
|
in case the versions (see above) are identical. */
|
||||||
versionNonce: number;
|
versionNonce: number;
|
||||||
isDeleted: boolean;
|
isDeleted: boolean;
|
||||||
|
/** List of groups the element belongs to.
|
||||||
|
Ordered from deepest to shallowest. */
|
||||||
groupIds: readonly GroupId[];
|
groupIds: readonly GroupId[];
|
||||||
|
/** Ids of (linear) elements that are bound to this element. */
|
||||||
boundElementIds: readonly ExcalidrawLinearElement["id"][] | null;
|
boundElementIds: readonly ExcalidrawLinearElement["id"][] | null;
|
||||||
}>;
|
}>;
|
||||||
|
|
||||||
|
|||||||
@@ -1,40 +1,36 @@
|
|||||||
import React, { PureComponent } from "react";
|
|
||||||
import throttle from "lodash.throttle";
|
import throttle from "lodash.throttle";
|
||||||
|
import React, { PureComponent } from "react";
|
||||||
|
import { ExcalidrawImperativeAPI } from "../../components/App";
|
||||||
|
import { ErrorDialog } from "../../components/ErrorDialog";
|
||||||
import { APP_NAME, ENV, EVENT } from "../../constants";
|
import { APP_NAME, ENV, EVENT } from "../../constants";
|
||||||
|
import { ImportedDataState } from "../../data/types";
|
||||||
import {
|
|
||||||
decryptAESGEM,
|
|
||||||
SocketUpdateDataSource,
|
|
||||||
getCollaborationLinkData,
|
|
||||||
generateCollaborationLink,
|
|
||||||
SOCKET_SERVER,
|
|
||||||
} from "../data";
|
|
||||||
import { isSavedToFirebase, saveToFirebase } from "../data/firebase";
|
|
||||||
|
|
||||||
import Portal from "./Portal";
|
|
||||||
import { AppState, Collaborator, Gesture } from "../../types";
|
|
||||||
import { ExcalidrawElement } from "../../element/types";
|
import { ExcalidrawElement } from "../../element/types";
|
||||||
import {
|
|
||||||
importUsernameFromLocalStorage,
|
|
||||||
saveUsernameToLocalStorage,
|
|
||||||
STORAGE_KEYS,
|
|
||||||
} from "../data/localStorage";
|
|
||||||
import { resolvablePromise, withBatchedUpdates } from "../../utils";
|
|
||||||
import {
|
import {
|
||||||
getSceneVersion,
|
getSceneVersion,
|
||||||
getSyncableElements,
|
getSyncableElements,
|
||||||
} from "../../packages/excalidraw/index";
|
} from "../../packages/excalidraw/index";
|
||||||
import RoomDialog from "./RoomDialog";
|
import { AppState, Collaborator, Gesture } from "../../types";
|
||||||
import { ErrorDialog } from "../../components/ErrorDialog";
|
import { resolvablePromise, withBatchedUpdates } from "../../utils";
|
||||||
import { ImportedDataState } from "../../data/types";
|
|
||||||
import { ExcalidrawImperativeAPI } from "../../components/App";
|
|
||||||
import {
|
import {
|
||||||
INITIAL_SCENE_UPDATE_TIMEOUT,
|
INITIAL_SCENE_UPDATE_TIMEOUT,
|
||||||
SCENE,
|
SCENE,
|
||||||
SYNC_FULL_SCENE_INTERVAL_MS,
|
SYNC_FULL_SCENE_INTERVAL_MS,
|
||||||
} from "../app_constants";
|
} from "../app_constants";
|
||||||
import { EVENT_SHARE, trackEvent } from "../../analytics";
|
import {
|
||||||
|
decryptAESGEM,
|
||||||
|
generateCollaborationLink,
|
||||||
|
getCollaborationLinkData,
|
||||||
|
SocketUpdateDataSource,
|
||||||
|
SOCKET_SERVER,
|
||||||
|
} from "../data";
|
||||||
|
import { isSavedToFirebase, saveToFirebase } from "../data/firebase";
|
||||||
|
import {
|
||||||
|
importUsernameFromLocalStorage,
|
||||||
|
saveUsernameToLocalStorage,
|
||||||
|
STORAGE_KEYS,
|
||||||
|
} from "../data/localStorage";
|
||||||
|
import Portal from "./Portal";
|
||||||
|
import RoomDialog from "./RoomDialog";
|
||||||
|
|
||||||
interface CollabState {
|
interface CollabState {
|
||||||
isCollaborating: boolean;
|
isCollaborating: boolean;
|
||||||
@@ -168,7 +164,6 @@ class CollabWrapper extends PureComponent<Props, CollabState> {
|
|||||||
elements,
|
elements,
|
||||||
commitToHistory: true,
|
commitToHistory: true,
|
||||||
});
|
});
|
||||||
trackEvent(EVENT_SHARE, "session start");
|
|
||||||
return this.initializeSocketClient();
|
return this.initializeSocketClient();
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -176,7 +171,6 @@ class CollabWrapper extends PureComponent<Props, CollabState> {
|
|||||||
this.saveCollabRoomToFirebase();
|
this.saveCollabRoomToFirebase();
|
||||||
window.history.pushState({}, APP_NAME, window.location.origin);
|
window.history.pushState({}, APP_NAME, window.location.origin);
|
||||||
this.destroySocketClient();
|
this.destroySocketClient();
|
||||||
trackEvent(EVENT_SHARE, "session end");
|
|
||||||
};
|
};
|
||||||
|
|
||||||
private destroySocketClient = () => {
|
private destroySocketClient = () => {
|
||||||
|
|||||||
@@ -1,12 +1,10 @@
|
|||||||
import React, { useRef } from "react";
|
import React, { useRef } from "react";
|
||||||
import { t } from "../../i18n";
|
|
||||||
import { Dialog } from "../../components/Dialog";
|
|
||||||
import { copyTextToSystemClipboard } from "../../clipboard";
|
import { copyTextToSystemClipboard } from "../../clipboard";
|
||||||
import { ToolButton } from "../../components/ToolButton";
|
import { Dialog } from "../../components/Dialog";
|
||||||
import { clipboard, start, stop } from "../../components/icons";
|
import { clipboard, start, stop } from "../../components/icons";
|
||||||
|
import { ToolButton } from "../../components/ToolButton";
|
||||||
|
import { t } from "../../i18n";
|
||||||
import "./RoomDialog.scss";
|
import "./RoomDialog.scss";
|
||||||
import { EVENT_SHARE, trackEvent } from "../../analytics";
|
|
||||||
|
|
||||||
const RoomDialog = ({
|
const RoomDialog = ({
|
||||||
handleClose,
|
handleClose,
|
||||||
@@ -30,7 +28,6 @@ const RoomDialog = ({
|
|||||||
const copyRoomLink = async () => {
|
const copyRoomLink = async () => {
|
||||||
try {
|
try {
|
||||||
await copyTextToSystemClipboard(activeRoomLink);
|
await copyTextToSystemClipboard(activeRoomLink);
|
||||||
trackEvent(EVENT_SHARE, "copy link");
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
setErrorMessage(error.message);
|
setErrorMessage(error.message);
|
||||||
}
|
}
|
||||||
@@ -95,7 +92,6 @@ const RoomDialog = ({
|
|||||||
value={username || ""}
|
value={username || ""}
|
||||||
className="RoomDialog-username TextInput"
|
className="RoomDialog-username TextInput"
|
||||||
onChange={(event) => onUsernameChange(event.target.value)}
|
onChange={(event) => onUsernameChange(event.target.value)}
|
||||||
onBlur={() => trackEvent(EVENT_SHARE, "name")}
|
|
||||||
onKeyPress={(event) => event.key === "Enter" && handleClose()}
|
onKeyPress={(event) => event.key === "Enter" && handleClose()}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -123,11 +119,7 @@ const RoomDialog = ({
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
return (
|
return (
|
||||||
<Dialog
|
<Dialog small onCloseRequest={handleClose} title={t("labels.createRoom")}>
|
||||||
maxWidth={800}
|
|
||||||
onCloseRequest={handleClose}
|
|
||||||
title={t("labels.createRoom")}
|
|
||||||
>
|
|
||||||
{renderRoomDialog()}
|
{renderRoomDialog()}
|
||||||
</Dialog>
|
</Dialog>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,16 +1,16 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import clsx from "clsx";
|
import clsx from "clsx";
|
||||||
import * as i18n from "../i18n";
|
import * as i18n from "../../i18n";
|
||||||
|
|
||||||
export const LanguageList = ({
|
export const LanguageList = ({
|
||||||
onChange,
|
onChange,
|
||||||
languages = i18n.languages,
|
languages = i18n.languages,
|
||||||
currentLanguage = i18n.getLanguage().lng,
|
currentLangCode = i18n.getLanguage().code,
|
||||||
floating,
|
floating,
|
||||||
}: {
|
}: {
|
||||||
languages?: { lng: string; label: string }[];
|
languages?: { code: string; label: string }[];
|
||||||
onChange: (value: string) => void;
|
onChange: (langCode: i18n.Language["code"]) => void;
|
||||||
currentLanguage?: string;
|
currentLangCode?: i18n.Language["code"];
|
||||||
floating?: boolean;
|
floating?: boolean;
|
||||||
}) => (
|
}) => (
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
@@ -19,12 +19,15 @@ export const LanguageList = ({
|
|||||||
"dropdown-select--floating": floating,
|
"dropdown-select--floating": floating,
|
||||||
})}
|
})}
|
||||||
onChange={({ target }) => onChange(target.value)}
|
onChange={({ target }) => onChange(target.value)}
|
||||||
value={currentLanguage}
|
value={currentLangCode}
|
||||||
aria-label={i18n.t("buttons.selectLanguage")}
|
aria-label={i18n.t("buttons.selectLanguage")}
|
||||||
>
|
>
|
||||||
{languages.map((language) => (
|
<option key={i18n.defaultLang.code} value={i18n.defaultLang.code}>
|
||||||
<option key={language.lng} value={language.lng}>
|
{i18n.defaultLang.label}
|
||||||
{language.label}
|
</option>
|
||||||
|
{languages.map((lang) => (
|
||||||
|
<option key={lang.code} value={lang.code}>
|
||||||
|
{lang.label}
|
||||||
</option>
|
</option>
|
||||||
))}
|
))}
|
||||||
</select>
|
</select>
|
||||||
@@ -1,10 +1,9 @@
|
|||||||
import { t } from "../../i18n";
|
|
||||||
import { ExcalidrawElement } from "../../element/types";
|
|
||||||
import { AppState } from "../../types";
|
|
||||||
import { ImportedDataState } from "../../data/types";
|
|
||||||
import { restore } from "../../data/restore";
|
|
||||||
import { EVENT_ACTION, EVENT_IO, trackEvent } from "../../analytics";
|
|
||||||
import { serializeAsJSON } from "../../data/json";
|
import { serializeAsJSON } from "../../data/json";
|
||||||
|
import { restore } from "../../data/restore";
|
||||||
|
import { ImportedDataState } from "../../data/types";
|
||||||
|
import { ExcalidrawElement } from "../../element/types";
|
||||||
|
import { t } from "../../i18n";
|
||||||
|
import { AppState } from "../../types";
|
||||||
|
|
||||||
const byteToHex = (byte: number): string => `0${byte.toString(16)}`.slice(-2);
|
const byteToHex = (byte: number): string => `0${byte.toString(16)}`.slice(-2);
|
||||||
|
|
||||||
@@ -192,7 +191,6 @@ const importFromBackend = async (
|
|||||||
data = await response.json();
|
data = await response.json();
|
||||||
}
|
}
|
||||||
|
|
||||||
trackEvent(EVENT_ACTION, "import");
|
|
||||||
return {
|
return {
|
||||||
elements: data.elements || null,
|
elements: data.elements || null,
|
||||||
appState: data.appState || null,
|
appState: data.appState || null,
|
||||||
@@ -276,7 +274,6 @@ export const exportToBackend = async (
|
|||||||
url.hash = `json=${json.id},${exportedKey.k!}`;
|
url.hash = `json=${json.id},${exportedKey.k!}`;
|
||||||
const urlString = url.toString();
|
const urlString = url.toString();
|
||||||
window.prompt(`🔒${t("alerts.uploadedSecurly")}`, urlString);
|
window.prompt(`🔒${t("alerts.uploadedSecurly")}`, urlString);
|
||||||
trackEvent(EVENT_IO, "export", "backend");
|
|
||||||
} else if (json.error_class === "RequestTooLargeError") {
|
} else if (json.error_class === "RequestTooLargeError") {
|
||||||
window.alert(t("alerts.couldNotCreateShareableLinkTooBig"));
|
window.alert(t("alerts.couldNotCreateShareableLinkTooBig"));
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -1,34 +1,53 @@
|
|||||||
import React, { useState, useLayoutEffect, useEffect, useRef } from "react";
|
import LanguageDetector from "i18next-browser-languagedetector";
|
||||||
|
import React, {
|
||||||
import Excalidraw from "../packages/excalidraw/index";
|
useCallback,
|
||||||
|
useEffect,
|
||||||
|
useLayoutEffect,
|
||||||
|
useRef,
|
||||||
|
useState,
|
||||||
|
} from "react";
|
||||||
|
import { trackEvent } from "../analytics";
|
||||||
|
import { getDefaultAppState } from "../appState";
|
||||||
|
import { ExcalidrawImperativeAPI } from "../components/App";
|
||||||
|
import { ErrorDialog } from "../components/ErrorDialog";
|
||||||
|
import { TopErrorBoundary } from "../components/TopErrorBoundary";
|
||||||
|
import { APP_NAME, EVENT, TITLE_TIMEOUT, VERSION_TIMEOUT } from "../constants";
|
||||||
|
import { ImportedDataState } from "../data/types";
|
||||||
|
import {
|
||||||
|
ExcalidrawElement,
|
||||||
|
NonDeletedExcalidrawElement,
|
||||||
|
} from "../element/types";
|
||||||
|
import { Language, t } from "../i18n";
|
||||||
|
import Excalidraw, {
|
||||||
|
defaultLang,
|
||||||
|
languages,
|
||||||
|
} from "../packages/excalidraw/index";
|
||||||
|
import { AppState, ExcalidrawAPIRefValue } from "../types";
|
||||||
|
import {
|
||||||
|
debounce,
|
||||||
|
getVersion,
|
||||||
|
ResolvablePromise,
|
||||||
|
resolvablePromise,
|
||||||
|
} from "../utils";
|
||||||
|
import { SAVE_TO_LOCAL_STORAGE_TIMEOUT } from "./app_constants";
|
||||||
|
import CollabWrapper, { CollabAPI } from "./collab/CollabWrapper";
|
||||||
|
import { LanguageList } from "./components/LanguageList";
|
||||||
|
import { exportToBackend, getCollaborationLinkData, loadScene } from "./data";
|
||||||
|
import { loadFromFirebase } from "./data/firebase";
|
||||||
import {
|
import {
|
||||||
getTotalStorageSize,
|
|
||||||
importFromLocalStorage,
|
importFromLocalStorage,
|
||||||
saveToLocalStorage,
|
saveToLocalStorage,
|
||||||
STORAGE_KEYS,
|
STORAGE_KEYS,
|
||||||
} from "./data/localStorage";
|
} from "./data/localStorage";
|
||||||
|
|
||||||
import { ImportedDataState } from "../data/types";
|
const languageDetector = new LanguageDetector();
|
||||||
import CollabWrapper, { CollabAPI } from "./collab/CollabWrapper";
|
languageDetector.init({
|
||||||
import { TopErrorBoundary } from "../components/TopErrorBoundary";
|
languageUtils: {
|
||||||
import { t } from "../i18n";
|
formatLanguageCode: (langCode: Language["code"]) => langCode,
|
||||||
import { exportToBackend, loadScene } from "./data";
|
isWhitelisted: () => true,
|
||||||
import { getCollaborationLinkData } from "./data";
|
},
|
||||||
import { EVENT } from "../constants";
|
checkWhitelist: false,
|
||||||
import { loadFromFirebase } from "./data/firebase";
|
});
|
||||||
import { ExcalidrawImperativeAPI } from "../components/App";
|
|
||||||
import { debounce, ResolvablePromise, resolvablePromise } from "../utils";
|
|
||||||
import { AppState, ExcalidrawAPIRefValue } from "../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<
|
const excalidrawRef: React.MutableRefObject<
|
||||||
MarkRequired<ExcalidrawAPIRefValue, "ready" | "readyPromise">
|
MarkRequired<ExcalidrawAPIRefValue, "ready" | "readyPromise">
|
||||||
@@ -93,7 +112,6 @@ type Scene = ImportedDataState & { commitToHistory: boolean };
|
|||||||
const initializeScene = async (opts: {
|
const initializeScene = async (opts: {
|
||||||
resetScene: ExcalidrawImperativeAPI["resetScene"];
|
resetScene: ExcalidrawImperativeAPI["resetScene"];
|
||||||
initializeSocketClient: CollabAPI["initializeSocketClient"];
|
initializeSocketClient: CollabAPI["initializeSocketClient"];
|
||||||
onLateInitialization?: (scene: Scene) => void;
|
|
||||||
}): Promise<Scene | null> => {
|
}): Promise<Scene | null> => {
|
||||||
const searchParams = new URLSearchParams(window.location.search);
|
const searchParams = new URLSearchParams(window.location.search);
|
||||||
const id = searchParams.get("id");
|
const id = searchParams.get("id");
|
||||||
@@ -124,17 +142,15 @@ const initializeScene = async (opts: {
|
|||||||
} else {
|
} else {
|
||||||
// https://github.com/excalidraw/excalidraw/issues/1919
|
// https://github.com/excalidraw/excalidraw/issues/1919
|
||||||
if (document.hidden) {
|
if (document.hidden) {
|
||||||
window.addEventListener(
|
return new Promise((resolve, reject) => {
|
||||||
"focus",
|
window.addEventListener(
|
||||||
() =>
|
"focus",
|
||||||
initializeScene(opts).then((_scene) => {
|
() => initializeScene(opts).then(resolve).catch(reject),
|
||||||
opts?.onLateInitialization?.(_scene || scene);
|
{
|
||||||
}),
|
once: true,
|
||||||
{
|
},
|
||||||
once: true,
|
);
|
||||||
},
|
});
|
||||||
);
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
isCollabScene = false;
|
isCollabScene = false;
|
||||||
@@ -146,7 +162,6 @@ const initializeScene = async (opts: {
|
|||||||
// into the remote scene
|
// into the remote scene
|
||||||
opts.resetScene();
|
opts.resetScene();
|
||||||
const scenePromise = opts.initializeSocketClient();
|
const scenePromise = opts.initializeSocketClient();
|
||||||
trackEvent(EVENT_SHARE, "session join");
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const [, roomId, roomKey] = getCollaborationLinkData(
|
const [, roomId, roomKey] = getCollaborationLinkData(
|
||||||
@@ -185,6 +200,8 @@ function ExcalidrawWrapper(props: { collab: CollabAPI }) {
|
|||||||
height: window.innerHeight,
|
height: window.innerHeight,
|
||||||
});
|
});
|
||||||
const [errorMessage, setErrorMessage] = useState("");
|
const [errorMessage, setErrorMessage] = useState("");
|
||||||
|
const currentLangCode = languageDetector.detect() || defaultLang.code;
|
||||||
|
const [langCode, setLangCode] = useState(currentLangCode);
|
||||||
|
|
||||||
useLayoutEffect(() => {
|
useLayoutEffect(() => {
|
||||||
const onResize = () => {
|
const onResize = () => {
|
||||||
@@ -212,19 +229,15 @@ function ExcalidrawWrapper(props: { collab: CollabAPI }) {
|
|||||||
const { collab } = props;
|
const { collab } = props;
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const storageSize = getTotalStorageSize();
|
// Delayed so that the app has a time to load the latest SW
|
||||||
if (storageSize) {
|
setTimeout(() => {
|
||||||
trackEvent(EVENT_LOAD, "storage", "size", storageSize);
|
trackEvent("load", "version", getVersion());
|
||||||
} else {
|
}, VERSION_TIMEOUT);
|
||||||
trackEvent(EVENT_LOAD, "first time");
|
|
||||||
}
|
|
||||||
excalidrawRef.current!.readyPromise.then((excalidrawApi) => {
|
excalidrawRef.current!.readyPromise.then((excalidrawApi) => {
|
||||||
initializeScene({
|
initializeScene({
|
||||||
resetScene: excalidrawApi.resetScene,
|
resetScene: excalidrawApi.resetScene,
|
||||||
initializeSocketClient: collab.initializeSocketClient,
|
initializeSocketClient: collab.initializeSocketClient,
|
||||||
onLateInitialization: (scene) => {
|
|
||||||
initialStatePromiseRef.current.promise.resolve(scene);
|
|
||||||
},
|
|
||||||
}).then((scene) => {
|
}).then((scene) => {
|
||||||
initialStatePromiseRef.current.promise.resolve(scene);
|
initialStatePromiseRef.current.promise.resolve(scene);
|
||||||
});
|
});
|
||||||
@@ -262,6 +275,10 @@ function ExcalidrawWrapper(props: { collab: CollabAPI }) {
|
|||||||
};
|
};
|
||||||
}, [collab.initializeSocketClient]);
|
}, [collab.initializeSocketClient]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
languageDetector.cacheUserLanguage(langCode);
|
||||||
|
}, [langCode]);
|
||||||
|
|
||||||
const onChange = (
|
const onChange = (
|
||||||
elements: readonly ExcalidrawElement[],
|
elements: readonly ExcalidrawElement[],
|
||||||
appState: AppState,
|
appState: AppState,
|
||||||
@@ -297,6 +314,32 @@ function ExcalidrawWrapper(props: { collab: CollabAPI }) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const renderFooter = useCallback(
|
||||||
|
(isMobile: boolean) => {
|
||||||
|
const renderLanguageList = () => (
|
||||||
|
<LanguageList
|
||||||
|
onChange={(langCode) => {
|
||||||
|
setLangCode(langCode);
|
||||||
|
}}
|
||||||
|
languages={languages}
|
||||||
|
floating={!isMobile}
|
||||||
|
currentLangCode={langCode}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
if (isMobile) {
|
||||||
|
return (
|
||||||
|
<fieldset>
|
||||||
|
<legend>{t("labels.language")}</legend>
|
||||||
|
{renderLanguageList()}
|
||||||
|
</fieldset>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return renderLanguageList();
|
||||||
|
},
|
||||||
|
[langCode],
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Excalidraw
|
<Excalidraw
|
||||||
@@ -310,6 +353,8 @@ function ExcalidrawWrapper(props: { collab: CollabAPI }) {
|
|||||||
isCollaborating={collab.isCollaborating}
|
isCollaborating={collab.isCollaborating}
|
||||||
onPointerUpdate={collab.onPointerUpdate}
|
onPointerUpdate={collab.onPointerUpdate}
|
||||||
onExportToBackend={onExportToBackend}
|
onExportToBackend={onExportToBackend}
|
||||||
|
renderFooter={renderFooter}
|
||||||
|
langCode={langCode}
|
||||||
/>
|
/>
|
||||||
{errorMessage && (
|
{errorMessage && (
|
||||||
<ErrorDialog
|
<ErrorDialog
|
||||||
|
|||||||
@@ -1,93 +1,81 @@
|
|||||||
import LanguageDetector from "i18next-browser-languagedetector";
|
import fallbackLangData from "./locales/en.json";
|
||||||
import { EVENT_CHANGE, trackEvent } from "./analytics";
|
|
||||||
|
|
||||||
import fallbackLanguageData from "./locales/en.json";
|
|
||||||
import percentages from "./locales/percentages.json";
|
import percentages from "./locales/percentages.json";
|
||||||
|
|
||||||
const COMPLETION_THRESHOLD_TO_EXCEED = 85;
|
const COMPLETION_THRESHOLD = 85;
|
||||||
|
|
||||||
interface Language {
|
export interface Language {
|
||||||
lng: string;
|
code: string;
|
||||||
label: string;
|
label: string;
|
||||||
rtl?: boolean;
|
rtl?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const allLanguages: Language[] = [
|
export const defaultLang = { code: "en", label: "English" };
|
||||||
{ lng: "ar-SA", label: "العربية", rtl: true },
|
|
||||||
{ lng: "bg-BG", label: "Български" },
|
|
||||||
{ lng: "ca-ES", label: "Catalan" },
|
|
||||||
{ lng: "de-DE", label: "Deutsch" },
|
|
||||||
{ lng: "el-GR", label: "Ελληνικά" },
|
|
||||||
{ lng: "es-ES", label: "Español" },
|
|
||||||
{ lng: "fa-IR", label: "فارسی", rtl: true },
|
|
||||||
{ lng: "fi-FI", label: "Suomi" },
|
|
||||||
{ lng: "fr-FR", label: "Français" },
|
|
||||||
{ lng: "he-IL", label: "עברית", rtl: true },
|
|
||||||
{ lng: "hi-IN", label: "हिन्दी" },
|
|
||||||
{ lng: "hu-HU", label: "Magyar" },
|
|
||||||
{ lng: "id-ID", label: "Bahasa Indonesia" },
|
|
||||||
{ lng: "it-IT", label: "Italiano" },
|
|
||||||
{ lng: "ja-JP", label: "日本語" },
|
|
||||||
{ lng: "ko-KR", label: "한국어" },
|
|
||||||
{ lng: "my-MM", label: "Burmese" },
|
|
||||||
{ lng: "nb-NO", label: "Norsk bokmål" },
|
|
||||||
{ lng: "nl-NL", label: "Nederlands" },
|
|
||||||
{ lng: "nn-NO", label: "Norsk nynorsk" },
|
|
||||||
{ lng: "pl-PL", label: "Polski" },
|
|
||||||
{ lng: "pt-PT", label: "Português" },
|
|
||||||
{ lng: "ro-RO", label: "Română" },
|
|
||||||
{ lng: "ru-RU", label: "Русский" },
|
|
||||||
{ lng: "sk-SK", label: "Slovenčina" },
|
|
||||||
{ lng: "sv-SE", label: "Svenska" },
|
|
||||||
{ lng: "tr-TR", label: "Türkçe" },
|
|
||||||
{ lng: "uk-UA", label: "Українська" },
|
|
||||||
{ lng: "zh-CN", label: "简体中文" },
|
|
||||||
{ lng: "zh-TW", label: "繁體中文" },
|
|
||||||
];
|
|
||||||
|
|
||||||
export const languages: Language[] = [{ lng: "en", label: "English" }]
|
const allLanguages: Language[] = [
|
||||||
.concat(
|
{ code: "ar-SA", label: "العربية", rtl: true },
|
||||||
allLanguages.sort((left, right) => (left.label > right.label ? 1 : -1)),
|
{ code: "bg-BG", label: "Български" },
|
||||||
)
|
{ code: "ca-ES", label: "Català" },
|
||||||
|
{ code: "de-DE", label: "Deutsch" },
|
||||||
|
{ code: "el-GR", label: "Ελληνικά" },
|
||||||
|
{ code: "es-ES", label: "Español" },
|
||||||
|
{ code: "fa-IR", label: "فارسی", rtl: true },
|
||||||
|
{ code: "fi-FI", label: "Suomi" },
|
||||||
|
{ code: "fr-FR", label: "Français" },
|
||||||
|
{ code: "he-IL", label: "עברית", rtl: true },
|
||||||
|
{ code: "hi-IN", label: "हिन्दी" },
|
||||||
|
{ code: "hu-HU", label: "Magyar" },
|
||||||
|
{ code: "id-ID", label: "Bahasa Indonesia" },
|
||||||
|
{ code: "it-IT", label: "Italiano" },
|
||||||
|
{ code: "ja-JP", label: "日本語" },
|
||||||
|
{ code: "ko-KR", label: "한국어" },
|
||||||
|
{ code: "my-MM", label: "Burmese" },
|
||||||
|
{ code: "nb-NO", label: "Norsk bokmål" },
|
||||||
|
{ code: "nl-NL", label: "Nederlands" },
|
||||||
|
{ code: "nn-NO", label: "Norsk nynorsk" },
|
||||||
|
{ code: "pa-IN", label: "ਪੰਜਾਬੀ" },
|
||||||
|
{ code: "pl-PL", label: "Polski" },
|
||||||
|
{ code: "pt-BR", label: "Português Brasileiro" },
|
||||||
|
{ code: "pt-PT", label: "Português" },
|
||||||
|
{ code: "ro-RO", label: "Română" },
|
||||||
|
{ code: "ru-RU", label: "Русский" },
|
||||||
|
{ code: "sk-SK", label: "Slovenčina" },
|
||||||
|
{ code: "sv-SE", label: "Svenska" },
|
||||||
|
{ code: "tr-TR", label: "Türkçe" },
|
||||||
|
{ code: "uk-UA", label: "Українська" },
|
||||||
|
{ code: "zh-CN", label: "简体中文" },
|
||||||
|
{ code: "zh-TW", label: "繁體中文" },
|
||||||
|
].concat([defaultLang]);
|
||||||
|
|
||||||
|
export const languages: Language[] = allLanguages
|
||||||
|
.sort((left, right) => (left.label > right.label ? 1 : -1))
|
||||||
.filter(
|
.filter(
|
||||||
(lang) =>
|
(lang) =>
|
||||||
(percentages as Record<string, number>)[lang.lng] >
|
(percentages as Record<string, number>)[lang.code] >=
|
||||||
COMPLETION_THRESHOLD_TO_EXCEED,
|
COMPLETION_THRESHOLD,
|
||||||
);
|
);
|
||||||
|
|
||||||
let currentLanguage = languages[0];
|
let currentLang: Language = defaultLang;
|
||||||
let currentLanguageData = {};
|
let currentLangData = {};
|
||||||
const fallbackLanguage = languages[0];
|
|
||||||
|
|
||||||
export const setLanguage = async (newLng: string | undefined) => {
|
export const setLanguage = async (lang: Language) => {
|
||||||
currentLanguage =
|
currentLang = lang;
|
||||||
languages.find((language) => language.lng === newLng) || fallbackLanguage;
|
document.documentElement.dir = currentLang.rtl ? "rtl" : "ltr";
|
||||||
|
|
||||||
document.documentElement.dir = currentLanguage.rtl ? "rtl" : "ltr";
|
currentLangData = await import(
|
||||||
|
/* webpackChunkName: "i18n-[request]" */ `./locales/${currentLang.code}.json`
|
||||||
currentLanguageData = await import(
|
|
||||||
/* webpackChunkName: "i18n-[request]" */ `./locales/${currentLanguage.lng}.json`
|
|
||||||
);
|
);
|
||||||
languageDetector.cacheUserLanguage(currentLanguage.lng);
|
|
||||||
trackEvent(EVENT_CHANGE, "language", currentLanguage.lng);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const setLanguageFirstTime = async () => {
|
export const setLanguageFirstTime = async (lang: Language) => {
|
||||||
const newLng: string | undefined = languageDetector.detect();
|
currentLang = lang;
|
||||||
|
document.documentElement.dir = currentLang.rtl ? "rtl" : "ltr";
|
||||||
|
|
||||||
currentLanguage =
|
currentLangData = await import(
|
||||||
languages.find((language) => language.lng === newLng) || fallbackLanguage;
|
/* webpackChunkName: "i18n-[request]" */ `./locales/${currentLang.code}.json`
|
||||||
|
|
||||||
document.documentElement.dir = currentLanguage.rtl ? "rtl" : "ltr";
|
|
||||||
|
|
||||||
currentLanguageData = await import(
|
|
||||||
/* webpackChunkName: "i18n-[request]" */ `./locales/${currentLanguage.lng}.json`
|
|
||||||
);
|
);
|
||||||
|
|
||||||
languageDetector.cacheUserLanguage(currentLanguage.lng);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getLanguage = () => currentLanguage;
|
export const getLanguage = () => currentLang;
|
||||||
|
|
||||||
const findPartsForData = (data: any, parts: string[]) => {
|
const findPartsForData = (data: any, parts: string[]) => {
|
||||||
for (let index = 0; index < parts.length; ++index) {
|
for (let index = 0; index < parts.length; ++index) {
|
||||||
@@ -106,8 +94,8 @@ const findPartsForData = (data: any, parts: string[]) => {
|
|||||||
export const t = (path: string, replacement?: { [key: string]: string }) => {
|
export const t = (path: string, replacement?: { [key: string]: string }) => {
|
||||||
const parts = path.split(".");
|
const parts = path.split(".");
|
||||||
let translation =
|
let translation =
|
||||||
findPartsForData(currentLanguageData, parts) ||
|
findPartsForData(currentLangData, parts) ||
|
||||||
findPartsForData(fallbackLanguageData, parts);
|
findPartsForData(fallbackLangData, parts);
|
||||||
if (translation === undefined) {
|
if (translation === undefined) {
|
||||||
throw new Error(`Can't find translation for ${path}`);
|
throw new Error(`Can't find translation for ${path}`);
|
||||||
}
|
}
|
||||||
@@ -119,12 +107,3 @@ export const t = (path: string, replacement?: { [key: string]: string }) => {
|
|||||||
}
|
}
|
||||||
return translation;
|
return translation;
|
||||||
};
|
};
|
||||||
|
|
||||||
const languageDetector = new LanguageDetector();
|
|
||||||
languageDetector.init({
|
|
||||||
languageUtils: {
|
|
||||||
formatLanguageCode: (lng: string) => lng,
|
|
||||||
isWhitelisted: () => true,
|
|
||||||
},
|
|
||||||
checkWhitelist: false,
|
|
||||||
});
|
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ export const IsMobileProvider = ({
|
|||||||
if (!query.current) {
|
if (!query.current) {
|
||||||
query.current = window.matchMedia
|
query.current = window.matchMedia
|
||||||
? window.matchMedia(
|
? window.matchMedia(
|
||||||
|
// keep up to date with _variables.scss
|
||||||
"(max-width: 640px), (max-height: 500px) and (max-width: 1000px)",
|
"(max-width: 640px), (max-height: 500px) and (max-width: 1000px)",
|
||||||
)
|
)
|
||||||
: (({
|
: (({
|
||||||
|
|||||||
@@ -40,6 +40,7 @@ export const KEYS = {
|
|||||||
D: "d",
|
D: "d",
|
||||||
E: "e",
|
E: "e",
|
||||||
L: "l",
|
L: "l",
|
||||||
|
O: "o",
|
||||||
P: "p",
|
P: "p",
|
||||||
Q: "q",
|
Q: "q",
|
||||||
R: "r",
|
R: "r",
|
||||||
|
|||||||
@@ -3,12 +3,10 @@
|
|||||||
Please do not contribute changes directly to these files, as we manage them with Crowdin. Instead:
|
Please do not contribute changes directly to these files, as we manage them with Crowdin. Instead:
|
||||||
|
|
||||||
- to request a new translation, [open an issue](https://github.com/excalidraw/excalidraw/issues/new/choose).
|
- to request a new translation, [open an issue](https://github.com/excalidraw/excalidraw/issues/new/choose).
|
||||||
- to update existing translations, [edit them on Crowdin](https://crowdin.com/translate/excalidraw/10)
|
- to update existing translations, [edit them on Crowdin](https://crowdin.com/translate/excalidraw/10) and we should have them included in the app soon!
|
||||||
and we should have them included in the app soon!
|
|
||||||
|
|
||||||
## Completion of translation
|
## Completion of translation
|
||||||
|
|
||||||
[percentages.json](./percentages.json) holds a percentage of completion for each language. We generate these
|
[percentages.json](./percentages.json) holds a percentage of completion for each language. We generate these automatically [on build time](./../../.github/workflows/locales-coverage.yml) when a new translation PR appears.
|
||||||
automatically [on build time](./../../.github/workflows/locales-coverage.yml) when a new translation PR appears.
|
|
||||||
|
|
||||||
We only make a language available on the app if it exceeds a certain threshold of completion.
|
We only make a language available on the app if it exceeds a certain threshold of completion.
|
||||||
|
|||||||
@@ -1,17 +1,18 @@
|
|||||||
{
|
{
|
||||||
"labels": {
|
"labels": {
|
||||||
"paste": "لصق",
|
"paste": "لصق",
|
||||||
|
"pasteCharts": "لصق الرسوم البيانية",
|
||||||
"selectAll": "تحديد الكل",
|
"selectAll": "تحديد الكل",
|
||||||
"multiSelect": "إضافة عنصر للتحديد",
|
"multiSelect": "إضافة عنصر للتحديد",
|
||||||
"moveCanvas": "نقل لوح رسم",
|
"moveCanvas": "نقل لوح رسم",
|
||||||
"cut": "",
|
"cut": "قص",
|
||||||
"copy": "نسخ",
|
"copy": "نسخ",
|
||||||
"copyAsPng": "نسخ إلى الحافظة بصيغة PNG",
|
"copyAsPng": "نسخ إلى الحافظة بصيغة PNG",
|
||||||
"copyAsSvg": "نسخ بصيغة SVG",
|
"copyAsSvg": "نسخ إلى الحافظة بصيغة SVG",
|
||||||
"bringForward": "ارقع للأمام",
|
"bringForward": "جلب للأمام",
|
||||||
"sendToBack": "أرسل للخلف",
|
"sendToBack": "أرسل للخلف",
|
||||||
"bringToFront": "أحضر للأمام",
|
"bringToFront": "أحضر للأمام",
|
||||||
"sendBackward": "أنزل للوراء",
|
"sendBackward": "أرسل للخلف",
|
||||||
"delete": "حذف",
|
"delete": "حذف",
|
||||||
"copyStyles": "نسخ النمط",
|
"copyStyles": "نسخ النمط",
|
||||||
"pasteStyles": "لصق النمط",
|
"pasteStyles": "لصق النمط",
|
||||||
@@ -29,11 +30,11 @@
|
|||||||
"edges": "الحواف",
|
"edges": "الحواف",
|
||||||
"sharp": "حادة",
|
"sharp": "حادة",
|
||||||
"round": "دائرية",
|
"round": "دائرية",
|
||||||
"arrowheads": "",
|
"arrowheads": "رؤوس الأسهم",
|
||||||
"arrowhead_none": "",
|
"arrowhead_none": "لا شيء",
|
||||||
"arrowhead_arrow": "",
|
"arrowhead_arrow": "سهم",
|
||||||
"arrowhead_bar": "",
|
"arrowhead_bar": "شريط",
|
||||||
"arrowhead_dot": "",
|
"arrowhead_dot": "نقطة",
|
||||||
"fontSize": "حجم الخط",
|
"fontSize": "حجم الخط",
|
||||||
"fontFamily": "نوع الخط",
|
"fontFamily": "نوع الخط",
|
||||||
"onlySelected": "المحدد فقط",
|
"onlySelected": "المحدد فقط",
|
||||||
@@ -60,7 +61,7 @@
|
|||||||
"architect": "معماري",
|
"architect": "معماري",
|
||||||
"artist": "رسام",
|
"artist": "رسام",
|
||||||
"cartoonist": "كرتوني",
|
"cartoonist": "كرتوني",
|
||||||
"fileTitle": "",
|
"fileTitle": "عنوان الملف",
|
||||||
"colorPicker": "اختيار الألوان",
|
"colorPicker": "اختيار الألوان",
|
||||||
"canvasBackground": "خلفية اللوحة",
|
"canvasBackground": "خلفية اللوحة",
|
||||||
"drawingCanvas": "لوحة الرسم",
|
"drawingCanvas": "لوحة الرسم",
|
||||||
@@ -69,19 +70,18 @@
|
|||||||
"language": "اللغة",
|
"language": "اللغة",
|
||||||
"createRoom": "مشاركة الجلسة مباشرة",
|
"createRoom": "مشاركة الجلسة مباشرة",
|
||||||
"duplicateSelection": "تكرار",
|
"duplicateSelection": "تكرار",
|
||||||
"untitled": "",
|
"untitled": "غير معنون",
|
||||||
"name": "الاسم",
|
"name": "الاسم",
|
||||||
"yourName": "اسمك",
|
"yourName": "اسمك",
|
||||||
"madeWithExcalidraw": "مصنوعة بواسطة Excalidraw",
|
"madeWithExcalidraw": "مصنوعة بواسطة Excalidraw",
|
||||||
"group": "تحديد مجموعة",
|
"group": "تحديد مجموعة",
|
||||||
"ungroup": "إلغاء تحديد مجموعة",
|
"ungroup": "إلغاء تحديد مجموعة",
|
||||||
"collaborators": "المتعاونون",
|
"collaborators": "المتعاونون",
|
||||||
"toggleGridMode": "التبديل إلى وضع الشبكة",
|
"gridMode": "وضع الشبكة",
|
||||||
"toggleStats": "",
|
|
||||||
"addToLibrary": "أضف إلى المكتبة",
|
"addToLibrary": "أضف إلى المكتبة",
|
||||||
"removeFromLibrary": "حذف من المكتبة",
|
"removeFromLibrary": "حذف من المكتبة",
|
||||||
"libraryLoadingMessage": "جارٍ تحميل المكتبة...",
|
"libraryLoadingMessage": "جارٍ تحميل المكتبة...",
|
||||||
"libraries": "",
|
"libraries": "تصفح المكتبات",
|
||||||
"loadingScene": "جاري تحميل المشهد...",
|
"loadingScene": "جاري تحميل المشهد...",
|
||||||
"align": "محاذاة",
|
"align": "محاذاة",
|
||||||
"alignTop": "محاذاة إلى اﻷعلى",
|
"alignTop": "محاذاة إلى اﻷعلى",
|
||||||
@@ -118,9 +118,10 @@
|
|||||||
"redo": "إعادة تنفيذ",
|
"redo": "إعادة تنفيذ",
|
||||||
"roomDialog": "بدء المشاركة الحية",
|
"roomDialog": "بدء المشاركة الحية",
|
||||||
"createNewRoom": "إنشاء غرفة جديدة",
|
"createNewRoom": "إنشاء غرفة جديدة",
|
||||||
"toggleFullScreen": "التبديل لوضع ملء الشاشة",
|
"fullScreen": "شاشة كاملة",
|
||||||
"toggleDarkMode": "تبديل الوضع الليلي",
|
"darkMode": "الوضع المظلم",
|
||||||
"toggleZenMode": "تبديل الوضع الليلي",
|
"lightMode": "الوضع المضيء",
|
||||||
|
"zenMode": "وضع التأمل",
|
||||||
"exitZenMode": "إلغاء الوضع الليلى"
|
"exitZenMode": "إلغاء الوضع الليلى"
|
||||||
},
|
},
|
||||||
"alerts": {
|
"alerts": {
|
||||||
@@ -136,7 +137,7 @@
|
|||||||
"loadSceneOverridePrompt": "تحميل الرسم الخارجي سيحل محل المحتوى الموجود لديك. هل ترغب في المتابعة؟",
|
"loadSceneOverridePrompt": "تحميل الرسم الخارجي سيحل محل المحتوى الموجود لديك. هل ترغب في المتابعة؟",
|
||||||
"errorLoadingLibrary": "حصل خطأ أثناء تحميل مكتبة الطرف الثالث.",
|
"errorLoadingLibrary": "حصل خطأ أثناء تحميل مكتبة الطرف الثالث.",
|
||||||
"confirmAddLibrary": "هذا سيضيف {{numShapes}} شكل إلى مكتبتك. هل أنت متأكد؟",
|
"confirmAddLibrary": "هذا سيضيف {{numShapes}} شكل إلى مكتبتك. هل أنت متأكد؟",
|
||||||
"imageDoesNotContainScene": "لا يحتوي ملف الصورة على بيانات المشهد. هل قمت بتمكين هذا أثناء التصدير؟",
|
"imageDoesNotContainScene": "استيراد الصور غير مدعوم في الوقت الراهن.\n\nهل تريد استيراد مشهد؟ لا يبدو أن هذه الصورة تحتوي على أي بيانات مشهد. هل قمت بسماح هذا أثناء التصدير؟",
|
||||||
"cannotRestoreFromImage": "تعذر استعادة المشهد من ملف الصورة"
|
"cannotRestoreFromImage": "تعذر استعادة المشهد من ملف الصورة"
|
||||||
},
|
},
|
||||||
"toolBar": {
|
"toolBar": {
|
||||||
@@ -161,6 +162,7 @@
|
|||||||
"freeDraw": "انقر واسحب، افرج عند الانتهاء",
|
"freeDraw": "انقر واسحب، افرج عند الانتهاء",
|
||||||
"text": "نصيحة: يمكنك أيضًا إضافة نص بالنقر المزدوج في أي مكان بأداة الاختيار",
|
"text": "نصيحة: يمكنك أيضًا إضافة نص بالنقر المزدوج في أي مكان بأداة الاختيار",
|
||||||
"linearElementMulti": "انقر فوق النقطة الأخيرة أو اضغط على Esc أو Enter للإنهاء",
|
"linearElementMulti": "انقر فوق النقطة الأخيرة أو اضغط على Esc أو Enter للإنهاء",
|
||||||
|
"lockAngle": "يمكنك تقييد الزاوية بالضغط على SHIFT",
|
||||||
"resize": "يمكنك تقييد النسب بالضغط على SHIFT أثناء تغيير الحجم،\nاضغط على ALT لتغيير الحجم من المركز",
|
"resize": "يمكنك تقييد النسب بالضغط على SHIFT أثناء تغيير الحجم،\nاضغط على ALT لتغيير الحجم من المركز",
|
||||||
"rotate": "يمكنك تقييد الزوايا من خلال الضغط على SHIFT أثناء الدوران",
|
"rotate": "يمكنك تقييد الزوايا من خلال الضغط على SHIFT أثناء الدوران",
|
||||||
"lineEditor_info": "انقر نقراً مزدوجاً أو اضغط Enter لتعديل النقاط",
|
"lineEditor_info": "انقر نقراً مزدوجاً أو اضغط Enter لتعديل النقاط",
|
||||||
@@ -197,38 +199,44 @@
|
|||||||
"errorDialog": {
|
"errorDialog": {
|
||||||
"title": "خطأ"
|
"title": "خطأ"
|
||||||
},
|
},
|
||||||
"shortcutsDialog": {
|
"helpDialog": {
|
||||||
"title": "اختصارات لوحة المفاتيح",
|
"blog": "",
|
||||||
"shapes": "الأشكال",
|
"click": "",
|
||||||
"or": "أو",
|
"curvedArrow": "",
|
||||||
"click": "انقر فوق",
|
"curvedLine": "",
|
||||||
"drag": "اسحب",
|
"documentation": "",
|
||||||
"curvedArrow": "سهم منحنى",
|
"drag": "",
|
||||||
"curvedLine": "خط منحنى",
|
"editor": "",
|
||||||
"editor": "المحرر",
|
"github": "",
|
||||||
"view": "المشهد",
|
"howto": "",
|
||||||
"blog": "اقرأ مدونتنا",
|
"or": "",
|
||||||
"howto": "اتبع دليلنا",
|
"preventBinding": "",
|
||||||
"github": "عثرت على مشكلة؟ إرسال",
|
"shapes": "",
|
||||||
"textNewLine": "إضافة سطر جديد (نص)",
|
"shortcuts": "",
|
||||||
"textFinish": "الانتهاء من تحرير (النص)",
|
"textFinish": "",
|
||||||
"zoomToFit": "تكبير لتلائم جميع العناصر",
|
"textNewLine": "",
|
||||||
"zoomToSelection": "",
|
"title": "",
|
||||||
"preventBinding": "منع ربط السهم"
|
"view": "",
|
||||||
|
"zoomToFit": "",
|
||||||
|
"zoomToSelection": ""
|
||||||
},
|
},
|
||||||
"encrypted": {
|
"encrypted": {
|
||||||
"tooltip": "رسوماتك مشفرة من النهاية إلى النهاية حتى أن خوادم Excalidraw لن تراها أبدا."
|
"tooltip": "رسوماتك مشفرة من النهاية إلى النهاية حتى أن خوادم Excalidraw لن تراها أبدا."
|
||||||
},
|
},
|
||||||
"stats": {
|
"stats": {
|
||||||
"angle": "",
|
"angle": "الزاوية",
|
||||||
"element": "",
|
"element": "عنصر",
|
||||||
"elements": "",
|
"elements": "العناصر",
|
||||||
"height": "",
|
"height": "الارتفاع",
|
||||||
"scene": "",
|
"scene": "المشهد",
|
||||||
"selected": "",
|
"selected": "المحدد",
|
||||||
"storage": "",
|
"storage": "التخزين",
|
||||||
"title": "",
|
"title": "إحصائيات للمهووسين",
|
||||||
"total": "",
|
"total": "المجموع",
|
||||||
"width": ""
|
"width": "العرض"
|
||||||
|
},
|
||||||
|
"toast": {
|
||||||
|
"copyStyles": "",
|
||||||
|
"copyToClipboardAsPng": ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
{
|
{
|
||||||
"labels": {
|
"labels": {
|
||||||
"paste": "Постави",
|
"paste": "Постави",
|
||||||
|
"pasteCharts": "Постави графики",
|
||||||
"selectAll": "Маркирай всичко",
|
"selectAll": "Маркирай всичко",
|
||||||
"multiSelect": "",
|
"multiSelect": "Добави елемент към селекция",
|
||||||
"moveCanvas": "",
|
"moveCanvas": "Премести платно",
|
||||||
"cut": "",
|
"cut": "Изрежи",
|
||||||
"copy": "Копирай",
|
"copy": "Копирай",
|
||||||
"copyAsPng": "Копиране в клипборда",
|
"copyAsPng": "Копиране в клипборда",
|
||||||
"copyAsSvg": "Копиране в клипборда",
|
"copyAsSvg": "Копиране в клипборда",
|
||||||
@@ -19,28 +20,28 @@
|
|||||||
"background": "Фон",
|
"background": "Фон",
|
||||||
"fill": "Наситеност",
|
"fill": "Наситеност",
|
||||||
"strokeWidth": "Ширина на щриха",
|
"strokeWidth": "Ширина на щриха",
|
||||||
"strokeStyle": "",
|
"strokeStyle": "Стил на линия",
|
||||||
"strokeStyle_solid": "",
|
"strokeStyle_solid": "Плътен",
|
||||||
"strokeStyle_dashed": "",
|
"strokeStyle_dashed": "Пунктир",
|
||||||
"strokeStyle_dotted": "",
|
"strokeStyle_dotted": "Пунктирано",
|
||||||
"sloppiness": "Небрежност",
|
"sloppiness": "Небрежност",
|
||||||
"opacity": "Непрозрачност",
|
"opacity": "Прозрачност",
|
||||||
"textAlign": "Подравняване на текста",
|
"textAlign": "Подравняване на текста",
|
||||||
"edges": "",
|
"edges": "Крайща",
|
||||||
"sharp": "",
|
"sharp": "Остър",
|
||||||
"round": "",
|
"round": "Закръглено",
|
||||||
"arrowheads": "",
|
"arrowheads": "Стрелки",
|
||||||
"arrowhead_none": "",
|
"arrowhead_none": "Без",
|
||||||
"arrowhead_arrow": "",
|
"arrowhead_arrow": "Стрелка",
|
||||||
"arrowhead_bar": "",
|
"arrowhead_bar": "Връх на стрелката",
|
||||||
"arrowhead_dot": "",
|
"arrowhead_dot": "Точка",
|
||||||
"fontSize": "Размер на шрифта",
|
"fontSize": "Размер на шрифта",
|
||||||
"fontFamily": "Семейство шрифтове",
|
"fontFamily": "Семейство шрифтове",
|
||||||
"onlySelected": "Само избраното",
|
"onlySelected": "Само избраното",
|
||||||
"withBackground": "С фон",
|
"withBackground": "С фон",
|
||||||
"exportEmbedScene": "",
|
"exportEmbedScene": "Вгради сцената във файл",
|
||||||
"exportEmbedScene_details": "",
|
"exportEmbedScene_details": "Данните от сцената ще бъдат екпортирани в PNG/SVG файл, за да може сцената да бъде възстановена от него.\nТова ще увеличи размера на файла.",
|
||||||
"addWatermark": "",
|
"addWatermark": "Добави \"Направено с Excalidraw\"",
|
||||||
"handDrawn": "Нарисувано на ръка",
|
"handDrawn": "Нарисувано на ръка",
|
||||||
"normal": "Нормален",
|
"normal": "Нормален",
|
||||||
"code": "Код",
|
"code": "Код",
|
||||||
@@ -50,7 +51,7 @@
|
|||||||
"veryLarge": "Много голям",
|
"veryLarge": "Много голям",
|
||||||
"solid": "Солиден",
|
"solid": "Солиден",
|
||||||
"hachure": "Хералдика",
|
"hachure": "Хералдика",
|
||||||
"crossHatch": "",
|
"crossHatch": "Двойно-пресечено",
|
||||||
"thin": "Тънък",
|
"thin": "Тънък",
|
||||||
"bold": "Ясно очертан",
|
"bold": "Ясно очертан",
|
||||||
"left": "Ляво",
|
"left": "Ляво",
|
||||||
@@ -60,7 +61,7 @@
|
|||||||
"architect": "Архитект",
|
"architect": "Архитект",
|
||||||
"artist": "Художник",
|
"artist": "Художник",
|
||||||
"cartoonist": "Карикатурист",
|
"cartoonist": "Карикатурист",
|
||||||
"fileTitle": "",
|
"fileTitle": "Заглавие на файл",
|
||||||
"colorPicker": "Избор на цвят",
|
"colorPicker": "Избор на цвят",
|
||||||
"canvasBackground": "Фон на платно",
|
"canvasBackground": "Фон на платно",
|
||||||
"drawingCanvas": "Платно за рисуване",
|
"drawingCanvas": "Платно за рисуване",
|
||||||
@@ -69,29 +70,28 @@
|
|||||||
"language": "Език",
|
"language": "Език",
|
||||||
"createRoom": "Споделете сесия за сътрудничество на живо",
|
"createRoom": "Споделете сесия за сътрудничество на живо",
|
||||||
"duplicateSelection": "Дублирай",
|
"duplicateSelection": "Дублирай",
|
||||||
"untitled": "",
|
"untitled": "Неозаглавено",
|
||||||
"name": "Име",
|
"name": "Име",
|
||||||
"yourName": "",
|
"yourName": "Вашето име",
|
||||||
"madeWithExcalidraw": "",
|
"madeWithExcalidraw": "Направено с Excalidraw",
|
||||||
"group": "",
|
"group": "Групирай селекцията",
|
||||||
"ungroup": "",
|
"ungroup": "Спри групирането на селекцията",
|
||||||
"collaborators": "",
|
"collaborators": "Сътрудници",
|
||||||
"toggleGridMode": "",
|
"gridMode": "Решетъчен режим",
|
||||||
"toggleStats": "",
|
"addToLibrary": "Добавяне към библиотеката",
|
||||||
"addToLibrary": "",
|
"removeFromLibrary": "Премахване от библиотеката",
|
||||||
"removeFromLibrary": "",
|
"libraryLoadingMessage": "Зареждане на библиотеката...",
|
||||||
"libraryLoadingMessage": "",
|
"libraries": "Разглеждане на библиотеките",
|
||||||
"libraries": "",
|
"loadingScene": "Зареждане на сцена...",
|
||||||
"loadingScene": "",
|
"align": "Подравняване",
|
||||||
"align": "",
|
"alignTop": "Подравняване отгоре",
|
||||||
"alignTop": "",
|
"alignBottom": "Подравняване отдолу",
|
||||||
"alignBottom": "",
|
"alignLeft": "Подравняване отляво",
|
||||||
"alignLeft": "",
|
"alignRight": "Подравняване отдясно",
|
||||||
"alignRight": "",
|
"centerVertically": "Центрирай вертикално",
|
||||||
"centerVertically": "",
|
"centerHorizontally": "Центрирай хоризонтално",
|
||||||
"centerHorizontally": "",
|
"distributeHorizontally": "Разпредели хоризонтално",
|
||||||
"distributeHorizontally": "",
|
"distributeVertically": "Разпредели вертикално"
|
||||||
"distributeVertically": ""
|
|
||||||
},
|
},
|
||||||
"buttons": {
|
"buttons": {
|
||||||
"clearReset": "Нулиране на платно",
|
"clearReset": "Нулиране на платно",
|
||||||
@@ -100,7 +100,7 @@
|
|||||||
"exportToSvg": "Изнасяне в SVG",
|
"exportToSvg": "Изнасяне в SVG",
|
||||||
"copyToClipboard": "Копиране в клипборда",
|
"copyToClipboard": "Копиране в клипборда",
|
||||||
"copyPngToClipboard": "Копирай PNG в клипборда",
|
"copyPngToClipboard": "Копирай PNG в клипборда",
|
||||||
"scale": "",
|
"scale": "Мащаб",
|
||||||
"save": "Запази",
|
"save": "Запази",
|
||||||
"saveAs": "Запиши като",
|
"saveAs": "Запиши като",
|
||||||
"load": "Зареждане",
|
"load": "Зареждане",
|
||||||
@@ -118,37 +118,38 @@
|
|||||||
"redo": "Повтори",
|
"redo": "Повтори",
|
||||||
"roomDialog": "Започнете сътрудничество на живо",
|
"roomDialog": "Започнете сътрудничество на живо",
|
||||||
"createNewRoom": "Създай нова стая",
|
"createNewRoom": "Създай нова стая",
|
||||||
"toggleFullScreen": "Превключване на цял екран",
|
"fullScreen": "На цял екран",
|
||||||
"toggleDarkMode": "",
|
"darkMode": "Тъмен режим",
|
||||||
"toggleZenMode": "",
|
"lightMode": "Светъл режим",
|
||||||
"exitZenMode": ""
|
"zenMode": "Режим Zen",
|
||||||
|
"exitZenMode": "Спиране на Zen режим"
|
||||||
},
|
},
|
||||||
"alerts": {
|
"alerts": {
|
||||||
"clearReset": "Това ще изчисти цялото платно. Сигурни ли сте?",
|
"clearReset": "Това ще изчисти цялото платно. Сигурни ли сте?",
|
||||||
"couldNotCreateShareableLink": "Връзката не може да бъде създадена.",
|
"couldNotCreateShareableLink": "Връзката не може да бъде създадена.",
|
||||||
"couldNotCreateShareableLinkTooBig": "",
|
"couldNotCreateShareableLinkTooBig": "Не може да се създаде връзка за споделяне: сцената е твърде голяма",
|
||||||
"couldNotLoadInvalidFile": "Невалиден файл не може да се зареди",
|
"couldNotLoadInvalidFile": "Невалиден файл не може да се зареди",
|
||||||
"importBackendFailed": "Импортирането от бекенд не беше успешно.",
|
"importBackendFailed": "Импортирането от бекенд не беше успешно.",
|
||||||
"cannotExportEmptyCanvas": "Не може да се експортира празно платно.",
|
"cannotExportEmptyCanvas": "Не може да се експортира празно платно.",
|
||||||
"couldNotCopyToClipboard": "Неуспешно копиране в клипборда. Опитайте да използвате браузъра Chrome.",
|
"couldNotCopyToClipboard": "Неуспешно копиране в клипборда. Опитайте да използвате браузъра Chrome.",
|
||||||
"decryptFailed": "Данните не можаха да се дешифрират.",
|
"decryptFailed": "Данните не можаха да се дешифрират.",
|
||||||
"uploadedSecurly": "Качването е защитено с криптиране от край до край, което означава, че сървърът Excalidraw и трети страни не могат да четат съдържанието.",
|
"uploadedSecurly": "Качването е защитено с криптиране от край до край, което означава, че сървърът Excalidraw и трети страни не могат да четат съдържанието.",
|
||||||
"loadSceneOverridePrompt": "",
|
"loadSceneOverridePrompt": "Зареждането на външна рисунка ще презапише настоящото ви съдържание. Желаете ли да продължите?",
|
||||||
"errorLoadingLibrary": "",
|
"errorLoadingLibrary": "Възникна грешка при зареждането на външна библиотека.",
|
||||||
"confirmAddLibrary": "",
|
"confirmAddLibrary": "Ще се добавят {{numShapes}} фигура(и) във вашата библиотека. Сигурни ли сте?",
|
||||||
"imageDoesNotContainScene": "",
|
"imageDoesNotContainScene": "Импортирането на картинки не се поддържва в момента.\n\nИскате да импортнете сцена? Тази картинка не съдържа данни от сцена. Разрешили ли сте последното при експортирането?",
|
||||||
"cannotRestoreFromImage": ""
|
"cannotRestoreFromImage": "Не може да бъде възстановена сцена от този файл"
|
||||||
},
|
},
|
||||||
"toolBar": {
|
"toolBar": {
|
||||||
"selection": "Селекция",
|
"selection": "Селекция",
|
||||||
"draw": "",
|
"draw": "Рисуване",
|
||||||
"rectangle": "Правоъгълник",
|
"rectangle": "Правоъгълник",
|
||||||
"diamond": "Диамант",
|
"diamond": "Диамант",
|
||||||
"ellipse": "Елипс",
|
"ellipse": "Елипс",
|
||||||
"arrow": "Стрелка",
|
"arrow": "Стрелка",
|
||||||
"line": "Линия",
|
"line": "Линия",
|
||||||
"text": "Текст",
|
"text": "Текст",
|
||||||
"library": "",
|
"library": "Библиотека",
|
||||||
"lock": "Поддържайте избрания инструмент активен след рисуване"
|
"lock": "Поддържайте избрания инструмент активен след рисуване"
|
||||||
},
|
},
|
||||||
"headings": {
|
"headings": {
|
||||||
@@ -158,19 +159,20 @@
|
|||||||
},
|
},
|
||||||
"hints": {
|
"hints": {
|
||||||
"linearElement": "Кликнете, за да стартирате няколко точки, плъзнете за една линия",
|
"linearElement": "Кликнете, за да стартирате няколко точки, плъзнете за една линия",
|
||||||
"freeDraw": "",
|
"freeDraw": "Натиснете и влачете, пуснете като сте готови",
|
||||||
"text": "",
|
"text": "Подсказка: Можете също да добавите текст като натиснете някъде два път с инструмента за селекция",
|
||||||
"linearElementMulti": "Кликнете върху последната точка или натиснете Escape или Enter, за да завършите",
|
"linearElementMulti": "Кликнете върху последната точка или натиснете Escape или Enter, за да завършите",
|
||||||
"resize": "",
|
"lockAngle": "Можете да ограничите ъгъла, като задържите SHIFT",
|
||||||
|
"resize": "Може да ограничите при преоразмеряване като задържите SHIFT,\nзадръжте ALT за преоразмерите през центъра",
|
||||||
"rotate": "Можете да ограничите ъглите, като държите SHIFT, докато се въртите",
|
"rotate": "Можете да ограничите ъглите, като държите SHIFT, докато се въртите",
|
||||||
"lineEditor_info": "",
|
"lineEditor_info": "Кликнете два пъти или натиснете Enter за да промените точките",
|
||||||
"lineEditor_pointSelected": "",
|
"lineEditor_pointSelected": "Натиснете Delete за да изтриете точка, CtrlOrCmd+D за дуплициране, или извлачете за да преместите",
|
||||||
"lineEditor_nothingSelected": ""
|
"lineEditor_nothingSelected": "Изберете точка за местене или изтриване, или пък задръжте Alt и натиснете за да добавите нови точки"
|
||||||
},
|
},
|
||||||
"canvasError": {
|
"canvasError": {
|
||||||
"cannotShowPreview": "",
|
"cannotShowPreview": "Невъзможност за показване на preview",
|
||||||
"canvasTooBig": "",
|
"canvasTooBig": "Платното е твърде голямо.",
|
||||||
"canvasTooBigTip": ""
|
"canvasTooBigTip": "Подсказка: пробвайте да приближите далечните елементи по-близко."
|
||||||
},
|
},
|
||||||
"errorSplash": {
|
"errorSplash": {
|
||||||
"headingMain_pre": "Среща грешка. Опитайте ",
|
"headingMain_pre": "Среща грешка. Опитайте ",
|
||||||
@@ -192,43 +194,49 @@
|
|||||||
"button_stopSession": "Стоп на сесията",
|
"button_stopSession": "Стоп на сесията",
|
||||||
"desc_inProgressIntro": "Сесията за сътрудничество на живо е в ход.",
|
"desc_inProgressIntro": "Сесията за сътрудничество на живо е в ход.",
|
||||||
"desc_shareLink": "Споделете тази връзка с всеки, с когото искате да си сътрудничите:",
|
"desc_shareLink": "Споделете тази връзка с всеки, с когото искате да си сътрудничите:",
|
||||||
"desc_exitSession": ""
|
"desc_exitSession": "Спирането на сесията ще ви изключи от стаята, но ще можете да продължите да работите със сцената, локално. Имайте предвид, че това няма да засегне други хора и те все още ще могат да си сътрудничат с тяхната версия."
|
||||||
},
|
},
|
||||||
"errorDialog": {
|
"errorDialog": {
|
||||||
"title": "Грешка"
|
"title": "Грешка"
|
||||||
},
|
},
|
||||||
"shortcutsDialog": {
|
"helpDialog": {
|
||||||
"title": "Клавиши за бърз достъп",
|
"blog": "",
|
||||||
"shapes": "Фигури",
|
"click": "",
|
||||||
"or": "или",
|
"curvedArrow": "",
|
||||||
"click": "клик",
|
"curvedLine": "",
|
||||||
"drag": "плъзнете",
|
"documentation": "",
|
||||||
"curvedArrow": "Извита стрелка",
|
"drag": "",
|
||||||
"curvedLine": "Извита линия",
|
"editor": "",
|
||||||
"editor": "Редактор",
|
"github": "",
|
||||||
"view": "Преглед",
|
"howto": "",
|
||||||
"blog": "Прочетете нашия блог",
|
"or": "",
|
||||||
"howto": "Следвайте нашите ръководства",
|
"preventBinding": "",
|
||||||
"github": "Намерихте проблем? Изпратете",
|
"shapes": "",
|
||||||
"textNewLine": "Добавяне на нов ред (текст)",
|
"shortcuts": "",
|
||||||
"textFinish": "Завършете редактиране (текст)",
|
"textFinish": "",
|
||||||
|
"textNewLine": "",
|
||||||
|
"title": "",
|
||||||
|
"view": "",
|
||||||
"zoomToFit": "",
|
"zoomToFit": "",
|
||||||
"zoomToSelection": "",
|
"zoomToSelection": ""
|
||||||
"preventBinding": ""
|
|
||||||
},
|
},
|
||||||
"encrypted": {
|
"encrypted": {
|
||||||
"tooltip": ""
|
"tooltip": "Вашите рисунки са криптирани от край до край, така че сървърите на Excalidraw няма да могат да ги виждат."
|
||||||
},
|
},
|
||||||
"stats": {
|
"stats": {
|
||||||
"angle": "",
|
"angle": "Ъгъл",
|
||||||
"element": "",
|
"element": "Елемент",
|
||||||
"elements": "",
|
"elements": "Елементи",
|
||||||
"height": "",
|
"height": "Височина",
|
||||||
"scene": "",
|
"scene": "Сцена",
|
||||||
"selected": "",
|
"selected": "Селектирано",
|
||||||
"storage": "",
|
"storage": "Съхранение на данни",
|
||||||
"title": "",
|
"title": "Статистика за хакери",
|
||||||
"total": "",
|
"total": "Общо",
|
||||||
"width": ""
|
"width": "Широчина"
|
||||||
|
},
|
||||||
|
"toast": {
|
||||||
|
"copyStyles": "",
|
||||||
|
"copyToClipboardAsPng": ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
{
|
{
|
||||||
"labels": {
|
"labels": {
|
||||||
"paste": "Enganxar",
|
"paste": "Enganxar",
|
||||||
|
"pasteCharts": "Enganxar diagrames",
|
||||||
"selectAll": "Seleccionar tot",
|
"selectAll": "Seleccionar tot",
|
||||||
"multiSelect": "Afegir element a la selecció",
|
"multiSelect": "Afegir element a la selecció",
|
||||||
"moveCanvas": "Moure el llenç",
|
"moveCanvas": "Moure el llenç",
|
||||||
"cut": "",
|
"cut": "Tallar",
|
||||||
"copy": "Copiar",
|
"copy": "Copiar",
|
||||||
"copyAsPng": "Copiar al porta-retalls com a PNG",
|
"copyAsPng": "Copiar al porta-retalls com a PNG",
|
||||||
"copyAsSvg": "Copiar al porta-retalls com a SVG",
|
"copyAsSvg": "Copiar al porta-retalls com a SVG",
|
||||||
@@ -29,17 +30,17 @@
|
|||||||
"edges": "Vores",
|
"edges": "Vores",
|
||||||
"sharp": "Agut",
|
"sharp": "Agut",
|
||||||
"round": "Arrodonit",
|
"round": "Arrodonit",
|
||||||
"arrowheads": "",
|
"arrowheads": "Puntes de fletxa",
|
||||||
"arrowhead_none": "",
|
"arrowhead_none": "Cap",
|
||||||
"arrowhead_arrow": "",
|
"arrowhead_arrow": "Fletxa",
|
||||||
"arrowhead_bar": "",
|
"arrowhead_bar": "Barra",
|
||||||
"arrowhead_dot": "",
|
"arrowhead_dot": "Punt",
|
||||||
"fontSize": "Mida de lletra",
|
"fontSize": "Mida de lletra",
|
||||||
"fontFamily": "Tipus de lletra",
|
"fontFamily": "Tipus de lletra",
|
||||||
"onlySelected": "Només seleccionats",
|
"onlySelected": "Només seleccionats",
|
||||||
"withBackground": "Amb fons",
|
"withBackground": "Amb fons",
|
||||||
"exportEmbedScene": "",
|
"exportEmbedScene": "Incrustar escena al fitxer exportat",
|
||||||
"exportEmbedScene_details": "",
|
"exportEmbedScene_details": "Les dades de l’escena es desaran al fitxer PNG/SVG de manera que es pugui restaurar l’escena.\nAugmentarà la mida del fitxer exportat.",
|
||||||
"addWatermark": "Afegir \"Fet amb Excalidraw\"",
|
"addWatermark": "Afegir \"Fet amb Excalidraw\"",
|
||||||
"handDrawn": "Dibuixat a mà",
|
"handDrawn": "Dibuixat a mà",
|
||||||
"normal": "Normal",
|
"normal": "Normal",
|
||||||
@@ -60,38 +61,37 @@
|
|||||||
"architect": "Arquitecte",
|
"architect": "Arquitecte",
|
||||||
"artist": "Artista",
|
"artist": "Artista",
|
||||||
"cartoonist": "Dibuixant",
|
"cartoonist": "Dibuixant",
|
||||||
"fileTitle": "",
|
"fileTitle": "Títol del fitxer",
|
||||||
"colorPicker": "Selector de colors",
|
"colorPicker": "Selector de colors",
|
||||||
"canvasBackground": "Fons de la tela",
|
"canvasBackground": "Fons del llenç",
|
||||||
"drawingCanvas": "Tela de dibuix",
|
"drawingCanvas": "Llenç de dibuix",
|
||||||
"layers": "Capes",
|
"layers": "Capes",
|
||||||
"actions": "Accions",
|
"actions": "Accions",
|
||||||
"language": "Llengua",
|
"language": "Llengua",
|
||||||
"createRoom": "Compartir una sessió de col·laboració en directe",
|
"createRoom": "Compartir una sessió de col·laboració en directe",
|
||||||
"duplicateSelection": "Duplicar",
|
"duplicateSelection": "Duplicar",
|
||||||
"untitled": "",
|
"untitled": "Sense títol",
|
||||||
"name": "Nom",
|
"name": "Nom",
|
||||||
"yourName": "El teu nom",
|
"yourName": "El teu nom",
|
||||||
"madeWithExcalidraw": "Fet amb Excalidraw",
|
"madeWithExcalidraw": "Fet amb Excalidraw",
|
||||||
"group": "Agrupar la selecció",
|
"group": "Agrupar la selecció",
|
||||||
"ungroup": "Desagrupar la selecció",
|
"ungroup": "Desagrupar la selecció",
|
||||||
"collaborators": "Col·laboradors",
|
"collaborators": "Col·laboradors",
|
||||||
"toggleGridMode": "Commutar línies de graella",
|
"gridMode": "Mode quadrícula",
|
||||||
"toggleStats": "",
|
|
||||||
"addToLibrary": "Afegir a la biblioteca",
|
"addToLibrary": "Afegir a la biblioteca",
|
||||||
"removeFromLibrary": "Eliminar de la biblioteca",
|
"removeFromLibrary": "Eliminar de la biblioteca",
|
||||||
"libraryLoadingMessage": "Carregant la biblioteca...",
|
"libraryLoadingMessage": "Carregant la biblioteca...",
|
||||||
"libraries": "",
|
"libraries": "Explorar biblioteques",
|
||||||
"loadingScene": "Carregant escena ...",
|
"loadingScene": "Carregant escena...",
|
||||||
"align": "",
|
"align": "Alinear",
|
||||||
"alignTop": "",
|
"alignTop": "Alinear a dalt",
|
||||||
"alignBottom": "",
|
"alignBottom": "Alinear a baix",
|
||||||
"alignLeft": "",
|
"alignLeft": "Alinear a l’esquerra",
|
||||||
"alignRight": "",
|
"alignRight": "Alinear a la dreta",
|
||||||
"centerVertically": "",
|
"centerVertically": "Centrar verticalment",
|
||||||
"centerHorizontally": "",
|
"centerHorizontally": "Centrar horitzontalment",
|
||||||
"distributeHorizontally": "",
|
"distributeHorizontally": "Distribuir horitzontalment",
|
||||||
"distributeVertically": ""
|
"distributeVertically": "Distribuir verticalment"
|
||||||
},
|
},
|
||||||
"buttons": {
|
"buttons": {
|
||||||
"clearReset": "Netejar el llenç",
|
"clearReset": "Netejar el llenç",
|
||||||
@@ -118,15 +118,16 @@
|
|||||||
"redo": "Refer",
|
"redo": "Refer",
|
||||||
"roomDialog": "Començar col·laboració en directe",
|
"roomDialog": "Començar col·laboració en directe",
|
||||||
"createNewRoom": "Crear sala nova",
|
"createNewRoom": "Crear sala nova",
|
||||||
"toggleFullScreen": "Commutar pantalla completa",
|
"fullScreen": "Pantalla completa",
|
||||||
"toggleDarkMode": "Commutar modo fosc",
|
"darkMode": "Mode fosc",
|
||||||
"toggleZenMode": "Commutar modo zen",
|
"lightMode": "Mode clar",
|
||||||
|
"zenMode": "Mode Zen",
|
||||||
"exitZenMode": "Sortir de modo zen"
|
"exitZenMode": "Sortir de modo zen"
|
||||||
},
|
},
|
||||||
"alerts": {
|
"alerts": {
|
||||||
"clearReset": "Tot el llenç s'esborrarà. Estàs segur?",
|
"clearReset": "Tot el llenç s'esborrarà. Estàs segur?",
|
||||||
"couldNotCreateShareableLink": "No s'ha pogut crear un enllaç per compartir.",
|
"couldNotCreateShareableLink": "No s'ha pogut crear un enllaç per compartir.",
|
||||||
"couldNotCreateShareableLinkTooBig": "",
|
"couldNotCreateShareableLinkTooBig": "No s’ha pogut crear un enllaç per compartir: l’escena és massa gran",
|
||||||
"couldNotLoadInvalidFile": "No s'ha pogut carregar un fitxer no vàlid",
|
"couldNotLoadInvalidFile": "No s'ha pogut carregar un fitxer no vàlid",
|
||||||
"importBackendFailed": "Importació fallida.",
|
"importBackendFailed": "Importació fallida.",
|
||||||
"cannotExportEmptyCanvas": "No es pot exportar un llenç buit.",
|
"cannotExportEmptyCanvas": "No es pot exportar un llenç buit.",
|
||||||
@@ -136,8 +137,8 @@
|
|||||||
"loadSceneOverridePrompt": "Si carregas aquest dibuix extern, substituirá el que tens. Vols continuar?",
|
"loadSceneOverridePrompt": "Si carregas aquest dibuix extern, substituirá el que tens. Vols continuar?",
|
||||||
"errorLoadingLibrary": "S'ha produït un error en carregar la biblioteca de tercers.",
|
"errorLoadingLibrary": "S'ha produït un error en carregar la biblioteca de tercers.",
|
||||||
"confirmAddLibrary": "Això afegirà {{numShapes}} forma(es) a la vostra biblioteca. Estàs segur?",
|
"confirmAddLibrary": "Això afegirà {{numShapes}} forma(es) a la vostra biblioteca. Estàs segur?",
|
||||||
"imageDoesNotContainScene": "",
|
"imageDoesNotContainScene": "En aquest moment no s’admet la importació d’imatges.\n\nVolies importar una escena? Sembla que aquesta imatge no conté cap dada d’escena. Ho has activat durant l'exportació?",
|
||||||
"cannotRestoreFromImage": ""
|
"cannotRestoreFromImage": "L’escena no s’ha pogut restaurar des d’aquest fitxer d’imatge"
|
||||||
},
|
},
|
||||||
"toolBar": {
|
"toolBar": {
|
||||||
"selection": "Selecció",
|
"selection": "Selecció",
|
||||||
@@ -161,6 +162,7 @@
|
|||||||
"freeDraw": "Fer clic i arrosegar, deixar anar al punt final",
|
"freeDraw": "Fer clic i arrosegar, deixar anar al punt final",
|
||||||
"text": "Consell: també pots afegir text fent doble clic a qualsevol lloc amb l'eina de selecció",
|
"text": "Consell: també pots afegir text fent doble clic a qualsevol lloc amb l'eina de selecció",
|
||||||
"linearElementMulti": "Fer clic a l'ultim punt, o polsar Escape o Enter per acabar",
|
"linearElementMulti": "Fer clic a l'ultim punt, o polsar Escape o Enter per acabar",
|
||||||
|
"lockAngle": "Per restringir els angles, mantenir premut el majúscul (SHIFT)",
|
||||||
"resize": "Per restringir les proporcions mentres es canvia la mida, mantenir premut el majúscul (SHIFT); per canviar la mida des del centre, mantenir premut ALT",
|
"resize": "Per restringir les proporcions mentres es canvia la mida, mantenir premut el majúscul (SHIFT); per canviar la mida des del centre, mantenir premut ALT",
|
||||||
"rotate": "Per restringir els angles mentre gira, mantenir premut el majúscul (SHIFT)",
|
"rotate": "Per restringir els angles mentre gira, mantenir premut el majúscul (SHIFT)",
|
||||||
"lineEditor_info": "Fes doble clic o premi Enter per editar punts",
|
"lineEditor_info": "Fes doble clic o premi Enter per editar punts",
|
||||||
@@ -168,9 +170,9 @@
|
|||||||
"lineEditor_nothingSelected": "Selecciona un punt per moure o eliminar, o manté premut Alt i fes clic per afegir punts nous"
|
"lineEditor_nothingSelected": "Selecciona un punt per moure o eliminar, o manté premut Alt i fes clic per afegir punts nous"
|
||||||
},
|
},
|
||||||
"canvasError": {
|
"canvasError": {
|
||||||
"cannotShowPreview": "",
|
"cannotShowPreview": "No es pot mostrar la vista prèvia",
|
||||||
"canvasTooBig": "",
|
"canvasTooBig": "Pot ser que el llenç sigui massa gran.",
|
||||||
"canvasTooBigTip": ""
|
"canvasTooBigTip": "Consell: prova d’acostar una mica els elements més allunyats."
|
||||||
},
|
},
|
||||||
"errorSplash": {
|
"errorSplash": {
|
||||||
"headingMain_pre": "S'ha produït un error. Intentar ",
|
"headingMain_pre": "S'ha produït un error. Intentar ",
|
||||||
@@ -197,38 +199,44 @@
|
|||||||
"errorDialog": {
|
"errorDialog": {
|
||||||
"title": "Error"
|
"title": "Error"
|
||||||
},
|
},
|
||||||
"shortcutsDialog": {
|
"helpDialog": {
|
||||||
"title": "Dreceres de teclat",
|
"blog": "",
|
||||||
"shapes": "Formes",
|
"click": "",
|
||||||
"or": "o",
|
"curvedArrow": "",
|
||||||
"click": "fer clic",
|
"curvedLine": "",
|
||||||
"drag": "arrosegar",
|
"documentation": "",
|
||||||
"curvedArrow": "Fletxa curva",
|
"drag": "",
|
||||||
"curvedLine": "Línea curva",
|
"editor": "",
|
||||||
"editor": "Editor",
|
"github": "",
|
||||||
"view": "Vista",
|
"howto": "",
|
||||||
"blog": "Llegir el nostre blog",
|
"or": "",
|
||||||
"howto": "Seguir els nostres guies",
|
"preventBinding": "",
|
||||||
"github": "Has trobat un problema? Enviar-ho",
|
"shapes": "",
|
||||||
"textNewLine": "Afegir línea nova (text)",
|
"shortcuts": "",
|
||||||
"textFinish": "Acabar d'editar (text)",
|
"textFinish": "",
|
||||||
"zoomToFit": "Zoom per veure tots els elements",
|
"textNewLine": "",
|
||||||
"zoomToSelection": "",
|
"title": "",
|
||||||
"preventBinding": "Prevenir vinculació de la fletxa"
|
"view": "",
|
||||||
|
"zoomToFit": "",
|
||||||
|
"zoomToSelection": ""
|
||||||
},
|
},
|
||||||
"encrypted": {
|
"encrypted": {
|
||||||
"tooltip": "Els vostres dibuixos estan xifrats de punta a punta de manera que els servidors d’Excalidraw no els veuran mai."
|
"tooltip": "Els vostres dibuixos estan xifrats de punta a punta de manera que els servidors d’Excalidraw no els veuran mai."
|
||||||
},
|
},
|
||||||
"stats": {
|
"stats": {
|
||||||
"angle": "",
|
"angle": "Angle",
|
||||||
"element": "",
|
"element": "Element",
|
||||||
"elements": "",
|
"elements": "Elements",
|
||||||
"height": "",
|
"height": "Altura",
|
||||||
"scene": "",
|
"scene": "Escena",
|
||||||
"selected": "",
|
"selected": "Seleccionat",
|
||||||
"storage": "",
|
"storage": "Emmagatzematge",
|
||||||
"title": "",
|
"title": "Estadístiques per nerds",
|
||||||
"total": "",
|
"total": "Total",
|
||||||
"width": ""
|
"width": "Amplada"
|
||||||
|
},
|
||||||
|
"toast": {
|
||||||
|
"copyStyles": "",
|
||||||
|
"copyToClipboardAsPng": ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
{
|
{
|
||||||
"labels": {
|
"labels": {
|
||||||
"paste": "Einfügen",
|
"paste": "Einfügen",
|
||||||
|
"pasteCharts": "Diagramme einfügen",
|
||||||
"selectAll": "Alle auswählen",
|
"selectAll": "Alle auswählen",
|
||||||
"multiSelect": "Element zur Auswahl hinzufügen",
|
"multiSelect": "Element zur Auswahl hinzufügen",
|
||||||
"moveCanvas": "Leinwand verschieben",
|
"moveCanvas": "Leinwand verschieben",
|
||||||
@@ -76,8 +77,7 @@
|
|||||||
"group": "Auswahl gruppieren",
|
"group": "Auswahl gruppieren",
|
||||||
"ungroup": "Gruppierung aufheben",
|
"ungroup": "Gruppierung aufheben",
|
||||||
"collaborators": "Mitarbeitende",
|
"collaborators": "Mitarbeitende",
|
||||||
"toggleGridMode": "Gitterlinien ein-/ausschalten",
|
"gridMode": "Rastermodus",
|
||||||
"toggleStats": "Statistiken für Nerds ein-/ausschalten",
|
|
||||||
"addToLibrary": "Zur Bibliothek hinzufügen",
|
"addToLibrary": "Zur Bibliothek hinzufügen",
|
||||||
"removeFromLibrary": "Aus Bibliothek entfernen",
|
"removeFromLibrary": "Aus Bibliothek entfernen",
|
||||||
"libraryLoadingMessage": "Lade Bibliothek...",
|
"libraryLoadingMessage": "Lade Bibliothek...",
|
||||||
@@ -118,9 +118,10 @@
|
|||||||
"redo": "Wiederholen",
|
"redo": "Wiederholen",
|
||||||
"roomDialog": "Live-Kollaborationssitzung starten",
|
"roomDialog": "Live-Kollaborationssitzung starten",
|
||||||
"createNewRoom": "Neuen Raum erstellen",
|
"createNewRoom": "Neuen Raum erstellen",
|
||||||
"toggleFullScreen": "Vollbild umschalten",
|
"fullScreen": "Vollbildanzeige",
|
||||||
"toggleDarkMode": "Dunkles Design umschalten",
|
"darkMode": "Dunkles Design",
|
||||||
"toggleZenMode": "Zen-Modus umschalten",
|
"lightMode": "Helles Design",
|
||||||
|
"zenMode": "Zen-Modus",
|
||||||
"exitZenMode": "Zen-Modus verlassen"
|
"exitZenMode": "Zen-Modus verlassen"
|
||||||
},
|
},
|
||||||
"alerts": {
|
"alerts": {
|
||||||
@@ -136,7 +137,7 @@
|
|||||||
"loadSceneOverridePrompt": "Das Laden der externen Zeichnung ersetzt den vorhandenen Inhalt. Möchtest Du fortfahren?",
|
"loadSceneOverridePrompt": "Das Laden der externen Zeichnung ersetzt den vorhandenen Inhalt. Möchtest Du fortfahren?",
|
||||||
"errorLoadingLibrary": "Beim Laden der Drittanbieter-Bibliothek ist ein Fehler aufgetreten.",
|
"errorLoadingLibrary": "Beim Laden der Drittanbieter-Bibliothek ist ein Fehler aufgetreten.",
|
||||||
"confirmAddLibrary": "Dieses fügt {{numShapes}} Form(en) zu deiner Bibliothek hinzu. Bist du sicher?",
|
"confirmAddLibrary": "Dieses fügt {{numShapes}} Form(en) zu deiner Bibliothek hinzu. Bist du sicher?",
|
||||||
"imageDoesNotContainScene": "Bilddatei enthält keine Zeichnungsdaten. Hast du das Einbetten beim Export aktiviert?",
|
"imageDoesNotContainScene": "Das Importieren von Bildern wird derzeit nicht unterstützt.\n\nMöchtest du eine Szene importieren? Dieses Bild scheint keine Zeichnungsdaten zu enthalten. Hast du dies beim Exportieren aktiviert?",
|
||||||
"cannotRestoreFromImage": "Die Zeichnung konnte aus dieser Bilddatei nicht wiederhergestellt werden"
|
"cannotRestoreFromImage": "Die Zeichnung konnte aus dieser Bilddatei nicht wiederhergestellt werden"
|
||||||
},
|
},
|
||||||
"toolBar": {
|
"toolBar": {
|
||||||
@@ -161,6 +162,7 @@
|
|||||||
"freeDraw": "Klicke und ziehe. Lass los, wenn du fertig bist",
|
"freeDraw": "Klicke und ziehe. Lass los, wenn du fertig bist",
|
||||||
"text": "Tipp: Du kannst auch Text hinzufügen indem Du mit dem Auswahlwerkzeug auf eine beliebige Stelle doppelklickst",
|
"text": "Tipp: Du kannst auch Text hinzufügen indem Du mit dem Auswahlwerkzeug auf eine beliebige Stelle doppelklickst",
|
||||||
"linearElementMulti": "Zum Beenden auf den letzten Punkt klicken oder Escape oder Eingabe drücken",
|
"linearElementMulti": "Zum Beenden auf den letzten Punkt klicken oder Escape oder Eingabe drücken",
|
||||||
|
"lockAngle": "Du kannst Winkel einschränken, indem du SHIFT gedrückt hältst",
|
||||||
"resize": "Du kannst die Proportionen einschränken, indem du SHIFT während der Größenänderung gedrückt hältst. Halte ALT gedrückt, um die Größe vom Zentrum aus zu ändern",
|
"resize": "Du kannst die Proportionen einschränken, indem du SHIFT während der Größenänderung gedrückt hältst. Halte ALT gedrückt, um die Größe vom Zentrum aus zu ändern",
|
||||||
"rotate": "Du kannst Winkel einschränken, indem du SHIFT während der Drehung gedrückt hältst",
|
"rotate": "Du kannst Winkel einschränken, indem du SHIFT während der Drehung gedrückt hältst",
|
||||||
"lineEditor_info": "Doppelklicken oder Eingabetaste drücken, um Punkte zu bearbeiten",
|
"lineEditor_info": "Doppelklicken oder Eingabetaste drücken, um Punkte zu bearbeiten",
|
||||||
@@ -197,24 +199,26 @@
|
|||||||
"errorDialog": {
|
"errorDialog": {
|
||||||
"title": "Fehler"
|
"title": "Fehler"
|
||||||
},
|
},
|
||||||
"shortcutsDialog": {
|
"helpDialog": {
|
||||||
"title": "Tastaturkürzel",
|
"blog": "Lies unseren Blog",
|
||||||
"shapes": "Formen",
|
|
||||||
"or": "oder",
|
|
||||||
"click": "klicken",
|
"click": "klicken",
|
||||||
"drag": "ziehen",
|
|
||||||
"curvedArrow": "Gebogener Pfeil",
|
"curvedArrow": "Gebogener Pfeil",
|
||||||
"curvedLine": "Gebogene Linie",
|
"curvedLine": "Gebogene Linie",
|
||||||
|
"documentation": "Dokumentation",
|
||||||
|
"drag": "ziehen",
|
||||||
"editor": "Editor",
|
"editor": "Editor",
|
||||||
"view": "Ansicht",
|
|
||||||
"blog": "Unseren Blog lesen",
|
|
||||||
"howto": "Folge unseren Anleitungen",
|
|
||||||
"github": "Ein Problem gefunden? Informiere uns",
|
"github": "Ein Problem gefunden? Informiere uns",
|
||||||
"textNewLine": "Neue Zeile hinzufügen (Text)",
|
"howto": "Folge unseren Anleitungen",
|
||||||
|
"or": "oder",
|
||||||
|
"preventBinding": "Pfeil-Bindung verhindern",
|
||||||
|
"shapes": "Formen",
|
||||||
|
"shortcuts": "Tastaturkürzel",
|
||||||
"textFinish": "Bearbeiten beenden (Text)",
|
"textFinish": "Bearbeiten beenden (Text)",
|
||||||
|
"textNewLine": "Neue Zeile hinzufügen (Text)",
|
||||||
|
"title": "Hilfe",
|
||||||
|
"view": "Ansicht",
|
||||||
"zoomToFit": "Zoomen um alle Elemente einzupassen",
|
"zoomToFit": "Zoomen um alle Elemente einzupassen",
|
||||||
"zoomToSelection": "",
|
"zoomToSelection": "Auf Auswahl zoomen"
|
||||||
"preventBinding": "Pfeil-Bindung verhindern"
|
|
||||||
},
|
},
|
||||||
"encrypted": {
|
"encrypted": {
|
||||||
"tooltip": "Da deine Zeichnungen Ende-zu-Ende verschlüsselt werden, sehen auch unsere Excalidraw-Server sie niemals."
|
"tooltip": "Da deine Zeichnungen Ende-zu-Ende verschlüsselt werden, sehen auch unsere Excalidraw-Server sie niemals."
|
||||||
@@ -230,5 +234,9 @@
|
|||||||
"title": "Statistiken für Nerds",
|
"title": "Statistiken für Nerds",
|
||||||
"total": "Gesamt",
|
"total": "Gesamt",
|
||||||
"width": "Breite"
|
"width": "Breite"
|
||||||
|
},
|
||||||
|
"toast": {
|
||||||
|
"copyStyles": "Formatierung kopiert.",
|
||||||
|
"copyToClipboardAsPng": "In die Zwischenablage als PNG kopiert."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
{
|
{
|
||||||
"labels": {
|
"labels": {
|
||||||
"paste": "Επικόλληση",
|
"paste": "Επικόλληση",
|
||||||
|
"pasteCharts": "Επικόλληση γραφημάτων",
|
||||||
"selectAll": "Επιλογή όλων",
|
"selectAll": "Επιλογή όλων",
|
||||||
"multiSelect": "Προσθέστε το στοιχείο στην επιλογή",
|
"multiSelect": "Προσθέστε το στοιχείο στην επιλογή",
|
||||||
"moveCanvas": "Μετακίνηση καμβά",
|
"moveCanvas": "Μετακίνηση καμβά",
|
||||||
@@ -37,7 +38,7 @@
|
|||||||
"fontSize": "Μέγεθος γραμματοσειράς",
|
"fontSize": "Μέγεθος γραμματοσειράς",
|
||||||
"fontFamily": "Γραμματοσειρά",
|
"fontFamily": "Γραμματοσειρά",
|
||||||
"onlySelected": "Μόνο τα Επιλεγμένα",
|
"onlySelected": "Μόνο τα Επιλεγμένα",
|
||||||
"withBackground": "Με Φόντο",
|
"withBackground": "Με φόντο",
|
||||||
"exportEmbedScene": "Ενσωμάτωση της σκηνής στο αρχείο προς εξαγωγή",
|
"exportEmbedScene": "Ενσωμάτωση της σκηνής στο αρχείο προς εξαγωγή",
|
||||||
"exportEmbedScene_details": "Τα δεδομένα σκηνής θα αποθηκευτούν στο αρχείο PNG/SVG προς εξαγωγή ώστε η σκηνή να είναι δυνατό να αποκατασταθεί από αυτό.\nΘα αυξήσει το μέγεθος του αρχείου προς εξαγωγή.",
|
"exportEmbedScene_details": "Τα δεδομένα σκηνής θα αποθηκευτούν στο αρχείο PNG/SVG προς εξαγωγή ώστε η σκηνή να είναι δυνατό να αποκατασταθεί από αυτό.\nΘα αυξήσει το μέγεθος του αρχείου προς εξαγωγή.",
|
||||||
"addWatermark": "Προσθήκη \"Φτιαγμένο με Excalidraw\"",
|
"addWatermark": "Προσθήκη \"Φτιαγμένο με Excalidraw\"",
|
||||||
@@ -76,8 +77,7 @@
|
|||||||
"group": "Δημιουργία ομάδας από επιλογή",
|
"group": "Δημιουργία ομάδας από επιλογή",
|
||||||
"ungroup": "Κατάργηση ομάδας από επιλογή",
|
"ungroup": "Κατάργηση ομάδας από επιλογή",
|
||||||
"collaborators": "Συνεργάτες",
|
"collaborators": "Συνεργάτες",
|
||||||
"toggleGridMode": "Εναλλαγή λειτουργίας πλέγματος",
|
"gridMode": "Εμφάνιση σε πλέγμα",
|
||||||
"toggleStats": "",
|
|
||||||
"addToLibrary": "Προσθήκη στη βιβλιοθήκη",
|
"addToLibrary": "Προσθήκη στη βιβλιοθήκη",
|
||||||
"removeFromLibrary": "Αφαίρεση από τη βιβλιοθήκη",
|
"removeFromLibrary": "Αφαίρεση από τη βιβλιοθήκη",
|
||||||
"libraryLoadingMessage": "Φόρτωση βιβλιοθήκης...",
|
"libraryLoadingMessage": "Φόρτωση βιβλιοθήκης...",
|
||||||
@@ -90,8 +90,8 @@
|
|||||||
"alignRight": "Στοίχιση δεξιά",
|
"alignRight": "Στοίχιση δεξιά",
|
||||||
"centerVertically": "Κέντρο κάθετα",
|
"centerVertically": "Κέντρο κάθετα",
|
||||||
"centerHorizontally": "Κέντρο οριζόντια",
|
"centerHorizontally": "Κέντρο οριζόντια",
|
||||||
"distributeHorizontally": "",
|
"distributeHorizontally": "Οριζόντια κατανομή",
|
||||||
"distributeVertically": ""
|
"distributeVertically": "Κατακόρυφη κατανομή"
|
||||||
},
|
},
|
||||||
"buttons": {
|
"buttons": {
|
||||||
"clearReset": "Επαναφορά του καμβά",
|
"clearReset": "Επαναφορά του καμβά",
|
||||||
@@ -118,15 +118,16 @@
|
|||||||
"redo": "Επαναφορά",
|
"redo": "Επαναφορά",
|
||||||
"roomDialog": "Έναρξη ζωντανής συνεργασίας",
|
"roomDialog": "Έναρξη ζωντανής συνεργασίας",
|
||||||
"createNewRoom": "Δημιουργία νέου χώρου",
|
"createNewRoom": "Δημιουργία νέου χώρου",
|
||||||
"toggleFullScreen": "Εναλλαγή πλήρους οθόνης",
|
"fullScreen": "Πλήρης οθόνη",
|
||||||
"toggleDarkMode": "Εναλλαγή εμφάνισης σε dark",
|
"darkMode": "Σκοτεινή λειτουργία",
|
||||||
"toggleZenMode": "Εναλλαγή λειτουργίας Zen",
|
"lightMode": "Φωτεινή λειτουργία",
|
||||||
|
"zenMode": "Λειτουργία Zεν",
|
||||||
"exitZenMode": "Έξοδος απο την λειτουργία Zen"
|
"exitZenMode": "Έξοδος απο την λειτουργία Zen"
|
||||||
},
|
},
|
||||||
"alerts": {
|
"alerts": {
|
||||||
"clearReset": "Αυτό θα σβήσει ολόκληρο τον καμβά. Είσαι σίγουρος;",
|
"clearReset": "Αυτό θα σβήσει ολόκληρο τον καμβά. Είσαι σίγουρος;",
|
||||||
"couldNotCreateShareableLink": "Δεν ήταν δυνατή η δημιουργία συνδέσμου κοινής χρήσης.",
|
"couldNotCreateShareableLink": "Δεν ήταν δυνατή η δημιουργία συνδέσμου κοινής χρήσης.",
|
||||||
"couldNotCreateShareableLinkTooBig": "",
|
"couldNotCreateShareableLinkTooBig": "Δεν ήταν δυνατή η δημιουργία κοινόχρηστου συνδέσμου: η σκηνή είναι πολύ μεγάλη",
|
||||||
"couldNotLoadInvalidFile": "Δεν μπόρεσε να ανοίξει εσφαλμένο αρχείο",
|
"couldNotLoadInvalidFile": "Δεν μπόρεσε να ανοίξει εσφαλμένο αρχείο",
|
||||||
"importBackendFailed": "Η εισαγωγή από το backend απέτυχε.",
|
"importBackendFailed": "Η εισαγωγή από το backend απέτυχε.",
|
||||||
"cannotExportEmptyCanvas": "Δεν είναι δυνατή η εξαγωγή κενού καμβά.",
|
"cannotExportEmptyCanvas": "Δεν είναι δυνατή η εξαγωγή κενού καμβά.",
|
||||||
@@ -136,7 +137,7 @@
|
|||||||
"loadSceneOverridePrompt": "Η φόρτωση εξωτερικού σχεδίου θα αντικαταστήσει το υπάρχον περιεχόμενο. Επιθυμείτε να συνεχίσετε;",
|
"loadSceneOverridePrompt": "Η φόρτωση εξωτερικού σχεδίου θα αντικαταστήσει το υπάρχον περιεχόμενο. Επιθυμείτε να συνεχίσετε;",
|
||||||
"errorLoadingLibrary": "Υπήρξε ένα σφάλμα κατά τη φόρτωση της βιβλιοθήκης τρίτου μέρους.",
|
"errorLoadingLibrary": "Υπήρξε ένα σφάλμα κατά τη φόρτωση της βιβλιοθήκης τρίτου μέρους.",
|
||||||
"confirmAddLibrary": "Αυτό θα προσθέσει {{numShapes}} σχήμα(τα) στη βιβιλιοθήκη σας. Είστε σίγουροι;",
|
"confirmAddLibrary": "Αυτό θα προσθέσει {{numShapes}} σχήμα(τα) στη βιβιλιοθήκη σας. Είστε σίγουροι;",
|
||||||
"imageDoesNotContainScene": "Το αρχείο εικόνας δεν έχει δεδομένα σκηνής. Το είχατε ενεργοποιήσει αυτό κατά την εξαγωγή;",
|
"imageDoesNotContainScene": "Η εισαγωγή εικόνων δεν υποστηρίζεται αυτή τη στιγμή.\n\nΜήπως θέλετε να εισαγάγετε μια σκηνή; Αυτή η εικόνα δεν φαίνεται να περιέχει δεδομένα σκηνής. Έχετε ενεργοποιήσει αυτό κατά την εξαγωγή;",
|
||||||
"cannotRestoreFromImage": "Η σκηνή δεν ήταν δυνατό να αποκατασταθεί από αυτό το αρχείο εικόνας"
|
"cannotRestoreFromImage": "Η σκηνή δεν ήταν δυνατό να αποκατασταθεί από αυτό το αρχείο εικόνας"
|
||||||
},
|
},
|
||||||
"toolBar": {
|
"toolBar": {
|
||||||
@@ -161,6 +162,7 @@
|
|||||||
"freeDraw": "Κάντε κλικ και σύρατε, απελευθερώσατε όταν έχετε τελειώσει",
|
"freeDraw": "Κάντε κλικ και σύρατε, απελευθερώσατε όταν έχετε τελειώσει",
|
||||||
"text": "Tip: μπορείτε επίσης να προσθέστε κείμενο με διπλό-κλικ οπουδήποτε με το εργαλείο επιλογών",
|
"text": "Tip: μπορείτε επίσης να προσθέστε κείμενο με διπλό-κλικ οπουδήποτε με το εργαλείο επιλογών",
|
||||||
"linearElementMulti": "Κάνε κλικ στο τελευταίο σημείο ή πάτησε Escape ή Enter για να τελειώσεις",
|
"linearElementMulti": "Κάνε κλικ στο τελευταίο σημείο ή πάτησε Escape ή Enter για να τελειώσεις",
|
||||||
|
"lockAngle": "Μπορείτε να περιορίσετε τη γωνία κρατώντας πατημένο το SHIFT",
|
||||||
"resize": "Μπορείς να περιορίσεις τις αναλογίες κρατώντας το SHIFT ενώ αλλάζεις μέγεθος,\nκράτησε πατημένο το ALT για αλλαγή μεγέθους από το κέντρο",
|
"resize": "Μπορείς να περιορίσεις τις αναλογίες κρατώντας το SHIFT ενώ αλλάζεις μέγεθος,\nκράτησε πατημένο το ALT για αλλαγή μεγέθους από το κέντρο",
|
||||||
"rotate": "Μπορείς να περιορίσεις τις γωνίες κρατώντας πατημένο το πλήκτρο SHIFT κατά την περιστροφή",
|
"rotate": "Μπορείς να περιορίσεις τις γωνίες κρατώντας πατημένο το πλήκτρο SHIFT κατά την περιστροφή",
|
||||||
"lineEditor_info": "Διπλό-κλικ ή πιέστε Enter για να επεξεργαστείτε τα σημεία",
|
"lineEditor_info": "Διπλό-κλικ ή πιέστε Enter για να επεξεργαστείτε τα σημεία",
|
||||||
@@ -168,9 +170,9 @@
|
|||||||
"lineEditor_nothingSelected": "Επιλέξτε ένα σημείο για μετακίνηση ή αφαίρεση, ή κρατήστε παρατεταμένα το Alt και κάντε κλικ για να προσθέσετε νέα σημεία"
|
"lineEditor_nothingSelected": "Επιλέξτε ένα σημείο για μετακίνηση ή αφαίρεση, ή κρατήστε παρατεταμένα το Alt και κάντε κλικ για να προσθέσετε νέα σημεία"
|
||||||
},
|
},
|
||||||
"canvasError": {
|
"canvasError": {
|
||||||
"cannotShowPreview": "",
|
"cannotShowPreview": "Αδυναμία εμφάνισης προεπισκόπησης",
|
||||||
"canvasTooBig": "",
|
"canvasTooBig": "Ο καμβάς μπορεί να είναι μεγάλος.",
|
||||||
"canvasTooBigTip": ""
|
"canvasTooBigTip": "Συμβουλή: προσπαθήστε να μετακινήσετε τα πιο απομακρυσμένα στοιχεία λίγο πιο κοντά μαζί."
|
||||||
},
|
},
|
||||||
"errorSplash": {
|
"errorSplash": {
|
||||||
"headingMain_pre": "Συνέβη κάποιο σφάλμα. Προσπάθησε ",
|
"headingMain_pre": "Συνέβη κάποιο σφάλμα. Προσπάθησε ",
|
||||||
@@ -197,24 +199,26 @@
|
|||||||
"errorDialog": {
|
"errorDialog": {
|
||||||
"title": "Σφάλμα"
|
"title": "Σφάλμα"
|
||||||
},
|
},
|
||||||
"shortcutsDialog": {
|
"helpDialog": {
|
||||||
"title": "Συντομεύσεις πληκτρολογίου",
|
"blog": "Διαβάστε το Blog μας",
|
||||||
"shapes": "Σχήματα",
|
|
||||||
"or": "ή",
|
|
||||||
"click": "κλικ",
|
"click": "κλικ",
|
||||||
"drag": "σύρε",
|
|
||||||
"curvedArrow": "Κυρτό βέλος",
|
"curvedArrow": "Κυρτό βέλος",
|
||||||
"curvedLine": "Κυρτή γραμμή",
|
"curvedLine": "Κυρτή γραμμή",
|
||||||
|
"documentation": "Εγχειρίδιο",
|
||||||
|
"drag": "σύρε",
|
||||||
"editor": "Επεξεργαστής",
|
"editor": "Επεξεργαστής",
|
||||||
"view": "Προβολή",
|
|
||||||
"blog": "Διαβάστε το ιστολόγιο μας",
|
|
||||||
"howto": "Ακολουθήστε τους οδηγούς μας",
|
|
||||||
"github": "Βρήκατε πρόβλημα; Υποβάλετε το",
|
"github": "Βρήκατε πρόβλημα; Υποβάλετε το",
|
||||||
"textNewLine": "Προσθήκη νέας γραμμής (κείμενο)",
|
"howto": "Ακολουθήστε τους οδηγούς μας",
|
||||||
|
"or": "ή",
|
||||||
|
"preventBinding": "Αποτροπή δέσμευσης βέλων",
|
||||||
|
"shapes": "Σχήματα",
|
||||||
|
"shortcuts": "Συντομεύσεις πληκτρολογίου",
|
||||||
"textFinish": "Ολοκλήρωση επεξεργασίας (κείμενο)",
|
"textFinish": "Ολοκλήρωση επεξεργασίας (κείμενο)",
|
||||||
|
"textNewLine": "Προσθήκη νέας γραμμής (κείμενο)",
|
||||||
|
"title": "Βοήθεια",
|
||||||
|
"view": "Προβολή",
|
||||||
"zoomToFit": "Zoom ώστε να χωρέσουν όλα τα στοιχεία",
|
"zoomToFit": "Zoom ώστε να χωρέσουν όλα τα στοιχεία",
|
||||||
"zoomToSelection": "",
|
"zoomToSelection": "Ζουμ στην επιλογή"
|
||||||
"preventBinding": "Αποτροπή δέσμευσης βέλων"
|
|
||||||
},
|
},
|
||||||
"encrypted": {
|
"encrypted": {
|
||||||
"tooltip": "Τα σχέδιά σου είναι κρυπτογραφημένα από άκρο σε άκρο, έτσι δεν θα έιναι ποτέ ορατά μέσα από τους διακομιστές του Excalidraw."
|
"tooltip": "Τα σχέδιά σου είναι κρυπτογραφημένα από άκρο σε άκρο, έτσι δεν θα έιναι ποτέ ορατά μέσα από τους διακομιστές του Excalidraw."
|
||||||
@@ -224,11 +228,15 @@
|
|||||||
"element": "Στοιχείο",
|
"element": "Στοιχείο",
|
||||||
"elements": "Στοιχεία",
|
"elements": "Στοιχεία",
|
||||||
"height": "Ύψος",
|
"height": "Ύψος",
|
||||||
"scene": "",
|
"scene": "Σκηνή",
|
||||||
"selected": "Επιλεγμένα",
|
"selected": "Επιλεγμένα",
|
||||||
"storage": "Χώρος",
|
"storage": "Χώρος",
|
||||||
"title": "",
|
"title": "Στατιστικά για σπασίκλες",
|
||||||
"total": "Σύνολο ",
|
"total": "Σύνολο ",
|
||||||
"width": "Πλάτος"
|
"width": "Πλάτος"
|
||||||
|
},
|
||||||
|
"toast": {
|
||||||
|
"copyStyles": "Αντιγράφηκαν στυλ.",
|
||||||
|
"copyToClipboardAsPng": "Αντιγράφτηκε στο πρόχειρο ως PNG."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
{
|
{
|
||||||
"labels": {
|
"labels": {
|
||||||
"paste": "Paste",
|
"paste": "Paste",
|
||||||
|
"pasteCharts": "Paste charts",
|
||||||
"selectAll": "Select all",
|
"selectAll": "Select all",
|
||||||
"multiSelect": "Add element to selection",
|
"multiSelect": "Add element to selection",
|
||||||
"moveCanvas": "Move canvas",
|
"moveCanvas": "Move canvas",
|
||||||
@@ -198,24 +199,26 @@
|
|||||||
"errorDialog": {
|
"errorDialog": {
|
||||||
"title": "Error"
|
"title": "Error"
|
||||||
},
|
},
|
||||||
"shortcutsDialog": {
|
"helpDialog": {
|
||||||
"title": "Keyboard shortcuts",
|
"blog": "Read our blog",
|
||||||
"shapes": "Shapes",
|
|
||||||
"or": "or",
|
|
||||||
"click": "click",
|
"click": "click",
|
||||||
"drag": "drag",
|
|
||||||
"curvedArrow": "Curved arrow",
|
"curvedArrow": "Curved arrow",
|
||||||
"curvedLine": "Curved line",
|
"curvedLine": "Curved line",
|
||||||
|
"documentation": "Documentation",
|
||||||
|
"drag": "drag",
|
||||||
"editor": "Editor",
|
"editor": "Editor",
|
||||||
"view": "View",
|
|
||||||
"blog": "Read our blog",
|
|
||||||
"howto": "Follow our guides",
|
|
||||||
"github": "Found an issue? Submit",
|
"github": "Found an issue? Submit",
|
||||||
"textNewLine": "Add new line (text)",
|
"howto": "Follow our guides",
|
||||||
|
"or": "or",
|
||||||
|
"preventBinding": "Prevent arrow binding",
|
||||||
|
"shapes": "Shapes",
|
||||||
|
"shortcuts": "Keyboard shortcuts",
|
||||||
"textFinish": "Finish editing (text)",
|
"textFinish": "Finish editing (text)",
|
||||||
|
"textNewLine": "Add new line (text)",
|
||||||
|
"title": "Help",
|
||||||
|
"view": "View",
|
||||||
"zoomToFit": "Zoom to fit all elements",
|
"zoomToFit": "Zoom to fit all elements",
|
||||||
"zoomToSelection": "Zoom to selection",
|
"zoomToSelection": "Zoom to selection"
|
||||||
"preventBinding": "Prevent arrow binding"
|
|
||||||
},
|
},
|
||||||
"encrypted": {
|
"encrypted": {
|
||||||
"tooltip": "Your drawings are end-to-end encrypted so Excalidraw's servers will never see them."
|
"tooltip": "Your drawings are end-to-end encrypted so Excalidraw's servers will never see them."
|
||||||
@@ -231,5 +234,9 @@
|
|||||||
"title": "Stats for nerds",
|
"title": "Stats for nerds",
|
||||||
"total": "Total",
|
"total": "Total",
|
||||||
"width": "Width"
|
"width": "Width"
|
||||||
|
},
|
||||||
|
"toast": {
|
||||||
|
"copyStyles": "Copied styles.",
|
||||||
|
"copyToClipboardAsPng": "Copied to clipboard as PNG."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
{
|
{
|
||||||
"labels": {
|
"labels": {
|
||||||
"paste": "Pegar",
|
"paste": "Pegar",
|
||||||
|
"pasteCharts": "Pegar gráficos",
|
||||||
"selectAll": "Seleccionar todo",
|
"selectAll": "Seleccionar todo",
|
||||||
"multiSelect": "Añadir elemento a la selección",
|
"multiSelect": "Añadir elemento a la selección",
|
||||||
"moveCanvas": "Mover el lienzo",
|
"moveCanvas": "Mover el lienzo",
|
||||||
"cut": "",
|
"cut": "Cortar",
|
||||||
"copy": "Copiar",
|
"copy": "Copiar",
|
||||||
"copyAsPng": "Copiar al portapapeles como PNG",
|
"copyAsPng": "Copiar al portapapeles como PNG",
|
||||||
"copyAsSvg": "Copiar al portapapeles como SVG",
|
"copyAsSvg": "Copiar al portapapeles como SVG",
|
||||||
@@ -21,7 +22,7 @@
|
|||||||
"strokeWidth": "Grosor del trazo",
|
"strokeWidth": "Grosor del trazo",
|
||||||
"strokeStyle": "Estilo del trazo",
|
"strokeStyle": "Estilo del trazo",
|
||||||
"strokeStyle_solid": "Sólido",
|
"strokeStyle_solid": "Sólido",
|
||||||
"strokeStyle_dashed": "Linea discontinua",
|
"strokeStyle_dashed": "Discontinua",
|
||||||
"strokeStyle_dotted": "Punteado",
|
"strokeStyle_dotted": "Punteado",
|
||||||
"sloppiness": "Estilo de trazo",
|
"sloppiness": "Estilo de trazo",
|
||||||
"opacity": "Opacidad",
|
"opacity": "Opacidad",
|
||||||
@@ -29,17 +30,17 @@
|
|||||||
"edges": "Bordes",
|
"edges": "Bordes",
|
||||||
"sharp": "Afilado",
|
"sharp": "Afilado",
|
||||||
"round": "Redondo",
|
"round": "Redondo",
|
||||||
"arrowheads": "",
|
"arrowheads": "Puntas de flecha",
|
||||||
"arrowhead_none": "",
|
"arrowhead_none": "Ninguna",
|
||||||
"arrowhead_arrow": "",
|
"arrowhead_arrow": "Flecha",
|
||||||
"arrowhead_bar": "",
|
"arrowhead_bar": "Barra",
|
||||||
"arrowhead_dot": "",
|
"arrowhead_dot": "Punto",
|
||||||
"fontSize": "Tamaño de la fuente",
|
"fontSize": "Tamaño de la fuente",
|
||||||
"fontFamily": "Tipo de fuente",
|
"fontFamily": "Tipo de fuente",
|
||||||
"onlySelected": "Sólo seleccionados",
|
"onlySelected": "Sólo seleccionados",
|
||||||
"withBackground": "Con fondo",
|
"withBackground": "Con fondo",
|
||||||
"exportEmbedScene": "",
|
"exportEmbedScene": "Insertar escena en el archivo exportado",
|
||||||
"exportEmbedScene_details": "",
|
"exportEmbedScene_details": "Los datos de escena se guardarán en el archivo PNG/SVG exportado, así la escena puede ser restaurada de la misma.\nEsto aumentará el tamaño del archivo exportado.",
|
||||||
"addWatermark": "Agregar \"Hecho con Excalidraw\"",
|
"addWatermark": "Agregar \"Hecho con Excalidraw\"",
|
||||||
"handDrawn": "Dibujado a mano",
|
"handDrawn": "Dibujado a mano",
|
||||||
"normal": "Normal",
|
"normal": "Normal",
|
||||||
@@ -60,7 +61,7 @@
|
|||||||
"architect": "Arquitecto",
|
"architect": "Arquitecto",
|
||||||
"artist": "Artista",
|
"artist": "Artista",
|
||||||
"cartoonist": "Caricatura",
|
"cartoonist": "Caricatura",
|
||||||
"fileTitle": "",
|
"fileTitle": "Título del archivo",
|
||||||
"colorPicker": "Selector de color",
|
"colorPicker": "Selector de color",
|
||||||
"canvasBackground": "Fondo del lienzo",
|
"canvasBackground": "Fondo del lienzo",
|
||||||
"drawingCanvas": "Lienzo de dibujo",
|
"drawingCanvas": "Lienzo de dibujo",
|
||||||
@@ -69,29 +70,28 @@
|
|||||||
"language": "Idioma",
|
"language": "Idioma",
|
||||||
"createRoom": "Compartir una sesión de colaboración en vivo",
|
"createRoom": "Compartir una sesión de colaboración en vivo",
|
||||||
"duplicateSelection": "Duplicar",
|
"duplicateSelection": "Duplicar",
|
||||||
"untitled": "",
|
"untitled": "Sin título",
|
||||||
"name": "Nombre",
|
"name": "Nombre",
|
||||||
"yourName": "Tu nombre",
|
"yourName": "Tu nombre",
|
||||||
"madeWithExcalidraw": "Hecho con Excalidraw",
|
"madeWithExcalidraw": "Hecho con Excalidraw",
|
||||||
"group": "Selección de grupo",
|
"group": "Agrupar selección",
|
||||||
"ungroup": "Desagrupar",
|
"ungroup": "Desagrupar selección",
|
||||||
"collaborators": "Colaboradores",
|
"collaborators": "Colaboradores",
|
||||||
"toggleGridMode": "Alternar modo cuadrícula",
|
"gridMode": "Modo cuadrícula",
|
||||||
"toggleStats": "",
|
|
||||||
"addToLibrary": "Añadir a la biblioteca",
|
"addToLibrary": "Añadir a la biblioteca",
|
||||||
"removeFromLibrary": "Eliminar de la biblioteca",
|
"removeFromLibrary": "Eliminar de la biblioteca",
|
||||||
"libraryLoadingMessage": "Cargando biblioteca...",
|
"libraryLoadingMessage": "Cargando biblioteca...",
|
||||||
"libraries": "",
|
"libraries": "Explorar bibliotecas",
|
||||||
"loadingScene": "Cargando escena...",
|
"loadingScene": "Cargando escena...",
|
||||||
"align": "",
|
"align": "Alinear",
|
||||||
"alignTop": "",
|
"alignTop": "Alineación superior",
|
||||||
"alignBottom": "",
|
"alignBottom": "Alineación inferior",
|
||||||
"alignLeft": "",
|
"alignLeft": "Alinear a la izquierda",
|
||||||
"alignRight": "",
|
"alignRight": "Alinear a la derecha",
|
||||||
"centerVertically": "",
|
"centerVertically": "Centrar verticalmente",
|
||||||
"centerHorizontally": "",
|
"centerHorizontally": "Centrar horizontalmente",
|
||||||
"distributeHorizontally": "",
|
"distributeHorizontally": "Distribuir horizontalmente",
|
||||||
"distributeVertically": ""
|
"distributeVertically": "Distribuir verticalmente"
|
||||||
},
|
},
|
||||||
"buttons": {
|
"buttons": {
|
||||||
"clearReset": "Limpiar lienzo y reiniciar el color de fondo",
|
"clearReset": "Limpiar lienzo y reiniciar el color de fondo",
|
||||||
@@ -100,7 +100,7 @@
|
|||||||
"exportToSvg": "Exportar a SVG",
|
"exportToSvg": "Exportar a SVG",
|
||||||
"copyToClipboard": "Copiar al portapapeles",
|
"copyToClipboard": "Copiar al portapapeles",
|
||||||
"copyPngToClipboard": "Copiar PNG al portapapeles",
|
"copyPngToClipboard": "Copiar PNG al portapapeles",
|
||||||
"scale": "Escala",
|
"scale": "Escalar",
|
||||||
"save": "Guardar",
|
"save": "Guardar",
|
||||||
"saveAs": "Guardar como",
|
"saveAs": "Guardar como",
|
||||||
"load": "Cargar",
|
"load": "Cargar",
|
||||||
@@ -110,7 +110,7 @@
|
|||||||
"scrollBackToContent": "Volver al contenido",
|
"scrollBackToContent": "Volver al contenido",
|
||||||
"zoomIn": "Acercarse",
|
"zoomIn": "Acercarse",
|
||||||
"zoomOut": "Alejarse",
|
"zoomOut": "Alejarse",
|
||||||
"resetZoom": "Restablecer zoom",
|
"resetZoom": "Restablecer acercamiento",
|
||||||
"menu": "Menú",
|
"menu": "Menú",
|
||||||
"done": "Hecho",
|
"done": "Hecho",
|
||||||
"edit": "Editar",
|
"edit": "Editar",
|
||||||
@@ -118,26 +118,27 @@
|
|||||||
"redo": "Rehacer",
|
"redo": "Rehacer",
|
||||||
"roomDialog": "Iniciar colaboración en vivo",
|
"roomDialog": "Iniciar colaboración en vivo",
|
||||||
"createNewRoom": "Crear nueva sala",
|
"createNewRoom": "Crear nueva sala",
|
||||||
"toggleFullScreen": "Alternar pantalla completa",
|
"fullScreen": "Pantalla completa",
|
||||||
"toggleDarkMode": "Cambiar a modo oscuro",
|
"darkMode": "Modo oscuro",
|
||||||
"toggleZenMode": "Alternar modo zen",
|
"lightMode": "Modo claro",
|
||||||
|
"zenMode": "Modo Zen",
|
||||||
"exitZenMode": "Salir del modo Zen"
|
"exitZenMode": "Salir del modo Zen"
|
||||||
},
|
},
|
||||||
"alerts": {
|
"alerts": {
|
||||||
"clearReset": "Esto limpiará todo el lienzo. Estás seguro?",
|
"clearReset": "Esto limpiará todo el lienzo. Estás seguro?",
|
||||||
"couldNotCreateShareableLink": "No se pudo crear un enlace para compartir.",
|
"couldNotCreateShareableLink": "No se pudo crear un enlace para compartir.",
|
||||||
"couldNotCreateShareableLinkTooBig": "",
|
"couldNotCreateShareableLinkTooBig": "No se pudo crear el enlace para compartir: la escena es demasiado grande",
|
||||||
"couldNotLoadInvalidFile": "No se pudo cargar el archivo inválido",
|
"couldNotLoadInvalidFile": "No se pudo cargar el archivo no válido",
|
||||||
"importBackendFailed": "La importación falló.",
|
"importBackendFailed": "La importación falló.",
|
||||||
"cannotExportEmptyCanvas": "No se puede exportar un lienzo vació",
|
"cannotExportEmptyCanvas": "No se puede exportar un lienzo vació",
|
||||||
"couldNotCopyToClipboard": "No se ha podido copiar al portapapeles, intente usar Chrome como navegador.",
|
"couldNotCopyToClipboard": "No se ha podido copiar al portapapeles, intente usar Chrome como navegador.",
|
||||||
"decryptFailed": "No se pudieron descifrar los datos.",
|
"decryptFailed": "No se pudieron descifrar los datos.",
|
||||||
"uploadedSecurly": "La carga ha sido asegurada con cifrado de extremo a extremo, lo que significa que el servidor de Excalidraw y terceros no pueden leer el contenido.",
|
"uploadedSecurly": "La carga ha sido asegurada con cifrado de principio a fin, lo que significa que el servidor de Excalidraw y terceros no pueden leer el contenido.",
|
||||||
"loadSceneOverridePrompt": "Si carga este dibujo externo, reemplazará el que tiene. ¿Desea continuar?",
|
"loadSceneOverridePrompt": "Si carga este dibujo externo, reemplazará el que tiene. ¿Desea continuar?",
|
||||||
"errorLoadingLibrary": "Se ha producido un error al cargar la biblioteca de terceros.",
|
"errorLoadingLibrary": "Se ha producido un error al cargar la biblioteca de terceros.",
|
||||||
"confirmAddLibrary": "Esto añadirá {{numShapes}} forma(s) a tu biblioteca. ¿Estás seguro?",
|
"confirmAddLibrary": "Esto añadirá {{numShapes}} forma(s) a tu biblioteca. ¿Estás seguro?",
|
||||||
"imageDoesNotContainScene": "",
|
"imageDoesNotContainScene": "La importación de imágenes no está homologada en este momento.\n\n¿Deseas importar una escena? Esta imagen no parece contener ningún dato de escena. ¿Lo has activado durante la exportación?",
|
||||||
"cannotRestoreFromImage": ""
|
"cannotRestoreFromImage": "No se pudo restaurar la escena desde este archivo de imagen"
|
||||||
},
|
},
|
||||||
"toolBar": {
|
"toolBar": {
|
||||||
"selection": "Selección",
|
"selection": "Selección",
|
||||||
@@ -157,78 +158,85 @@
|
|||||||
"shapes": "Formas"
|
"shapes": "Formas"
|
||||||
},
|
},
|
||||||
"hints": {
|
"hints": {
|
||||||
"linearElement": "Haga clic para dibujar multiples puntos o arrastre para una sola línea",
|
"linearElement": "Haz clic para dibujar múltiples puntos, arrastrar para solo una línea",
|
||||||
"freeDraw": "Haz clic y arrastra, suelta al terminar",
|
"freeDraw": "Haz clic y arrastra, suelta al terminar",
|
||||||
"text": "Consejo: también puedes añadir texto haciendo doble clic en cualquier lugar con la herramienta de selección",
|
"text": "Consejo: también puedes añadir texto haciendo doble clic en cualquier lugar con la herramienta de selección",
|
||||||
"linearElementMulti": "Haga clic en el último punto o pulse Escape o Enter para finalizar",
|
"linearElementMulti": "Haz clic en el último punto o presiona Escape o Enter para finalizar",
|
||||||
|
"lockAngle": "Puedes restringir el ángulo manteniendo presionado el botón SHIFT",
|
||||||
"resize": "Para mantener las proporciones mantén SHIFT presionado mientras modificas el tamaño, \nmantén presionado ALT para modificar el tamaño desde el centro",
|
"resize": "Para mantener las proporciones mantén SHIFT presionado mientras modificas el tamaño, \nmantén presionado ALT para modificar el tamaño desde el centro",
|
||||||
"rotate": "Puede restringir los ángulos manteniendo presionado SHIFT mientras gira",
|
"rotate": "Puedes restringir los ángulos manteniendo presionado SHIFT mientras giras",
|
||||||
"lineEditor_info": "haga doble clic o pulse Enter para editar puntos",
|
"lineEditor_info": "Doble clic o pulse Enter para editar puntos",
|
||||||
"lineEditor_pointSelected": "Presione Suprimir para eliminar el punto, CtrlOrCmd+D para duplicarlo, o arrástrelo para moverlo",
|
"lineEditor_pointSelected": "Presione Suprimir para eliminar el punto, CtrlOrCmd+D para duplicarlo, o arrástrelo para moverlo",
|
||||||
"lineEditor_nothingSelected": "Seleccione un punto para mover o eliminar, o mantenga pulsado Alt y haga clic para añadir nuevos puntos"
|
"lineEditor_nothingSelected": "Selecciona un punto sea para mover o eliminar, o mantén pulsado Alt y haz clic para añadir nuevos puntos"
|
||||||
},
|
},
|
||||||
"canvasError": {
|
"canvasError": {
|
||||||
"cannotShowPreview": "",
|
"cannotShowPreview": "No se puede mostrar la vista previa",
|
||||||
"canvasTooBig": "",
|
"canvasTooBig": "El lienzo podría ser demasiado grande.",
|
||||||
"canvasTooBigTip": ""
|
"canvasTooBigTip": "Sugerencia: intenta acercar un poco más los elementos más lejanos."
|
||||||
},
|
},
|
||||||
"errorSplash": {
|
"errorSplash": {
|
||||||
"headingMain_pre": "Se encontró un error. Intente ",
|
"headingMain_pre": "Se encontró un error. Intente ",
|
||||||
"headingMain_button": "recargando la página.",
|
"headingMain_button": "recargando la página.",
|
||||||
"clearCanvasMessage": "Si la recarga no funciona, intente ",
|
"clearCanvasMessage": "Si la recarga no funciona, intente ",
|
||||||
"clearCanvasMessage_button": "limpiando el lienzo.",
|
"clearCanvasMessage_button": "limpiando el lienzo.",
|
||||||
"clearCanvasCaveat": " Esto resultará en la pérdida del trabajo ",
|
"clearCanvasCaveat": " Esto provocará la pérdida de su trabajo ",
|
||||||
"trackedToSentry_pre": "El error con el identificador ",
|
"trackedToSentry_pre": "El error con el identificador ",
|
||||||
"trackedToSentry_post": " fue rastreado en nuestro sistema.",
|
"trackedToSentry_post": " fue rastreado en nuestro sistema.",
|
||||||
"openIssueMessage_pre": "Fuimos muy cautelosos para no incluir la información de tu escena en el error. Si tu escena no es privada, por favor considera seguir nuestro ",
|
"openIssueMessage_pre": "Fuimos muy cautelosos de no incluir la información de tu escena en el error. Si tu escena no es privada, por favor considera seguir nuestro ",
|
||||||
"openIssueMessage_button": "seguimiento de errores.",
|
"openIssueMessage_button": "rastreador de errores.",
|
||||||
"openIssueMessage_post": " Por favor, incluya la siguiente información copiándola y pegándola en el issue de GitHub.",
|
"openIssueMessage_post": " Por favor, incluya la siguiente información copiándola y pegándola en el issue de GitHub.",
|
||||||
"sceneContent": "Contenido de la escena:"
|
"sceneContent": "Contenido de la escena:"
|
||||||
},
|
},
|
||||||
"roomDialog": {
|
"roomDialog": {
|
||||||
"desc_intro": "Puedes invitar a gente a tu escena actual para colaborar contigo.",
|
"desc_intro": "Puede invitar a otras personas a tu actual escena para que colaboren contigo.",
|
||||||
"desc_privacy": "No te preocupes, la sesión usa encriptación de extremo a extremo, por lo que todo lo que se dibuje se mantendrá privado. Ni siquiera nuestro servidor podrá ver lo que haces.",
|
"desc_privacy": "No te preocupes, la sesión usa encriptación de punta a punta, por lo que todo lo que se dibuje se mantendrá privadamente. Ni siquiera nuestro servidor podrá ver lo que haces.",
|
||||||
"button_startSession": "Iniciar sesión",
|
"button_startSession": "Iniciar sesión",
|
||||||
"button_stopSession": "Detener sesión",
|
"button_stopSession": "Detener sesión",
|
||||||
"desc_inProgressIntro": "La sesión de colaboración en vivo está ahora en progreso.",
|
"desc_inProgressIntro": "La sesión de colaboración en vivo está ahora en progreso.",
|
||||||
"desc_shareLink": "Comparte este enlace con tus colaboradores:",
|
"desc_shareLink": "Comparte este enlace con cualquier persona con quien quieras colaborar:",
|
||||||
"desc_exitSession": "Detener la sesión te desconectará de la sala, pero podrás seguir trabajando con la escena localmente. Ten en cuenta que esto no afectará a otras personas, y que seguirán siendo capaces de colaborar en su versión."
|
"desc_exitSession": "Detener la sesión te desconectará de la sala, pero podrás seguir trabajando con la escena en su computadora, esto es de modo local. Ten en cuenta que esto no afectará a otras personas, y que las mismas seguirán siendo capaces de colaborar en tu escena."
|
||||||
},
|
},
|
||||||
"errorDialog": {
|
"errorDialog": {
|
||||||
"title": "Error"
|
"title": "Error"
|
||||||
},
|
},
|
||||||
"shortcutsDialog": {
|
"helpDialog": {
|
||||||
"title": "Atajos del teclado",
|
|
||||||
"shapes": "Formas",
|
|
||||||
"or": "o",
|
|
||||||
"click": "hacer clic",
|
|
||||||
"drag": "arrastrar",
|
|
||||||
"curvedArrow": "Flecha curva",
|
|
||||||
"curvedLine": "Línea curva",
|
|
||||||
"editor": "Editor",
|
|
||||||
"view": "Vista",
|
|
||||||
"blog": "Lee nuestro blog",
|
"blog": "Lee nuestro blog",
|
||||||
"howto": "Sigue nuestras guías",
|
"click": "click",
|
||||||
|
"curvedArrow": "Flecha curvada",
|
||||||
|
"curvedLine": "Línea curva",
|
||||||
|
"documentation": "Documentación",
|
||||||
|
"drag": "arrastrar",
|
||||||
|
"editor": "Editor",
|
||||||
"github": "¿Has encontrado un problema? Envíalo",
|
"github": "¿Has encontrado un problema? Envíalo",
|
||||||
"textNewLine": "Añadir nueva línea (texto)",
|
"howto": "Siga nuestras guías",
|
||||||
|
"or": "o",
|
||||||
|
"preventBinding": "Evitar yuxtaposición de flechas",
|
||||||
|
"shapes": "Formas",
|
||||||
|
"shortcuts": "Atajos del teclado",
|
||||||
"textFinish": "Finalizar edición (texto)",
|
"textFinish": "Finalizar edición (texto)",
|
||||||
"zoomToFit": "Ajustar para mostrar todos los elementos",
|
"textNewLine": "Añadir nueva línea (texto)",
|
||||||
"zoomToSelection": "",
|
"title": "Ayuda",
|
||||||
"preventBinding": "Evitar enlace de flecha"
|
"view": "Vista",
|
||||||
|
"zoomToFit": "Ajustar la vista para mostrar todos los elementos",
|
||||||
|
"zoomToSelection": "Hacer zoom a la selección"
|
||||||
},
|
},
|
||||||
"encrypted": {
|
"encrypted": {
|
||||||
"tooltip": "Tus dibujos están cifrados de punto a punto, por lo que los servidores de Excalidraw nunca los verán."
|
"tooltip": "Tus dibujos están cifrados de punto a punto, por lo que los servidores de Excalidraw nunca los verán."
|
||||||
},
|
},
|
||||||
"stats": {
|
"stats": {
|
||||||
"angle": "",
|
"angle": "Ángulo",
|
||||||
"element": "",
|
"element": "Elemento",
|
||||||
"elements": "",
|
"elements": "Elementos",
|
||||||
"height": "",
|
"height": "Alto",
|
||||||
"scene": "",
|
"scene": "Escena",
|
||||||
"selected": "",
|
"selected": "Seleccionado",
|
||||||
"storage": "",
|
"storage": "Almacenamiento",
|
||||||
"title": "",
|
"title": "Estadísticas para nerds",
|
||||||
"total": "",
|
"total": "Total",
|
||||||
"width": ""
|
"width": "Ancho"
|
||||||
|
},
|
||||||
|
"toast": {
|
||||||
|
"copyStyles": "Estilos copiados.",
|
||||||
|
"copyToClipboardAsPng": "Copiado al portapapeles como PNG."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
{
|
{
|
||||||
"labels": {
|
"labels": {
|
||||||
"paste": "جای گذاری",
|
"paste": "جای گذاری",
|
||||||
|
"pasteCharts": "قراردادن نمودار",
|
||||||
"selectAll": "انتخاب همه",
|
"selectAll": "انتخاب همه",
|
||||||
"multiSelect": "یک ایتم به انتخاب شده ها اضافه کنید.",
|
"multiSelect": "یک ایتم به انتخاب شده ها اضافه کنید.",
|
||||||
"moveCanvas": "بوم را حرکت بدهید",
|
"moveCanvas": "بوم را حرکت بدهید",
|
||||||
"cut": "",
|
"cut": "جابجایی",
|
||||||
"copy": "کپی",
|
"copy": "کپی",
|
||||||
"copyAsPng": "کپی در حافطه موقت به صورت PNG",
|
"copyAsPng": "کپی در حافطه موقت به صورت PNG",
|
||||||
"copyAsSvg": "کپی در حافطه موقت به صورت SVG",
|
"copyAsSvg": "کپی در حافطه موقت به صورت SVG",
|
||||||
@@ -13,8 +14,8 @@
|
|||||||
"bringToFront": "جلو آوردن",
|
"bringToFront": "جلو آوردن",
|
||||||
"sendBackward": "پس فرستادن",
|
"sendBackward": "پس فرستادن",
|
||||||
"delete": "حذف",
|
"delete": "حذف",
|
||||||
"copyStyles": "کپی استایل",
|
"copyStyles": "کپی سبک",
|
||||||
"pasteStyles": "چسباندن استایل",
|
"pasteStyles": "جای گذاری سبک",
|
||||||
"stroke": "خط",
|
"stroke": "خط",
|
||||||
"background": "پس زمینه",
|
"background": "پس زمینه",
|
||||||
"fill": "رنگ آمیزی",
|
"fill": "رنگ آمیزی",
|
||||||
@@ -29,11 +30,11 @@
|
|||||||
"edges": "لبه ها",
|
"edges": "لبه ها",
|
||||||
"sharp": "تیز",
|
"sharp": "تیز",
|
||||||
"round": "دور",
|
"round": "دور",
|
||||||
"arrowheads": "",
|
"arrowheads": "سر پیکان",
|
||||||
"arrowhead_none": "",
|
"arrowhead_none": "هیچ کدام",
|
||||||
"arrowhead_arrow": "",
|
"arrowhead_arrow": "فلش",
|
||||||
"arrowhead_bar": "",
|
"arrowhead_bar": "میله ای",
|
||||||
"arrowhead_dot": "",
|
"arrowhead_dot": "نقطه",
|
||||||
"fontSize": "اندازه قلم",
|
"fontSize": "اندازه قلم",
|
||||||
"fontFamily": "نوع قلم",
|
"fontFamily": "نوع قلم",
|
||||||
"onlySelected": "فقط انتخاب شده ها",
|
"onlySelected": "فقط انتخاب شده ها",
|
||||||
@@ -60,7 +61,7 @@
|
|||||||
"architect": "معمار",
|
"architect": "معمار",
|
||||||
"artist": "هنرمند",
|
"artist": "هنرمند",
|
||||||
"cartoonist": "کارتونیست",
|
"cartoonist": "کارتونیست",
|
||||||
"fileTitle": "",
|
"fileTitle": "عنوان فایل",
|
||||||
"colorPicker": "انتخابگر رنگ",
|
"colorPicker": "انتخابگر رنگ",
|
||||||
"canvasBackground": "بوم",
|
"canvasBackground": "بوم",
|
||||||
"drawingCanvas": "بوم نقاشی",
|
"drawingCanvas": "بوم نقاشی",
|
||||||
@@ -69,19 +70,18 @@
|
|||||||
"language": "زبان",
|
"language": "زبان",
|
||||||
"createRoom": "اشتراک گذاری جلسه همکاری زنده",
|
"createRoom": "اشتراک گذاری جلسه همکاری زنده",
|
||||||
"duplicateSelection": "تکرار",
|
"duplicateSelection": "تکرار",
|
||||||
"untitled": "",
|
"untitled": "بدون عنوان",
|
||||||
"name": "نام",
|
"name": "نام",
|
||||||
"yourName": "نام شما",
|
"yourName": "نام شما",
|
||||||
"madeWithExcalidraw": "ساخته شده با Excalidraw",
|
"madeWithExcalidraw": "ساخته شده با Excalidraw",
|
||||||
"group": "گروهبندی انتخابها",
|
"group": "گروهبندی انتخابها",
|
||||||
"ungroup": "حذف گروهبندی انتخابها",
|
"ungroup": "حذف گروهبندی انتخابها",
|
||||||
"collaborators": "همکاران",
|
"collaborators": "همکاران",
|
||||||
"toggleGridMode": "سويچ خطوط راهنما",
|
"gridMode": "حالت شبکه ای",
|
||||||
"toggleStats": "",
|
|
||||||
"addToLibrary": "افزودن به کتابخانه",
|
"addToLibrary": "افزودن به کتابخانه",
|
||||||
"removeFromLibrary": "حذف از کتابخانه",
|
"removeFromLibrary": "حذف از کتابخانه",
|
||||||
"libraryLoadingMessage": "بارگذاری کتابخانه...",
|
"libraryLoadingMessage": "بارگذاری کتابخانه...",
|
||||||
"libraries": "",
|
"libraries": "مرور کردن کتابخانه ها",
|
||||||
"loadingScene": "باگذاری صحنه...",
|
"loadingScene": "باگذاری صحنه...",
|
||||||
"align": "تراز",
|
"align": "تراز",
|
||||||
"alignTop": "تراز به بالا",
|
"alignTop": "تراز به بالا",
|
||||||
@@ -118,9 +118,10 @@
|
|||||||
"redo": "از سر",
|
"redo": "از سر",
|
||||||
"roomDialog": "همکاری آنلاین را شروع کنید",
|
"roomDialog": "همکاری آنلاین را شروع کنید",
|
||||||
"createNewRoom": "ایجاد یک اتاق جدید",
|
"createNewRoom": "ایجاد یک اتاق جدید",
|
||||||
"toggleFullScreen": "تغییر به حالت تمام صفحه",
|
"fullScreen": "تمامصفحه",
|
||||||
"toggleDarkMode": "تغییر به حالت تاریک",
|
"darkMode": "حالت تیره",
|
||||||
"toggleZenMode": "تغییر به حالت تمرکز",
|
"lightMode": "حالت روشن",
|
||||||
|
"zenMode": "حالت ذن",
|
||||||
"exitZenMode": "خروج از حالت تمرکز"
|
"exitZenMode": "خروج از حالت تمرکز"
|
||||||
},
|
},
|
||||||
"alerts": {
|
"alerts": {
|
||||||
@@ -136,7 +137,7 @@
|
|||||||
"loadSceneOverridePrompt": "بارگزاری یک طرح خارجی محتوای فعلی رو از بین میبرد. آیا میخواهید ادامه دهید؟",
|
"loadSceneOverridePrompt": "بارگزاری یک طرح خارجی محتوای فعلی رو از بین میبرد. آیا میخواهید ادامه دهید؟",
|
||||||
"errorLoadingLibrary": "خطایی در بارگذاری کتابخانه ثالث وجود داشت.",
|
"errorLoadingLibrary": "خطایی در بارگذاری کتابخانه ثالث وجود داشت.",
|
||||||
"confirmAddLibrary": "{{numShapes}} از اشکال به کتابخانه شما اضافه خواهد شد. مطمئن هستید؟",
|
"confirmAddLibrary": "{{numShapes}} از اشکال به کتابخانه شما اضافه خواهد شد. مطمئن هستید؟",
|
||||||
"imageDoesNotContainScene": "فایل تصویر دارای محتوای صحنه نیست. آیا در هنگام خروجی گرفتن آن را فعال کردهاید؟",
|
"imageDoesNotContainScene": "وارد کردن تصویر در این لحظه امکان پذیر نمی باشد.\nآیا مایل به وارد کردن یک صحنه هستید؟ این تصویر به نظر می رسد که فاقد هرگونه اطلاعاتی مربوط به صحنه باشد. آیا این گزینه را در زمان وارد کردن تصویر فعال کرده اید؟",
|
||||||
"cannotRestoreFromImage": "صحنه را نمی توان از این فایل تصویری بازیابی کرد"
|
"cannotRestoreFromImage": "صحنه را نمی توان از این فایل تصویری بازیابی کرد"
|
||||||
},
|
},
|
||||||
"toolBar": {
|
"toolBar": {
|
||||||
@@ -161,6 +162,7 @@
|
|||||||
"freeDraw": "کلیک کنید و بکشید و وقتی کار تمام شد رها کنید",
|
"freeDraw": "کلیک کنید و بکشید و وقتی کار تمام شد رها کنید",
|
||||||
"text": "نکته: با برنامه انتخاب شده شما میتوانید با دوبار کلیک کردن هرکجا میخواید متن اظاف کنید",
|
"text": "نکته: با برنامه انتخاب شده شما میتوانید با دوبار کلیک کردن هرکجا میخواید متن اظاف کنید",
|
||||||
"linearElementMulti": "روی آخرین نقطه کلیک کنید یا کلید ESC را بزنید یا کلید Enter را بزنید برای اتمام کار",
|
"linearElementMulti": "روی آخرین نقطه کلیک کنید یا کلید ESC را بزنید یا کلید Enter را بزنید برای اتمام کار",
|
||||||
|
"lockAngle": "با نگه داشتن SHIFT هنگام چرخش می توانید زاویه ها را محدود کنید",
|
||||||
"resize": "می توانید با نگه داشتن SHIFT در هنگام تغییر اندازه، نسبت ها را محدود کنید،ALT را برای تغییر اندازه از مرکز نگه دارید",
|
"resize": "می توانید با نگه داشتن SHIFT در هنگام تغییر اندازه، نسبت ها را محدود کنید،ALT را برای تغییر اندازه از مرکز نگه دارید",
|
||||||
"rotate": "با نگه داشتن SHIFT هنگام چرخش می توانید زاویه ها را محدود کنید",
|
"rotate": "با نگه داشتن SHIFT هنگام چرخش می توانید زاویه ها را محدود کنید",
|
||||||
"lineEditor_info": "دوبار کلیک کنید یا Enter را فشار دهید تا نقاط را ویرایش کنید",
|
"lineEditor_info": "دوبار کلیک کنید یا Enter را فشار دهید تا نقاط را ویرایش کنید",
|
||||||
@@ -177,7 +179,7 @@
|
|||||||
"headingMain_button": "در حال بازنشانی صفحه.",
|
"headingMain_button": "در حال بازنشانی صفحه.",
|
||||||
"clearCanvasMessage": "اگر بازنشانی صفحه مشکل را حل نکرد این را امتحان کنید ",
|
"clearCanvasMessage": "اگر بازنشانی صفحه مشکل را حل نکرد این را امتحان کنید ",
|
||||||
"clearCanvasMessage_button": "در حال تمیز کردن بوم",
|
"clearCanvasMessage_button": "در حال تمیز کردن بوم",
|
||||||
"clearCanvasCaveat": " این باعث میشود کارهای شما از بین برود ",
|
"clearCanvasCaveat": " این باعث میشود کارهای شما ذخیره نشود ",
|
||||||
"trackedToSentry_pre": "خطا در شناسه ",
|
"trackedToSentry_pre": "خطا در شناسه ",
|
||||||
"trackedToSentry_post": " در سیستم ما رهگیری شد.",
|
"trackedToSentry_post": " در سیستم ما رهگیری شد.",
|
||||||
"openIssueMessage_pre": "ما خیلی محتاط هستیم که اطلاعات شما را در خطا قرار ندهیم. با این حال اگر اطلاعات شما خصوصی نیست لطفا پیگیری کنید ",
|
"openIssueMessage_pre": "ما خیلی محتاط هستیم که اطلاعات شما را در خطا قرار ندهیم. با این حال اگر اطلاعات شما خصوصی نیست لطفا پیگیری کنید ",
|
||||||
@@ -197,38 +199,44 @@
|
|||||||
"errorDialog": {
|
"errorDialog": {
|
||||||
"title": "خطا"
|
"title": "خطا"
|
||||||
},
|
},
|
||||||
"shortcutsDialog": {
|
"helpDialog": {
|
||||||
"title": "میانبرهای صفحه کلید",
|
"blog": "",
|
||||||
"shapes": "شکلها",
|
"click": "",
|
||||||
"or": "یا",
|
"curvedArrow": "",
|
||||||
"click": "کلیک",
|
"curvedLine": "",
|
||||||
"drag": "کشیدن",
|
"documentation": "",
|
||||||
"curvedArrow": "فلش خمیده",
|
"drag": "",
|
||||||
"curvedLine": "منحنی",
|
"editor": "",
|
||||||
"editor": "ویرایشگر",
|
"github": "",
|
||||||
"view": "نمایش",
|
"howto": "",
|
||||||
"blog": "بلاگ ما را بخوانید",
|
"or": "",
|
||||||
"howto": "راهنمای ما را دنبال کنید",
|
"preventBinding": "",
|
||||||
"github": "اشکالی می بینید؟ گزارش دهید",
|
"shapes": "",
|
||||||
"textNewLine": "یک خط جدید اضافه کنید (متن)",
|
"shortcuts": "",
|
||||||
"textFinish": "پایان ویرایش (متن)",
|
"textFinish": "",
|
||||||
"zoomToFit": "بزرگنمایی برای دیدن تمام آیتم ها",
|
"textNewLine": "",
|
||||||
"zoomToSelection": "",
|
"title": "",
|
||||||
"preventBinding": "مانع شدن از چسبیدن فلش ها"
|
"view": "",
|
||||||
|
"zoomToFit": "",
|
||||||
|
"zoomToSelection": ""
|
||||||
},
|
},
|
||||||
"encrypted": {
|
"encrypted": {
|
||||||
"tooltip": "شما در یک محیط رمزگزاری شده دو طرفه در حال طراحی هستید پس Excalidraw هرگز طرح های شما را نمیبند."
|
"tooltip": "شما در یک محیط رمزگزاری شده دو طرفه در حال طراحی هستید پس Excalidraw هرگز طرح های شما را نمیبند."
|
||||||
},
|
},
|
||||||
"stats": {
|
"stats": {
|
||||||
"angle": "",
|
"angle": "زاویه",
|
||||||
"element": "",
|
"element": "اِلمان",
|
||||||
"elements": "",
|
"elements": "اِلمان ها",
|
||||||
"height": "",
|
"height": "ارتفاع",
|
||||||
"scene": "",
|
"scene": "صحنه",
|
||||||
"selected": "",
|
"selected": "انتخاب شده",
|
||||||
"storage": "",
|
"storage": "حافظه",
|
||||||
"title": "",
|
"title": "آمار برای نردها",
|
||||||
"total": "",
|
"total": "مجموع",
|
||||||
"width": ""
|
"width": "عرض"
|
||||||
|
},
|
||||||
|
"toast": {
|
||||||
|
"copyStyles": "",
|
||||||
|
"copyToClipboardAsPng": ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
{
|
{
|
||||||
"labels": {
|
"labels": {
|
||||||
"paste": "Liitä",
|
"paste": "Liitä",
|
||||||
|
"pasteCharts": "Liitä kaaviot",
|
||||||
"selectAll": "Valitse kaikki",
|
"selectAll": "Valitse kaikki",
|
||||||
"multiSelect": "Lisää kohde valintaan",
|
"multiSelect": "Lisää kohde valintaan",
|
||||||
"moveCanvas": "Siirrä piirtoaluetta",
|
"moveCanvas": "Siirrä piirtoaluetta",
|
||||||
@@ -76,8 +77,7 @@
|
|||||||
"group": "Ryhmitä valinta",
|
"group": "Ryhmitä valinta",
|
||||||
"ungroup": "Pura valittu ryhmä",
|
"ungroup": "Pura valittu ryhmä",
|
||||||
"collaborators": "Yhteistyökumppanit",
|
"collaborators": "Yhteistyökumppanit",
|
||||||
"toggleGridMode": "Ruudukko päälle/pois",
|
"gridMode": "Ruudukkotila",
|
||||||
"toggleStats": "Nörttien tilastot päälle/pois",
|
|
||||||
"addToLibrary": "Lisää kirjastoon",
|
"addToLibrary": "Lisää kirjastoon",
|
||||||
"removeFromLibrary": "Poista kirjastosta",
|
"removeFromLibrary": "Poista kirjastosta",
|
||||||
"libraryLoadingMessage": "Ladataan kirjastoa...",
|
"libraryLoadingMessage": "Ladataan kirjastoa...",
|
||||||
@@ -118,9 +118,10 @@
|
|||||||
"redo": "Tee uudelleen",
|
"redo": "Tee uudelleen",
|
||||||
"roomDialog": "Aloita live-yhteistyö",
|
"roomDialog": "Aloita live-yhteistyö",
|
||||||
"createNewRoom": "Luo huone",
|
"createNewRoom": "Luo huone",
|
||||||
"toggleFullScreen": "Koko näytön tila päälle/pois",
|
"fullScreen": "Koko näyttö",
|
||||||
"toggleDarkMode": "Pimeä tila päälle/pois",
|
"darkMode": "Tumma tila",
|
||||||
"toggleZenMode": "Zen-tila päälle",
|
"lightMode": "Vaalea tila",
|
||||||
|
"zenMode": "Zen-tila",
|
||||||
"exitZenMode": "Poistu zen-tilasta"
|
"exitZenMode": "Poistu zen-tilasta"
|
||||||
},
|
},
|
||||||
"alerts": {
|
"alerts": {
|
||||||
@@ -136,7 +137,7 @@
|
|||||||
"loadSceneOverridePrompt": "Ulkopuolisen piirroksen lataaminen korvaa nykyisen sisältösi. Haluatko jatkaa?",
|
"loadSceneOverridePrompt": "Ulkopuolisen piirroksen lataaminen korvaa nykyisen sisältösi. Haluatko jatkaa?",
|
||||||
"errorLoadingLibrary": "Kolmannen osapuolen kirjastoa ladattaessa tapahtui virhe.",
|
"errorLoadingLibrary": "Kolmannen osapuolen kirjastoa ladattaessa tapahtui virhe.",
|
||||||
"confirmAddLibrary": "Tämä lisää {{numShapes}} muotoa kirjastoosi. Oletko varma?",
|
"confirmAddLibrary": "Tämä lisää {{numShapes}} muotoa kirjastoosi. Oletko varma?",
|
||||||
"imageDoesNotContainScene": "Kuvatiedosto ei sisällä teostietoja. Valitsitko sisällyttää ne tallennusvaiheessa?",
|
"imageDoesNotContainScene": "Kuvien lisääminen ei ole tällä hetkellä mahdollista.\n\nHaluatko tuoda piirroksen? Tämä kuva ei näytä sisältävän tarvittavia tietoja. Oletko ottanut piirrostietojen tallennuksen käyttöön viennin aikana?",
|
||||||
"cannotRestoreFromImage": "Teosta ei voitu palauttaa tästä kuvatiedostosta"
|
"cannotRestoreFromImage": "Teosta ei voitu palauttaa tästä kuvatiedostosta"
|
||||||
},
|
},
|
||||||
"toolBar": {
|
"toolBar": {
|
||||||
@@ -161,6 +162,7 @@
|
|||||||
"freeDraw": "Paina ja raahaa, päästä irti kun olet valmis",
|
"freeDraw": "Paina ja raahaa, päästä irti kun olet valmis",
|
||||||
"text": "Vinkki: voit myös lisätä tekstiä kaksoisnapsauttamalla mihin tahansa valintatyökalulla",
|
"text": "Vinkki: voit myös lisätä tekstiä kaksoisnapsauttamalla mihin tahansa valintatyökalulla",
|
||||||
"linearElementMulti": "Klikkaa viimeistä pistettä, paina Escape tai paina Enter lopettaaksesi",
|
"linearElementMulti": "Klikkaa viimeistä pistettä, paina Escape tai paina Enter lopettaaksesi",
|
||||||
|
"lockAngle": "Voit rajoittaa kulmaa pitämällä SHIFT pohjassa",
|
||||||
"resize": "Voit rajoittaa mittasuhteet pitämällä SHIFT pohjassa kun muutat kokoa, pidä ALT pohjassa muuttaaksesi kokoa keskipisteen suhteen",
|
"resize": "Voit rajoittaa mittasuhteet pitämällä SHIFT pohjassa kun muutat kokoa, pidä ALT pohjassa muuttaaksesi kokoa keskipisteen suhteen",
|
||||||
"rotate": "Voit rajoittaa kulman pitämällä SHIFT pohjassa pyörittäessäsi",
|
"rotate": "Voit rajoittaa kulman pitämällä SHIFT pohjassa pyörittäessäsi",
|
||||||
"lineEditor_info": "Kaksoisnapauta tai paina Enter muokataksesi pisteitä",
|
"lineEditor_info": "Kaksoisnapauta tai paina Enter muokataksesi pisteitä",
|
||||||
@@ -197,24 +199,26 @@
|
|||||||
"errorDialog": {
|
"errorDialog": {
|
||||||
"title": "Virhe"
|
"title": "Virhe"
|
||||||
},
|
},
|
||||||
"shortcutsDialog": {
|
"helpDialog": {
|
||||||
"title": "Pikanäppäimet",
|
"blog": "Lue blogiamme",
|
||||||
"shapes": "Muodot",
|
|
||||||
"or": "tai",
|
|
||||||
"click": "klikkaa",
|
"click": "klikkaa",
|
||||||
"drag": "vedä",
|
|
||||||
"curvedArrow": "Kaareva nuoli",
|
"curvedArrow": "Kaareva nuoli",
|
||||||
"curvedLine": "Kaareva viiva",
|
"curvedLine": "Kaareva viiva",
|
||||||
"editor": "Editori",
|
"documentation": "Käyttöohjeet",
|
||||||
"view": "Näkymä",
|
"drag": "vedä",
|
||||||
"blog": "Lue blogiamme",
|
"editor": "Muokkausohjelma",
|
||||||
"howto": "Seuraa oppaitamme",
|
|
||||||
"github": "Löysitkö ongelman? Kerro meille",
|
"github": "Löysitkö ongelman? Kerro meille",
|
||||||
"textNewLine": "Lisää uusi rivi (teksti)",
|
"howto": "Seuraa oppaitamme",
|
||||||
|
"or": "tai",
|
||||||
|
"preventBinding": "Estä nuolten kiinnitys",
|
||||||
|
"shapes": "Muodot",
|
||||||
|
"shortcuts": "Pikanäppäimet",
|
||||||
"textFinish": "Lopeta muokkaus (teksti)",
|
"textFinish": "Lopeta muokkaus (teksti)",
|
||||||
"zoomToFit": "Zoomaa kaikki elementit näkyviin",
|
"textNewLine": "Lisää uusi rivi (teksti)",
|
||||||
"zoomToSelection": "Zoomaa valintaan",
|
"title": "Ohjeet",
|
||||||
"preventBinding": "Estä nuolten sitominen"
|
"view": "Näkymä",
|
||||||
|
"zoomToFit": "Näytä kaikki elementit",
|
||||||
|
"zoomToSelection": "Näytä valinta"
|
||||||
},
|
},
|
||||||
"encrypted": {
|
"encrypted": {
|
||||||
"tooltip": "Piirroksesi ovat päästä päähän salattuja, joten Excalidrawin palvelimet eivät koskaan näe niitä."
|
"tooltip": "Piirroksesi ovat päästä päähän salattuja, joten Excalidrawin palvelimet eivät koskaan näe niitä."
|
||||||
@@ -230,5 +234,9 @@
|
|||||||
"title": "Nörttien tilastot",
|
"title": "Nörttien tilastot",
|
||||||
"total": "Yhteensä",
|
"total": "Yhteensä",
|
||||||
"width": "Leveys"
|
"width": "Leveys"
|
||||||
|
},
|
||||||
|
"toast": {
|
||||||
|
"copyStyles": "Tyylit kopioitu.",
|
||||||
|
"copyToClipboardAsPng": "Kopioitu leikepöydälle PNG-tiedostona."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
{
|
{
|
||||||
"labels": {
|
"labels": {
|
||||||
"paste": "Coller",
|
"paste": "Coller",
|
||||||
|
"pasteCharts": "Coller les graphiques",
|
||||||
"selectAll": "Tout sélectionner",
|
"selectAll": "Tout sélectionner",
|
||||||
"multiSelect": "Ajouter l'élément à la sélection",
|
"multiSelect": "Ajouter l'élément à la sélection",
|
||||||
"moveCanvas": "Déplacer le canvas",
|
"moveCanvas": "Déplacer le canevas",
|
||||||
"cut": "Couper",
|
"cut": "Couper",
|
||||||
"copy": "Copier",
|
"copy": "Copier",
|
||||||
"copyAsPng": "Copier dans le presse-papier en PNG",
|
"copyAsPng": "Copier dans le presse-papier en PNG",
|
||||||
@@ -18,7 +19,7 @@
|
|||||||
"stroke": "Contour",
|
"stroke": "Contour",
|
||||||
"background": "Arrière-plan",
|
"background": "Arrière-plan",
|
||||||
"fill": "Remplissage",
|
"fill": "Remplissage",
|
||||||
"strokeWidth": "Largeur du contour",
|
"strokeWidth": "Largeur du trait",
|
||||||
"strokeStyle": "Style du trait",
|
"strokeStyle": "Style du trait",
|
||||||
"strokeStyle_solid": "Plein",
|
"strokeStyle_solid": "Plein",
|
||||||
"strokeStyle_dashed": "Tirets",
|
"strokeStyle_dashed": "Tirets",
|
||||||
@@ -27,10 +28,10 @@
|
|||||||
"opacity": "Opacité",
|
"opacity": "Opacité",
|
||||||
"textAlign": "Alignement du texte",
|
"textAlign": "Alignement du texte",
|
||||||
"edges": "Angles",
|
"edges": "Angles",
|
||||||
"sharp": "Aigu",
|
"sharp": "Pointus",
|
||||||
"round": "Rond",
|
"round": "Arrondis",
|
||||||
"arrowheads": "Extrémités de ligne",
|
"arrowheads": "Extrémités de flèche",
|
||||||
"arrowhead_none": "Aucun",
|
"arrowhead_none": "Aucune",
|
||||||
"arrowhead_arrow": "Flèche",
|
"arrowhead_arrow": "Flèche",
|
||||||
"arrowhead_bar": "Barre",
|
"arrowhead_bar": "Barre",
|
||||||
"arrowhead_dot": "Point",
|
"arrowhead_dot": "Point",
|
||||||
@@ -40,8 +41,8 @@
|
|||||||
"withBackground": "Avec arrière-plan",
|
"withBackground": "Avec arrière-plan",
|
||||||
"exportEmbedScene": "Intégrer la scène au fichier exporté",
|
"exportEmbedScene": "Intégrer la scène au fichier exporté",
|
||||||
"exportEmbedScene_details": "Les données de scène seront enregistrées dans le fichier PNG/SVG exporté, afin que la scène puisse être restaurée à partir de celui-ci.\nCela augmentera la taille du fichier exporté.",
|
"exportEmbedScene_details": "Les données de scène seront enregistrées dans le fichier PNG/SVG exporté, afin que la scène puisse être restaurée à partir de celui-ci.\nCela augmentera la taille du fichier exporté.",
|
||||||
"addWatermark": "Ajouter \"Fabriqué avec Excalidraw\"",
|
"addWatermark": "Ajouter \"Fait avec Excalidraw\"",
|
||||||
"handDrawn": "Manuscrite",
|
"handDrawn": "À main levée",
|
||||||
"normal": "Normale",
|
"normal": "Normale",
|
||||||
"code": "Code",
|
"code": "Code",
|
||||||
"small": "Petit",
|
"small": "Petit",
|
||||||
@@ -62,8 +63,8 @@
|
|||||||
"cartoonist": "Caricaturiste",
|
"cartoonist": "Caricaturiste",
|
||||||
"fileTitle": "Titre du fichier",
|
"fileTitle": "Titre du fichier",
|
||||||
"colorPicker": "Sélecteur de couleur",
|
"colorPicker": "Sélecteur de couleur",
|
||||||
"canvasBackground": "Fond du canevas",
|
"canvasBackground": "Arrière-plan du canevas",
|
||||||
"drawingCanvas": "Canvas de dessin",
|
"drawingCanvas": "Zone de dessin",
|
||||||
"layers": "Calques",
|
"layers": "Calques",
|
||||||
"actions": "Actions",
|
"actions": "Actions",
|
||||||
"language": "Langue",
|
"language": "Langue",
|
||||||
@@ -72,16 +73,15 @@
|
|||||||
"untitled": "Sans-titre",
|
"untitled": "Sans-titre",
|
||||||
"name": "Nom",
|
"name": "Nom",
|
||||||
"yourName": "Votre nom",
|
"yourName": "Votre nom",
|
||||||
"madeWithExcalidraw": "Fabriqué avec Excalidraw",
|
"madeWithExcalidraw": "Fait avec Excalidraw",
|
||||||
"group": "Grouper la sélection",
|
"group": "Grouper la sélection",
|
||||||
"ungroup": "Dégrouper la sélection",
|
"ungroup": "Dégrouper la sélection",
|
||||||
"collaborators": "Collaborateurs",
|
"collaborators": "Collaborateurs",
|
||||||
"toggleGridMode": "Basculer le mode grille",
|
"gridMode": "Mode grille",
|
||||||
"toggleStats": "Activer/désactiver les stats pour les nerds",
|
|
||||||
"addToLibrary": "Ajouter à la bibliothèque",
|
"addToLibrary": "Ajouter à la bibliothèque",
|
||||||
"removeFromLibrary": "Supprimer de la bibliothèque",
|
"removeFromLibrary": "Supprimer de la bibliothèque",
|
||||||
"libraryLoadingMessage": "Chargement de la bibliothèque...",
|
"libraryLoadingMessage": "Chargement de la bibliothèque...",
|
||||||
"libraries": "Explorer les bibliothèques",
|
"libraries": "Parcourir les bibliothèques",
|
||||||
"loadingScene": "Chargement de la scène...",
|
"loadingScene": "Chargement de la scène...",
|
||||||
"align": "Aligner",
|
"align": "Aligner",
|
||||||
"alignTop": "Aligner en haut",
|
"alignTop": "Aligner en haut",
|
||||||
@@ -90,16 +90,16 @@
|
|||||||
"alignRight": "Aligner à droite",
|
"alignRight": "Aligner à droite",
|
||||||
"centerVertically": "Centrer verticalement",
|
"centerVertically": "Centrer verticalement",
|
||||||
"centerHorizontally": "Centrer horizontalement",
|
"centerHorizontally": "Centrer horizontalement",
|
||||||
"distributeHorizontally": "Répartir horizontalement",
|
"distributeHorizontally": "Distribuer horizontalement",
|
||||||
"distributeVertically": "Répartir verticalement"
|
"distributeVertically": "Distribuer verticalement"
|
||||||
},
|
},
|
||||||
"buttons": {
|
"buttons": {
|
||||||
"clearReset": "Effacer le canvas & réinitialiser la couleur d'arrière-plan",
|
"clearReset": "Réinitialiser le canevas",
|
||||||
"export": "Exporter",
|
"export": "Exporter",
|
||||||
"exportToPng": "Exporter en PNG",
|
"exportToPng": "Exporter en PNG",
|
||||||
"exportToSvg": "Exporter en SVG",
|
"exportToSvg": "Exporter en SVG",
|
||||||
"copyToClipboard": "Copier dans le presse-papier",
|
"copyToClipboard": "Copier dans le presse-papier",
|
||||||
"copyPngToClipboard": "Copier le PNG dans le presse-papier",
|
"copyPngToClipboard": "Copier le PNG vers le presse-papier",
|
||||||
"scale": "Échelle",
|
"scale": "Échelle",
|
||||||
"save": "Sauvegarder",
|
"save": "Sauvegarder",
|
||||||
"saveAs": "Enregistrer sous",
|
"saveAs": "Enregistrer sous",
|
||||||
@@ -116,27 +116,28 @@
|
|||||||
"edit": "Modifier",
|
"edit": "Modifier",
|
||||||
"undo": "Annuler",
|
"undo": "Annuler",
|
||||||
"redo": "Rétablir",
|
"redo": "Rétablir",
|
||||||
"roomDialog": "Démarrer le collaboration en temps réel",
|
"roomDialog": "Démarrer la collaboration en direct",
|
||||||
"createNewRoom": "Créer un nouveau salon",
|
"createNewRoom": "Créer une nouvelle salle",
|
||||||
"toggleFullScreen": "Activer/désactiver le mode plein écran",
|
"fullScreen": "Plein écran",
|
||||||
"toggleDarkMode": "Activer/désactiver le mode sombre",
|
"darkMode": "Mode sombre",
|
||||||
"toggleZenMode": "Activer/désactiver le mode zen",
|
"lightMode": "Mode clair",
|
||||||
|
"zenMode": "Mode zen",
|
||||||
"exitZenMode": "Quitter le mode zen"
|
"exitZenMode": "Quitter le mode zen"
|
||||||
},
|
},
|
||||||
"alerts": {
|
"alerts": {
|
||||||
"clearReset": "L'intégralité du canvas va être effacé. Êtes-vous sur ?",
|
"clearReset": "L'intégralité du canevas va être effacée. Êtes-vous sûr ?",
|
||||||
"couldNotCreateShareableLink": "Impossible de créer un lien de partage.",
|
"couldNotCreateShareableLink": "Impossible de créer un lien de partage.",
|
||||||
"couldNotCreateShareableLinkTooBig": "Impossible de créer un lien partageable : la scène est trop volumineuse",
|
"couldNotCreateShareableLinkTooBig": "Impossible de créer un lien partageable : la scène est trop volumineuse",
|
||||||
"couldNotLoadInvalidFile": "Impossible de charger un fichier invalide",
|
"couldNotLoadInvalidFile": "Impossible de charger un fichier invalide",
|
||||||
"importBackendFailed": "L'import depuis le backend a échoué.",
|
"importBackendFailed": "L'importation depuis le backend a échoué.",
|
||||||
"cannotExportEmptyCanvas": "Impossible d'exporter un canvas vide.",
|
"cannotExportEmptyCanvas": "Impossible d'exporter un canevas vide.",
|
||||||
"couldNotCopyToClipboard": "Impossible de copier dans le presse-papier. Essayez d'utiliser le navigateur Chrome.",
|
"couldNotCopyToClipboard": "Impossible de copier dans le presse-papier. Essayez d'utiliser le navigateur Chrome.",
|
||||||
"decryptFailed": "Les données n'ont pas pu être déchiffrées.",
|
"decryptFailed": "Les données n'ont pas pu être déchiffrées.",
|
||||||
"uploadedSecurly": "Le téléchargement a été sécurisé avec un chiffrement de bout en bout, ce qui signifie que ni Excalidraw ni personne d'autre ne peut en lire le contenu.",
|
"uploadedSecurly": "Le téléchargement a été sécurisé avec un chiffrement de bout en bout, ce qui signifie que ni Excalidraw ni personne d'autre ne peut en lire le contenu.",
|
||||||
"loadSceneOverridePrompt": "Le chargement d'un dessin externe remplacera votre contenu actuel. Souhaitez-vous continuer ?",
|
"loadSceneOverridePrompt": "Le chargement d'un dessin externe remplacera votre contenu actuel. Souhaitez-vous continuer ?",
|
||||||
"errorLoadingLibrary": "Une erreur s'est produite lors du chargement de la bibliothèque tierce.",
|
"errorLoadingLibrary": "Une erreur s'est produite lors du chargement de la bibliothèque tierce.",
|
||||||
"confirmAddLibrary": "Cela va ajouter {{numShapes}} forme(s) à votre bibliothèque. Êtes-vous sûr(e) ?",
|
"confirmAddLibrary": "Cela va ajouter {{numShapes}} forme(s) à votre bibliothèque. Êtes-vous sûr·e ?",
|
||||||
"imageDoesNotContainScene": "Le fichier image ne contient pas de données de scène. L'avez-vous activé lors de l'export ?",
|
"imageDoesNotContainScene": "L'importation d'images n'est pas prise en charge pour le moment.\n\nVouliez-vous importer une scène ? Cette image ne semble pas contenir de données de scène. Avez-vous activé cette option lors de l'exportation ?",
|
||||||
"cannotRestoreFromImage": "Impossible de restaurer la scène depuis ce fichier image"
|
"cannotRestoreFromImage": "Impossible de restaurer la scène depuis ce fichier image"
|
||||||
},
|
},
|
||||||
"toolBar": {
|
"toolBar": {
|
||||||
@@ -159,62 +160,65 @@
|
|||||||
"hints": {
|
"hints": {
|
||||||
"linearElement": "Cliquez pour démarrer plusieurs points, faites glisser pour une seule ligne",
|
"linearElement": "Cliquez pour démarrer plusieurs points, faites glisser pour une seule ligne",
|
||||||
"freeDraw": "Cliquez et faites glissez, relâchez quand vous avez terminé",
|
"freeDraw": "Cliquez et faites glissez, relâchez quand vous avez terminé",
|
||||||
"text": "Astuce : vous pouvez également ajouter du texte en double-cliquant n'importe où avec l'outil de sélection",
|
"text": "Astuce : vous pouvez aussi ajouter du texte en double-cliquant n'importe où avec l'outil de sélection",
|
||||||
"linearElementMulti": "Cliquez sur le dernier point ou appuyez sur Échap ou Entrée pour terminer",
|
"linearElementMulti": "Cliquez sur le dernier point ou appuyez sur Échap ou Entrée pour terminer",
|
||||||
"resize": "Vous pouvez conserver les proportions en maintenant la touche SHIFT pendant le redimensionnement,\nen maintenant la touche ALT pour redimensionner par rapport au centre",
|
"lockAngle": "Vous pouvez restreindre l'angle en maintenant MAJ",
|
||||||
"rotate": "Vous pouvez contraindre les angles en maintenant MAJ enfoncé pendant la rotation",
|
"resize": "Vous pouvez conserver les proportions en maintenant la touche MAJ pendant le redimensionnement,\nmaintenez la touche ALT pour redimensionner par rapport au centre",
|
||||||
|
"rotate": "Vous pouvez restreindre les angles en maintenant MAJ pendant la rotation",
|
||||||
"lineEditor_info": "Double-cliquez ou appuyez sur Entrée pour éditer les points",
|
"lineEditor_info": "Double-cliquez ou appuyez sur Entrée pour éditer les points",
|
||||||
"lineEditor_pointSelected": "Appuyez sur Supprimer pour supprimer le point, Ctrl ou Cmd+D pour le dupliquer, ou faites-le glisser pour le déplacer",
|
"lineEditor_pointSelected": "Appuyez sur Supprimer pour supprimer le point, Ctrl ou Cmd+D pour le dupliquer, ou faites-le glisser pour le déplacer",
|
||||||
"lineEditor_nothingSelected": "Sélectionnez un point à déplacer ou à supprimer, ou maintenez Alt enfoncé et cliquez pour ajouter de nouveaux points"
|
"lineEditor_nothingSelected": "Sélectionnez un point à déplacer ou supprimer, ou maintenez Alt et cliquez pour ajouter de nouveaux points"
|
||||||
},
|
},
|
||||||
"canvasError": {
|
"canvasError": {
|
||||||
"cannotShowPreview": "Impossible d’afficher l’aperçu",
|
"cannotShowPreview": "Impossible d’afficher l’aperçu",
|
||||||
"canvasTooBig": "Le tableau peut être trop grand.",
|
"canvasTooBig": "Le canevas est peut-être trop grand.",
|
||||||
"canvasTooBigTip": "Astuce : essayez de rapprocher un peu les éléments les plus éloignés ensemble."
|
"canvasTooBigTip": "Astuce : essayez de rapprocher un peu les éléments les plus éloignés."
|
||||||
},
|
},
|
||||||
"errorSplash": {
|
"errorSplash": {
|
||||||
"headingMain_pre": "Une erreur est survenue. Essayez ",
|
"headingMain_pre": "Une erreur est survenue. Essayez ",
|
||||||
"headingMain_button": "rechargement de la page.",
|
"headingMain_button": "de recharger la page.",
|
||||||
"clearCanvasMessage": "Si le rechargement ne résout pas l'erreur, essayez ",
|
"clearCanvasMessage": "Si le rechargement ne résout pas l'erreur, essayez ",
|
||||||
"clearCanvasMessage_button": "effacement du canvas.",
|
"clearCanvasMessage_button": "effacement du canevas.",
|
||||||
"clearCanvasCaveat": " Cela entraînera une perte du travail ",
|
"clearCanvasCaveat": " Cela entraînera une perte du travail ",
|
||||||
"trackedToSentry_pre": "L'erreur avec l'identifiant ",
|
"trackedToSentry_pre": "L'erreur avec l'identifiant ",
|
||||||
"trackedToSentry_post": " a été enregistrée dans notre système.",
|
"trackedToSentry_post": " a été enregistrée dans notre système.",
|
||||||
"openIssueMessage_pre": "Nous avons été très prudents de ne pas inclure les informations de votre scène dans l'erreur. Si votre scène n'est pas privée, veuillez envisager de poursuivre sur notre ",
|
"openIssueMessage_pre": "Nous avons fait très attention à ne pas inclure les informations de votre scène dans l'erreur. Si votre scène n'est pas privée, veuillez envisager de poursuivre sur notre ",
|
||||||
"openIssueMessage_button": "outil de suivi des bugs.",
|
"openIssueMessage_button": "outil de suivi des bugs.",
|
||||||
"openIssueMessage_post": " Veuillez inclure les informations ci-dessous en les copiant-collant dans le ticket GitHub.",
|
"openIssueMessage_post": " Veuillez inclure les informations ci-dessous en les copiant-collant dans le ticket GitHub.",
|
||||||
"sceneContent": "Contenu de la scène :"
|
"sceneContent": "Contenu de la scène :"
|
||||||
},
|
},
|
||||||
"roomDialog": {
|
"roomDialog": {
|
||||||
"desc_intro": "Vous pouvez inviter des personnes dans votre scène actuelle à collaborer avec vous.",
|
"desc_intro": "Vous pouvez inviter des personnes à collaborer avec vous sur votre scène actuelle.",
|
||||||
"desc_privacy": "Ne vous inquiétez pas, la session utilise le chiffrement de bout en bout, donc tout ce que vous dessinez restera privé. Même notre serveur ne sera pas en mesure de voir ce que vous faites.",
|
"desc_privacy": "Pas d'inquiétude, la session utilise le chiffrement de bout en bout, donc tout ce que vous dessinez restera privé. Même notre serveur ne pourra voir ce que vous faites.",
|
||||||
"button_startSession": "Démarrer la session",
|
"button_startSession": "Démarrer la session",
|
||||||
"button_stopSession": "Arrêter la session",
|
"button_stopSession": "Arrêter la session",
|
||||||
"desc_inProgressIntro": "La session de collaboration en direct est maintenant en cours.",
|
"desc_inProgressIntro": "La session de collaboration en direct est maintenant en cours.",
|
||||||
"desc_shareLink": "Partagez ce lien avec ceux avec qui vous souhaitez collaborer :",
|
"desc_shareLink": "Partagez ce lien avec les personnes avec lesquelles vous souhaitez collaborer :",
|
||||||
"desc_exitSession": "Arrêter la session vous déconnectera du salon, mais vous pourrez continuer à travailler avec la scène, localement. Notez que cela n'affectera pas les autres personnes, et ils seront toujours en mesure de collaborer sur leur version."
|
"desc_exitSession": "Arrêter la session vous déconnectera de la salle, mais vous pourrez continuer à travailler avec la scène, localement. Notez que cela n'affectera pas les autres personnes, et ils pourront toujours collaborer sur leur version."
|
||||||
},
|
},
|
||||||
"errorDialog": {
|
"errorDialog": {
|
||||||
"title": "Erreur"
|
"title": "Erreur"
|
||||||
},
|
},
|
||||||
"shortcutsDialog": {
|
"helpDialog": {
|
||||||
"title": "Raccourcis clavier",
|
"blog": "Lire notre blog",
|
||||||
"shapes": "Formes",
|
"click": "clic",
|
||||||
"or": "ou",
|
|
||||||
"click": "cliquer",
|
|
||||||
"drag": "glisser",
|
|
||||||
"curvedArrow": "Flèche courbée",
|
"curvedArrow": "Flèche courbée",
|
||||||
"curvedLine": "Ligne courbée",
|
"curvedLine": "Ligne courbée",
|
||||||
|
"documentation": "Documentation",
|
||||||
|
"drag": "glisser",
|
||||||
"editor": "Éditeur",
|
"editor": "Éditeur",
|
||||||
"view": "Afficher",
|
"github": "Problème trouvé ? Soumettre",
|
||||||
"blog": "Lisez notre blog",
|
|
||||||
"howto": "Suivez nos guides",
|
"howto": "Suivez nos guides",
|
||||||
"github": "Vous avez trouvé un problème ? Envoyer",
|
"or": "ou",
|
||||||
"textNewLine": "Ajouter une nouvelle ligne (texte)",
|
"preventBinding": "Empêcher la liaison de flèche",
|
||||||
|
"shapes": "Formes",
|
||||||
|
"shortcuts": "Raccourcis clavier",
|
||||||
"textFinish": "Terminer l'édition (texte)",
|
"textFinish": "Terminer l'édition (texte)",
|
||||||
"zoomToFit": "Zoomer pour visualiser tous les éléments",
|
"textNewLine": "Ajouter une nouvelle ligne (texte)",
|
||||||
"zoomToSelection": "Zoom sur la sélection",
|
"title": "Aide",
|
||||||
"preventBinding": "Empêcher la liaison de la flèche"
|
"view": "Affichage",
|
||||||
|
"zoomToFit": "Zoomer pour voir tous les éléments",
|
||||||
|
"zoomToSelection": "Zoomer sur la sélection"
|
||||||
},
|
},
|
||||||
"encrypted": {
|
"encrypted": {
|
||||||
"tooltip": "Vos dessins sont chiffrés de bout en bout, les serveurs d'Excalidraw ne les verront jamais."
|
"tooltip": "Vos dessins sont chiffrés de bout en bout, les serveurs d'Excalidraw ne les verront jamais."
|
||||||
@@ -227,8 +231,12 @@
|
|||||||
"scene": "Scène",
|
"scene": "Scène",
|
||||||
"selected": "Sélectionné",
|
"selected": "Sélectionné",
|
||||||
"storage": "Stockage",
|
"storage": "Stockage",
|
||||||
"title": "Stats pour nerds",
|
"title": "Stats pour les nerds",
|
||||||
"total": "Total",
|
"total": "Total",
|
||||||
"width": "Largeur"
|
"width": "Largeur"
|
||||||
|
},
|
||||||
|
"toast": {
|
||||||
|
"copyStyles": "Styles copiés.",
|
||||||
|
"copyToClipboardAsPng": "Copié vers le presse-papier en PNG."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
{
|
{
|
||||||
"labels": {
|
"labels": {
|
||||||
"paste": "הדבק",
|
"paste": "הדבק",
|
||||||
|
"pasteCharts": "הדבק גרפים",
|
||||||
"selectAll": "בחר הכל",
|
"selectAll": "בחר הכל",
|
||||||
"multiSelect": "",
|
"multiSelect": "הוסף אובייקט לבחירה",
|
||||||
"moveCanvas": "",
|
"moveCanvas": "הזז את הקנבס",
|
||||||
"cut": "",
|
"cut": "חתוך",
|
||||||
"copy": "העתק",
|
"copy": "העתק",
|
||||||
"copyAsPng": "העתק ללוח כ PNG",
|
"copyAsPng": "העתק ללוח כ PNG",
|
||||||
"copyAsSvg": "העתק ללוח כ SVG",
|
"copyAsSvg": "העתק ללוח כ SVG",
|
||||||
@@ -26,20 +27,20 @@
|
|||||||
"sloppiness": "סגנון",
|
"sloppiness": "סגנון",
|
||||||
"opacity": "אטימות",
|
"opacity": "אטימות",
|
||||||
"textAlign": "יישור טקסט",
|
"textAlign": "יישור טקסט",
|
||||||
"edges": "",
|
"edges": "קצוות",
|
||||||
"sharp": "",
|
"sharp": "חד",
|
||||||
"round": "",
|
"round": "עגול",
|
||||||
"arrowheads": "",
|
"arrowheads": "ראשי חצים",
|
||||||
"arrowhead_none": "",
|
"arrowhead_none": "ללא",
|
||||||
"arrowhead_arrow": "",
|
"arrowhead_arrow": "חץ",
|
||||||
"arrowhead_bar": "",
|
"arrowhead_bar": "שורה",
|
||||||
"arrowhead_dot": "",
|
"arrowhead_dot": "נקודה",
|
||||||
"fontSize": "גודל גופן",
|
"fontSize": "גודל גופן",
|
||||||
"fontFamily": "סוג הגופן",
|
"fontFamily": "סוג הגופן",
|
||||||
"onlySelected": "רק מה שנבחר",
|
"onlySelected": "רק מה שנבחר",
|
||||||
"withBackground": "עם רקע",
|
"withBackground": "עם רקע",
|
||||||
"exportEmbedScene": "",
|
"exportEmbedScene": "שלב את התצוגה בקובץ המיוצא",
|
||||||
"exportEmbedScene_details": "",
|
"exportEmbedScene_details": "מידע התצוגה יישמר לקובץ המיוצא מסוג PNG/SVG כך שיהיה ניתן לשחזרה ממנו.\nהפעולה תגדיל את גודל הקובץ המיוצא.",
|
||||||
"addWatermark": "הוסף \"נוצר באמצעות Excalidraw\"",
|
"addWatermark": "הוסף \"נוצר באמצעות Excalidraw\"",
|
||||||
"handDrawn": "כתב יד",
|
"handDrawn": "כתב יד",
|
||||||
"normal": "רגיל",
|
"normal": "רגיל",
|
||||||
@@ -60,7 +61,7 @@
|
|||||||
"architect": "ארכיטקט",
|
"architect": "ארכיטקט",
|
||||||
"artist": "אמן",
|
"artist": "אמן",
|
||||||
"cartoonist": "קריקטוריסט",
|
"cartoonist": "קריקטוריסט",
|
||||||
"fileTitle": "",
|
"fileTitle": "כותרת הקובץ",
|
||||||
"colorPicker": "בחירת צבע",
|
"colorPicker": "בחירת צבע",
|
||||||
"canvasBackground": "רקע הלוח",
|
"canvasBackground": "רקע הלוח",
|
||||||
"drawingCanvas": "לוח ציור",
|
"drawingCanvas": "לוח ציור",
|
||||||
@@ -69,29 +70,28 @@
|
|||||||
"language": "שפה",
|
"language": "שפה",
|
||||||
"createRoom": "התחל שיתוף פעולה חי",
|
"createRoom": "התחל שיתוף פעולה חי",
|
||||||
"duplicateSelection": "שכפל",
|
"duplicateSelection": "שכפל",
|
||||||
"untitled": "",
|
"untitled": "ללא כותרת",
|
||||||
"name": "שם",
|
"name": "שם",
|
||||||
"yourName": "שם",
|
"yourName": "שם",
|
||||||
"madeWithExcalidraw": "נוצר באמצעות Excalidraw",
|
"madeWithExcalidraw": "נוצר באמצעות Excalidraw",
|
||||||
"group": "אחד לקבוצה",
|
"group": "אחד לקבוצה",
|
||||||
"ungroup": "פרק קבוצה",
|
"ungroup": "פרק קבוצה",
|
||||||
"collaborators": "",
|
"collaborators": "שותפים",
|
||||||
"toggleGridMode": "",
|
"gridMode": "מצב רשת",
|
||||||
"toggleStats": "",
|
"addToLibrary": "הוסף לספריה",
|
||||||
"addToLibrary": "",
|
"removeFromLibrary": "הסר מספריה",
|
||||||
"removeFromLibrary": "",
|
"libraryLoadingMessage": "טוען ספריה...",
|
||||||
"libraryLoadingMessage": "",
|
"libraries": "דפדף בספריות",
|
||||||
"libraries": "",
|
"loadingScene": "טוען תצוגה...",
|
||||||
"loadingScene": "",
|
"align": "יישר",
|
||||||
"align": "",
|
"alignTop": "יישר למעלה",
|
||||||
"alignTop": "",
|
"alignBottom": "יישר למטה",
|
||||||
"alignBottom": "",
|
"alignLeft": "יישר לשמאל",
|
||||||
"alignLeft": "",
|
"alignRight": "יישר לימין",
|
||||||
"alignRight": "",
|
"centerVertically": "מרכז אנכית",
|
||||||
"centerVertically": "",
|
"centerHorizontally": "מרכז אופקית",
|
||||||
"centerHorizontally": "",
|
"distributeHorizontally": "חלוקה אופקית",
|
||||||
"distributeHorizontally": "",
|
"distributeVertically": "חלוקה אנכית"
|
||||||
"distributeVertically": ""
|
|
||||||
},
|
},
|
||||||
"buttons": {
|
"buttons": {
|
||||||
"clearReset": "אפס את הלוח",
|
"clearReset": "אפס את הלוח",
|
||||||
@@ -100,9 +100,9 @@
|
|||||||
"exportToSvg": "יצא ל SVG",
|
"exportToSvg": "יצא ל SVG",
|
||||||
"copyToClipboard": "העתק ללוח",
|
"copyToClipboard": "העתק ללוח",
|
||||||
"copyPngToClipboard": "העתק PNG ללוח",
|
"copyPngToClipboard": "העתק PNG ללוח",
|
||||||
"scale": "",
|
"scale": "קנה מידה",
|
||||||
"save": "שמור",
|
"save": "שמור",
|
||||||
"saveAs": "",
|
"saveAs": "שמירה בשם",
|
||||||
"load": "טען",
|
"load": "טען",
|
||||||
"getShareableLink": "קבל קישור לשיתוף",
|
"getShareableLink": "קבל קישור לשיתוף",
|
||||||
"close": "סגור",
|
"close": "סגור",
|
||||||
@@ -118,26 +118,27 @@
|
|||||||
"redo": "בצע מחדש",
|
"redo": "בצע מחדש",
|
||||||
"roomDialog": "התחל שיתוף חי",
|
"roomDialog": "התחל שיתוף חי",
|
||||||
"createNewRoom": "צור חדר",
|
"createNewRoom": "צור חדר",
|
||||||
"toggleFullScreen": "הפעל/הפסק מסך מלא",
|
"fullScreen": "מסך מלא",
|
||||||
"toggleDarkMode": "",
|
"darkMode": "מצב כהה",
|
||||||
"toggleZenMode": "התחל/הפסק מצב תפריט מרחף",
|
"lightMode": "מצב בהיר",
|
||||||
|
"zenMode": "מצב זן",
|
||||||
"exitZenMode": "צא ממצב תפריט מרחף"
|
"exitZenMode": "צא ממצב תפריט מרחף"
|
||||||
},
|
},
|
||||||
"alerts": {
|
"alerts": {
|
||||||
"clearReset": "פעולה זו תנקה את כל הלוח. אתה בטוח?",
|
"clearReset": "פעולה זו תנקה את כל הלוח. אתה בטוח?",
|
||||||
"couldNotCreateShareableLink": "לא ניתן לייצר לינק לשיתוף.",
|
"couldNotCreateShareableLink": "לא ניתן לייצר לינק לשיתוף.",
|
||||||
"couldNotCreateShareableLinkTooBig": "",
|
"couldNotCreateShareableLinkTooBig": "לא הצלחנו לייצר קישור לשיתוף: התצוגה גדולה מדי",
|
||||||
"couldNotLoadInvalidFile": "לא ניתן לטעון קובץ שאיננו תואם",
|
"couldNotLoadInvalidFile": "לא ניתן לטעון קובץ שאיננו תואם",
|
||||||
"importBackendFailed": "ייבוא מהשרת נכשל.",
|
"importBackendFailed": "ייבוא מהשרת נכשל.",
|
||||||
"cannotExportEmptyCanvas": "לא ניתן לייצא לוח ריק.",
|
"cannotExportEmptyCanvas": "לא ניתן לייצא לוח ריק.",
|
||||||
"couldNotCopyToClipboard": "לא ניתן להעתיק ללוח. נסה להשתמש בדפדפן Chrome.",
|
"couldNotCopyToClipboard": "לא ניתן להעתיק ללוח. נסה להשתמש בדפדפן Chrome.",
|
||||||
"decryptFailed": "לא ניתן לפענח מידע.",
|
"decryptFailed": "לא ניתן לפענח מידע.",
|
||||||
"uploadedSecurly": "ההעלאה הוצפנה מקצה לקצה, ולכן שרת Excalidraw וצד שלישי לא יכולים לקרוא את התוכן.",
|
"uploadedSecurly": "ההעלאה הוצפנה מקצה לקצה, ולכן שרת Excalidraw וצד שלישי לא יכולים לקרוא את התוכן.",
|
||||||
"loadSceneOverridePrompt": "",
|
"loadSceneOverridePrompt": "טעינה של ציור חיצוני תחליף את התוכן הקיים שלך. האם תרצה להמשיך?",
|
||||||
"errorLoadingLibrary": "",
|
"errorLoadingLibrary": "קרתה שגיאה בטעינת הספריה החיצונית.",
|
||||||
"confirmAddLibrary": "",
|
"confirmAddLibrary": "הפעולה תוסיף {{numShapes}} צורה(ות) לספריה שלך. האם אתה בטוח?",
|
||||||
"imageDoesNotContainScene": "",
|
"imageDoesNotContainScene": "אין תמיכה בייבוא תמונות כעת.\n\nהאם אתה רוצה לייבא תצוגה? התמונה הזאת אינה מכילה מידע על תצוגה. האם הפעלת את האפשרות הזאת בזמן הוצאת המידע?",
|
||||||
"cannotRestoreFromImage": ""
|
"cannotRestoreFromImage": "לא הצלחנו לשחזר את התצוגה מקובץ התמונה"
|
||||||
},
|
},
|
||||||
"toolBar": {
|
"toolBar": {
|
||||||
"selection": "בחירה",
|
"selection": "בחירה",
|
||||||
@@ -148,7 +149,7 @@
|
|||||||
"arrow": "חץ",
|
"arrow": "חץ",
|
||||||
"line": "קו",
|
"line": "קו",
|
||||||
"text": "טקסט",
|
"text": "טקסט",
|
||||||
"library": "",
|
"library": "ספריה",
|
||||||
"lock": "השאר את הכלי הנבחר פעיל גם לאחר סיום הציור"
|
"lock": "השאר את הכלי הנבחר פעיל גם לאחר סיום הציור"
|
||||||
},
|
},
|
||||||
"headings": {
|
"headings": {
|
||||||
@@ -159,18 +160,19 @@
|
|||||||
"hints": {
|
"hints": {
|
||||||
"linearElement": "הקלק בשביל לבחור נקודות מרובות, גרור בשביל קו בודד",
|
"linearElement": "הקלק בשביל לבחור נקודות מרובות, גרור בשביל קו בודד",
|
||||||
"freeDraw": "לחץ וגרור, שחרר כשסיימת",
|
"freeDraw": "לחץ וגרור, שחרר כשסיימת",
|
||||||
"text": "",
|
"text": "טיפ: אפשר להוסיף טקסט על ידי לחיצה כפולה בכל מקום עם כלי הבחירה",
|
||||||
"linearElementMulti": "הקלק על הנקודה האחרונה או הקש Escape או Enter לסיום",
|
"linearElementMulti": "הקלק על הנקודה האחרונה או הקש Escape או Enter לסיום",
|
||||||
|
"lockAngle": "אתה יכול להגביל זווית ע״י לחיצה על SHIFT",
|
||||||
"resize": "ניתן להגביל פרופורציות על ידי לחיצה על SHIFT תוך כדי שינוי גודל,\nהחזק ALT בשביל לשנות גודל ביחס למרכז",
|
"resize": "ניתן להגביל פרופורציות על ידי לחיצה על SHIFT תוך כדי שינוי גודל,\nהחזק ALT בשביל לשנות גודל ביחס למרכז",
|
||||||
"rotate": "ניתן להגביל זוויות על ידי לחיצה על SHIFT תוך כדי סיבוב",
|
"rotate": "ניתן להגביל זוויות על ידי לחיצה על SHIFT תוך כדי סיבוב",
|
||||||
"lineEditor_info": "",
|
"lineEditor_info": "לחץ לחיצה כפולה או אנטר לעריכת הנקודות",
|
||||||
"lineEditor_pointSelected": "",
|
"lineEditor_pointSelected": "לחץ על Delete להסרת נקודה, CtrlOrCmd+D לשכפל, או גרור להזזה",
|
||||||
"lineEditor_nothingSelected": ""
|
"lineEditor_nothingSelected": "בחר נקודה להזזה או הסרה, או החזק את כפתור Alt והקלק להוספת נקודות חדשות"
|
||||||
},
|
},
|
||||||
"canvasError": {
|
"canvasError": {
|
||||||
"cannotShowPreview": "",
|
"cannotShowPreview": "לא הצלחנו להציג את התצוגה המקדימה",
|
||||||
"canvasTooBig": "",
|
"canvasTooBig": "הקנבס עלול להיות גדול מדי.",
|
||||||
"canvasTooBigTip": ""
|
"canvasTooBigTip": "טיפ: נסה להזיז את האלמנטים הרחוקים ביותר מעט קרוב יותר יחד."
|
||||||
},
|
},
|
||||||
"errorSplash": {
|
"errorSplash": {
|
||||||
"headingMain_pre": "אירעה שגיאה. נסה ",
|
"headingMain_pre": "אירעה שגיאה. נסה ",
|
||||||
@@ -197,38 +199,44 @@
|
|||||||
"errorDialog": {
|
"errorDialog": {
|
||||||
"title": "שגיאה"
|
"title": "שגיאה"
|
||||||
},
|
},
|
||||||
"shortcutsDialog": {
|
"helpDialog": {
|
||||||
"title": "קיצורי מקלדת",
|
"blog": "",
|
||||||
"shapes": "צורות",
|
"click": "",
|
||||||
"or": "או",
|
"curvedArrow": "",
|
||||||
"click": "לחץ",
|
"curvedLine": "",
|
||||||
"drag": "גרור",
|
"documentation": "",
|
||||||
"curvedArrow": "חץ מעוקל",
|
"drag": "",
|
||||||
"curvedLine": "קו מעוקל",
|
"editor": "",
|
||||||
"editor": "עורך",
|
"github": "",
|
||||||
"view": "תצוגה",
|
"howto": "",
|
||||||
"blog": "קרא את הבלוג שלנו",
|
"or": "",
|
||||||
"howto": "עקוב אחר המדריכים שלנו",
|
"preventBinding": "",
|
||||||
"github": "מצאת בעיה? דווח",
|
"shapes": "",
|
||||||
"textNewLine": "הוסף שורה חדשה (טקסט)",
|
"shortcuts": "",
|
||||||
"textFinish": "סיים עריכה (טקסט)",
|
"textFinish": "",
|
||||||
"zoomToFit": "זום להתאמת כל האלמנטים למסך",
|
"textNewLine": "",
|
||||||
"zoomToSelection": "",
|
"title": "",
|
||||||
"preventBinding": ""
|
"view": "",
|
||||||
|
"zoomToFit": "",
|
||||||
|
"zoomToSelection": ""
|
||||||
},
|
},
|
||||||
"encrypted": {
|
"encrypted": {
|
||||||
"tooltip": "הרישומים שלך מוצפנים מקצה לקצה כך שהשרתים של Excalidraw לא יראו אותם לעולם."
|
"tooltip": "הרישומים שלך מוצפנים מקצה לקצה כך שהשרתים של Excalidraw לא יראו אותם לעולם."
|
||||||
},
|
},
|
||||||
"stats": {
|
"stats": {
|
||||||
"angle": "",
|
"angle": "זווית",
|
||||||
"element": "",
|
"element": "אלמנט",
|
||||||
"elements": "",
|
"elements": "אלמנטים",
|
||||||
"height": "",
|
"height": "גובה",
|
||||||
"scene": "",
|
"scene": "תצוגה",
|
||||||
"selected": "",
|
"selected": "נבחר/ים",
|
||||||
"storage": "",
|
"storage": "אחסון",
|
||||||
"title": "",
|
"title": "סטטיסטיקות לחנונים",
|
||||||
"total": "",
|
"total": "סה״כ",
|
||||||
"width": ""
|
"width": "רוחב"
|
||||||
|
},
|
||||||
|
"toast": {
|
||||||
|
"copyStyles": "",
|
||||||
|
"copyToClipboardAsPng": ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
{
|
{
|
||||||
"labels": {
|
"labels": {
|
||||||
"paste": "चिपकाएँ",
|
"paste": "चिपकाएँ",
|
||||||
|
"pasteCharts": "चार्ट चिपकाएँ",
|
||||||
"selectAll": "सभी चुनें",
|
"selectAll": "सभी चुनें",
|
||||||
"multiSelect": "आकार को चयन में जोड़ें",
|
"multiSelect": "आकार को चयन में जोड़ें",
|
||||||
"moveCanvas": "कैनवास को स्थानांतरित करें",
|
"moveCanvas": "कैनवास को स्थानांतरित करें",
|
||||||
@@ -29,17 +30,17 @@
|
|||||||
"edges": "किनारा",
|
"edges": "किनारा",
|
||||||
"sharp": "नुकीला",
|
"sharp": "नुकीला",
|
||||||
"round": "गोल",
|
"round": "गोल",
|
||||||
"arrowheads": "",
|
"arrowheads": "तीर शीर्ष",
|
||||||
"arrowhead_none": "",
|
"arrowhead_none": "कोई भी नहीं",
|
||||||
"arrowhead_arrow": "तीर",
|
"arrowhead_arrow": "तीर",
|
||||||
"arrowhead_bar": "",
|
"arrowhead_bar": "बार",
|
||||||
"arrowhead_dot": "",
|
"arrowhead_dot": "बिंदु",
|
||||||
"fontSize": "फ़ॉन्ट का आकार",
|
"fontSize": "फ़ॉन्ट का आकार",
|
||||||
"fontFamily": "फ़ॉन्ट का परिवार",
|
"fontFamily": "फ़ॉन्ट का परिवार",
|
||||||
"onlySelected": "केवल चयनित",
|
"onlySelected": "केवल चयनित",
|
||||||
"withBackground": "बैकग्राउंड के साथ",
|
"withBackground": "बैकग्राउंड के साथ",
|
||||||
"exportEmbedScene": "",
|
"exportEmbedScene": "निर्यात एम्बेड दृश्य",
|
||||||
"exportEmbedScene_details": "",
|
"exportEmbedScene_details": "निर्यात एम्बेड दृश्य विवरण",
|
||||||
"addWatermark": "ऐड \"मेड विथ एक्सकैलिडराव\"",
|
"addWatermark": "ऐड \"मेड विथ एक्सकैलिडराव\"",
|
||||||
"handDrawn": "हाथ से बनाया हुआ",
|
"handDrawn": "हाथ से बनाया हुआ",
|
||||||
"normal": "साधारण",
|
"normal": "साधारण",
|
||||||
@@ -60,7 +61,7 @@
|
|||||||
"architect": "वास्तुकार",
|
"architect": "वास्तुकार",
|
||||||
"artist": "कलाकार",
|
"artist": "कलाकार",
|
||||||
"cartoonist": "व्यंग्य चित्रकार",
|
"cartoonist": "व्यंग्य चित्रकार",
|
||||||
"fileTitle": "",
|
"fileTitle": "फ़ाइल का शीर्षक",
|
||||||
"colorPicker": "रंग चयन",
|
"colorPicker": "रंग चयन",
|
||||||
"canvasBackground": "कैनवास बैकग्राउंड",
|
"canvasBackground": "कैनवास बैकग्राउंड",
|
||||||
"drawingCanvas": "कैनवास बना रहे हैं",
|
"drawingCanvas": "कैनवास बना रहे हैं",
|
||||||
@@ -76,22 +77,21 @@
|
|||||||
"group": "समूह चयन",
|
"group": "समूह चयन",
|
||||||
"ungroup": "समूह चयन असमूहीकृत करें",
|
"ungroup": "समूह चयन असमूहीकृत करें",
|
||||||
"collaborators": "सहयोगी",
|
"collaborators": "सहयोगी",
|
||||||
"toggleGridMode": "टॉगल ग्रिड मोड",
|
"gridMode": "ग्रिड मॉड",
|
||||||
"toggleStats": "",
|
|
||||||
"addToLibrary": "लाइब्रेरी से जोड़ें",
|
"addToLibrary": "लाइब्रेरी से जोड़ें",
|
||||||
"removeFromLibrary": "लाइब्रेरी से निकालें",
|
"removeFromLibrary": "लाइब्रेरी से निकालें",
|
||||||
"libraryLoadingMessage": "लाइब्रेरी खुल रही है",
|
"libraryLoadingMessage": "लाइब्रेरी खुल रही है",
|
||||||
"libraries": "",
|
"libraries": "लाइब्रेरी ब्राउज़ करें",
|
||||||
"loadingScene": "दृश्य खुल रहा है",
|
"loadingScene": "दृश्य खुल रहा है",
|
||||||
"align": "",
|
"align": "संरेखित करें",
|
||||||
"alignTop": "",
|
"alignTop": "ऊपर संरेखित करें",
|
||||||
"alignBottom": "",
|
"alignBottom": "नीचे संरेखित करें",
|
||||||
"alignLeft": "",
|
"alignLeft": "बायें संरेखित करें",
|
||||||
"alignRight": "",
|
"alignRight": "दायें संरेखित करें",
|
||||||
"centerVertically": "",
|
"centerVertically": "लंबवत केन्द्रित",
|
||||||
"centerHorizontally": "",
|
"centerHorizontally": "क्षैतिज केन्द्रित",
|
||||||
"distributeHorizontally": "",
|
"distributeHorizontally": "क्षैतिज रूप से वितरित करें",
|
||||||
"distributeVertically": ""
|
"distributeVertically": "खड़ी रूप से वितरित करें"
|
||||||
},
|
},
|
||||||
"buttons": {
|
"buttons": {
|
||||||
"clearReset": "कैनवास रीसेट करें",
|
"clearReset": "कैनवास रीसेट करें",
|
||||||
@@ -100,7 +100,7 @@
|
|||||||
"exportToSvg": "Svg के रूप में निर्यात करे",
|
"exportToSvg": "Svg के रूप में निर्यात करे",
|
||||||
"copyToClipboard": "क्लिपबोर्ड पर प्रतिलिपि बनाएँ",
|
"copyToClipboard": "क्लिपबोर्ड पर प्रतिलिपि बनाएँ",
|
||||||
"copyPngToClipboard": "क्लिपबोर्ड पर कॉपी करें,पीएनजी के रूप में",
|
"copyPngToClipboard": "क्लिपबोर्ड पर कॉपी करें,पीएनजी के रूप में",
|
||||||
"scale": "",
|
"scale": "पैमाना",
|
||||||
"save": "सहेजें",
|
"save": "सहेजें",
|
||||||
"saveAs": "सेव करे इस तरह",
|
"saveAs": "सेव करे इस तरह",
|
||||||
"load": "लोड करें",
|
"load": "लोड करें",
|
||||||
@@ -118,15 +118,16 @@
|
|||||||
"redo": "फिर से करें",
|
"redo": "फिर से करें",
|
||||||
"roomDialog": "लाइव सहयोग शुरू करें",
|
"roomDialog": "लाइव सहयोग शुरू करें",
|
||||||
"createNewRoom": "एक नया कमरा बनाएं",
|
"createNewRoom": "एक नया कमरा बनाएं",
|
||||||
"toggleFullScreen": "पूर्णस्क्रीन चालू करें",
|
"fullScreen": "पूरी स्क्रीन",
|
||||||
"toggleDarkMode": "",
|
"darkMode": "डार्क मोड",
|
||||||
"toggleZenMode": "टॉगल ज़ेन मोड",
|
"lightMode": "लाइट मोड",
|
||||||
|
"zenMode": "ज़ेन मोड",
|
||||||
"exitZenMode": "जेन मोड से बाहर निकलें"
|
"exitZenMode": "जेन मोड से बाहर निकलें"
|
||||||
},
|
},
|
||||||
"alerts": {
|
"alerts": {
|
||||||
"clearReset": "इससे पूरा कैनवास साफ हो जाएगा। क्या आपको यकीन है?",
|
"clearReset": "इससे पूरा कैनवास साफ हो जाएगा। क्या आपको यकीन है?",
|
||||||
"couldNotCreateShareableLink": "साझा करने योग्य लिंक नहीं बनाया जा सका।",
|
"couldNotCreateShareableLink": "साझा करने योग्य लिंक नहीं बनाया जा सका।",
|
||||||
"couldNotCreateShareableLinkTooBig": "",
|
"couldNotCreateShareableLinkTooBig": "लिंक शेयर नहीं कर सकता: दृश्य बहुत बड़ा",
|
||||||
"couldNotLoadInvalidFile": "अमान्य फ़ाइल लोड नहीं की जा सकी",
|
"couldNotLoadInvalidFile": "अमान्य फ़ाइल लोड नहीं की जा सकी",
|
||||||
"importBackendFailed": "बैकएंड से आयात करना विफल रहा।",
|
"importBackendFailed": "बैकएंड से आयात करना विफल रहा।",
|
||||||
"cannotExportEmptyCanvas": "खाली कैनवास निर्यात नहीं कर सकता।",
|
"cannotExportEmptyCanvas": "खाली कैनवास निर्यात नहीं कर सकता।",
|
||||||
@@ -134,10 +135,10 @@
|
|||||||
"decryptFailed": "डेटा को डिक्रिप्ट नहीं किया जा सका।",
|
"decryptFailed": "डेटा को डिक्रिप्ट नहीं किया जा सका।",
|
||||||
"uploadedSecurly": "अपलोड को एंड-टू-एंड एन्क्रिप्शन के साथ सुरक्षित किया गया है, जिसका मतलब है कि एक्सक्लूसिव सर्वर और थर्ड पार्टी कंटेंट नहीं पढ़ सकते हैं।",
|
"uploadedSecurly": "अपलोड को एंड-टू-एंड एन्क्रिप्शन के साथ सुरक्षित किया गया है, जिसका मतलब है कि एक्सक्लूसिव सर्वर और थर्ड पार्टी कंटेंट नहीं पढ़ सकते हैं।",
|
||||||
"loadSceneOverridePrompt": "लोड हो रहा है बाहरी ड्राइंग आपके मौजूदा सामग्री को बदल देगा। क्या आप जारी रखना चाहते हैं?",
|
"loadSceneOverridePrompt": "लोड हो रहा है बाहरी ड्राइंग आपके मौजूदा सामग्री को बदल देगा। क्या आप जारी रखना चाहते हैं?",
|
||||||
"errorLoadingLibrary": "",
|
"errorLoadingLibrary": "लाइब्रेरी लोड करने में त्रुटि",
|
||||||
"confirmAddLibrary": "",
|
"confirmAddLibrary": "लाइब्रेरी जोड़ें पुष्टि करें आकार संख्या",
|
||||||
"imageDoesNotContainScene": "",
|
"imageDoesNotContainScene": "दृश्य में छवि नहीं है",
|
||||||
"cannotRestoreFromImage": ""
|
"cannotRestoreFromImage": "छवि फ़ाइल बहाल दृश्य नहीं है"
|
||||||
},
|
},
|
||||||
"toolBar": {
|
"toolBar": {
|
||||||
"selection": "चयन",
|
"selection": "चयन",
|
||||||
@@ -148,7 +149,7 @@
|
|||||||
"arrow": "तीर",
|
"arrow": "तीर",
|
||||||
"line": "रेखा",
|
"line": "रेखा",
|
||||||
"text": "पाठ",
|
"text": "पाठ",
|
||||||
"library": "",
|
"library": "लाइब्रेरी",
|
||||||
"lock": "ड्राइंग के बाद चयनित टूल को सक्रिय रखें"
|
"lock": "ड्राइंग के बाद चयनित टूल को सक्रिय रखें"
|
||||||
},
|
},
|
||||||
"headings": {
|
"headings": {
|
||||||
@@ -159,8 +160,9 @@
|
|||||||
"hints": {
|
"hints": {
|
||||||
"linearElement": "कई बिंदुओं को शुरू करने के लिए क्लिक करें, सिंगल लाइन के लिए खींचें",
|
"linearElement": "कई बिंदुओं को शुरू करने के लिए क्लिक करें, सिंगल लाइन के लिए खींचें",
|
||||||
"freeDraw": "क्लिक करें और खींचें। समाप्त करने के लिए, छोड़ो",
|
"freeDraw": "क्लिक करें और खींचें। समाप्त करने के लिए, छोड़ो",
|
||||||
"text": "",
|
"text": "आप चयन टूल से कहीं भी डबल-क्लिक करके टेक्स्ट जोड़ सकते हैं",
|
||||||
"linearElementMulti": "अंतिम बिंदु पर क्लिक करें या समाप्त होने के लिए एस्केप या एंटर दबाएं",
|
"linearElementMulti": "अंतिम बिंदु पर क्लिक करें या समाप्त होने के लिए एस्केप या एंटर दबाएं",
|
||||||
|
"lockAngle": "आप घूर्णन करते समय SHIFT पकड़कर कोणों को मोड़ सकते हैं",
|
||||||
"resize": "आकार बदलते समय आप SHIFT को पकड़ कर अनुपात में कमी कर सकते हैं,\nकेंद्र से आकार बदलने के लिए ALT दबाए रखें",
|
"resize": "आकार बदलते समय आप SHIFT को पकड़ कर अनुपात में कमी कर सकते हैं,\nकेंद्र से आकार बदलने के लिए ALT दबाए रखें",
|
||||||
"rotate": "आप घूर्णन करते समय SHIFT पकड़कर कोणों को विवश कर सकते हैं",
|
"rotate": "आप घूर्णन करते समय SHIFT पकड़कर कोणों को विवश कर सकते हैं",
|
||||||
"lineEditor_info": "बिंदुओं को संपादित करने के लिए Enter पर डबल-क्लिक करें या दबाएँ",
|
"lineEditor_info": "बिंदुओं को संपादित करने के लिए Enter पर डबल-क्लिक करें या दबाएँ",
|
||||||
@@ -168,9 +170,9 @@
|
|||||||
"lineEditor_nothingSelected": "स्थानांतरित करने या हटाने के लिए एक बिंदु का चयन करें, या Alt दबाए रखें और नए बिंदुओं को जोड़ने के लिए क्लिक करें"
|
"lineEditor_nothingSelected": "स्थानांतरित करने या हटाने के लिए एक बिंदु का चयन करें, या Alt दबाए रखें और नए बिंदुओं को जोड़ने के लिए क्लिक करें"
|
||||||
},
|
},
|
||||||
"canvasError": {
|
"canvasError": {
|
||||||
"cannotShowPreview": "",
|
"cannotShowPreview": "पूर्वावलोकन नहीं दिखा सकते हैं",
|
||||||
"canvasTooBig": "",
|
"canvasTooBig": "कैनवास बहुत बड़ा",
|
||||||
"canvasTooBigTip": ""
|
"canvasTooBigTip": "कैनवास बहुत बड़ा टिप"
|
||||||
},
|
},
|
||||||
"errorSplash": {
|
"errorSplash": {
|
||||||
"headingMain_pre": "एक त्रुटि का सामना करना पड़ा। प्रयत्न ",
|
"headingMain_pre": "एक त्रुटि का सामना करना पड़ा। प्रयत्न ",
|
||||||
@@ -197,38 +199,44 @@
|
|||||||
"errorDialog": {
|
"errorDialog": {
|
||||||
"title": "गलती"
|
"title": "गलती"
|
||||||
},
|
},
|
||||||
"shortcutsDialog": {
|
"helpDialog": {
|
||||||
"title": "कीबोर्ड के शॉर्टकट्स",
|
"blog": "",
|
||||||
"shapes": "आकृतियाँ",
|
"click": "",
|
||||||
"or": "या",
|
"curvedArrow": "",
|
||||||
"click": "क्लिक करें",
|
"curvedLine": "",
|
||||||
"drag": "खींचें",
|
"documentation": "",
|
||||||
"curvedArrow": "घुमावदार तीर",
|
"drag": "",
|
||||||
"curvedLine": "घुमावदार रेखा",
|
"editor": "",
|
||||||
"editor": "संपादक",
|
"github": "",
|
||||||
"view": "दृश्य",
|
"howto": "",
|
||||||
"blog": "हमारा ब्लॉग पढे",
|
"or": "",
|
||||||
"howto": "हमारे गाइड का पालन करें",
|
"preventBinding": "",
|
||||||
"github": "एक मुद्दा मिला? प्रस्तुत करे",
|
"shapes": "",
|
||||||
"textNewLine": "नई पंक्ति (पाठ) जोड़ें",
|
"shortcuts": "",
|
||||||
"textFinish": "संपादन समाप्त करें (पाठ)",
|
"textFinish": "",
|
||||||
"zoomToFit": "सभी तत्वों को फिट करने के लिए ज़ूम करें",
|
"textNewLine": "",
|
||||||
"zoomToSelection": "",
|
"title": "",
|
||||||
"preventBinding": ""
|
"view": "",
|
||||||
|
"zoomToFit": "",
|
||||||
|
"zoomToSelection": ""
|
||||||
},
|
},
|
||||||
"encrypted": {
|
"encrypted": {
|
||||||
"tooltip": "आपके चित्र अंत-से-अंत एन्क्रिप्टेड हैं, इसलिए एक्सक्लूसिव्रॉव के सर्वर उन्हें कभी नहीं देखेंगे।"
|
"tooltip": "आपके चित्र अंत-से-अंत एन्क्रिप्टेड हैं, इसलिए एक्सक्लूसिव्रॉव के सर्वर उन्हें कभी नहीं देखेंगे।"
|
||||||
},
|
},
|
||||||
"stats": {
|
"stats": {
|
||||||
"angle": "कोण",
|
"angle": "कोण",
|
||||||
"element": "",
|
"element": "एलिमेंट",
|
||||||
"elements": "",
|
"elements": "एलिमेंट",
|
||||||
"height": "ऊंचाई",
|
"height": "ऊंचाई",
|
||||||
"scene": "दृश्य",
|
"scene": "दृश्य",
|
||||||
"selected": "चयनित",
|
"selected": "चयनित",
|
||||||
"storage": "संग्रह",
|
"storage": "संग्रह",
|
||||||
"title": "",
|
"title": "बेवकूफ के लिए आँकड़े",
|
||||||
"total": "कुल",
|
"total": "कुल",
|
||||||
"width": "चौड़ाई"
|
"width": "चौड़ाई"
|
||||||
|
},
|
||||||
|
"toast": {
|
||||||
|
"copyStyles": "",
|
||||||
|
"copyToClipboardAsPng": ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,97 +1,97 @@
|
|||||||
{
|
{
|
||||||
"labels": {
|
"labels": {
|
||||||
"paste": "Beillesztés",
|
"paste": "Beillesztés",
|
||||||
|
"pasteCharts": "Grafikon beillesztése",
|
||||||
"selectAll": "Összes kijelölése",
|
"selectAll": "Összes kijelölése",
|
||||||
"multiSelect": "",
|
"multiSelect": "Elem hozzáadása a kijelöléshez",
|
||||||
"moveCanvas": "",
|
"moveCanvas": "Vászon mozgatása",
|
||||||
"cut": "",
|
"cut": "Kivágás",
|
||||||
"copy": "Másolás",
|
"copy": "Másolás",
|
||||||
"copyAsPng": "Vágólapra másolás mint PNG",
|
"copyAsPng": "Vágólapra másolás mint PNG",
|
||||||
"copyAsSvg": "Vágólapra másolás mint SVG",
|
"copyAsSvg": "Vágólapra másolás mint SVG",
|
||||||
"bringForward": "Előrébb hozás",
|
"bringForward": "Előrébb hozás",
|
||||||
"sendToBack": "",
|
"sendToBack": "Hátraküldés",
|
||||||
"bringToFront": "",
|
"bringToFront": "Előrehozás",
|
||||||
"sendBackward": "",
|
"sendBackward": "Hátrébb küldés",
|
||||||
"delete": "Törlés",
|
"delete": "Törlés",
|
||||||
"copyStyles": "",
|
"copyStyles": "Stílus másolása",
|
||||||
"pasteStyles": "",
|
"pasteStyles": "Stílus beillesztése",
|
||||||
"stroke": "Körvonal",
|
"stroke": "Körvonal",
|
||||||
"background": "Háttér",
|
"background": "Háttér",
|
||||||
"fill": "Kitöltés",
|
"fill": "Kitöltés",
|
||||||
"strokeWidth": "",
|
"strokeWidth": "Körvonal vastagsága",
|
||||||
"strokeStyle": "",
|
"strokeStyle": "Körvonal stílusa",
|
||||||
"strokeStyle_solid": "",
|
"strokeStyle_solid": "Kitöltött",
|
||||||
"strokeStyle_dashed": "",
|
"strokeStyle_dashed": "Szaggatott",
|
||||||
"strokeStyle_dotted": "",
|
"strokeStyle_dotted": "Pontozott",
|
||||||
"sloppiness": "Stílus",
|
"sloppiness": "Stílus",
|
||||||
"opacity": "Áttetszőség",
|
"opacity": "Áttetszőség",
|
||||||
"textAlign": "",
|
"textAlign": "Szöveg igazítása",
|
||||||
"edges": "",
|
"edges": "Szélek",
|
||||||
"sharp": "",
|
"sharp": "Éles",
|
||||||
"round": "",
|
"round": "Kerek",
|
||||||
"arrowheads": "",
|
"arrowheads": "Nyílhegyek",
|
||||||
"arrowhead_none": "",
|
"arrowhead_none": "Nincs",
|
||||||
"arrowhead_arrow": "",
|
"arrowhead_arrow": "Nyíl",
|
||||||
"arrowhead_bar": "",
|
"arrowhead_bar": "Oszlop",
|
||||||
"arrowhead_dot": "",
|
"arrowhead_dot": "Pont",
|
||||||
"fontSize": "",
|
"fontSize": "Betűméret",
|
||||||
"fontFamily": "",
|
"fontFamily": "Betűkészlet család",
|
||||||
"onlySelected": "Csak a kiválasztott",
|
"onlySelected": "Csak a kijelölt",
|
||||||
"withBackground": "Háttérrel együtt",
|
"withBackground": "Háttérrel",
|
||||||
"exportEmbedScene": "",
|
"exportEmbedScene": "Jelenet beágyazása az exportált fájlba",
|
||||||
"exportEmbedScene_details": "",
|
"exportEmbedScene_details": "A jelenetet leíró adatok hozzá lesznek adva a PNG/SVG fájlhoz, így a jelenetet vissza lehet majd tölteni belőle. Ez megnöveli a fájl méretét.",
|
||||||
"addWatermark": "",
|
"addWatermark": "Add hozzá, hogy \"Excalidraw-val készült\"",
|
||||||
"handDrawn": "",
|
"handDrawn": "Kézzel rajzolt",
|
||||||
"normal": "Normál",
|
"normal": "Normál",
|
||||||
"code": "Code",
|
"code": "Kód",
|
||||||
"small": "Kicsi",
|
"small": "Kicsi",
|
||||||
"medium": "Közepes",
|
"medium": "Közepes",
|
||||||
"large": "Nagy",
|
"large": "Nagy",
|
||||||
"veryLarge": "",
|
"veryLarge": "Nagyon nagy",
|
||||||
"solid": "Kitöltött",
|
"solid": "Kitöltött",
|
||||||
"hachure": "Vonalkázott",
|
"hachure": "Vonalkázott",
|
||||||
"crossHatch": "",
|
"crossHatch": "Keresztcsíkozott",
|
||||||
"thin": "Vékony",
|
"thin": "Vékony",
|
||||||
"bold": "Félkövér",
|
"bold": "Félkövér",
|
||||||
"left": "Bal",
|
"left": "Bal",
|
||||||
"center": "",
|
"center": "Közép",
|
||||||
"right": "",
|
"right": "Jobb",
|
||||||
"extraBold": "",
|
"extraBold": "Extra Félkövér",
|
||||||
"architect": "Tervezői",
|
"architect": "Tervezői",
|
||||||
"artist": "Művészi",
|
"artist": "Művészi",
|
||||||
"cartoonist": "Karikatúrás",
|
"cartoonist": "Karikatúrás",
|
||||||
"fileTitle": "",
|
"fileTitle": "Fájl címe",
|
||||||
"colorPicker": "Színválasztó",
|
"colorPicker": "Színválasztó",
|
||||||
"canvasBackground": "Vászon háttérszíne",
|
"canvasBackground": "Vászon háttérszíne",
|
||||||
"drawingCanvas": "",
|
"drawingCanvas": "Rajzvászon",
|
||||||
"layers": "Rétegek",
|
"layers": "Rétegek",
|
||||||
"actions": "Műveletek",
|
"actions": "Műveletek",
|
||||||
"language": "Nyelv",
|
"language": "Nyelv",
|
||||||
"createRoom": "Élő együttmüködés megosztása",
|
"createRoom": "Élő együttmüködés megosztása",
|
||||||
"duplicateSelection": "",
|
"duplicateSelection": "Duplikálás",
|
||||||
"untitled": "",
|
"untitled": "Névtelen",
|
||||||
"name": "Név",
|
"name": "Név",
|
||||||
"yourName": "",
|
"yourName": "Neved",
|
||||||
"madeWithExcalidraw": "",
|
"madeWithExcalidraw": "Excalidraw-val készült",
|
||||||
"group": "",
|
"group": "Csoportosítás",
|
||||||
"ungroup": "",
|
"ungroup": "Csoportbontás",
|
||||||
"collaborators": "",
|
"collaborators": "Közreműködők",
|
||||||
"toggleGridMode": "",
|
"gridMode": "Hálómód",
|
||||||
"toggleStats": "",
|
"addToLibrary": "Hozzáadás a könyvtárhoz",
|
||||||
"addToLibrary": "",
|
"removeFromLibrary": "Eltávólítás a könyvtárból",
|
||||||
"removeFromLibrary": "",
|
"libraryLoadingMessage": "Könyvtár betöltése...",
|
||||||
"libraryLoadingMessage": "",
|
"libraries": "Könyvtárak böngészése",
|
||||||
"libraries": "",
|
"loadingScene": "Jelenet betöltése...",
|
||||||
"loadingScene": "",
|
"align": "Igazítás",
|
||||||
"align": "",
|
"alignTop": "Felülre igazítás",
|
||||||
"alignTop": "",
|
"alignBottom": "Alulra igazítás",
|
||||||
"alignBottom": "",
|
"alignLeft": "Balra igazítás",
|
||||||
"alignLeft": "",
|
"alignRight": "Jobbra igazítás",
|
||||||
"alignRight": "",
|
"centerVertically": "Függőlegesen középre igazított",
|
||||||
"centerVertically": "",
|
"centerHorizontally": "Vízszintesen középre igazított",
|
||||||
"centerHorizontally": "",
|
"distributeHorizontally": "Vízszintes elosztás",
|
||||||
"distributeHorizontally": "",
|
"distributeVertically": "Függőleges elosztás"
|
||||||
"distributeVertically": ""
|
|
||||||
},
|
},
|
||||||
"buttons": {
|
"buttons": {
|
||||||
"clearReset": "Vászon törlése",
|
"clearReset": "Vászon törlése",
|
||||||
@@ -100,13 +100,13 @@
|
|||||||
"exportToSvg": "Exportálás SVG-be",
|
"exportToSvg": "Exportálás SVG-be",
|
||||||
"copyToClipboard": "Vágólapra másolás",
|
"copyToClipboard": "Vágólapra másolás",
|
||||||
"copyPngToClipboard": "PNG másolása a vágólapra",
|
"copyPngToClipboard": "PNG másolása a vágólapra",
|
||||||
"scale": "",
|
"scale": "Nagyítás",
|
||||||
"save": "Mentés",
|
"save": "Mentés",
|
||||||
"saveAs": "",
|
"saveAs": "Mentés másként",
|
||||||
"load": "Betöltés",
|
"load": "Betöltés",
|
||||||
"getShareableLink": "Megosztható link létrehozása",
|
"getShareableLink": "Megosztható link létrehozása",
|
||||||
"close": "Bezárás",
|
"close": "Bezárás",
|
||||||
"selectLanguage": "",
|
"selectLanguage": "Nyelv kiválasztása",
|
||||||
"scrollBackToContent": "Visszagörgetés a tartalomhoz",
|
"scrollBackToContent": "Visszagörgetés a tartalomhoz",
|
||||||
"zoomIn": "Nagyítás",
|
"zoomIn": "Nagyítás",
|
||||||
"zoomOut": "Kicsinyítés",
|
"zoomOut": "Kicsinyítés",
|
||||||
@@ -114,121 +114,129 @@
|
|||||||
"menu": "Menü",
|
"menu": "Menü",
|
||||||
"done": "Kész",
|
"done": "Kész",
|
||||||
"edit": "Szerkesztés",
|
"edit": "Szerkesztés",
|
||||||
"undo": "Visszavonás",
|
"undo": "Vissza",
|
||||||
"redo": "Újra végrehajtás",
|
"redo": "Újra",
|
||||||
"roomDialog": "Élő együttműködés indítása",
|
"roomDialog": "Élő együttműködés indítása",
|
||||||
"createNewRoom": "Új szoba létrehozása",
|
"createNewRoom": "Új szoba létrehozása",
|
||||||
"toggleFullScreen": "",
|
"fullScreen": "Teljes képernyő",
|
||||||
"toggleDarkMode": "",
|
"darkMode": "Sötét mód",
|
||||||
"toggleZenMode": "",
|
"lightMode": "Világos mód",
|
||||||
"exitZenMode": ""
|
"zenMode": "Letisztult mód",
|
||||||
|
"exitZenMode": "Kilépés a letisztult módból"
|
||||||
},
|
},
|
||||||
"alerts": {
|
"alerts": {
|
||||||
"clearReset": "Ez a művelet törli a vászont. Biztos benne?",
|
"clearReset": "Ez a művelet törli a vászont. Biztos benne?",
|
||||||
"couldNotCreateShareableLink": "Nem sikerült megosztható linket létrehozni.",
|
"couldNotCreateShareableLink": "Nem sikerült megosztható linket létrehozni.",
|
||||||
"couldNotCreateShareableLinkTooBig": "",
|
"couldNotCreateShareableLinkTooBig": "Nem sikerült megosztható linket látrehozni: túl nagy a jelenet",
|
||||||
"couldNotLoadInvalidFile": "",
|
"couldNotLoadInvalidFile": "Nem sikerült betölteni a helytelen fájlt",
|
||||||
"importBackendFailed": "Nem sikerült betölteni a szerverről.",
|
"importBackendFailed": "Nem sikerült betölteni a szerverről.",
|
||||||
"cannotExportEmptyCanvas": "Üres vászont nem lehet exportálni.",
|
"cannotExportEmptyCanvas": "Üres vászont nem lehet exportálni.",
|
||||||
"couldNotCopyToClipboard": "Nem sikerült vágólapra menteni. Próbálja meg Chrome böngészővel.",
|
"couldNotCopyToClipboard": "Nem sikerült vágólapra menteni. Próbáld meg Chrome böngészővel.",
|
||||||
"decryptFailed": "Nem sikerült dekódolni az adatot.",
|
"decryptFailed": "Nem sikerült visszafejteni a titkosított adatot.",
|
||||||
"uploadedSecurly": "A feltöltést végpontok közötti titkosítással biztosítottuk, ami azt jelenti, hogy az Excalidraw szerver és harmadik felek nem tudják elolvasni a feltöltés tartalmát.",
|
"uploadedSecurly": "A feltöltést végpontok közötti titkosítással biztosítottuk, ami azt jelenti, hogy egy harmadik fél nem tudja megnézni a tartalmát, beleértve az Excalidraw szervereit is.",
|
||||||
"loadSceneOverridePrompt": "",
|
"loadSceneOverridePrompt": "A betöltött külső rajz felül fogja írnia meglévőt. Szeretnéd folytatni?",
|
||||||
"errorLoadingLibrary": "",
|
"errorLoadingLibrary": "Hibába ütközött a harmarmadik féltől származó könyvtár betöltése.",
|
||||||
"confirmAddLibrary": "",
|
"confirmAddLibrary": "Ez a művelet {{numShapes}} formát fog hozzáadni a könyvtáradhoz. Biztos vagy benne?",
|
||||||
"imageDoesNotContainScene": "",
|
"imageDoesNotContainScene": "Képek importálása egyelőre nem támogatott.\n\nEgy jelenetet szeretnél betölteni? Úgy tűnik ez a kép fájl nem tartalmazza a szükséges adatokat. Exportáláskor ezt egy külön opcióval lehet beállítani.",
|
||||||
"cannotRestoreFromImage": ""
|
"cannotRestoreFromImage": "A jelenet visszaállítása nem sikerült ebből a kép fájlból"
|
||||||
},
|
},
|
||||||
"toolBar": {
|
"toolBar": {
|
||||||
"selection": "Kiválasztás",
|
"selection": "Kijelölés",
|
||||||
"draw": "",
|
"draw": "Szabadkézi rajz",
|
||||||
"rectangle": "Téglalap",
|
"rectangle": "Téglalap",
|
||||||
"diamond": "Rombusz",
|
"diamond": "Rombusz",
|
||||||
"ellipse": "Ellipszis",
|
"ellipse": "Ellipszis",
|
||||||
"arrow": "Nyíl",
|
"arrow": "Nyíl",
|
||||||
"line": "Vonal",
|
"line": "Vonal",
|
||||||
"text": "Szöveg",
|
"text": "Szöveg",
|
||||||
"library": "",
|
"library": "Könyvtár",
|
||||||
"lock": "Rajzolás után az aktív eszközt tartsa kiválasztva"
|
"lock": "Rajzolás után az aktív eszközt tartsa kijelölve"
|
||||||
},
|
},
|
||||||
"headings": {
|
"headings": {
|
||||||
"canvasActions": "Vászon műveletek",
|
"canvasActions": "Vászon műveletek",
|
||||||
"selectedShapeActions": "Kiválasztott forma műveletei",
|
"selectedShapeActions": "Kijelölt forma műveletei",
|
||||||
"shapes": "Formák"
|
"shapes": "Alakzatok"
|
||||||
},
|
},
|
||||||
"hints": {
|
"hints": {
|
||||||
"linearElement": "Kattintson a több pont elindításához, húzza az egyenes vonalhoz",
|
"linearElement": "Kattintással görbe, az eger húzásával pedig egyenes nyilat rajzolhatsz",
|
||||||
"freeDraw": "",
|
"freeDraw": "Kattints és húzd, majd engedd el, amikor végeztél",
|
||||||
"text": "",
|
"text": "Tipp: A kijelölés eszközzel a dupla kattintás új szöveget hoz létre",
|
||||||
"linearElementMulti": "Kattintson az utolsó pontra, vagy nyomja meg az Escape vagy az Enter billentyűt a befejezéshez",
|
"linearElementMulti": "Kattints a következő ív pozíciójára, vagy fejezd be a nyilat az Escape vagy Enter megnyomásával",
|
||||||
"resize": "",
|
"lockAngle": "A SHIFT billentyű lenyomva tartásával korlátozhatja forgatás szögét",
|
||||||
|
"resize": "A SHIFT billentyű lenyomva tartásával az átméretezés megtartja az arányokat,\naz ALT lenyomva tartásával pedig a középpont egy helyben marad",
|
||||||
"rotate": "A SHIFT billentyű lenyomva tartásával korlátozhatja a szögek illesztését",
|
"rotate": "A SHIFT billentyű lenyomva tartásával korlátozhatja a szögek illesztését",
|
||||||
"lineEditor_info": "",
|
"lineEditor_info": "Kattints duplán, vagy nyomj entert a pontok szerkesztéséhez",
|
||||||
"lineEditor_pointSelected": "",
|
"lineEditor_pointSelected": "Nyomd meg a delete gombot a pont eltávolításához, Ctrl vagy Cmd + D-t a duplikáláshoz, vagy húzva mozgasd",
|
||||||
"lineEditor_nothingSelected": ""
|
"lineEditor_nothingSelected": "Válassz ki egy pontot a mozgatáshoz vagy törtléshez, vagy az Alt lenyomása mellett kattintva hozz létre új pontokat"
|
||||||
},
|
},
|
||||||
"canvasError": {
|
"canvasError": {
|
||||||
"cannotShowPreview": "",
|
"cannotShowPreview": "Előnézet nem jeleníthető meg",
|
||||||
"canvasTooBig": "",
|
"canvasTooBig": "A vászon talán túl nagy.",
|
||||||
"canvasTooBigTip": ""
|
"canvasTooBigTip": "Tipp: próbáld meg a legtávolabbi elemeket közelebb hozni egy máshoz."
|
||||||
},
|
},
|
||||||
"errorSplash": {
|
"errorSplash": {
|
||||||
"headingMain_pre": "Hiba történt. Próbálja ",
|
"headingMain_pre": "Hiba történt. Próbáld ",
|
||||||
"headingMain_button": "újratölteni az oldalt.",
|
"headingMain_button": "újratölteni az oldalt.",
|
||||||
"clearCanvasMessage": "Ha az újratöltés nem működik, próbálja ",
|
"clearCanvasMessage": "Ha az újratöltés nem működik, próbáld ",
|
||||||
"clearCanvasMessage_button": "törölni a vászont.",
|
"clearCanvasMessage_button": "letörölni a vászont.",
|
||||||
"clearCanvasCaveat": " Ezzel elveszik minden eddigi munkája ",
|
"clearCanvasCaveat": " Ezzel az eddigi munka elveszik ",
|
||||||
"trackedToSentry_pre": "A hibakód azonosítóval ",
|
"trackedToSentry_pre": "A hibakód azonosítóval ",
|
||||||
"trackedToSentry_post": " nyomon van követve a rendszerünkben.",
|
"trackedToSentry_post": " nyomon van követve a rendszerünkben.",
|
||||||
"openIssueMessage_pre": "Nagyon vigyáztunk, hogy ne adjunk meg a jelenetre vonatkozó információkat a hibában. Ha a jeleneted nem bizalmas, kérjük fontolja meg a jelenet hozzáadását a hibakövető rendszerünkben ",
|
"openIssueMessage_pre": "Vigyáztunk arra, hogy a jelenthez tartozó információ ne jelenjen meg a hibaüzenetben. Ha a jeleneted nem bizalmas, kérjük add hozzá a ",
|
||||||
"openIssueMessage_button": "hibabejelentő.",
|
"openIssueMessage_button": "hibakövető rendszerünkhöz.",
|
||||||
"openIssueMessage_post": " Kérjük, másolja be az alábbi információkat a GitHub problémába.",
|
"openIssueMessage_post": " Kérjük, másolja be az alábbi információkat a GitHub problémába.",
|
||||||
"sceneContent": "Jelenet tartalma:"
|
"sceneContent": "Jelenet tartalma:"
|
||||||
},
|
},
|
||||||
"roomDialog": {
|
"roomDialog": {
|
||||||
"desc_intro": "Meghívhat embereket a jelenlegi jelenetbe, hogy együttműködjenek önnel.",
|
"desc_intro": "Meghívhat embereket a jelenlegi jelenetbe, hogy együttműködjenek önnel.",
|
||||||
"desc_privacy": "Ne aggódjon, a munkamenet végpontok közötti titkosítást használ, tehát bármit rajzol, privát marad. Még a szerverünk sem fogja látni, hogy mit rajzolt.",
|
"desc_privacy": "Ne aggódj, a munkamenet végpontok közötti titkosítást használ, tehát bármit rajzolsz, privát marad. Még a szerverünkről se lehet belenézni.",
|
||||||
"button_startSession": "Munkamenet indítása",
|
"button_startSession": "Munkamenet indítása",
|
||||||
"button_stopSession": "Munkamenet leállítása",
|
"button_stopSession": "Munkamenet leállítása",
|
||||||
"desc_inProgressIntro": "Az élő együttműködési munkamenet folyamatban van.",
|
"desc_inProgressIntro": "Az élő együttműködési munkamenet folyamatban van.",
|
||||||
"desc_shareLink": "Ossza meg ezt a linket bárkivel, akivel együtt szeretne működni:",
|
"desc_shareLink": "Ossza meg ezt a linket bárkivel, akivel együtt szeretne működni:",
|
||||||
"desc_exitSession": ""
|
"desc_exitSession": "Az munkamenet leállítása kilépteti önt a szobából, de folytathatja a munkát a saját gépén. Vegye figyelembe, hogy ez nem érinti más emberek munkáját és ők továbbra is együttműködhetnek a saját változatukon."
|
||||||
},
|
},
|
||||||
"errorDialog": {
|
"errorDialog": {
|
||||||
"title": ""
|
"title": "Hiba"
|
||||||
},
|
},
|
||||||
"shortcutsDialog": {
|
"helpDialog": {
|
||||||
"title": "",
|
"blog": "",
|
||||||
"shapes": "",
|
|
||||||
"or": "",
|
|
||||||
"click": "",
|
"click": "",
|
||||||
"drag": "",
|
|
||||||
"curvedArrow": "",
|
"curvedArrow": "",
|
||||||
"curvedLine": "",
|
"curvedLine": "",
|
||||||
|
"documentation": "",
|
||||||
|
"drag": "",
|
||||||
"editor": "",
|
"editor": "",
|
||||||
"view": "",
|
|
||||||
"blog": "",
|
|
||||||
"howto": "",
|
|
||||||
"github": "",
|
"github": "",
|
||||||
"textNewLine": "",
|
"howto": "",
|
||||||
|
"or": "",
|
||||||
|
"preventBinding": "",
|
||||||
|
"shapes": "",
|
||||||
|
"shortcuts": "",
|
||||||
"textFinish": "",
|
"textFinish": "",
|
||||||
|
"textNewLine": "",
|
||||||
|
"title": "",
|
||||||
|
"view": "",
|
||||||
"zoomToFit": "",
|
"zoomToFit": "",
|
||||||
"zoomToSelection": "",
|
"zoomToSelection": ""
|
||||||
"preventBinding": ""
|
|
||||||
},
|
},
|
||||||
"encrypted": {
|
"encrypted": {
|
||||||
"tooltip": ""
|
"tooltip": "A rajzaidat végpontok közötti titkosítással tároljuk, tehát az Excalidraw szervereiről se tud más belenézni."
|
||||||
},
|
},
|
||||||
"stats": {
|
"stats": {
|
||||||
"angle": "",
|
"angle": "Szög",
|
||||||
"element": "",
|
"element": "Elem",
|
||||||
"elements": "",
|
"elements": "Elemek",
|
||||||
"height": "",
|
"height": "Magasság",
|
||||||
"scene": "",
|
"scene": "Jelenet",
|
||||||
"selected": "",
|
"selected": "Kijelölt",
|
||||||
"storage": "",
|
"storage": "Tárhely",
|
||||||
"title": "",
|
"title": "Statisztikák",
|
||||||
"total": "",
|
"total": "Összesen",
|
||||||
"width": ""
|
"width": "Szélesség"
|
||||||
|
},
|
||||||
|
"toast": {
|
||||||
|
"copyStyles": "",
|
||||||
|
"copyToClipboardAsPng": ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||