Compare commits
154 Commits
change-grid
...
cascadia
| Author | SHA1 | Date | |
|---|---|---|---|
| 105f7fdaad | |||
| 053353841a | |||
| 286642ffcf | |||
| 7090938ec1 | |||
| 6233bc52e3 | |||
| 7f8ee06710 | |||
| 3e200634e0 | |||
| 701b02d6df | |||
| 535b2b682e | |||
| 1a4a736d94 | |||
| bb207ef861 | |||
| 2fcbf8658f | |||
| 6a8c6e7f47 | |||
| f962503425 | |||
| 02fdc506ee | |||
| 2ba6088e97 | |||
| 4e421e6e9e | |||
| d213dbb42d | |||
| a5779dd5d8 | |||
| 628b4c1eec | |||
| 31333e597b | |||
| 872b340f1b | |||
| 83e8167adf | |||
| baea88942c | |||
| ae35037a3b | |||
| 4f31ae1e4b | |||
| 704ee30ae6 | |||
| 176baef9c2 | |||
| 3558f07fe0 | |||
| fd24c74ab1 | |||
| ae6892501d | |||
| 1e6adaf0b5 | |||
| 7ccd38a37f | |||
| e6ce9e0ea7 | |||
| c0e05445b1 | |||
| b44531d94a | |||
| 464c2cc05e | |||
| bd13c2ed48 | |||
| 2e58aaae66 | |||
| d4c14d484c | |||
| 06d1871640 | |||
| e7a59335e4 | |||
| 0dbef18044 | |||
| e16c2d592f | |||
| 1f4cf4610f | |||
| 1bee959660 | |||
| f609a4ac3a | |||
| eee9d1bc16 | |||
| ecdc2582e9 | |||
| 245e13a884 | |||
| 8a322b22ed | |||
| 3f784d76fc | |||
| ba5afe9139 | |||
| 9e6f351672 | |||
| fcd10a6a43 | |||
| 3bc18f6aed | |||
| 7c5481b877 | |||
| d17464fbaa | |||
| baf9da2b83 | |||
| 4bfcf105a5 | |||
| 74e82d0d7c | |||
| 9dd2257932 | |||
| 9c0f832a41 | |||
| 6cafb6bb90 | |||
| e6cd97c4f2 | |||
| ba9b65b051 | |||
| 830fb64a25 | |||
| 5b343a9d46 | |||
| f8beb305de | |||
| 4b4eecbd27 | |||
| e8bd910b9b | |||
| a9a3e1bca5 | |||
| 2a922dd477 | |||
| 07dab85ebf | |||
| d2ce4a7523 | |||
| dc73f3a9eb | |||
| d100f38750 | |||
| 503500cc74 | |||
| 1a828a43d9 | |||
| d88884466b | |||
| d3d470ac3d | |||
| 54df521a78 | |||
| c5557b5cc1 | |||
| 51875fd627 | |||
| b2ba61bbcf | |||
| 3b7f62c9a0 | |||
| aeafb81479 | |||
| dfe81bf6b2 | |||
| 1d332d597a | |||
| b5fc8757a4 | |||
| ecbd5ba55d | |||
| 6967d8c985 | |||
| 4b253c7362 | |||
| 4fdddb518a | |||
| 0b2e4dd60b | |||
| f162512988 | |||
| 2f7154cdf2 | |||
| 5ab0ce5a33 | |||
| 073f4032f3 | |||
| 73cba59d2d | |||
| 4085071347 | |||
| 8a63187d4f | |||
| 9c51ba6067 | |||
| bf50c9cae7 | |||
| 1801048763 | |||
| 414deea084 | |||
| 979d28d5c6 | |||
| 1f8b7e417f | |||
| 8c968cd13e | |||
| f798000006 | |||
| 7ff3a71179 | |||
| 6c0804d4c3 | |||
| ae8e7aca16 | |||
| 842b185aa6 | |||
| f59387471e | |||
| 60eb709eb3 | |||
| 02539bbb89 | |||
| 77ae5d4605 | |||
| bdd4f69bf6 | |||
| 87ca829490 | |||
| a31a7fd766 | |||
| e70f02063f | |||
| 769f727bd4 | |||
| bc994fcbe2 | |||
| ac5e058222 | |||
| d61970cdac | |||
| 489d4b7469 | |||
| d88de08872 | |||
| 42882e2a93 | |||
| f6374e5bde | |||
| aac9d4e837 | |||
| d9103b8b24 | |||
| 8b56346011 | |||
| e63a0ec5be | |||
| 86222662f2 | |||
| 066560311b | |||
| d6ca981f7a | |||
| f7f98d9dda | |||
| 1a67642fd1 | |||
| 6aa22bada8 | |||
| 00209ef9c3 | |||
| b79ef0d428 | |||
| dc25fe06d0 | |||
| 1e17c1967b | |||
| 23a8891e0e | |||
| 6c81a32d62 | |||
| f0f5430313 | |||
| e18e945cd3 | |||
| bd0c6e63ff | |||
| dbae33e4f8 | |||
| 0f4a053759 | |||
| 1837147c55 | |||
| 15f698dc21 | |||
| ce507b0a0b |
+6
-6
@@ -1,10 +1,10 @@
|
|||||||
*
|
*
|
||||||
|
!.env
|
||||||
|
!.eslintrc.json
|
||||||
|
!.npmrc
|
||||||
|
!.prettierrc
|
||||||
|
!package.json
|
||||||
!public/
|
!public/
|
||||||
!src/
|
!src/
|
||||||
!.npmrc
|
|
||||||
!.eslintrc.json
|
|
||||||
!.prettierrc
|
|
||||||
!package-lock.json
|
|
||||||
!package.json
|
|
||||||
!tsconfig.json
|
!tsconfig.json
|
||||||
!.env
|
!yarn.lock
|
||||||
|
|||||||
+1
-1
@@ -2,6 +2,7 @@
|
|||||||
"extends": ["prettier", "react-app"],
|
"extends": ["prettier", "react-app"],
|
||||||
"plugins": ["prettier"],
|
"plugins": ["prettier"],
|
||||||
"rules": {
|
"rules": {
|
||||||
|
"@typescript-eslint/no-unused-vars": "warn",
|
||||||
"curly": "warn",
|
"curly": "warn",
|
||||||
"dot-notation": "warn",
|
"dot-notation": "warn",
|
||||||
"import/no-anonymous-default-export": "off",
|
"import/no-anonymous-default-export": "off",
|
||||||
@@ -22,7 +23,6 @@
|
|||||||
],
|
],
|
||||||
"no-unneeded-ternary": "warn",
|
"no-unneeded-ternary": "warn",
|
||||||
"no-unused-expressions": "warn",
|
"no-unused-expressions": "warn",
|
||||||
"no-unused-vars": "warn",
|
|
||||||
"no-useless-return": "warn",
|
"no-useless-return": "warn",
|
||||||
"no-var": "warn",
|
"no-var": "warn",
|
||||||
"object-shorthand": "warn",
|
"object-shorthand": "warn",
|
||||||
|
|||||||
@@ -1,36 +1,33 @@
|
|||||||
version: 2
|
version: 2
|
||||||
updates:
|
updates:
|
||||||
- package-ecosystem: npm
|
- package-ecosystem: npm
|
||||||
directory: "/"
|
directory: /
|
||||||
schedule:
|
schedule:
|
||||||
interval: weekly
|
interval: weekly
|
||||||
day: sunday
|
day: sunday
|
||||||
time: "01:00"
|
time: "01:00"
|
||||||
open-pull-requests-limit: 99
|
|
||||||
reviewers:
|
reviewers:
|
||||||
- lipis
|
- lipis
|
||||||
assignees:
|
assignees:
|
||||||
- lipis
|
- lipis
|
||||||
|
|
||||||
- package-ecosystem: npm
|
- package-ecosystem: npm
|
||||||
directory: "/src/packages/excalidraw/"
|
directory: /src/packages/excalidraw/
|
||||||
schedule:
|
schedule:
|
||||||
interval: weekly
|
interval: weekly
|
||||||
day: sunday
|
day: sunday
|
||||||
time: "01:00"
|
time: "01:00"
|
||||||
open-pull-requests-limit: 99
|
|
||||||
reviewers:
|
reviewers:
|
||||||
- ad1992
|
- ad1992
|
||||||
assignees:
|
assignees:
|
||||||
- ad1992
|
- ad1992
|
||||||
|
|
||||||
- package-ecosystem: npm
|
- package-ecosystem: npm
|
||||||
directory: "/src/packages/utils/"
|
directory: /src/packages/utils/
|
||||||
schedule:
|
schedule:
|
||||||
interval: weekly
|
interval: weekly
|
||||||
day: sunday
|
day: sunday
|
||||||
time: "01:00"
|
time: "01:00"
|
||||||
open-pull-requests-limit: 99
|
|
||||||
reviewers:
|
reviewers:
|
||||||
- ad1992
|
- ad1992
|
||||||
assignees:
|
assignees:
|
||||||
|
|||||||
@@ -6,9 +6,8 @@ on:
|
|||||||
- master
|
- master
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build-docker:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v1
|
- uses: actions/checkout@v2
|
||||||
- run: docker build -t excalidraw .
|
- run: docker build -t excalidraw .
|
||||||
|
|||||||
@@ -7,27 +7,23 @@ on:
|
|||||||
pull_request:
|
pull_request:
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
packages:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v1
|
- uses: actions/checkout@v2
|
||||||
|
|
||||||
- name: Setup Node.js 14.x
|
- name: Setup Node.js 14.x
|
||||||
uses: actions/setup-node@v1
|
uses: actions/setup-node@v2
|
||||||
with:
|
with:
|
||||||
node-version: 14.x
|
node-version: 14.x
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: |
|
run: |
|
||||||
npm ci
|
yarn --frozen-lockfile
|
||||||
npm ci --prefix src/packages/excalidraw
|
yarn --cwd src/packages/excalidraw
|
||||||
npm ci --prefix src/packages/utils
|
yarn --cwd src/packages/utils
|
||||||
|
|
||||||
- name: Build @excalidraw/excalidraw
|
- name: Build @excalidraw/excalidraw
|
||||||
run: |
|
run: |
|
||||||
npm run pack --prefix src/packages/excalidraw
|
yarn --cwd src/packages/excalidraw run pack
|
||||||
|
|
||||||
- name: Build @excalidraw/utils
|
- name: Build @excalidraw/utils
|
||||||
run: |
|
run: |
|
||||||
npm run pack --prefix src/packages/utils
|
yarn --cwd src/packages/utils run pack
|
||||||
|
|||||||
@@ -1,11 +1,14 @@
|
|||||||
name: Cancel previous runs
|
name: Cancel previous runs
|
||||||
|
|
||||||
on: push
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- master
|
||||||
|
pull_request:
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
cancel:
|
cancel:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
timeout-minutes: 3
|
timeout-minutes: 3
|
||||||
steps:
|
steps:
|
||||||
- uses: styfle/cancel-workflow-action@0.6.0
|
- uses: styfle/cancel-workflow-action@0.6.0
|
||||||
|
|||||||
@@ -1,22 +1,22 @@
|
|||||||
name: Lint
|
name: Lint
|
||||||
|
|
||||||
on: push
|
on: pull_request
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
lint:
|
lint:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v1
|
- uses: actions/checkout@v2
|
||||||
|
|
||||||
- name: Setup Node.js 14.x
|
- name: Setup Node.js 14.x
|
||||||
uses: actions/setup-node@v1
|
uses: actions/setup-node@v2
|
||||||
with:
|
with:
|
||||||
node-version: 14.x
|
node-version: 14.x
|
||||||
|
|
||||||
- name: Install and lint
|
- name: Install and lint
|
||||||
run: |
|
run: |
|
||||||
npm ci
|
yarn --frozen-lockfile
|
||||||
npm run test:other
|
yarn test:other
|
||||||
npm run test:code
|
yarn test:code
|
||||||
npm run test:typecheck
|
yarn test:typecheck
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ name: Build locales coverage
|
|||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- "l10n_master"
|
- l10n_master
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
locales:
|
locales:
|
||||||
@@ -15,13 +15,13 @@ jobs:
|
|||||||
token: ${{ secrets.PUSH_TRANSLATIONS_COVERAGE_PAT }}
|
token: ${{ secrets.PUSH_TRANSLATIONS_COVERAGE_PAT }}
|
||||||
|
|
||||||
- name: Setup Node.js 14.x
|
- name: Setup Node.js 14.x
|
||||||
uses: actions/setup-node@v1
|
uses: actions/setup-node@v2
|
||||||
with:
|
with:
|
||||||
node-version: 14.x
|
node-version: 14.x
|
||||||
|
|
||||||
- name: Create report file
|
- name: Create report file
|
||||||
run: |
|
run: |
|
||||||
npm run locales-coverage
|
yarn locales-coverage
|
||||||
FILE_CHANGED=$(git diff src/locales/percentages.json)
|
FILE_CHANGED=$(git diff src/locales/percentages.json)
|
||||||
if [ ! -z "${FILE_CHANGED}" ]; then
|
if [ ! -z "${FILE_CHANGED}" ]; then
|
||||||
git config --global user.name 'Excalidraw Bot'
|
git config --global user.name 'Excalidraw Bot'
|
||||||
@@ -33,7 +33,7 @@ jobs:
|
|||||||
- name: Construct comment body
|
- name: Construct comment body
|
||||||
id: getCommentBody
|
id: getCommentBody
|
||||||
run: |
|
run: |
|
||||||
body=$(npm run locales-coverage:description | grep '^[^>]')
|
body=$(yarn locales-coverage:description | grep '^[^>]')
|
||||||
body="${body//'%'/'%25'}"
|
body="${body//'%'/'%25'}"
|
||||||
body="${body//$'\n'/'%0A'}"
|
body="${body//$'\n'/'%0A'}"
|
||||||
body="${body//$'\r'/'%0D'}"
|
body="${body//$'\r'/'%0D'}"
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
name: "Semantic PR title"
|
name: Semantic PR title
|
||||||
|
|
||||||
on:
|
on:
|
||||||
pull_request_target:
|
pull_request_target:
|
||||||
@@ -8,9 +8,8 @@ on:
|
|||||||
- synchronize
|
- synchronize
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
main:
|
semantic:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: amannn/action-semantic-pull-request@v3.0.0
|
- uses: amannn/action-semantic-pull-request@v3.0.0
|
||||||
env:
|
env:
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
name: New Sentry Production Release
|
name: New Sentry production release
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
@@ -6,28 +6,23 @@ on:
|
|||||||
- master
|
- master
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
release:
|
sentry:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v1.0.0
|
- uses: actions/checkout@v2
|
||||||
|
|
||||||
- name: Setup Node.js 14.x
|
- name: Setup Node.js 14.x
|
||||||
uses: actions/setup-node@v1
|
uses: actions/setup-node@v2
|
||||||
with:
|
with:
|
||||||
node-version: 14.x
|
node-version: 14.x
|
||||||
|
|
||||||
- name: Install and build
|
- name: Install and build
|
||||||
run: |
|
run: |
|
||||||
npm ci
|
yarn --frozen-lockfile
|
||||||
npm run build:app
|
yarn build:app
|
||||||
env:
|
env:
|
||||||
CI: true
|
CI: true
|
||||||
|
|
||||||
- name: Install Sentry
|
- name: Install Sentry
|
||||||
run: |
|
run: |
|
||||||
curl -sL https://sentry.io/get-cli/ | bash
|
curl -sL https://sentry.io/get-cli/ | bash
|
||||||
|
|
||||||
- name: Create new Sentry release
|
- name: Create new Sentry release
|
||||||
run: |
|
run: |
|
||||||
export SENTRY_RELEASE=$(sentry-cli releases propose-version)
|
export SENTRY_RELEASE=$(sentry-cli releases propose-version)
|
||||||
|
|||||||
@@ -1,20 +1,17 @@
|
|||||||
name: Tests
|
name: Tests
|
||||||
|
|
||||||
on: push
|
on: pull_request
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
test:
|
test:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v1
|
- uses: actions/checkout@v2
|
||||||
|
|
||||||
- name: Setup Node.js 14.x
|
- name: Setup Node.js 14.x
|
||||||
uses: actions/setup-node@v1
|
uses: actions/setup-node@v2
|
||||||
with:
|
with:
|
||||||
node-version: 14.x
|
node-version: 14.x
|
||||||
|
|
||||||
- name: Install and test
|
- name: Install and test
|
||||||
run: |
|
run: |
|
||||||
npm ci
|
yarn --frozen-lockfile
|
||||||
npm run test:app
|
yarn test:app
|
||||||
|
|||||||
+1
-1
@@ -16,7 +16,7 @@ firebase
|
|||||||
logs
|
logs
|
||||||
node_modules
|
node_modules
|
||||||
npm-debug.log*
|
npm-debug.log*
|
||||||
|
package-lock.json
|
||||||
static
|
static
|
||||||
yarn-debug.log*
|
yarn-debug.log*
|
||||||
yarn-error.log*
|
yarn-error.log*
|
||||||
yarn.lock
|
|
||||||
|
|||||||
@@ -1,4 +0,0 @@
|
|||||||
{
|
|
||||||
"proseWrap": "never",
|
|
||||||
"trailingComma": "all"
|
|
||||||
}
|
|
||||||
+1
-2
@@ -19,7 +19,7 @@
|
|||||||
### Option 2 - CodeSandbox
|
### Option 2 - CodeSandbox
|
||||||
|
|
||||||
1. Go to https://codesandbox.io/s/github/excalidraw/excalidraw
|
1. Go to https://codesandbox.io/s/github/excalidraw/excalidraw
|
||||||
1. Connect your Github account
|
1. Connect your GitHub account
|
||||||
1. Go to Git tab on left side
|
1. Go to Git tab on left side
|
||||||
1. Tap on `Fork Sandbox`
|
1. Tap on `Fork Sandbox`
|
||||||
1. Write your code
|
1. Write your code
|
||||||
@@ -35,7 +35,6 @@ Make sure the title starts with a semantic prefix:
|
|||||||
|
|
||||||
- **feat**: A new feature
|
- **feat**: A new feature
|
||||||
- **fix**: A bug fix
|
- **fix**: A bug fix
|
||||||
- **improvement**: An improvement to a current feature
|
|
||||||
- **docs**: Documentation only changes
|
- **docs**: Documentation only changes
|
||||||
- **style**: Changes that do not affect the meaning of the code (white-space, formatting, missing semi-colons, etc)
|
- **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
|
- **refactor**: A code change that neither fixes a bug nor adds a feature
|
||||||
|
|||||||
+3
-3
@@ -2,13 +2,13 @@ FROM node:14-alpine AS build
|
|||||||
|
|
||||||
WORKDIR /opt/node_app
|
WORKDIR /opt/node_app
|
||||||
|
|
||||||
COPY package.json package-lock.json ./
|
COPY package.json yarn.lock ./
|
||||||
RUN npm i --no-optional
|
RUN yarn --ignore-optional
|
||||||
|
|
||||||
ARG NODE_ENV=production
|
ARG NODE_ENV=production
|
||||||
|
|
||||||
COPY . .
|
COPY . .
|
||||||
RUN npm run build:app:docker
|
RUN yarn build:app:docker
|
||||||
|
|
||||||
FROM nginx:1.17-alpine
|
FROM nginx:1.17-alpine
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
<a href="https://excalidraw.com">
|
<a href="https://excalidraw.com">
|
||||||
<img width="540" src="./public/og-image-sm.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.<br>Collaborative and end to end encrypted.</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">
|
||||||
@@ -11,6 +11,7 @@
|
|||||||
<img src="https://badges.crowdin.net/excalidraw/localized.svg">
|
<img src="https://badges.crowdin.net/excalidraw/localized.svg">
|
||||||
</a>
|
</a>
|
||||||
</p>
|
</p>
|
||||||
|
<p>Ask questions or hang out on our <a target="_blank" href="https://discord.gg/UexuTaE">discord.gg/UexuTaE</a>.</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
## Try it now
|
## Try it now
|
||||||
@@ -19,6 +20,14 @@ Go to [excalidraw.com](https://excalidraw.com) to start sketching.
|
|||||||
|
|
||||||
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/).
|
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/).
|
||||||
|
|
||||||
|
## Supporting Excalidraw
|
||||||
|
|
||||||
|
If you like the project, you can become a sponsor at [Open Collective](https://opencollective.com/excalidraw).
|
||||||
|
|
||||||
|
[<img src="https://opencollective.com/excalidraw/tiers/sponsors/0/avatar.svg?avatarHeight=120">](https://opencollective.com/excalidraw/tiers/sponsors/0/website) [<img src="https://opencollective.com/excalidraw/tiers/sponsors/1/avatar.svg?avatarHeight=120">](https://opencollective.com/excalidraw/tiers/sponsors/1/website) [<img src="https://opencollective.com/excalidraw/tiers/sponsors/2/avatar.svg?avatarHeight=120">](https://opencollective.com/excalidraw/tiers/sponsors/2/website) [<img src="https://opencollective.com/excalidraw/tiers/sponsors/3/avatar.svg?avatarHeight=120">](https://opencollective.com/excalidraw/tiers/sponsors/3/website) [<img src="https://opencollective.com/excalidraw/tiers/sponsors/4/avatar.svg?avatarHeight=120">](https://opencollective.com/excalidraw/tiers/sponsors/4/website) [<img src="https://opencollective.com/excalidraw/tiers/sponsors/5/avatar.svg?avatarHeight=120">](https://opencollective.com/excalidraw/tiers/sponsors/5/website) [<img src="https://opencollective.com/excalidraw/tiers/sponsors/6/avatar.svg?avatarHeight=120">](https://opencollective.com/excalidraw/tiers/sponsors/6/website) [<img src="https://opencollective.com/excalidraw/tiers/sponsors/7/avatar.svg?avatarHeight=120">](https://opencollective.com/excalidraw/tiers/sponsors/7/website) [<img src="https://opencollective.com/excalidraw/tiers/sponsors/8/avatar.svg?avatarHeight=120">](https://opencollective.com/excalidraw/tiers/sponsors/8/website) [<img src="https://opencollective.com/excalidraw/tiers/sponsors/9/avatar.svg?avatarHeight=120">](https://opencollective.com/excalidraw/tiers/sponsors/9/website) [<img src="https://opencollective.com/excalidraw/tiers/sponsors/10/avatar.svg?avatarHeight=120">](https://opencollective.com/excalidraw/tiers/sponsors/10/website)
|
||||||
|
|
||||||
|
<a href="https://opencollective.com/excalidraw#category-CONTRIBUTE" target="_blank"><img src="https://opencollective.com/excalidraw/tiers/backers.svg?avatarHeight=32"/></a>
|
||||||
|
|
||||||
## Documentation
|
## Documentation
|
||||||
|
|
||||||
### Shortcuts
|
### Shortcuts
|
||||||
@@ -41,7 +50,7 @@ Translations will be available on the app if they exceed a certain threshold of
|
|||||||
|
|
||||||
### Create a collaboration session manually
|
### Create a collaboration session manually
|
||||||
|
|
||||||
In order to create a session manually you just need to generate a link of this form:
|
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}
|
https://excalidraw.com/#room=[0-9a-f]{20},[a-zA-Z0-9_-]{22}
|
||||||
@@ -61,18 +70,28 @@ The second set of digits is the encryption key. The Excalidraw server doesn’t
|
|||||||
|
|
||||||
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).
|
||||||
|
|
||||||
## Developement
|
## Embedding Excalidraw in your App?
|
||||||
|
|
||||||
|
Try out [`@excalidraw/excalidraw`](https://www.npmjs.com/package/@excalidraw/excalidraw). This package allows you to easily embed Excalidraw as a React component into your apps.
|
||||||
|
|
||||||
|
## Development
|
||||||
|
|
||||||
### Code Sandbox
|
### Code Sandbox
|
||||||
|
|
||||||
- Go to https://codesandbox.io/s/github/excalidraw/excalidraw
|
- Go to https://codesandbox.io/s/github/excalidraw/excalidraw
|
||||||
- You may need to sign in with Github and reload the page
|
- You may need to sign in with GitHub and reload the page
|
||||||
- You can start coding instantly, and even send PRs from there!
|
- You can start coding instantly, and even send PRs from there!
|
||||||
|
|
||||||
### Local Installation
|
### Local Installation
|
||||||
|
|
||||||
These instructions will get you a copy of the project up and running on your local machine for development and testing purposes.
|
These instructions will get you a copy of the project up and running on your local machine for development and testing purposes.
|
||||||
|
|
||||||
|
#### Requirements
|
||||||
|
|
||||||
|
- [Node.js](https://nodejs.org/en/)
|
||||||
|
- [Yarn](https://yarnpkg.com/getting-started/install)
|
||||||
|
- [Git](https://git-scm.com/downloads)
|
||||||
|
|
||||||
#### Clone the repo
|
#### Clone the repo
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
@@ -81,26 +100,26 @@ git clone https://github.com/excalidraw/excalidraw.git
|
|||||||
|
|
||||||
#### Commands
|
#### Commands
|
||||||
|
|
||||||
| Command | Description |
|
| Command | Description |
|
||||||
| --------------------- | --------------------------------- |
|
| ------------------ | --------------------------------- |
|
||||||
| `npm install` | Install the dependencies |
|
| `yarn` | Install the dependencies |
|
||||||
| `npm start` | Run the project |
|
| `yarn start` | Run the project |
|
||||||
| `npm run fix` | Reformat all files with Prettier |
|
| `yarn fix` | Reformat all files with Prettier |
|
||||||
| `npm test` | Run tests |
|
| `yarn test` | Run tests |
|
||||||
| `npm run test:update` | Update test snapshots |
|
| `yarn test:update` | Update test snapshots |
|
||||||
| `npm run test:code` | Test for formatting with Prettier |
|
| `yarn test:code` | Test for formatting with Prettier |
|
||||||
|
|
||||||
#### Docker Compose
|
#### Docker Compose
|
||||||
|
|
||||||
You can use docker-compose to work on excalidraw locally if you don't want to setup a Node.js env.
|
You can use docker-compose to work on Excalidraw locally if you don't want to setup a Node.js env.
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
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.
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
docker build -t excalidraw/excalidraw .
|
docker build -t excalidraw/excalidraw .
|
||||||
@@ -111,7 +130,7 @@ The Docker image is free of analytics and other tracking libraries.
|
|||||||
|
|
||||||
**At the moment, self-hosting your own instance doesn't support sharing or collaboration features.**
|
**At the moment, self-hosting your own instance doesn't support sharing or collaboration features.**
|
||||||
|
|
||||||
We are working towards providing a full-fledged solution for self hosting your own Excalidraw.
|
We are working towards providing a full-fledged solution for self-hosting your own Excalidraw.
|
||||||
|
|
||||||
## Contributing
|
## Contributing
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -18,7 +18,7 @@ services:
|
|||||||
volumes:
|
volumes:
|
||||||
- ./:/opt/node_app/app:delegated
|
- ./:/opt/node_app/app:delegated
|
||||||
- ./package.json:/opt/node_app/package.json
|
- ./package.json:/opt/node_app/package.json
|
||||||
- ./package-lock.json:/opt/node_app/package-lock.json
|
- ./yarn.lock:/opt/node_app/yarn.lock
|
||||||
- notused:/opt/node_app/app/node_modules
|
- notused:/opt/node_app/app/node_modules
|
||||||
|
|
||||||
volumes:
|
volumes:
|
||||||
|
|||||||
Generated
-23407
File diff suppressed because it is too large
Load Diff
+26
-23
@@ -19,21 +19,20 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@sentry/browser": "6.0.3",
|
"@sentry/browser": "6.2.0",
|
||||||
"@sentry/integrations": "6.0.3",
|
"@sentry/integrations": "6.2.0",
|
||||||
"@testing-library/jest-dom": "5.11.9",
|
"@testing-library/jest-dom": "5.11.9",
|
||||||
"@testing-library/react": "11.2.3",
|
"@testing-library/react": "11.2.5",
|
||||||
"@types/jest": "26.0.20",
|
"@types/jest": "26.0.20",
|
||||||
"@types/react": "17.0.0",
|
"@types/react": "17.0.2",
|
||||||
"@types/react-dom": "17.0.0",
|
"@types/react-dom": "17.0.1",
|
||||||
"@types/socket.io-client": "1.4.35",
|
"@types/socket.io-client": "1.4.35",
|
||||||
"browser-fs-access": "0.13.0",
|
"browser-fs-access": "0.14.0",
|
||||||
"clsx": "1.1.1",
|
"clsx": "1.1.1",
|
||||||
"firebase": "8.2.5",
|
"firebase": "8.2.9",
|
||||||
"i18next-browser-languagedetector": "6.0.1",
|
"i18next-browser-languagedetector": "6.0.1",
|
||||||
"lodash.throttle": "4.1.1",
|
"lodash.throttle": "4.1.1",
|
||||||
"nanoid": "3.1.20",
|
"nanoid": "3.1.20",
|
||||||
"node-sass": "4.14.1",
|
|
||||||
"open-color": "1.8.0",
|
"open-color": "1.8.0",
|
||||||
"pako": "1.0.11",
|
"pako": "1.0.11",
|
||||||
"png-chunk-text": "1.0.0",
|
"png-chunk-text": "1.0.0",
|
||||||
@@ -43,26 +42,29 @@
|
|||||||
"pwacompat": "2.0.17",
|
"pwacompat": "2.0.17",
|
||||||
"react": "17.0.1",
|
"react": "17.0.1",
|
||||||
"react-dom": "17.0.1",
|
"react-dom": "17.0.1",
|
||||||
"react-scripts": "4.0.1",
|
"react-scripts": "4.0.3",
|
||||||
"roughjs": "4.3.1",
|
"roughjs": "4.3.1",
|
||||||
|
"sass": "1.32.8",
|
||||||
"socket.io-client": "2.3.1",
|
"socket.io-client": "2.3.1",
|
||||||
"typescript": "4.1.3"
|
"typescript": "4.1.5"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@excalidraw/prettier-config": "1.0.2",
|
||||||
"@types/lodash.throttle": "4.1.6",
|
"@types/lodash.throttle": "4.1.6",
|
||||||
"@types/pako": "1.0.1",
|
"@types/pako": "1.0.1",
|
||||||
"eslint-config-prettier": "7.2.0",
|
"@types/resize-observer-browser": "0.1.5",
|
||||||
|
"eslint-config-prettier": "8.1.0",
|
||||||
"eslint-plugin-prettier": "3.3.1",
|
"eslint-plugin-prettier": "3.3.1",
|
||||||
"firebase-tools": "9.2.2",
|
"firebase-tools": "9.5.0",
|
||||||
"husky": "4.3.8",
|
"husky": "4.3.8",
|
||||||
"jest-canvas-mock": "2.3.0",
|
"jest-canvas-mock": "2.3.1",
|
||||||
"lint-staged": "10.5.3",
|
"lint-staged": "10.5.4",
|
||||||
"pepjs": "0.5.3",
|
"pepjs": "0.5.3",
|
||||||
"prettier": "2.2.1",
|
"prettier": "2.2.1",
|
||||||
"rewire": "5.0.0"
|
"rewire": "5.0.0"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=12.0.0"
|
"node": ">=14.0.0"
|
||||||
},
|
},
|
||||||
"homepage": ".",
|
"homepage": ".",
|
||||||
"husky": {
|
"husky": {
|
||||||
@@ -77,28 +79,29 @@
|
|||||||
"resetMocks": false
|
"resetMocks": false
|
||||||
},
|
},
|
||||||
"name": "excalidraw",
|
"name": "excalidraw",
|
||||||
|
"prettier": "@excalidraw/prettier-config",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build-node": "node ./scripts/build-node.js",
|
"build-node": "node ./scripts/build-node.js",
|
||||||
"build:app:docker": "REACT_APP_DISABLE_SENTRY=true react-scripts build",
|
"build:app:docker": "REACT_APP_DISABLE_SENTRY=true react-scripts build",
|
||||||
"build:app": "REACT_APP_GIT_SHA=$VERCEL_GIT_COMMIT_SHA react-scripts build",
|
"build:app": "REACT_APP_GIT_SHA=$VERCEL_GIT_COMMIT_SHA react-scripts build",
|
||||||
"build:version": "node ./scripts/build-version.js",
|
"build:version": "node ./scripts/build-version.js",
|
||||||
"build": "npm run build:app && npm run build:version",
|
"build": "yarn build:app && yarn build:version",
|
||||||
"eject": "react-scripts eject",
|
"eject": "react-scripts eject",
|
||||||
"fix:code": "npm run test:code -- --fix",
|
"fix:code": "yarn test:code --fix",
|
||||||
"fix:other": "npm run prettier -- --write",
|
"fix:other": "yarn prettier --write",
|
||||||
"fix": "npm run fix:other && npm run fix:code",
|
"fix": "yarn fix:other && yarn fix:code",
|
||||||
"locales-coverage": "node scripts/build-locales-coverage.js",
|
"locales-coverage": "node scripts/build-locales-coverage.js",
|
||||||
"locales-coverage:description": "node scripts/locales-coverage-description.js",
|
"locales-coverage:description": "node scripts/locales-coverage-description.js",
|
||||||
"prettier": "prettier \"**/*.{css,scss,json,md,html,yml}\" --ignore-path=.eslintignore",
|
"prettier": "prettier \"**/*.{css,scss,json,md,html,yml}\" --ignore-path=.eslintignore",
|
||||||
"start": "react-scripts start",
|
"start": "react-scripts start",
|
||||||
"test:all": "npm run test:typecheck && npm run test:code && npm run test:other && npm run test:app -- --watchAll=false",
|
"test:all": "yarn test:typecheck && yarn test:code && yarn test:other && yarn test:app --watchAll=false",
|
||||||
"test:app": "react-scripts test --passWithNoTests",
|
"test:app": "react-scripts test --passWithNoTests",
|
||||||
"test:code": "eslint --max-warnings=0 --ignore-path .gitignore --ext .js,.ts,.tsx .",
|
"test:code": "eslint --max-warnings=0 --ignore-path .gitignore --ext .js,.ts,.tsx .",
|
||||||
"test:debug": "react-scripts --inspect-brk test --runInBand --no-cache",
|
"test:debug": "react-scripts --inspect-brk test --runInBand --no-cache",
|
||||||
"test:other": "npm run prettier -- --list-different",
|
"test:other": "yarn prettier --list-different",
|
||||||
"test:typecheck": "tsc",
|
"test:typecheck": "tsc",
|
||||||
"test:update": "npm run test:app -- --updateSnapshot --watchAll=false",
|
"test:update": "yarn test:app --updateSnapshot --watchAll=false",
|
||||||
"test": "npm run test:app"
|
"test": "yarn test:app"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+1
-1
@@ -1,7 +1,7 @@
|
|||||||
/* http://www.eaglefonts.com/fg-virgil-ttf-131249.htm */
|
/* http://www.eaglefonts.com/fg-virgil-ttf-131249.htm */
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: "Virgil";
|
font-family: "Virgil";
|
||||||
src: url("FG_Virgil.woff2");
|
src: url("Virgil.woff2");
|
||||||
font-display: swap;
|
font-display: swap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+5
-2
@@ -57,9 +57,10 @@
|
|||||||
|
|
||||||
<!-- Excalidraw version -->
|
<!-- Excalidraw version -->
|
||||||
<meta name="version" content="{version}" />
|
<meta name="version" content="{version}" />
|
||||||
|
|
||||||
<link
|
<link
|
||||||
rel="preload"
|
rel="preload"
|
||||||
href="FG_Virgil.woff2"
|
href="Virgil.woff2"
|
||||||
as="font"
|
as="font"
|
||||||
type="font/woff2"
|
type="font/woff2"
|
||||||
crossorigin="anonymous"
|
crossorigin="anonymous"
|
||||||
@@ -85,7 +86,9 @@
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<link rel="stylesheet" href="fonts.css" type="text/css" />
|
<link rel="stylesheet" href="fonts.css" type="text/css" />
|
||||||
|
<script>
|
||||||
|
window.EXCALIDRAW_ASSET_PATH = "/";
|
||||||
|
</script>
|
||||||
<% if (process.env.REACT_APP_GOOGLE_ANALYTICS_ID) { %>
|
<% if (process.env.REACT_APP_GOOGLE_ANALYTICS_ID) { %>
|
||||||
<script
|
<script
|
||||||
async
|
async
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
|
|
||||||
// In order to run:
|
// In order to run:
|
||||||
// npm install canvas # please do not check it in
|
// npm install canvas # please do not check it in
|
||||||
// npm run build-node
|
// yarn build-node
|
||||||
// node build/static/js/build-node.js
|
// node build/static/js/build-node.js
|
||||||
// open test.png
|
// open test.png
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import { ProjectName } from "../components/ProjectName";
|
|||||||
import { ToolButton } from "../components/ToolButton";
|
import { ToolButton } from "../components/ToolButton";
|
||||||
import "../components/ToolIcon.scss";
|
import "../components/ToolIcon.scss";
|
||||||
import { Tooltip } from "../components/Tooltip";
|
import { Tooltip } from "../components/Tooltip";
|
||||||
|
import { DarkModeToggle, Appearence } from "../components/DarkModeToggle";
|
||||||
import { loadFromJSON, saveAsJSON } from "../data";
|
import { loadFromJSON, saveAsJSON } from "../data";
|
||||||
import { t } from "../i18n";
|
import { t } from "../i18n";
|
||||||
import useIsMobile from "../is-mobile";
|
import useIsMobile from "../is-mobile";
|
||||||
@@ -96,9 +97,24 @@ export const actionChangeShouldAddWatermark = register({
|
|||||||
export const actionSaveScene = register({
|
export const actionSaveScene = register({
|
||||||
name: "saveScene",
|
name: "saveScene",
|
||||||
perform: async (elements, appState, value) => {
|
perform: async (elements, appState, value) => {
|
||||||
|
const fileHandleExists = !!appState.fileHandle;
|
||||||
try {
|
try {
|
||||||
const { fileHandle } = await saveAsJSON(elements, appState);
|
const { fileHandle } = await saveAsJSON(elements, appState);
|
||||||
return { commitToHistory: false, appState: { ...appState, fileHandle } };
|
return {
|
||||||
|
commitToHistory: false,
|
||||||
|
appState: {
|
||||||
|
...appState,
|
||||||
|
fileHandle,
|
||||||
|
toastMessage: fileHandleExists
|
||||||
|
? fileHandle.name
|
||||||
|
? t("toast.fileSavedToFilename").replace(
|
||||||
|
"{filename}",
|
||||||
|
`"${fileHandle.name}"`,
|
||||||
|
)
|
||||||
|
: t("toast.fileSaved")
|
||||||
|
: null,
|
||||||
|
},
|
||||||
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error?.name !== "AbortError") {
|
if (error?.name !== "AbortError") {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
@@ -189,3 +205,31 @@ export const actionLoadScene = register({
|
|||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const actionExportWithDarkMode = register({
|
||||||
|
name: "exportWithDarkMode",
|
||||||
|
perform: (_elements, appState, value) => {
|
||||||
|
return {
|
||||||
|
appState: { ...appState, exportWithDarkMode: value },
|
||||||
|
commitToHistory: false,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
PanelComponent: ({ appState, updateData }) => (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: "flex",
|
||||||
|
justifyContent: "flex-end",
|
||||||
|
marginTop: "-45px",
|
||||||
|
marginBottom: "10px",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<DarkModeToggle
|
||||||
|
value={appState.exportWithDarkMode ? "dark" : "light"}
|
||||||
|
onChange={(appearance: Appearence) => {
|
||||||
|
updateData(appearance === "dark");
|
||||||
|
}}
|
||||||
|
title={t("labels.toggleExportColorScheme")}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
});
|
||||||
|
|||||||
@@ -83,7 +83,7 @@ export const actionFinalize = register({
|
|||||||
// If the multi point line closes the loop,
|
// If the multi point line closes the loop,
|
||||||
// set the last point to first point.
|
// set the last point to first point.
|
||||||
// This ensures that loop remains closed at different scales.
|
// This ensures that loop remains closed at different scales.
|
||||||
const isLoop = isPathALoop(multiPointElement.points);
|
const isLoop = isPathALoop(multiPointElement.points, appState.zoom.value);
|
||||||
if (
|
if (
|
||||||
multiPointElement.type === "line" ||
|
multiPointElement.type === "line" ||
|
||||||
multiPointElement.type === "draw"
|
multiPointElement.type === "draw"
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ export const actionGoToCollaborator = register({
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { background, stroke } = getClientColors(clientId);
|
const { background, stroke } = getClientColors(clientId, appState);
|
||||||
const shortName = getClientInitials(collaborator.username);
|
const shortName = getClientInitials(collaborator.username);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -17,6 +17,6 @@ export const actionToggleGridMode = register({
|
|||||||
};
|
};
|
||||||
},
|
},
|
||||||
checked: (appState: AppState) => appState.gridSize !== null,
|
checked: (appState: AppState) => appState.gridSize !== null,
|
||||||
contextItemLabel: "labels.gridMode",
|
contextItemLabel: "labels.showGrid",
|
||||||
keyTest: (event) => event[KEYS.CTRL_OR_CMD] && event.code === CODES.QUOTE,
|
keyTest: (event) => event[KEYS.CTRL_OR_CMD] && event.code === CODES.QUOTE,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import {
|
|||||||
} from "./types";
|
} from "./types";
|
||||||
import { ExcalidrawElement } from "../element/types";
|
import { ExcalidrawElement } from "../element/types";
|
||||||
import { AppState, ExcalidrawProps } from "../types";
|
import { AppState, ExcalidrawProps } from "../types";
|
||||||
|
import { MODES } from "../constants";
|
||||||
|
|
||||||
// This is the <App> component, but for now we don't care about anything but its
|
// This is the <App> component, but for now we don't care about anything but its
|
||||||
// `canvas` state.
|
// `canvas` state.
|
||||||
@@ -68,7 +69,7 @@ export class ActionManager implements ActionsManagerInterface {
|
|||||||
}
|
}
|
||||||
const { viewModeEnabled } = this.getAppState();
|
const { viewModeEnabled } = this.getAppState();
|
||||||
if (viewModeEnabled) {
|
if (viewModeEnabled) {
|
||||||
if (data[0].name !== "viewMode") {
|
if (!Object.values(MODES).includes(data[0].name)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -85,7 +85,8 @@ export type ActionName =
|
|||||||
| "alignHorizontallyCentered"
|
| "alignHorizontallyCentered"
|
||||||
| "distributeHorizontally"
|
| "distributeHorizontally"
|
||||||
| "distributeVertically"
|
| "distributeVertically"
|
||||||
| "viewMode";
|
| "viewMode"
|
||||||
|
| "exportWithDarkMode";
|
||||||
|
|
||||||
export interface Action {
|
export interface Action {
|
||||||
name: ActionName;
|
name: ActionName;
|
||||||
|
|||||||
@@ -40,6 +40,7 @@ export const getDefaultAppState = (): Omit<
|
|||||||
errorMessage: null,
|
errorMessage: null,
|
||||||
exportBackground: true,
|
exportBackground: true,
|
||||||
exportEmbedScene: false,
|
exportEmbedScene: false,
|
||||||
|
exportWithDarkMode: false,
|
||||||
fileHandle: null,
|
fileHandle: null,
|
||||||
gridSize: null,
|
gridSize: null,
|
||||||
height: window.innerHeight,
|
height: window.innerHeight,
|
||||||
@@ -118,6 +119,7 @@ const APP_STATE_STORAGE_CONF = (<
|
|||||||
errorMessage: { browser: false, export: false },
|
errorMessage: { browser: false, export: false },
|
||||||
exportBackground: { browser: true, export: false },
|
exportBackground: { browser: true, export: false },
|
||||||
exportEmbedScene: { browser: true, export: false },
|
exportEmbedScene: { browser: true, export: false },
|
||||||
|
exportWithDarkMode: { browser: true, export: false },
|
||||||
fileHandle: { browser: false, export: false },
|
fileHandle: { browser: false, export: false },
|
||||||
gridSize: { browser: true, export: true },
|
gridSize: { browser: true, export: true },
|
||||||
height: { browser: false, export: false },
|
height: { browser: false, export: false },
|
||||||
|
|||||||
+8
-1
@@ -1,6 +1,13 @@
|
|||||||
import colors from "./colors";
|
import colors from "./colors";
|
||||||
|
import { AppState } from "./types";
|
||||||
|
|
||||||
export const getClientColors = (clientId: string) => {
|
export const getClientColors = (clientId: string, appState: AppState) => {
|
||||||
|
if (appState?.collaborators) {
|
||||||
|
const currentUser = appState.collaborators.get(clientId);
|
||||||
|
if (currentUser?.color) {
|
||||||
|
return currentUser.color;
|
||||||
|
}
|
||||||
|
}
|
||||||
// Naive way of getting an integer out of the clientId
|
// Naive way of getting an integer out of the clientId
|
||||||
const sum = clientId.split("").reduce((a, str) => a + str.charCodeAt(0), 0);
|
const sum = clientId.split("").reduce((a, str) => a + str.charCodeAt(0), 0);
|
||||||
|
|
||||||
|
|||||||
+80
-25
@@ -4,7 +4,6 @@ import { RoughCanvas } from "roughjs/bin/canvas";
|
|||||||
import rough from "roughjs/bin/rough";
|
import rough from "roughjs/bin/rough";
|
||||||
import clsx from "clsx";
|
import clsx from "clsx";
|
||||||
|
|
||||||
import "../actions";
|
|
||||||
import {
|
import {
|
||||||
actionAddToLibrary,
|
actionAddToLibrary,
|
||||||
actionBringForward,
|
actionBringForward,
|
||||||
@@ -48,9 +47,11 @@ import {
|
|||||||
ELEMENT_TRANSLATE_AMOUNT,
|
ELEMENT_TRANSLATE_AMOUNT,
|
||||||
ENV,
|
ENV,
|
||||||
EVENT,
|
EVENT,
|
||||||
|
GRID_SIZE,
|
||||||
LINE_CONFIRM_THRESHOLD,
|
LINE_CONFIRM_THRESHOLD,
|
||||||
MIME_TYPES,
|
MIME_TYPES,
|
||||||
POINTER_BUTTON,
|
POINTER_BUTTON,
|
||||||
|
SCROLL_TIMEOUT,
|
||||||
TAP_TWICE_TIMEOUT,
|
TAP_TWICE_TIMEOUT,
|
||||||
TEXT_TO_CENTER_SNAP_THRESHOLD,
|
TEXT_TO_CENTER_SNAP_THRESHOLD,
|
||||||
TOUCH_CTX_MENU_TIMEOUT,
|
TOUCH_CTX_MENU_TIMEOUT,
|
||||||
@@ -299,6 +300,8 @@ class App extends React.Component<ExcalidrawProps, AppState> {
|
|||||||
offsetTop,
|
offsetTop,
|
||||||
excalidrawRef,
|
excalidrawRef,
|
||||||
viewModeEnabled = false,
|
viewModeEnabled = false,
|
||||||
|
zenModeEnabled = false,
|
||||||
|
gridModeEnabled = false,
|
||||||
} = props;
|
} = props;
|
||||||
this.state = {
|
this.state = {
|
||||||
...defaultAppState,
|
...defaultAppState,
|
||||||
@@ -307,6 +310,8 @@ class App extends React.Component<ExcalidrawProps, AppState> {
|
|||||||
height,
|
height,
|
||||||
...this.getCanvasOffsets({ offsetLeft, offsetTop }),
|
...this.getCanvasOffsets({ offsetLeft, offsetTop }),
|
||||||
viewModeEnabled,
|
viewModeEnabled,
|
||||||
|
zenModeEnabled,
|
||||||
|
gridSize: gridModeEnabled ? GRID_SIZE : null,
|
||||||
};
|
};
|
||||||
if (excalidrawRef) {
|
if (excalidrawRef) {
|
||||||
const readyPromise =
|
const readyPromise =
|
||||||
@@ -453,10 +458,15 @@ class App extends React.Component<ExcalidrawProps, AppState> {
|
|||||||
onExportToBackend={onExportToBackend}
|
onExportToBackend={onExportToBackend}
|
||||||
renderCustomFooter={renderFooter}
|
renderCustomFooter={renderFooter}
|
||||||
viewModeEnabled={viewModeEnabled}
|
viewModeEnabled={viewModeEnabled}
|
||||||
|
showExitZenModeBtn={
|
||||||
|
typeof this.props?.zenModeEnabled === "undefined" && zenModeEnabled
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
|
<div className="excalidraw-textEditorContainer" />
|
||||||
{this.state.showStats && (
|
{this.state.showStats && (
|
||||||
<Stats
|
<Stats
|
||||||
appState={this.state}
|
appState={this.state}
|
||||||
|
setAppState={this.setAppState}
|
||||||
elements={this.scene.getElements()}
|
elements={this.scene.getElements()}
|
||||||
onClose={this.toggleStats}
|
onClose={this.toggleStats}
|
||||||
/>
|
/>
|
||||||
@@ -509,11 +519,21 @@ class App extends React.Component<ExcalidrawProps, AppState> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let viewModeEnabled = actionResult?.appState?.viewModeEnabled || false;
|
let viewModeEnabled = actionResult?.appState?.viewModeEnabled || false;
|
||||||
|
let zenModeEnabled = actionResult?.appState?.zenModeEnabled || false;
|
||||||
|
let gridSize = actionResult?.appState?.gridSize || null;
|
||||||
|
|
||||||
if (typeof this.props.viewModeEnabled !== "undefined") {
|
if (typeof this.props.viewModeEnabled !== "undefined") {
|
||||||
viewModeEnabled = this.props.viewModeEnabled;
|
viewModeEnabled = this.props.viewModeEnabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (typeof this.props.zenModeEnabled !== "undefined") {
|
||||||
|
zenModeEnabled = this.props.zenModeEnabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof this.props.gridModeEnabled !== "undefined") {
|
||||||
|
gridSize = this.props.gridModeEnabled ? GRID_SIZE : null;
|
||||||
|
}
|
||||||
|
|
||||||
this.setState(
|
this.setState(
|
||||||
(state) => ({
|
(state) => ({
|
||||||
...actionResult.appState,
|
...actionResult.appState,
|
||||||
@@ -524,6 +544,8 @@ class App extends React.Component<ExcalidrawProps, AppState> {
|
|||||||
offsetTop: state.offsetTop,
|
offsetTop: state.offsetTop,
|
||||||
offsetLeft: state.offsetLeft,
|
offsetLeft: state.offsetLeft,
|
||||||
viewModeEnabled,
|
viewModeEnabled,
|
||||||
|
zenModeEnabled,
|
||||||
|
gridSize,
|
||||||
}),
|
}),
|
||||||
() => {
|
() => {
|
||||||
if (actionResult.syncHistory) {
|
if (actionResult.syncHistory) {
|
||||||
@@ -650,19 +672,24 @@ class App extends React.Component<ExcalidrawProps, AppState> {
|
|||||||
|
|
||||||
scene.appState = {
|
scene.appState = {
|
||||||
...scene.appState,
|
...scene.appState,
|
||||||
...calculateScrollCenter(
|
|
||||||
scene.elements,
|
|
||||||
{
|
|
||||||
...scene.appState,
|
|
||||||
width: this.state.width,
|
|
||||||
height: this.state.height,
|
|
||||||
offsetTop: this.state.offsetTop,
|
|
||||||
offsetLeft: this.state.offsetLeft,
|
|
||||||
},
|
|
||||||
null,
|
|
||||||
),
|
|
||||||
isLoading: false,
|
isLoading: false,
|
||||||
};
|
};
|
||||||
|
if (initialData?.scrollToCenter) {
|
||||||
|
scene.appState = {
|
||||||
|
...scene.appState,
|
||||||
|
...calculateScrollCenter(
|
||||||
|
scene.elements,
|
||||||
|
{
|
||||||
|
...scene.appState,
|
||||||
|
width: this.state.width,
|
||||||
|
height: this.state.height,
|
||||||
|
offsetTop: this.state.offsetTop,
|
||||||
|
offsetLeft: this.state.offsetLeft,
|
||||||
|
},
|
||||||
|
null,
|
||||||
|
),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
this.resetHistory();
|
this.resetHistory();
|
||||||
this.syncActionResult({
|
this.syncActionResult({
|
||||||
@@ -804,6 +831,7 @@ class App extends React.Component<ExcalidrawProps, AppState> {
|
|||||||
|
|
||||||
document.addEventListener(EVENT.PASTE, this.pasteFromClipboard);
|
document.addEventListener(EVENT.PASTE, this.pasteFromClipboard);
|
||||||
document.addEventListener(EVENT.CUT, this.onCut);
|
document.addEventListener(EVENT.CUT, this.onCut);
|
||||||
|
document.addEventListener(EVENT.SCROLL, this.onScroll);
|
||||||
|
|
||||||
window.addEventListener(EVENT.RESIZE, this.onResize, false);
|
window.addEventListener(EVENT.RESIZE, this.onResize, false);
|
||||||
window.addEventListener(EVENT.UNLOAD, this.onUnload, false);
|
window.addEventListener(EVENT.UNLOAD, this.onUnload, false);
|
||||||
@@ -843,6 +871,15 @@ class App extends React.Component<ExcalidrawProps, AppState> {
|
|||||||
this.addEventListeners();
|
this.addEventListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (prevProps.zenModeEnabled !== this.props.zenModeEnabled) {
|
||||||
|
this.setState({ zenModeEnabled: !!this.props.zenModeEnabled });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (prevProps.gridModeEnabled !== this.props.gridModeEnabled) {
|
||||||
|
this.setState({
|
||||||
|
gridSize: this.props.gridModeEnabled ? GRID_SIZE : null,
|
||||||
|
});
|
||||||
|
}
|
||||||
document
|
document
|
||||||
.querySelector(".excalidraw")
|
.querySelector(".excalidraw")
|
||||||
?.classList.toggle("Appearance_dark", this.state.appearance === "dark");
|
?.classList.toggle("Appearance_dark", this.state.appearance === "dark");
|
||||||
@@ -882,6 +919,7 @@ class App extends React.Component<ExcalidrawProps, AppState> {
|
|||||||
const pointerViewportCoords: SceneState["remotePointerViewportCoords"] = {};
|
const pointerViewportCoords: SceneState["remotePointerViewportCoords"] = {};
|
||||||
const remoteSelectedElementIds: SceneState["remoteSelectedElementIds"] = {};
|
const remoteSelectedElementIds: SceneState["remoteSelectedElementIds"] = {};
|
||||||
const pointerUsernames: { [id: string]: string } = {};
|
const pointerUsernames: { [id: string]: string } = {};
|
||||||
|
const pointerUserStates: { [id: string]: string } = {};
|
||||||
this.state.collaborators.forEach((user, socketId) => {
|
this.state.collaborators.forEach((user, socketId) => {
|
||||||
if (user.selectedElementIds) {
|
if (user.selectedElementIds) {
|
||||||
for (const id of Object.keys(user.selectedElementIds)) {
|
for (const id of Object.keys(user.selectedElementIds)) {
|
||||||
@@ -897,6 +935,9 @@ class App extends React.Component<ExcalidrawProps, AppState> {
|
|||||||
if (user.username) {
|
if (user.username) {
|
||||||
pointerUsernames[socketId] = user.username;
|
pointerUsernames[socketId] = user.username;
|
||||||
}
|
}
|
||||||
|
if (user.userState) {
|
||||||
|
pointerUserStates[socketId] = user.userState;
|
||||||
|
}
|
||||||
pointerViewportCoords[socketId] = sceneCoordsToViewportCoords(
|
pointerViewportCoords[socketId] = sceneCoordsToViewportCoords(
|
||||||
{
|
{
|
||||||
sceneX: user.pointer.x,
|
sceneX: user.pointer.x,
|
||||||
@@ -931,10 +972,12 @@ class App extends React.Component<ExcalidrawProps, AppState> {
|
|||||||
remotePointerButton: cursorButton,
|
remotePointerButton: cursorButton,
|
||||||
remoteSelectedElementIds,
|
remoteSelectedElementIds,
|
||||||
remotePointerUsernames: pointerUsernames,
|
remotePointerUsernames: pointerUsernames,
|
||||||
|
remotePointerUserStates: pointerUserStates,
|
||||||
shouldCacheIgnoreZoom: this.state.shouldCacheIgnoreZoom,
|
shouldCacheIgnoreZoom: this.state.shouldCacheIgnoreZoom,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
renderOptimizations: true,
|
renderOptimizations: true,
|
||||||
|
renderScrollbars: !isMobile(),
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
if (scrollBars) {
|
if (scrollBars) {
|
||||||
@@ -963,6 +1006,10 @@ class App extends React.Component<ExcalidrawProps, AppState> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private onScroll = debounce(() => {
|
||||||
|
this.setState({ ...this.getCanvasOffsets() });
|
||||||
|
}, SCROLL_TIMEOUT);
|
||||||
|
|
||||||
// Copy/paste
|
// Copy/paste
|
||||||
|
|
||||||
private onCut = withBatchedUpdates((event: ClipboardEvent) => {
|
private onCut = withBatchedUpdates((event: ClipboardEvent) => {
|
||||||
@@ -1515,6 +1562,7 @@ class App extends React.Component<ExcalidrawProps, AppState> {
|
|||||||
textWysiwyg({
|
textWysiwyg({
|
||||||
id: element.id,
|
id: element.id,
|
||||||
appState: this.state,
|
appState: this.state,
|
||||||
|
canvas: this.canvas,
|
||||||
getViewportCoords: (x, y) => {
|
getViewportCoords: (x, y) => {
|
||||||
const { x: viewportX, y: viewportY } = sceneCoordsToViewportCoords(
|
const { x: viewportX, y: viewportY } = sceneCoordsToViewportCoords(
|
||||||
{
|
{
|
||||||
@@ -1823,10 +1871,11 @@ class App extends React.Component<ExcalidrawProps, AppState> {
|
|||||||
if (isHoldingSpace || isPanning || isDraggingScrollBar) {
|
if (isHoldingSpace || isPanning || isDraggingScrollBar) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const isPointerOverScrollBars = isOverScrollBars(
|
const isPointerOverScrollBars = isOverScrollBars(
|
||||||
currentScrollBars,
|
currentScrollBars,
|
||||||
event.clientX,
|
event.clientX - this.state.offsetLeft,
|
||||||
event.clientY,
|
event.clientY - this.state.offsetTop,
|
||||||
);
|
);
|
||||||
const isOverScrollBar = isPointerOverScrollBars.isOverEither;
|
const isOverScrollBar = isPointerOverScrollBars.isOverEither;
|
||||||
if (!this.state.draggingElement && !this.state.multiElement) {
|
if (!this.state.draggingElement && !this.state.multiElement) {
|
||||||
@@ -1920,7 +1969,7 @@ class App extends React.Component<ExcalidrawProps, AppState> {
|
|||||||
points: points.slice(0, -1),
|
points: points.slice(0, -1),
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
if (isPathALoop(points)) {
|
if (isPathALoop(points, this.state.zoom.value)) {
|
||||||
document.documentElement.style.cursor = CURSOR_TYPE.POINTER;
|
document.documentElement.style.cursor = CURSOR_TYPE.POINTER;
|
||||||
}
|
}
|
||||||
// update last uncommitted point
|
// update last uncommitted point
|
||||||
@@ -2282,8 +2331,8 @@ class App extends React.Component<ExcalidrawProps, AppState> {
|
|||||||
),
|
),
|
||||||
scrollbars: isOverScrollBars(
|
scrollbars: isOverScrollBars(
|
||||||
currentScrollBars,
|
currentScrollBars,
|
||||||
event.clientX,
|
event.clientX - this.state.offsetLeft,
|
||||||
event.clientY,
|
event.clientY - this.state.offsetTop,
|
||||||
),
|
),
|
||||||
// we need to duplicate because we'll be updating this state
|
// we need to duplicate because we'll be updating this state
|
||||||
lastCoords: { ...origin },
|
lastCoords: { ...origin },
|
||||||
@@ -2592,7 +2641,10 @@ class App extends React.Component<ExcalidrawProps, AppState> {
|
|||||||
const { multiElement } = this.state;
|
const { multiElement } = this.state;
|
||||||
|
|
||||||
// finalize if completing a loop
|
// finalize if completing a loop
|
||||||
if (multiElement.type === "line" && isPathALoop(multiElement.points)) {
|
if (
|
||||||
|
multiElement.type === "line" &&
|
||||||
|
isPathALoop(multiElement.points, this.state.zoom.value)
|
||||||
|
) {
|
||||||
mutateElement(multiElement, {
|
mutateElement(multiElement, {
|
||||||
lastCommittedPoint:
|
lastCommittedPoint:
|
||||||
multiElement.points[multiElement.points.length - 1],
|
multiElement.points[multiElement.points.length - 1],
|
||||||
@@ -3663,15 +3715,16 @@ class App extends React.Component<ExcalidrawProps, AppState> {
|
|||||||
options.push(actionCopyAsSvg);
|
options.push(actionCopyAsSvg);
|
||||||
}
|
}
|
||||||
if (!element) {
|
if (!element) {
|
||||||
const viewModeOptions: ContextMenuOption[] = [
|
const viewModeOptions = [
|
||||||
...options,
|
...options,
|
||||||
|
typeof this.props.gridModeEnabled === "undefined" &&
|
||||||
|
actionToggleGridMode,
|
||||||
|
typeof this.props.zenModeEnabled === "undefined" && actionToggleZenMode,
|
||||||
|
typeof this.props.viewModeEnabled === "undefined" &&
|
||||||
|
actionToggleViewMode,
|
||||||
actionToggleStats,
|
actionToggleStats,
|
||||||
];
|
];
|
||||||
|
|
||||||
if (typeof this.props.viewModeEnabled === "undefined") {
|
|
||||||
viewModeOptions.push(actionToggleViewMode);
|
|
||||||
}
|
|
||||||
|
|
||||||
ContextMenu.push({
|
ContextMenu.push({
|
||||||
options: viewModeOptions,
|
options: viewModeOptions,
|
||||||
top: clientY,
|
top: clientY,
|
||||||
@@ -3709,8 +3762,10 @@ class App extends React.Component<ExcalidrawProps, AppState> {
|
|||||||
separator,
|
separator,
|
||||||
actionSelectAll,
|
actionSelectAll,
|
||||||
separator,
|
separator,
|
||||||
actionToggleGridMode,
|
typeof this.props.gridModeEnabled === "undefined" &&
|
||||||
actionToggleZenMode,
|
actionToggleGridMode,
|
||||||
|
typeof this.props.zenModeEnabled === "undefined" &&
|
||||||
|
actionToggleZenMode,
|
||||||
typeof this.props.viewModeEnabled === "undefined" &&
|
typeof this.props.viewModeEnabled === "undefined" &&
|
||||||
actionToggleViewMode,
|
actionToggleViewMode,
|
||||||
actionToggleStats,
|
actionToggleStats,
|
||||||
|
|||||||
@@ -2,9 +2,10 @@
|
|||||||
|
|
||||||
.excalidraw {
|
.excalidraw {
|
||||||
.CollabButton.is-collaborating {
|
.CollabButton.is-collaborating {
|
||||||
background-color: var(--button-special-active-background-color);
|
background-color: var(--button-special-active-bg-color);
|
||||||
|
|
||||||
.ToolIcon__icon svg {
|
.ToolIcon__icon svg,
|
||||||
|
.ToolIcon__label {
|
||||||
color: var(--icon-green-fill-color);
|
color: var(--icon-green-fill-color);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,8 +25,8 @@ const CollabButton = ({
|
|||||||
onClick={onClick}
|
onClick={onClick}
|
||||||
icon={users}
|
icon={users}
|
||||||
type="button"
|
type="button"
|
||||||
title={t("buttons.roomDialog")}
|
title={t("labels.liveCollaboration")}
|
||||||
aria-label={t("buttons.roomDialog")}
|
aria-label={t("labels.liveCollaboration")}
|
||||||
showAriaLabel={useIsMobile()}
|
showAriaLabel={useIsMobile()}
|
||||||
>
|
>
|
||||||
{collaboratorCount > 0 && (
|
{collaboratorCount > 0 && (
|
||||||
|
|||||||
@@ -2,9 +2,9 @@
|
|||||||
|
|
||||||
.excalidraw {
|
.excalidraw {
|
||||||
.color-picker {
|
.color-picker {
|
||||||
background: var(--popup-background-color);
|
background: var(--popup-bg-color);
|
||||||
border: 0px solid transparentize($oc-white, 0.75);
|
border: 0 solid transparentize($oc-white, 0.75);
|
||||||
box-shadow: transparentize($oc-black, 0.75) 0px 1px 4px;
|
box-shadow: transparentize($oc-black, 0.75) 0 1px 4px;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
|
|
||||||
@@ -24,11 +24,11 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.color-picker-triangle {
|
.color-picker-triangle {
|
||||||
width: 0px;
|
width: 0;
|
||||||
height: 0px;
|
height: 0;
|
||||||
border-style: solid;
|
border-style: solid;
|
||||||
border-width: 0px 9px 10px;
|
border-width: 0 9px 10px;
|
||||||
border-color: transparent transparent var(--popup-background-color);
|
border-color: transparent transparent var(--popup-bg-color);
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: -10px;
|
top: -10px;
|
||||||
|
|
||||||
@@ -84,12 +84,12 @@
|
|||||||
|
|
||||||
.color-picker-transparent {
|
.color-picker-transparent {
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
box-shadow: transparentize($oc-black, 0.9) 0px 0px 0px 1px inset;
|
box-shadow: transparentize($oc-black, 0.9) 0 0 0 1px inset;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0px;
|
top: 0;
|
||||||
right: 0px;
|
right: 0;
|
||||||
bottom: 0px;
|
bottom: 0;
|
||||||
left: 0px;
|
left: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.color-picker-transparent,
|
.color-picker-transparent,
|
||||||
@@ -104,11 +104,11 @@
|
|||||||
width: 1.875rem;
|
width: 1.875rem;
|
||||||
|
|
||||||
:root[dir="ltr"] & {
|
:root[dir="ltr"] & {
|
||||||
border-radius: 4px 0px 0px 4px;
|
border-radius: 4px 0 0 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
:root[dir="rtl"] & {
|
:root[dir="rtl"] & {
|
||||||
border-radius: 0px 4px 4px 0px;
|
border-radius: 0 4px 4px 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
color: var(--input-label-color);
|
color: var(--input-label-color);
|
||||||
@@ -144,7 +144,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.color-input-container:focus-within .color-picker-hash::after {
|
.color-input-container:focus-within .color-picker-hash::after {
|
||||||
background: var(--input-background-color);
|
background: var(--input-bg-color);
|
||||||
|
|
||||||
:root[dir="ltr"] & {
|
:root[dir="ltr"] & {
|
||||||
right: -2px;
|
right: -2px;
|
||||||
@@ -163,19 +163,19 @@
|
|||||||
width: 12ch; /* length of `transparent` + 1 */
|
width: 12ch; /* length of `transparent` + 1 */
|
||||||
margin: 0;
|
margin: 0;
|
||||||
font-size: 1rem;
|
font-size: 1rem;
|
||||||
background-color: var(--input-background-color);
|
background-color: var(--input-bg-color);
|
||||||
color: var(--text-color-primary);
|
color: var(--text-primary-color);
|
||||||
border: 0px;
|
border: 0;
|
||||||
outline: none;
|
outline: none;
|
||||||
height: 1.75em;
|
height: 1.75em;
|
||||||
box-shadow: var(--input-border-color) 0px 0px 0px 1px inset;
|
box-shadow: var(--input-border-color) 0 0 0 1px inset;
|
||||||
|
|
||||||
:root[dir="ltr"] & {
|
:root[dir="ltr"] & {
|
||||||
border-radius: 0px 4px 4px 0px;
|
border-radius: 0 4px 4px 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
:root[dir="rtl"] & {
|
:root[dir="rtl"] & {
|
||||||
border-radius: 4px 0px 0px 4px;
|
border-radius: 4px 0 0 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
float: left;
|
float: left;
|
||||||
@@ -228,7 +228,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.color-picker-type-elementBackground .color-picker-keybinding {
|
.color-picker-type-elementBackground .color-picker-keybinding {
|
||||||
color: #fff;
|
color: $oc-white;
|
||||||
}
|
}
|
||||||
|
|
||||||
.color-picker-swatch[aria-label="transparent"] .color-picker-keybinding {
|
.color-picker-swatch[aria-label="transparent"] .color-picker-keybinding {
|
||||||
@@ -241,10 +241,10 @@
|
|||||||
|
|
||||||
&.Appearance_dark {
|
&.Appearance_dark {
|
||||||
.color-picker-type-elementBackground .color-picker-keybinding {
|
.color-picker-type-elementBackground .color-picker-keybinding {
|
||||||
color: #000;
|
color: $oc-black;
|
||||||
}
|
}
|
||||||
.color-picker-swatch[aria-label="transparent"] .color-picker-keybinding {
|
.color-picker-swatch[aria-label="transparent"] .color-picker-keybinding {
|
||||||
color: #000;
|
color: $oc-black;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,13 +4,13 @@
|
|||||||
.context-menu {
|
.context-menu {
|
||||||
position: relative;
|
position: relative;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
box-shadow: 0px 3px 10px transparentize($oc-black, 0.8);
|
box-shadow: 0 3px 10px transparentize($oc-black, 0.8);
|
||||||
padding: 0;
|
padding: 0;
|
||||||
list-style: none;
|
list-style: none;
|
||||||
user-select: none;
|
user-select: none;
|
||||||
margin: -0.25rem 0 0 0.125rem;
|
margin: -0.25rem 0 0 0.125rem;
|
||||||
padding: 0.5rem 0;
|
padding: 0.5rem 0;
|
||||||
background-color: var(--popup-secondary-background-color);
|
background-color: var(--popup-secondary-bg-color);
|
||||||
border: 1px solid var(--button-gray-3);
|
border: 1px solid var(--button-gray-3);
|
||||||
cursor: default;
|
cursor: default;
|
||||||
}
|
}
|
||||||
@@ -61,12 +61,12 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.context-menu-option:hover {
|
.context-menu-option:hover {
|
||||||
color: var(--popup-background-color);
|
color: var(--popup-bg-color);
|
||||||
background-color: var(--select-highlight-color);
|
background-color: var(--select-highlight-color);
|
||||||
|
|
||||||
&.dangerous {
|
&.dangerous {
|
||||||
.context-menu-option__label {
|
.context-menu-option__label {
|
||||||
color: var(--popup-background-color);
|
color: var(--popup-bg-color);
|
||||||
}
|
}
|
||||||
background-color: $oc-red-6;
|
background-color: $oc-red-6;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,13 +10,18 @@ export type Appearence = "light" | "dark";
|
|||||||
export const DarkModeToggle = (props: {
|
export const DarkModeToggle = (props: {
|
||||||
value: Appearence;
|
value: Appearence;
|
||||||
onChange: (value: Appearence) => void;
|
onChange: (value: Appearence) => void;
|
||||||
|
title?: string;
|
||||||
}) => {
|
}) => {
|
||||||
|
const title = props.title
|
||||||
|
? props.title
|
||||||
|
: props.value === "dark"
|
||||||
|
? t("buttons.lightMode")
|
||||||
|
: t("buttons.darkMode");
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<label
|
<label
|
||||||
className={`ToolIcon ToolIcon_type_floating ToolIcon_size_M`}
|
className={`ToolIcon ToolIcon_type_floating ToolIcon_size_M`}
|
||||||
title={
|
title={title}
|
||||||
props.value === "dark" ? t("buttons.lightMode") : t("buttons.darkMode")
|
|
||||||
}
|
|
||||||
>
|
>
|
||||||
<input
|
<input
|
||||||
className="ToolIcon_type_checkbox ToolIcon_toggle_opaque"
|
className="ToolIcon_type_checkbox ToolIcon_toggle_opaque"
|
||||||
@@ -25,11 +30,7 @@ export const DarkModeToggle = (props: {
|
|||||||
props.onChange(event.target.checked ? "dark" : "light")
|
props.onChange(event.target.checked ? "dark" : "light")
|
||||||
}
|
}
|
||||||
checked={props.value === "dark"}
|
checked={props.value === "dark"}
|
||||||
aria-label={
|
aria-label={title}
|
||||||
props.value === "dark"
|
|
||||||
? t("buttons.lightMode")
|
|
||||||
: t("buttons.darkMode")
|
|
||||||
}
|
|
||||||
/>
|
/>
|
||||||
<div className="ToolIcon__icon">
|
<div className="ToolIcon__icon">
|
||||||
{props.value === "light" ? ICONS.MOON : ICONS.SUN}
|
{props.value === "light" ? ICONS.MOON : ICONS.SUN}
|
||||||
|
|||||||
@@ -45,7 +45,7 @@
|
|||||||
position: sticky;
|
position: sticky;
|
||||||
top: 0;
|
top: 0;
|
||||||
padding: calc(var(--space-factor) * 2);
|
padding: calc(var(--space-factor) * 2);
|
||||||
background: var(--bg-color-island);
|
background: var(--island-bg-color);
|
||||||
font-size: 1.25em;
|
font-size: 1.25em;
|
||||||
|
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
|
|||||||
@@ -19,6 +19,9 @@ import { ToolButton } from "./ToolButton";
|
|||||||
const scales = [1, 2, 3];
|
const scales = [1, 2, 3];
|
||||||
const defaultScale = scales.includes(devicePixelRatio) ? devicePixelRatio : 1;
|
const defaultScale = scales.includes(devicePixelRatio) ? devicePixelRatio : 1;
|
||||||
|
|
||||||
|
const supportsContextFilters =
|
||||||
|
"filter" in document.createElement("canvas").getContext("2d")!;
|
||||||
|
|
||||||
export const ErrorCanvasPreview = () => {
|
export const ErrorCanvasPreview = () => {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
@@ -128,6 +131,8 @@ const ExportModal = ({
|
|||||||
return (
|
return (
|
||||||
<div className="ExportDialog">
|
<div className="ExportDialog">
|
||||||
<div className="ExportDialog__preview" ref={previewRef} />
|
<div className="ExportDialog__preview" ref={previewRef} />
|
||||||
|
{supportsContextFilters &&
|
||||||
|
actionManager.renderAction("exportWithDarkMode")}
|
||||||
<Stack.Col gap={2} align="center">
|
<Stack.Col gap={2} align="center">
|
||||||
<div className="ExportDialog__actions">
|
<div className="ExportDialog__actions">
|
||||||
<Stack.Row gap={2}>
|
<Stack.Row gap={2}>
|
||||||
|
|||||||
@@ -224,7 +224,7 @@ export const HelpDialog = ({ onClose }: { onClose?: () => void }) => {
|
|||||||
shortcuts={[getShortcutKey("Alt+Z")]}
|
shortcuts={[getShortcutKey("Alt+Z")]}
|
||||||
/>
|
/>
|
||||||
<Shortcut
|
<Shortcut
|
||||||
label={t("labels.gridMode")}
|
label={t("labels.showGrid")}
|
||||||
shortcuts={[getShortcutKey("CtrlOrCmd+'")]}
|
shortcuts={[getShortcutKey("CtrlOrCmd+'")]}
|
||||||
/>
|
/>
|
||||||
<Shortcut
|
<Shortcut
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ $wide-viewport-width: 1000px;
|
|||||||
|
|
||||||
> span {
|
> span {
|
||||||
padding: 0.2rem 0.4rem;
|
padding: 0.2rem 0.4rem;
|
||||||
background-color: var(--overlay-background-color);
|
background-color: var(--overlay-bg-color);
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,9 +8,9 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.picker {
|
.picker {
|
||||||
background: var(--popup-background-color);
|
background: var(--popup-bg-color);
|
||||||
border: 0px solid transparentize($oc-white, 0.75);
|
border: 0 solid transparentize($oc-white, 0.75);
|
||||||
box-shadow: transparentize($oc-black, 0.75) 0px 1px 4px;
|
box-shadow: transparentize($oc-black, 0.75) 0 1px 4px;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
}
|
}
|
||||||
@@ -56,8 +56,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.picker-triangle {
|
.picker-triangle {
|
||||||
width: 0px;
|
width: 0;
|
||||||
height: 0px;
|
height: 0;
|
||||||
position: relative;
|
position: relative;
|
||||||
top: -10px;
|
top: -10px;
|
||||||
:root[dir="ltr"] & {
|
:root[dir="ltr"] & {
|
||||||
@@ -73,7 +73,7 @@
|
|||||||
content: "";
|
content: "";
|
||||||
position: absolute;
|
position: absolute;
|
||||||
border-style: solid;
|
border-style: solid;
|
||||||
border-width: 0px 9px 10px;
|
border-width: 0 9px 10px;
|
||||||
border-color: transparent transparent transparentize($oc-black, 0.9);
|
border-color: transparent transparent transparentize($oc-black, 0.9);
|
||||||
top: -1px;
|
top: -1px;
|
||||||
}
|
}
|
||||||
@@ -82,8 +82,8 @@
|
|||||||
content: "";
|
content: "";
|
||||||
position: absolute;
|
position: absolute;
|
||||||
border-style: solid;
|
border-style: solid;
|
||||||
border-width: 0px 9px 10px;
|
border-width: 0 9px 10px;
|
||||||
border-color: transparent transparent var(--popup-background-color);
|
border-color: transparent transparent var(--popup-bg-color);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -121,7 +121,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.picker-type-elementBackground .picker-keybinding {
|
.picker-type-elementBackground .picker-keybinding {
|
||||||
color: #fff;
|
color: $oc-white;
|
||||||
}
|
}
|
||||||
|
|
||||||
.picker-swatch[aria-label="transparent"] .picker-keybinding {
|
.picker-swatch[aria-label="transparent"] .picker-keybinding {
|
||||||
@@ -134,10 +134,10 @@
|
|||||||
|
|
||||||
&.Appearance_dark {
|
&.Appearance_dark {
|
||||||
.picker-type-elementBackground .picker-keybinding {
|
.picker-type-elementBackground .picker-keybinding {
|
||||||
color: #000;
|
color: $oc-black;
|
||||||
}
|
}
|
||||||
.picker-swatch[aria-label="transparent"] .picker-keybinding {
|
.picker-swatch[aria-label="transparent"] .picker-keybinding {
|
||||||
color: #000;
|
color: $oc-black;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,7 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
|
|
||||||
import { LoadingMessage } from "./LoadingMessage";
|
import { LoadingMessage } from "./LoadingMessage";
|
||||||
import {
|
import { defaultLang, Language, languages, setLanguage } from "../i18n";
|
||||||
defaultLang,
|
|
||||||
Language,
|
|
||||||
languages,
|
|
||||||
setLanguageFirstTime,
|
|
||||||
} from "../i18n";
|
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
langCode: Language["code"];
|
langCode: Language["code"];
|
||||||
@@ -23,7 +18,7 @@ export class InitializeApp extends React.Component<Props, State> {
|
|||||||
const currentLang =
|
const currentLang =
|
||||||
languages.find((lang) => lang.code === this.props.langCode) ||
|
languages.find((lang) => lang.code === this.props.langCode) ||
|
||||||
defaultLang;
|
defaultLang;
|
||||||
await setLanguageFirstTime(currentLang);
|
await setLanguage(currentLang);
|
||||||
this.setState({
|
this.setState({
|
||||||
isLoading: false,
|
isLoading: false,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
.excalidraw {
|
.excalidraw {
|
||||||
.Island {
|
.Island {
|
||||||
--padding: 0;
|
--padding: 0;
|
||||||
background-color: var(--bg-color-island);
|
background-color: var(--island-bg-color);
|
||||||
backdrop-filter: saturate(100%) blur(10px);
|
backdrop-filter: saturate(100%) blur(10px);
|
||||||
box-shadow: var(--shadow-island);
|
box-shadow: var(--shadow-island);
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
|
|||||||
@@ -19,9 +19,9 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
a {
|
a {
|
||||||
margin-left: auto;
|
margin-inline-start: auto;
|
||||||
// 17px for scrollbar (needed for overlay scrollbars on Big Sur?) + 1px extra
|
// 17px for scrollbar (needed for overlay scrollbars on Big Sur?) + 1px extra
|
||||||
padding-right: 18px;
|
padding-inline-end: 18px;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -38,6 +38,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.layer-ui__wrapper {
|
.layer-ui__wrapper {
|
||||||
|
z-index: var(--zIndex-layerUI);
|
||||||
|
|
||||||
.encrypted-icon {
|
.encrypted-icon {
|
||||||
position: relative;
|
position: relative;
|
||||||
margin-inline-start: 15px;
|
margin-inline-start: 15px;
|
||||||
@@ -71,7 +73,7 @@
|
|||||||
&__footer {
|
&__footer {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
z-index: 100;
|
z-index: 100;
|
||||||
bottom: 0px;
|
bottom: 0;
|
||||||
|
|
||||||
:root[dir="ltr"] & {
|
:root[dir="ltr"] & {
|
||||||
right: 0;
|
right: 0;
|
||||||
@@ -92,7 +94,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
:root[dir="ltr"] &.transition-right {
|
:root[dir="ltr"] &.transition-right {
|
||||||
transform: translate(999px, 0px);
|
transform: translate(999px, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
:root[dir="rtl"] &.transition-left {
|
:root[dir="rtl"] &.transition-left {
|
||||||
|
|||||||
+58
-28
@@ -11,7 +11,7 @@ import { CLASSES } from "../constants";
|
|||||||
import { exportCanvas } from "../data";
|
import { exportCanvas } from "../data";
|
||||||
import { importLibraryFromJSON, saveLibraryAsJSON } from "../data/json";
|
import { importLibraryFromJSON, saveLibraryAsJSON } from "../data/json";
|
||||||
import { Library } from "../data/library";
|
import { Library } from "../data/library";
|
||||||
import { showSelectedShapeActions } from "../element";
|
import { isTextElement, showSelectedShapeActions } from "../element";
|
||||||
import { NonDeletedExcalidrawElement } from "../element/types";
|
import { NonDeletedExcalidrawElement } from "../element/types";
|
||||||
import { Language, t } from "../i18n";
|
import { Language, t } from "../i18n";
|
||||||
import useIsMobile from "../is-mobile";
|
import useIsMobile from "../is-mobile";
|
||||||
@@ -27,7 +27,7 @@ import { ExportCB, ExportDialog } from "./ExportDialog";
|
|||||||
import { FixedSideContainer } from "./FixedSideContainer";
|
import { FixedSideContainer } from "./FixedSideContainer";
|
||||||
import { GitHubCorner } from "./GitHubCorner";
|
import { GitHubCorner } from "./GitHubCorner";
|
||||||
import { HintViewer } from "./HintViewer";
|
import { HintViewer } from "./HintViewer";
|
||||||
import { exportFile, load, shield } from "./icons";
|
import { exportFile, load, shield, trash } from "./icons";
|
||||||
import { Island } from "./Island";
|
import { Island } from "./Island";
|
||||||
import "./LayerUI.scss";
|
import "./LayerUI.scss";
|
||||||
import { LibraryUnit } from "./LibraryUnit";
|
import { LibraryUnit } from "./LibraryUnit";
|
||||||
@@ -52,6 +52,7 @@ interface LayerUIProps {
|
|||||||
onLockToggle: () => void;
|
onLockToggle: () => void;
|
||||||
onInsertElements: (elements: readonly NonDeletedExcalidrawElement[]) => void;
|
onInsertElements: (elements: readonly NonDeletedExcalidrawElement[]) => void;
|
||||||
zenModeEnabled: boolean;
|
zenModeEnabled: boolean;
|
||||||
|
showExitZenModeBtn: boolean;
|
||||||
toggleZenMode: () => void;
|
toggleZenMode: () => void;
|
||||||
langCode: Language["code"];
|
langCode: Language["code"];
|
||||||
isCollaborating: boolean;
|
isCollaborating: boolean;
|
||||||
@@ -99,6 +100,7 @@ const LibraryMenuItems = ({
|
|||||||
onInsertShape,
|
onInsertShape,
|
||||||
pendingElements,
|
pendingElements,
|
||||||
setAppState,
|
setAppState,
|
||||||
|
setLibraryItems,
|
||||||
}: {
|
}: {
|
||||||
library: LibraryItems;
|
library: LibraryItems;
|
||||||
pendingElements: LibraryItem;
|
pendingElements: LibraryItem;
|
||||||
@@ -106,6 +108,7 @@ const LibraryMenuItems = ({
|
|||||||
onInsertShape: (elements: LibraryItem) => void;
|
onInsertShape: (elements: LibraryItem) => void;
|
||||||
onAddToLibrary: (elements: LibraryItem) => void;
|
onAddToLibrary: (elements: LibraryItem) => void;
|
||||||
setAppState: React.Component<any, AppState>["setState"];
|
setAppState: React.Component<any, AppState>["setState"];
|
||||||
|
setLibraryItems: (library: LibraryItems) => void;
|
||||||
}) => {
|
}) => {
|
||||||
const isMobile = useIsMobile();
|
const isMobile = useIsMobile();
|
||||||
const numCells = library.length + (pendingElements.length > 0 ? 1 : 0);
|
const numCells = library.length + (pendingElements.length > 0 ? 1 : 0);
|
||||||
@@ -149,6 +152,19 @@ const LibraryMenuItems = ({
|
|||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
<ToolButton
|
||||||
|
key="reset"
|
||||||
|
type="button"
|
||||||
|
title={t("buttons.resetLibrary")}
|
||||||
|
aria-label={t("buttons.resetLibrary")}
|
||||||
|
icon={trash}
|
||||||
|
onClick={() => {
|
||||||
|
if (window.confirm(t("alerts.resetLibrary"))) {
|
||||||
|
Library.resetLibrary();
|
||||||
|
setLibraryItems([]);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
<a href="https://libraries.excalidraw.com" target="_excalidraw_libraries">
|
<a href="https://libraries.excalidraw.com" target="_excalidraw_libraries">
|
||||||
{t("labels.libraries")}
|
{t("labels.libraries")}
|
||||||
@@ -280,6 +296,7 @@ const LibraryMenu = ({
|
|||||||
onInsertShape={onInsertShape}
|
onInsertShape={onInsertShape}
|
||||||
pendingElements={pendingElements}
|
pendingElements={pendingElements}
|
||||||
setAppState={setAppState}
|
setAppState={setAppState}
|
||||||
|
setLibraryItems={setLibraryItems}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</Island>
|
</Island>
|
||||||
@@ -296,6 +313,7 @@ const LayerUI = ({
|
|||||||
onLockToggle,
|
onLockToggle,
|
||||||
onInsertElements,
|
onInsertElements,
|
||||||
zenModeEnabled,
|
zenModeEnabled,
|
||||||
|
showExitZenModeBtn,
|
||||||
toggleZenMode,
|
toggleZenMode,
|
||||||
isCollaborating,
|
isCollaborating,
|
||||||
onExportToBackend,
|
onExportToBackend,
|
||||||
@@ -424,7 +442,15 @@ const LayerUI = ({
|
|||||||
"transition-left": zenModeEnabled,
|
"transition-left": zenModeEnabled,
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
<Island className={CLASSES.SHAPE_ACTIONS_MENU} padding={2}>
|
<Island
|
||||||
|
className={CLASSES.SHAPE_ACTIONS_MENU}
|
||||||
|
padding={2}
|
||||||
|
style={{
|
||||||
|
// we want to make sure this doesn't overflow so substracting 200
|
||||||
|
// which is approximately height of zoom footer and top left menu items with some buffer
|
||||||
|
maxHeight: `${appState.height - 200}px`,
|
||||||
|
}}
|
||||||
|
>
|
||||||
<SelectedShapeActions
|
<SelectedShapeActions
|
||||||
appState={appState}
|
appState={appState}
|
||||||
elements={elements}
|
elements={elements}
|
||||||
@@ -513,17 +539,18 @@ const LayerUI = ({
|
|||||||
"transition-right": zenModeEnabled,
|
"transition-right": zenModeEnabled,
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
{Array.from(appState.collaborators)
|
{appState.collaborators.size > 0 &&
|
||||||
// Collaborator is either not initialized or is actually the current user.
|
Array.from(appState.collaborators)
|
||||||
.filter(([_, client]) => Object.keys(client).length !== 0)
|
// Collaborator is either not initialized or is actually the current user.
|
||||||
.map(([clientId, client]) => (
|
.filter(([_, client]) => Object.keys(client).length !== 0)
|
||||||
<Tooltip
|
.map(([clientId, client]) => (
|
||||||
label={client.username || "Unknown user"}
|
<Tooltip
|
||||||
key={clientId}
|
label={client.username || "Unknown user"}
|
||||||
>
|
key={clientId}
|
||||||
{actionManager.renderAction("goToCollaborator", clientId)}
|
>
|
||||||
</Tooltip>
|
{actionManager.renderAction("goToCollaborator", clientId)}
|
||||||
))}
|
</Tooltip>
|
||||||
|
))}
|
||||||
</UserList>
|
</UserList>
|
||||||
</div>
|
</div>
|
||||||
</FixedSideContainer>
|
</FixedSideContainer>
|
||||||
@@ -578,24 +605,12 @@ const LayerUI = ({
|
|||||||
</div>
|
</div>
|
||||||
<button
|
<button
|
||||||
className={clsx("disable-zen-mode", {
|
className={clsx("disable-zen-mode", {
|
||||||
"disable-zen-mode--visible": zenModeEnabled,
|
"disable-zen-mode--visible": showExitZenModeBtn,
|
||||||
})}
|
})}
|
||||||
onClick={toggleZenMode}
|
onClick={toggleZenMode}
|
||||||
>
|
>
|
||||||
{t("buttons.exitZenMode")}
|
{t("buttons.exitZenMode")}
|
||||||
</button>
|
</button>
|
||||||
{appState.scrolledOutside && (
|
|
||||||
<button
|
|
||||||
className="scroll-back-to-content"
|
|
||||||
onClick={() => {
|
|
||||||
setAppState({
|
|
||||||
...calculateScrollCenter(elements, appState, canvas),
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{t("buttons.scrollBackToContent")}
|
|
||||||
</button>
|
|
||||||
)}
|
|
||||||
</footer>
|
</footer>
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -647,7 +662,10 @@ const LayerUI = ({
|
|||||||
) : (
|
) : (
|
||||||
<div
|
<div
|
||||||
className={clsx("layer-ui__wrapper", {
|
className={clsx("layer-ui__wrapper", {
|
||||||
"disable-pointerEvents": appState.cursorButton === "down",
|
"disable-pointerEvents":
|
||||||
|
appState.draggingElement ||
|
||||||
|
appState.resizingElement ||
|
||||||
|
(appState.editingElement && !isTextElement(appState.editingElement)),
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
{dialogs}
|
{dialogs}
|
||||||
@@ -655,6 +673,18 @@ const LayerUI = ({
|
|||||||
{renderBottomAppMenu()}
|
{renderBottomAppMenu()}
|
||||||
{renderGitHubCorner()}
|
{renderGitHubCorner()}
|
||||||
{renderFooter()}
|
{renderFooter()}
|
||||||
|
{appState.scrolledOutside && (
|
||||||
|
<button
|
||||||
|
className="scroll-back-to-content"
|
||||||
|
onClick={() => {
|
||||||
|
setAppState({
|
||||||
|
...calculateScrollCenter(elements, appState, canvas),
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{t("buttons.scrollBackToContent")}
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -46,9 +46,9 @@ export const MobileMenu = ({
|
|||||||
renderCustomFooter,
|
renderCustomFooter,
|
||||||
viewModeEnabled,
|
viewModeEnabled,
|
||||||
}: MobileMenuProps) => {
|
}: MobileMenuProps) => {
|
||||||
const renderFixedSideContainer = () => {
|
const renderToolbar = () => {
|
||||||
return (
|
return (
|
||||||
<FixedSideContainer side="top">
|
<FixedSideContainer side="top" className="App-top-bar">
|
||||||
<Section heading="shapes">
|
<Section heading="shapes">
|
||||||
{(heading) => (
|
{(heading) => (
|
||||||
<Stack.Col gap={4} align="center">
|
<Stack.Col gap={4} align="center">
|
||||||
@@ -136,7 +136,7 @@ export const MobileMenu = ({
|
|||||||
};
|
};
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{!viewModeEnabled && renderFixedSideContainer()}
|
{!viewModeEnabled && renderToolbar()}
|
||||||
<div
|
<div
|
||||||
className="App-bottom-bar"
|
className="App-bottom-bar"
|
||||||
style={{
|
style={{
|
||||||
@@ -152,24 +152,26 @@ export const MobileMenu = ({
|
|||||||
<Stack.Col gap={4}>
|
<Stack.Col gap={4}>
|
||||||
{renderCanvasActions()}
|
{renderCanvasActions()}
|
||||||
{renderCustomFooter?.(true)}
|
{renderCustomFooter?.(true)}
|
||||||
<fieldset>
|
{appState.collaborators.size > 0 && (
|
||||||
<legend>{t("labels.collaborators")}</legend>
|
<fieldset>
|
||||||
<UserList mobile>
|
<legend>{t("labels.collaborators")}</legend>
|
||||||
{Array.from(appState.collaborators)
|
<UserList mobile>
|
||||||
// Collaborator is either not initialized or is actually the current user.
|
{Array.from(appState.collaborators)
|
||||||
.filter(
|
// Collaborator is either not initialized or is actually the current user.
|
||||||
([_, client]) => Object.keys(client).length !== 0,
|
.filter(
|
||||||
)
|
([_, client]) => Object.keys(client).length !== 0,
|
||||||
.map(([clientId, client]) => (
|
)
|
||||||
<React.Fragment key={clientId}>
|
.map(([clientId, client]) => (
|
||||||
{actionManager.renderAction(
|
<React.Fragment key={clientId}>
|
||||||
"goToCollaborator",
|
{actionManager.renderAction(
|
||||||
clientId,
|
"goToCollaborator",
|
||||||
)}
|
clientId,
|
||||||
</React.Fragment>
|
)}
|
||||||
))}
|
</React.Fragment>
|
||||||
</UserList>
|
))}
|
||||||
</fieldset>
|
</UserList>
|
||||||
|
</fieldset>
|
||||||
|
)}
|
||||||
</Stack.Col>
|
</Stack.Col>
|
||||||
</div>
|
</div>
|
||||||
</Section>
|
</Section>
|
||||||
|
|||||||
@@ -1,8 +1,13 @@
|
|||||||
@import "../css/variables.module";
|
@import "../css/variables.module";
|
||||||
|
|
||||||
.excalidraw {
|
.excalidraw {
|
||||||
|
&.excalidraw-modal-container {
|
||||||
|
position: absolute;
|
||||||
|
z-index: 10;
|
||||||
|
}
|
||||||
|
|
||||||
.Modal {
|
.Modal {
|
||||||
position: fixed;
|
position: absolute;
|
||||||
top: 0;
|
top: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
right: 0;
|
right: 0;
|
||||||
@@ -15,7 +20,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.Modal__background {
|
.Modal__background {
|
||||||
position: fixed;
|
position: absolute;
|
||||||
top: 0;
|
top: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
right: 0;
|
right: 0;
|
||||||
@@ -39,10 +44,10 @@
|
|||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
|
|
||||||
// for modals, reset blurry bg
|
// for modals, reset blurry bg
|
||||||
background: var(--bg-color-island);
|
background: var(--island-bg-color);
|
||||||
backdrop-filter: none;
|
backdrop-filter: none;
|
||||||
|
|
||||||
border: 1px solid var(--dialog-border);
|
border: 1px solid var(--dialog-border-color);
|
||||||
box-shadow: 0 2px 10px transparentize($oc-black, 0.75);
|
box-shadow: 0 2px 10px transparentize($oc-black, 0.75);
|
||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
|
|
||||||
@@ -82,7 +87,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.Modal__content {
|
.Modal__content {
|
||||||
position: fixed;
|
position: absolute;
|
||||||
top: 0;
|
top: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
right: 0;
|
right: 0;
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ const useBodyRoot = () => {
|
|||||||
?.classList.contains("Appearance_dark");
|
?.classList.contains("Appearance_dark");
|
||||||
const div = document.createElement("div");
|
const div = document.createElement("div");
|
||||||
|
|
||||||
div.classList.add("excalidraw");
|
div.classList.add("excalidraw", "excalidraw-modal-container");
|
||||||
|
|
||||||
if (isDarkTheme) {
|
if (isDarkTheme) {
|
||||||
div.classList.add("Appearance_dark");
|
div.classList.add("Appearance_dark");
|
||||||
|
|||||||
@@ -1,14 +1,6 @@
|
|||||||
.excalidraw {
|
.excalidraw {
|
||||||
.popover {
|
.popover {
|
||||||
position: absolute;
|
position: fixed;
|
||||||
z-index: 10;
|
z-index: 10;
|
||||||
}
|
}
|
||||||
|
|
||||||
.popover .cover {
|
|
||||||
position: fixed;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
right: 0;
|
|
||||||
bottom: 0;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
+42
-40
@@ -1,51 +1,53 @@
|
|||||||
@import "../css/variables.module";
|
@import "../css/variables.module";
|
||||||
|
|
||||||
.Stats {
|
.excalidraw {
|
||||||
position: fixed;
|
.Stats {
|
||||||
top: 64px;
|
position: absolute;
|
||||||
right: 12px;
|
top: 64px;
|
||||||
font-size: 12px;
|
right: 12px;
|
||||||
z-index: 999;
|
font-size: 12px;
|
||||||
|
z-index: 999;
|
||||||
|
|
||||||
h3 {
|
h3 {
|
||||||
margin: 0 24px 8px 0;
|
margin: 0 24px 8px 0;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
.close {
|
.close {
|
||||||
float: right;
|
float: right;
|
||||||
height: 16px;
|
height: 16px;
|
||||||
width: 16px;
|
width: 16px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
svg {
|
svg {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
table {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
th {
|
||||||
|
border-bottom: 1px solid var(--input-border-color);
|
||||||
|
padding: 4px;
|
||||||
|
}
|
||||||
|
tr {
|
||||||
|
td:nth-child(2) {
|
||||||
|
min-width: 24px;
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
table {
|
:root[dir="rtl"] & {
|
||||||
width: 100%;
|
left: 12px;
|
||||||
th {
|
right: initial;
|
||||||
border-bottom: 1px solid var(--input-border-color);
|
|
||||||
padding: 4px;
|
h3 {
|
||||||
}
|
margin: 0 0 8px 24px;
|
||||||
tr {
|
}
|
||||||
td:nth-child(2) {
|
.close {
|
||||||
min-width: 24px;
|
float: left;
|
||||||
text-align: right;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
:root[dir="rtl"] & {
|
|
||||||
left: 12px;
|
|
||||||
right: initial;
|
|
||||||
|
|
||||||
h3 {
|
|
||||||
margin: 0 0 8px 24px;
|
|
||||||
}
|
|
||||||
.close {
|
|
||||||
float: left;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
import React, { useEffect, useState } from "react";
|
import React, { useEffect, useState } from "react";
|
||||||
|
import { copyTextToSystemClipboard } from "../clipboard";
|
||||||
|
import { DEFAULT_VERSION } from "../constants";
|
||||||
import { getCommonBounds } from "../element/bounds";
|
import { getCommonBounds } from "../element/bounds";
|
||||||
import { NonDeletedExcalidrawElement } from "../element/types";
|
import { NonDeletedExcalidrawElement } from "../element/types";
|
||||||
import {
|
import {
|
||||||
@@ -9,7 +11,7 @@ import { t } from "../i18n";
|
|||||||
import useIsMobile from "../is-mobile";
|
import useIsMobile from "../is-mobile";
|
||||||
import { getTargetElements } from "../scene";
|
import { getTargetElements } from "../scene";
|
||||||
import { AppState } from "../types";
|
import { AppState } from "../types";
|
||||||
import { debounce, nFormatter } from "../utils";
|
import { debounce, getVersion, nFormatter } from "../utils";
|
||||||
import { close } from "./icons";
|
import { close } from "./icons";
|
||||||
import { Island } from "./Island";
|
import { Island } from "./Island";
|
||||||
import "./Stats.scss";
|
import "./Stats.scss";
|
||||||
@@ -25,6 +27,7 @@ const getStorageSizes = debounce((cb: (sizes: StorageSizes) => void) => {
|
|||||||
|
|
||||||
export const Stats = (props: {
|
export const Stats = (props: {
|
||||||
appState: AppState;
|
appState: AppState;
|
||||||
|
setAppState: React.Component<any, AppState>["setState"];
|
||||||
elements: readonly NonDeletedExcalidrawElement[];
|
elements: readonly NonDeletedExcalidrawElement[];
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
}) => {
|
}) => {
|
||||||
@@ -50,6 +53,17 @@ export const Stats = (props: {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const version = getVersion();
|
||||||
|
let hash;
|
||||||
|
let timestamp;
|
||||||
|
|
||||||
|
if (version !== DEFAULT_VERSION) {
|
||||||
|
timestamp = version.slice(0, 16).replace("T", " ");
|
||||||
|
hash = version.slice(21);
|
||||||
|
} else {
|
||||||
|
timestamp = t("stats.versionNotAvailable");
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="Stats">
|
<div className="Stats">
|
||||||
<Island padding={2}>
|
<Island padding={2}>
|
||||||
@@ -156,6 +170,28 @@ export const Stats = (props: {
|
|||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
)}
|
)}
|
||||||
|
<tr>
|
||||||
|
<th colSpan={2}>{t("stats.version")}</th>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td
|
||||||
|
colSpan={2}
|
||||||
|
style={{ textAlign: "center", cursor: "pointer" }}
|
||||||
|
onClick={async () => {
|
||||||
|
try {
|
||||||
|
await copyTextToSystemClipboard(getVersion());
|
||||||
|
props.setAppState({
|
||||||
|
toastMessage: t("toast.copyToClipboard"),
|
||||||
|
});
|
||||||
|
} catch {}
|
||||||
|
}}
|
||||||
|
title={t("stats.versionCopy")}
|
||||||
|
>
|
||||||
|
{timestamp}
|
||||||
|
<br />
|
||||||
|
{hash}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</Island>
|
</Island>
|
||||||
|
|||||||
@@ -2,18 +2,18 @@
|
|||||||
|
|
||||||
.excalidraw {
|
.excalidraw {
|
||||||
.TextInput {
|
.TextInput {
|
||||||
color: var(--text-color-primary);
|
color: var(--text-primary-color);
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
border: 1.5px solid var(--button-gray-1);
|
border: 1.5px solid var(--button-gray-1);
|
||||||
line-height: 1;
|
line-height: 1;
|
||||||
padding: 0.75rem;
|
padding: 0.75rem;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
border-radius: var(--space-factor);
|
border-radius: var(--space-factor);
|
||||||
background-color: var(--input-background-color);
|
background-color: var(--input-bg-color);
|
||||||
|
|
||||||
&:not(:focus) {
|
&:not(:focus) {
|
||||||
&:hover {
|
&:hover {
|
||||||
background-color: var(--input-hover-background-color);
|
background-color: var(--input-hover-bg-color);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
left: 50%;
|
left: 50%;
|
||||||
margin-left: -150px;
|
margin-left: -150px;
|
||||||
padding: 4px 0;
|
padding: 4px 0;
|
||||||
position: fixed;
|
position: absolute;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
width: 300px;
|
width: 300px;
|
||||||
z-index: 999999;
|
z-index: 999999;
|
||||||
|
|||||||
+19
-2
@@ -1,9 +1,10 @@
|
|||||||
import { FontFamily } from "./element/types";
|
import { FontFamily } from "./element/types";
|
||||||
|
import cssVariables from "./css/variables.module.scss";
|
||||||
|
|
||||||
export const APP_NAME = "Excalidraw";
|
export const APP_NAME = "Excalidraw";
|
||||||
|
|
||||||
export const DRAGGING_THRESHOLD = 10; // 10px
|
export const DRAGGING_THRESHOLD = 10; // px
|
||||||
export const LINE_CONFIRM_THRESHOLD = 10; // 10px
|
export const LINE_CONFIRM_THRESHOLD = 8; // px
|
||||||
export const ELEMENT_SHIFT_TRANSLATE_AMOUNT = 5;
|
export const ELEMENT_SHIFT_TRANSLATE_AMOUNT = 5;
|
||||||
export const ELEMENT_TRANSLATE_AMOUNT = 1;
|
export const ELEMENT_TRANSLATE_AMOUNT = 1;
|
||||||
export const TEXT_TO_CENTER_SNAP_THRESHOLD = 30;
|
export const TEXT_TO_CENTER_SNAP_THRESHOLD = 30;
|
||||||
@@ -46,6 +47,8 @@ export enum EVENT {
|
|||||||
TOUCH_START = "touchstart",
|
TOUCH_START = "touchstart",
|
||||||
TOUCH_END = "touchend",
|
TOUCH_END = "touchend",
|
||||||
HASHCHANGE = "hashchange",
|
HASHCHANGE = "hashchange",
|
||||||
|
VISIBILITY_CHANGE = "visibilitychange",
|
||||||
|
SCROLL = "scroll",
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ENV = {
|
export const ENV = {
|
||||||
@@ -91,5 +94,19 @@ export const TOUCH_CTX_MENU_TIMEOUT = 500;
|
|||||||
export const TITLE_TIMEOUT = 10000;
|
export const TITLE_TIMEOUT = 10000;
|
||||||
export const TOAST_TIMEOUT = 5000;
|
export const TOAST_TIMEOUT = 5000;
|
||||||
export const VERSION_TIMEOUT = 30000;
|
export const VERSION_TIMEOUT = 30000;
|
||||||
|
export const SCROLL_TIMEOUT = 500;
|
||||||
|
|
||||||
export const ZOOM_STEP = 0.1;
|
export const ZOOM_STEP = 0.1;
|
||||||
|
|
||||||
|
// Report a user inactive after IDLE_THRESHOLD milliseconds
|
||||||
|
export const IDLE_THRESHOLD = 60_000;
|
||||||
|
// Report a user active each ACTIVE_THRESHOLD milliseconds
|
||||||
|
export const ACTIVE_THRESHOLD = 3_000;
|
||||||
|
|
||||||
|
export const MODES = {
|
||||||
|
VIEW: "viewMode",
|
||||||
|
ZEN: "zenMode",
|
||||||
|
GRID: "gridMode",
|
||||||
|
};
|
||||||
|
|
||||||
|
export const APPEARANCE_FILTER = cssVariables.appearanceFilter;
|
||||||
|
|||||||
+28
-15
@@ -1,10 +1,15 @@
|
|||||||
@import "./variables.module";
|
@import "./variables.module";
|
||||||
@import "./theme";
|
@import "./theme";
|
||||||
|
|
||||||
|
:root {
|
||||||
|
--zIndex-canvas: 1;
|
||||||
|
--zIndex-wysiwyg: 2;
|
||||||
|
--zIndex-layerUI: 3;
|
||||||
|
}
|
||||||
|
|
||||||
.excalidraw {
|
.excalidraw {
|
||||||
color: var(--text-color-primary);
|
color: var(--text-primary-color);
|
||||||
display: flex;
|
display: flex;
|
||||||
position: fixed;
|
|
||||||
top: 0;
|
top: 0;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
@@ -30,6 +35,8 @@
|
|||||||
image-rendering: pixelated; // chromium
|
image-rendering: pixelated; // chromium
|
||||||
// NOTE: must be declared *after* the above
|
// NOTE: must be declared *after* the above
|
||||||
image-rendering: -moz-crisp-edges; // FF
|
image-rendering: -moz-crisp-edges; // FF
|
||||||
|
|
||||||
|
z-index: var(--zIndex-canvas);
|
||||||
}
|
}
|
||||||
|
|
||||||
&.Appearance_dark {
|
&.Appearance_dark {
|
||||||
@@ -43,10 +50,10 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.FixedSideContainer {
|
.FixedSideContainer {
|
||||||
padding-top: var(--sat, 0px);
|
padding-top: var(--sat, 0);
|
||||||
padding-right: var(--sar, 0px);
|
padding-right: var(--sar, 0);
|
||||||
padding-bottom: var(--sab, 0px);
|
padding-bottom: var(--sab, 0);
|
||||||
padding-left: var(--sal, 0px);
|
padding-left: var(--sal, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
.panelRow {
|
.panelRow {
|
||||||
@@ -64,7 +71,7 @@
|
|||||||
margin-top: 0.333rem;
|
margin-top: 0.333rem;
|
||||||
margin-bottom: 0.333rem;
|
margin-bottom: 0.333rem;
|
||||||
font-size: 0.75rem;
|
font-size: 0.75rem;
|
||||||
color: var(--text-color-primary);
|
color: var(--text-primary-color);
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
@@ -216,6 +223,13 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.App-top-bar {
|
||||||
|
z-index: var(--zIndex-layerUI);
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
.App-bottom-bar {
|
.App-bottom-bar {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0;
|
top: 0;
|
||||||
@@ -223,10 +237,10 @@
|
|||||||
left: 0;
|
left: 0;
|
||||||
right: 0;
|
right: 0;
|
||||||
--bar-padding: calc(4 * var(--space-factor));
|
--bar-padding: calc(4 * var(--space-factor));
|
||||||
padding-top: #{"max(var(--bar-padding), var(--sat, 0px))"};
|
padding-top: #{"max(var(--bar-padding), var(--sat,0))"};
|
||||||
padding-right: var(--sar, 0px);
|
padding-right: var(--sar, 0);
|
||||||
padding-bottom: var(--sab, 0px);
|
padding-bottom: var(--sab, 0);
|
||||||
padding-left: var(--sal, 0px);
|
padding-left: var(--sal, 0);
|
||||||
z-index: 4;
|
z-index: 4;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: flex-end;
|
align-items: flex-end;
|
||||||
@@ -243,7 +257,7 @@
|
|||||||
pointer-events: initial;
|
pointer-events: initial;
|
||||||
|
|
||||||
.panelColumn {
|
.panelColumn {
|
||||||
padding: 8px 8px 0px 8px;
|
padding: 8px 8px 0 8px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -347,7 +361,6 @@
|
|||||||
|
|
||||||
.App-menu__left {
|
.App-menu__left {
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
max-height: calc(100vh - 236px);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.dropdown-select {
|
.dropdown-select {
|
||||||
@@ -419,7 +432,7 @@
|
|||||||
|
|
||||||
.scroll-back-to-content {
|
.scroll-back-to-content {
|
||||||
color: var(--popup-text-color);
|
color: var(--popup-text-color);
|
||||||
position: fixed;
|
position: absolute;
|
||||||
left: 50%;
|
left: 50%;
|
||||||
bottom: 30px;
|
bottom: 30px;
|
||||||
transform: translateX(-50%);
|
transform: translateX(-50%);
|
||||||
@@ -447,7 +460,7 @@
|
|||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
.scroll-back-to-content {
|
.scroll-back-to-content {
|
||||||
bottom: calc(80px + var(--sab, 0px));
|
bottom: calc(80px + var(--sab, 0));
|
||||||
z-index: -1;
|
z-index: -1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+43
-42
@@ -1,43 +1,44 @@
|
|||||||
@import "open-color/open-color.scss";
|
@import "open-color/open-color.scss";
|
||||||
|
@import "./variables.module.scss";
|
||||||
|
|
||||||
:root {
|
:root {
|
||||||
--bg-color-island: rgba(255, 255, 255, 0.9);
|
--appearance-filter: none;
|
||||||
--popup-background-color: #{$oc-white};
|
--button-destructive-bg-color: #{$oc-red-1};
|
||||||
--space-factor: 0.25rem;
|
--button-destructive-color: #{$oc-red-9};
|
||||||
--button-gray-1: #{$oc-gray-2};
|
--button-gray-1: #{$oc-gray-2};
|
||||||
--button-gray-2: #{$oc-gray-4};
|
--button-gray-2: #{$oc-gray-4};
|
||||||
--button-gray-3: #{$oc-gray-5};
|
--button-gray-3: #{$oc-gray-5};
|
||||||
--input-border-color: #{$oc-gray-3};
|
--button-special-active-bg-color: #{$oc-green-0};
|
||||||
--input-background-color: #{$oc-white};
|
--dialog-border-color: #{$oc-gray-6};
|
||||||
--input-hover-background-color: #{$oc-gray-1};
|
--dropdown-icon: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" width="292.4" height="292.4" viewBox="0 0 292 292"><path d="M287 197L159 69c-4-3-8-5-13-5s-9 2-13 5L5 197c-3 4-5 8-5 13s2 9 5 13c4 4 8 5 13 5h256c5 0 9-1 13-5s5-8 5-13-1-9-5-13z"/></svg>');
|
||||||
--input-label-color: #{$oc-gray-7};
|
--focus-highlight-color: #{$oc-blue-2};
|
||||||
--icon-fill-color: #{$oc-black};
|
--icon-fill-color: #{$oc-black};
|
||||||
--icon-green-fill-color: #{$oc-green-9};
|
--icon-green-fill-color: #{$oc-green-9};
|
||||||
|
--input-bg-color: #{$oc-white};
|
||||||
|
--input-border-color: #{$oc-gray-3};
|
||||||
|
--input-hover-bg-color: #{$oc-gray-1};
|
||||||
|
--input-label-color: #{$oc-gray-7};
|
||||||
|
--island-bg-color: rgba(255, 255, 255, 0.9);
|
||||||
--keybinding-color: #{$oc-gray-5};
|
--keybinding-color: #{$oc-gray-5};
|
||||||
--sat: env(safe-area-inset-top);
|
--link-color: #{$oc-blue-7};
|
||||||
|
--overlay-bg-color: #{transparentize($oc-white, 0.12)};
|
||||||
|
--popup-bg-color: #{$oc-white};
|
||||||
|
--popup-secondary-bg-color: #{$oc-gray-1};
|
||||||
|
--popup-text-color: #{$oc-black};
|
||||||
|
--popup-text-inverted-color: #{$oc-white};
|
||||||
--sab: env(safe-area-inset-bottom);
|
--sab: env(safe-area-inset-bottom);
|
||||||
--sal: env(safe-area-inset-left);
|
--sal: env(safe-area-inset-left);
|
||||||
--sar: env(safe-area-inset-right);
|
--sar: env(safe-area-inset-right);
|
||||||
--text-color-primary: #{$oc-gray-8};
|
--sat: env(safe-area-inset-top);
|
||||||
--shadow-island: 0 1px 5px #{transparentize($oc-black, 0.85)};
|
|
||||||
--overlay-background-color: #{transparentize($oc-white, 0.12)};
|
|
||||||
--dropdown-icon: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" width="292.4" height="292.4" viewBox="0 0 292 292"><path d="M287 197L159 69c-4-3-8-5-13-5s-9 2-13 5L5 197c-3 4-5 8-5 13s2 9 5 13c4 4 8 5 13 5h256c5 0 9-1 13-5s5-8 5-13-1-9-5-13z"/></svg>');
|
|
||||||
--focus-highlight-color: #{$oc-blue-2};
|
|
||||||
--select-highlight-color: #{$oc-blue-5};
|
--select-highlight-color: #{$oc-blue-5};
|
||||||
--appearance-filter: none;
|
--shadow-island: 0 1px 5px #{transparentize($oc-black, 0.85)};
|
||||||
--button-special-active-background-color: #{$oc-green-0};
|
--space-factor: 0.25rem;
|
||||||
--button-destructive-color: #{$oc-red-9};
|
--text-primary-color: #{$oc-gray-8};
|
||||||
--button-destructive-background-color: #{$oc-red-1};
|
|
||||||
--popup-secondary-background-color: #{$oc-gray-1};
|
|
||||||
--popup-text-color: #{$oc-black};
|
|
||||||
--popup-text-inverted-color: #{$oc-white};
|
|
||||||
--dialog-border: #{$oc-gray-6};
|
|
||||||
--link-color: #{$oc-blue-7};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.excalidraw {
|
.excalidraw {
|
||||||
&.Appearance_dark {
|
&.Appearance_dark {
|
||||||
background: #000;
|
background: $oc-black;
|
||||||
|
|
||||||
&.Appearance_dark-background-none {
|
&.Appearance_dark-background-none {
|
||||||
background: none;
|
background: none;
|
||||||
@@ -45,31 +46,31 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
&.Appearance_dark {
|
&.Appearance_dark {
|
||||||
--text-color-primary: #{$oc-gray-4};
|
--appearance-filter: #{$appearance-filter};
|
||||||
--bg-color-island: #1e1e1e;
|
--button-destructive-bg-color: #5a0000;
|
||||||
--popup-background-color: #2c2c2c;
|
--button-destructive-color: #{$oc-red-3};
|
||||||
--button-gray-1: #363636;
|
--button-gray-1: #363636;
|
||||||
--button-gray-2: #272727;
|
--button-gray-2: #272727;
|
||||||
--button-gray-3: #222;
|
--button-gray-3: #222;
|
||||||
--input-border-color: #2e2e2e;
|
--button-special-active-bg-color: #204624;
|
||||||
--input-background-color: #121212;
|
--dialog-border-color: #{$oc-gray-9};
|
||||||
--input-hover-background-color: #181818;
|
|
||||||
--input-label-color: #{$oc-gray-2};
|
|
||||||
--icon-fill-color: #{$oc-gray-4};
|
|
||||||
--icon-green-fill-color: #{$oc-green-4};
|
|
||||||
--keybinding-color: #{$oc-gray-6};
|
|
||||||
--shadow-island: 0 1px 5px #{transparentize($oc-black, 0.7)};
|
|
||||||
--overlay-background-color: rgba(30, 30, 30, 0.88);
|
|
||||||
--dropdown-icon: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" width="292.4" height="292.4" viewBox="0 0 292 292"><path fill="%23ced4da" d="M287 197L159 69c-4-3-8-5-13-5s-9 2-13 5L5 197c-3 4-5 8-5 13s2 9 5 13c4 4 8 5 13 5h256c5 0 9-1 13-5s5-8 5-13-1-9-5-13z"/></svg>');
|
--dropdown-icon: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" width="292.4" height="292.4" viewBox="0 0 292 292"><path fill="%23ced4da" d="M287 197L159 69c-4-3-8-5-13-5s-9 2-13 5L5 197c-3 4-5 8-5 13s2 9 5 13c4 4 8 5 13 5h256c5 0 9-1 13-5s5-8 5-13-1-9-5-13z"/></svg>');
|
||||||
--focus-highlight-color: #{$oc-blue-6};
|
--focus-highlight-color: #{$oc-blue-6};
|
||||||
--select-highlight-color: #{$oc-blue-4};
|
--icon-fill-color: #{$oc-gray-4};
|
||||||
--appearance-filter: invert(93%) hue-rotate(180deg);
|
--icon-green-fill-color: #{$oc-green-4};
|
||||||
--button-special-active-background-color: #204624;
|
--input-bg-color: #121212;
|
||||||
--button-destructive-color: #{$oc-red-3};
|
--input-border-color: #2e2e2e;
|
||||||
--button-destructive-background-color: #5a0000;
|
--input-hover-bg-color: #181818;
|
||||||
--popup-secondary-background-color: #222;
|
--input-label-color: #{$oc-gray-2};
|
||||||
|
--island-bg-color: #1e1e1e;
|
||||||
|
--keybinding-color: #{$oc-gray-6};
|
||||||
|
--overlay-bg-color: #{transparentize($oc-gray-8, 0.88)};
|
||||||
|
--popup-bg-color: #2c2c2c;
|
||||||
|
--popup-secondary-bg-color: #222;
|
||||||
--popup-text-color: #{$oc-gray-4};
|
--popup-text-color: #{$oc-gray-4};
|
||||||
--popup-text-inverted-color: #2c2c2c;
|
--popup-text-inverted-color: #2c2c2c;
|
||||||
--dialog-border: #{$oc-gray-9};
|
--select-highlight-color: #{$oc-blue-4};
|
||||||
|
--shadow-island: 0 1px 5px #{transparentize($oc-black, 0.7)};
|
||||||
|
--text-primary-color: #{$oc-gray-4};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,9 @@
|
|||||||
|
|
||||||
// keep up to date with is-mobile.tsx
|
// keep up to date with is-mobile.tsx
|
||||||
$is-mobile-query: "(max-width: 600px), (max-height: 500px) and (max-width: 1000px)";
|
$is-mobile-query: "(max-width: 600px), (max-height: 500px) and (max-width: 1000px)";
|
||||||
|
$appearance-filter: "invert(93%) hue-rotate(180deg)";
|
||||||
|
|
||||||
:export {
|
:export {
|
||||||
isMobileQuery: unquote($is-mobile-query);
|
isMobileQuery: unquote($is-mobile-query);
|
||||||
|
appearanceFilter: unquote($appearance-filter);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -41,6 +41,7 @@ export const exportCanvas = async (
|
|||||||
if (type === "svg" || type === "clipboard-svg") {
|
if (type === "svg" || type === "clipboard-svg") {
|
||||||
const tempSvg = exportToSvg(elements, {
|
const tempSvg = exportToSvg(elements, {
|
||||||
exportBackground,
|
exportBackground,
|
||||||
|
exportWithDarkMode: appState.exportWithDarkMode,
|
||||||
viewBackgroundColor,
|
viewBackgroundColor,
|
||||||
exportPadding,
|
exportPadding,
|
||||||
scale,
|
scale,
|
||||||
|
|||||||
+1
-1
@@ -141,7 +141,7 @@ export const restoreElements = (
|
|||||||
}, [] as ExcalidrawElement[]);
|
}, [] as ExcalidrawElement[]);
|
||||||
};
|
};
|
||||||
|
|
||||||
const restoreAppState = (
|
export const restoreAppState = (
|
||||||
appState: ImportedDataState["appState"],
|
appState: ImportedDataState["appState"],
|
||||||
localAppState: Partial<AppState> | null,
|
localAppState: Partial<AppState> | null,
|
||||||
): AppState => {
|
): AppState => {
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ export interface ImportedDataState {
|
|||||||
source?: string;
|
source?: string;
|
||||||
elements?: DataState["elements"] | null;
|
elements?: DataState["elements"] | null;
|
||||||
appState?: Partial<DataState["appState"]> | null;
|
appState?: Partial<DataState["appState"]> | null;
|
||||||
|
scrollToCenter?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface LibraryData {
|
export interface LibraryData {
|
||||||
|
|||||||
@@ -129,7 +129,7 @@ export class LinearElementEditor {
|
|||||||
isDragging &&
|
isDragging &&
|
||||||
(activePointIndex === 0 || activePointIndex === element.points.length - 1)
|
(activePointIndex === 0 || activePointIndex === element.points.length - 1)
|
||||||
) {
|
) {
|
||||||
if (isPathALoop(element.points)) {
|
if (isPathALoop(element.points, appState.zoom.value)) {
|
||||||
LinearElementEditor.movePoint(
|
LinearElementEditor.movePoint(
|
||||||
element,
|
element,
|
||||||
activePointIndex,
|
activePointIndex,
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ export const textWysiwyg = ({
|
|||||||
onSubmit,
|
onSubmit,
|
||||||
getViewportCoords,
|
getViewportCoords,
|
||||||
element,
|
element,
|
||||||
|
canvas,
|
||||||
}: {
|
}: {
|
||||||
id: ExcalidrawElement["id"];
|
id: ExcalidrawElement["id"];
|
||||||
appState: AppState;
|
appState: AppState;
|
||||||
@@ -45,6 +46,7 @@ export const textWysiwyg = ({
|
|||||||
onSubmit: (text: string) => void;
|
onSubmit: (text: string) => void;
|
||||||
getViewportCoords: (x: number, y: number) => [number, number];
|
getViewportCoords: (x: number, y: number) => [number, number];
|
||||||
element: ExcalidrawElement;
|
element: ExcalidrawElement;
|
||||||
|
canvas: HTMLCanvasElement | null;
|
||||||
}) => {
|
}) => {
|
||||||
const updateWysiwygStyle = () => {
|
const updateWysiwygStyle = () => {
|
||||||
const updatedElement = Scene.getScene(element)?.getElement(id);
|
const updatedElement = Scene.getScene(element)?.getElement(id);
|
||||||
@@ -89,9 +91,6 @@ export const textWysiwyg = ({
|
|||||||
editable.dataset.type = "wysiwyg";
|
editable.dataset.type = "wysiwyg";
|
||||||
// prevent line wrapping on Safari
|
// prevent line wrapping on Safari
|
||||||
editable.wrap = "off";
|
editable.wrap = "off";
|
||||||
editable.className = `excalidraw ${
|
|
||||||
appState.appearance === "dark" ? "Appearance_dark" : ""
|
|
||||||
}`;
|
|
||||||
|
|
||||||
Object.assign(editable.style, {
|
Object.assign(editable.style, {
|
||||||
position: "fixed",
|
position: "fixed",
|
||||||
@@ -107,6 +106,8 @@ export const textWysiwyg = ({
|
|||||||
overflow: "hidden",
|
overflow: "hidden",
|
||||||
// prevent line wrapping (`whitespace: nowrap` doesn't work on FF)
|
// prevent line wrapping (`whitespace: nowrap` doesn't work on FF)
|
||||||
whiteSpace: "pre",
|
whiteSpace: "pre",
|
||||||
|
// must be specified because in dark mode canvas creates a stacking context
|
||||||
|
zIndex: "var(--zIndex-wysiwyg)",
|
||||||
});
|
});
|
||||||
|
|
||||||
updateWysiwygStyle();
|
updateWysiwygStyle();
|
||||||
@@ -152,6 +153,10 @@ export const textWysiwyg = ({
|
|||||||
editable.oninput = null;
|
editable.oninput = null;
|
||||||
editable.onkeydown = null;
|
editable.onkeydown = null;
|
||||||
|
|
||||||
|
if (observer) {
|
||||||
|
observer.disconnect();
|
||||||
|
}
|
||||||
|
|
||||||
window.removeEventListener("resize", updateWysiwygStyle);
|
window.removeEventListener("resize", updateWysiwygStyle);
|
||||||
window.removeEventListener("wheel", stopEvent, true);
|
window.removeEventListener("wheel", stopEvent, true);
|
||||||
window.removeEventListener("pointerdown", onPointerDown);
|
window.removeEventListener("pointerdown", onPointerDown);
|
||||||
@@ -160,7 +165,7 @@ export const textWysiwyg = ({
|
|||||||
|
|
||||||
unbindUpdate();
|
unbindUpdate();
|
||||||
|
|
||||||
document.body.removeChild(editable);
|
editable.remove();
|
||||||
};
|
};
|
||||||
|
|
||||||
const rebindBlur = () => {
|
const rebindBlur = () => {
|
||||||
@@ -198,15 +203,27 @@ export const textWysiwyg = ({
|
|||||||
let isDestroyed = false;
|
let isDestroyed = false;
|
||||||
|
|
||||||
editable.onblur = handleSubmit;
|
editable.onblur = handleSubmit;
|
||||||
// reposition wysiwyg in case of window resize. Happens on mobile when
|
|
||||||
// device keyboard is opened.
|
// reposition wysiwyg in case of canvas is resized. Using ResizeObserver
|
||||||
window.addEventListener("resize", updateWysiwygStyle);
|
// is preferred so we catch changes from host, where window may not resize.
|
||||||
|
let observer: ResizeObserver | null = null;
|
||||||
|
if (canvas && "ResizeObserver" in window) {
|
||||||
|
observer = new window.ResizeObserver(() => {
|
||||||
|
updateWysiwygStyle();
|
||||||
|
});
|
||||||
|
observer.observe(canvas);
|
||||||
|
} else {
|
||||||
|
window.addEventListener("resize", updateWysiwygStyle);
|
||||||
|
}
|
||||||
|
|
||||||
window.addEventListener("pointerdown", onPointerDown);
|
window.addEventListener("pointerdown", onPointerDown);
|
||||||
window.addEventListener("wheel", stopEvent, {
|
window.addEventListener("wheel", stopEvent, {
|
||||||
passive: false,
|
passive: false,
|
||||||
capture: true,
|
capture: true,
|
||||||
});
|
});
|
||||||
document.body.appendChild(editable);
|
document
|
||||||
|
.querySelector(".excalidraw-textEditorContainer")!
|
||||||
|
.appendChild(editable);
|
||||||
editable.focus();
|
editable.focus();
|
||||||
editable.select();
|
editable.select();
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -19,12 +19,16 @@ import {
|
|||||||
} from "../app_constants";
|
} from "../app_constants";
|
||||||
import {
|
import {
|
||||||
decryptAESGEM,
|
decryptAESGEM,
|
||||||
generateCollaborationLink,
|
generateCollaborationLinkData,
|
||||||
getCollaborationLinkData,
|
getCollaborationLink,
|
||||||
SocketUpdateDataSource,
|
SocketUpdateDataSource,
|
||||||
SOCKET_SERVER,
|
SOCKET_SERVER,
|
||||||
} from "../data";
|
} from "../data";
|
||||||
import { isSavedToFirebase, saveToFirebase } from "../data/firebase";
|
import {
|
||||||
|
isSavedToFirebase,
|
||||||
|
loadFromFirebase,
|
||||||
|
saveToFirebase,
|
||||||
|
} from "../data/firebase";
|
||||||
import {
|
import {
|
||||||
importUsernameFromLocalStorage,
|
importUsernameFromLocalStorage,
|
||||||
saveUsernameToLocalStorage,
|
saveUsernameToLocalStorage,
|
||||||
@@ -33,20 +37,25 @@ import {
|
|||||||
import Portal from "./Portal";
|
import Portal from "./Portal";
|
||||||
import RoomDialog from "./RoomDialog";
|
import RoomDialog from "./RoomDialog";
|
||||||
import { createInverseContext } from "../../createInverseContext";
|
import { createInverseContext } from "../../createInverseContext";
|
||||||
|
import { t } from "../../i18n";
|
||||||
|
import { UserIdleState } from "./types";
|
||||||
|
import { IDLE_THRESHOLD, ACTIVE_THRESHOLD } from "../../constants";
|
||||||
|
|
||||||
interface CollabState {
|
interface CollabState {
|
||||||
isCollaborating: boolean;
|
|
||||||
modalIsShown: boolean;
|
modalIsShown: boolean;
|
||||||
errorMessage: string;
|
errorMessage: string;
|
||||||
username: string;
|
username: string;
|
||||||
|
userState: UserIdleState;
|
||||||
activeRoomLink: string;
|
activeRoomLink: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
type CollabInstance = InstanceType<typeof CollabWrapper>;
|
type CollabInstance = InstanceType<typeof CollabWrapper>;
|
||||||
|
|
||||||
export interface CollabAPI {
|
export interface CollabAPI {
|
||||||
isCollaborating: CollabState["isCollaborating"];
|
/** function so that we can access the latest value from stale callbacks */
|
||||||
|
isCollaborating: () => boolean;
|
||||||
username: CollabState["username"];
|
username: CollabState["username"];
|
||||||
|
userState: CollabState["userState"];
|
||||||
onPointerUpdate: CollabInstance["onPointerUpdate"];
|
onPointerUpdate: CollabInstance["onPointerUpdate"];
|
||||||
initializeSocketClient: CollabInstance["initializeSocketClient"];
|
initializeSocketClient: CollabInstance["initializeSocketClient"];
|
||||||
onCollabButtonClick: CollabInstance["onCollabButtonClick"];
|
onCollabButtonClick: CollabInstance["onCollabButtonClick"];
|
||||||
@@ -72,6 +81,10 @@ export { CollabContext, CollabContextConsumer };
|
|||||||
class CollabWrapper extends PureComponent<Props, CollabState> {
|
class CollabWrapper extends PureComponent<Props, CollabState> {
|
||||||
portal: Portal;
|
portal: Portal;
|
||||||
excalidrawAPI: Props["excalidrawAPI"];
|
excalidrawAPI: Props["excalidrawAPI"];
|
||||||
|
isCollaborating: boolean = false;
|
||||||
|
activeIntervalId: number | null;
|
||||||
|
idleTimeoutId: number | null;
|
||||||
|
|
||||||
private socketInitializationTimer?: NodeJS.Timeout;
|
private socketInitializationTimer?: NodeJS.Timeout;
|
||||||
private lastBroadcastedOrReceivedSceneVersion: number = -1;
|
private lastBroadcastedOrReceivedSceneVersion: number = -1;
|
||||||
private collaborators = new Map<string, Collaborator>();
|
private collaborators = new Map<string, Collaborator>();
|
||||||
@@ -79,14 +92,16 @@ class CollabWrapper extends PureComponent<Props, CollabState> {
|
|||||||
constructor(props: Props) {
|
constructor(props: Props) {
|
||||||
super(props);
|
super(props);
|
||||||
this.state = {
|
this.state = {
|
||||||
isCollaborating: false,
|
|
||||||
modalIsShown: false,
|
modalIsShown: false,
|
||||||
errorMessage: "",
|
errorMessage: "",
|
||||||
username: importUsernameFromLocalStorage() || "",
|
username: importUsernameFromLocalStorage() || "",
|
||||||
|
userState: UserIdleState.ACTIVE,
|
||||||
activeRoomLink: "",
|
activeRoomLink: "",
|
||||||
};
|
};
|
||||||
this.portal = new Portal(this);
|
this.portal = new Portal(this);
|
||||||
this.excalidrawAPI = props.excalidrawAPI;
|
this.excalidrawAPI = props.excalidrawAPI;
|
||||||
|
this.activeIntervalId = null;
|
||||||
|
this.idleTimeoutId = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
@@ -110,18 +125,32 @@ class CollabWrapper extends PureComponent<Props, CollabState> {
|
|||||||
componentWillUnmount() {
|
componentWillUnmount() {
|
||||||
window.removeEventListener(EVENT.BEFORE_UNLOAD, this.beforeUnload);
|
window.removeEventListener(EVENT.BEFORE_UNLOAD, this.beforeUnload);
|
||||||
window.removeEventListener(EVENT.UNLOAD, this.onUnload);
|
window.removeEventListener(EVENT.UNLOAD, this.onUnload);
|
||||||
|
window.removeEventListener(EVENT.POINTER_MOVE, this.onPointerMove);
|
||||||
|
window.removeEventListener(
|
||||||
|
EVENT.VISIBILITY_CHANGE,
|
||||||
|
this.onVisibilityChange,
|
||||||
|
);
|
||||||
|
if (this.activeIntervalId) {
|
||||||
|
window.clearInterval(this.activeIntervalId);
|
||||||
|
this.activeIntervalId = null;
|
||||||
|
}
|
||||||
|
if (this.idleTimeoutId) {
|
||||||
|
window.clearTimeout(this.idleTimeoutId);
|
||||||
|
this.idleTimeoutId = null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private onUnload = () => {
|
private onUnload = () => {
|
||||||
this.destroySocketClient();
|
this.destroySocketClient({ isUnload: true });
|
||||||
};
|
};
|
||||||
|
|
||||||
private beforeUnload = withBatchedUpdates((event: BeforeUnloadEvent) => {
|
private beforeUnload = withBatchedUpdates((event: BeforeUnloadEvent) => {
|
||||||
const syncableElements = getSyncableElements(
|
const syncableElements = getSyncableElements(
|
||||||
this.getSceneElementsIncludingDeleted(),
|
this.getSceneElementsIncludingDeleted(),
|
||||||
);
|
);
|
||||||
|
|
||||||
if (
|
if (
|
||||||
this.state.isCollaborating &&
|
this.isCollaborating &&
|
||||||
!isSavedToFirebase(this.portal, syncableElements)
|
!isSavedToFirebase(this.portal, syncableElements)
|
||||||
) {
|
) {
|
||||||
// this won't run in time if user decides to leave the site, but
|
// this won't run in time if user decides to leave the site, but
|
||||||
@@ -133,7 +162,7 @@ class CollabWrapper extends PureComponent<Props, CollabState> {
|
|||||||
event.returnValue = "";
|
event.returnValue = "";
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.state.isCollaborating || this.portal.roomId) {
|
if (this.isCollaborating || this.portal.roomId) {
|
||||||
try {
|
try {
|
||||||
localStorage?.setItem(
|
localStorage?.setItem(
|
||||||
STORAGE_KEYS.LOCAL_STORAGE_KEY_COLLAB_FORCE_FLAG,
|
STORAGE_KEYS.LOCAL_STORAGE_KEY_COLLAB_FORCE_FLAG,
|
||||||
@@ -159,143 +188,192 @@ class CollabWrapper extends PureComponent<Props, CollabState> {
|
|||||||
};
|
};
|
||||||
|
|
||||||
openPortal = async () => {
|
openPortal = async () => {
|
||||||
window.history.pushState({}, APP_NAME, await generateCollaborationLink());
|
return this.initializeSocketClient(null);
|
||||||
const elements = this.excalidrawAPI.getSceneElements();
|
|
||||||
// remove deleted elements from elements array & history to ensure we don't
|
|
||||||
// expose potentially sensitive user data in case user manually deletes
|
|
||||||
// existing elements (or clears scene), which would otherwise be persisted
|
|
||||||
// to database even if deleted before creating the room.
|
|
||||||
this.excalidrawAPI.history.clear();
|
|
||||||
this.excalidrawAPI.updateScene({
|
|
||||||
elements,
|
|
||||||
commitToHistory: true,
|
|
||||||
});
|
|
||||||
return this.initializeSocketClient();
|
|
||||||
};
|
};
|
||||||
|
|
||||||
closePortal = () => {
|
closePortal = () => {
|
||||||
this.saveCollabRoomToFirebase();
|
this.saveCollabRoomToFirebase();
|
||||||
window.history.pushState({}, APP_NAME, window.location.origin);
|
if (window.confirm(t("alerts.collabStopOverridePrompt"))) {
|
||||||
this.destroySocketClient();
|
window.history.pushState({}, APP_NAME, window.location.origin);
|
||||||
|
this.destroySocketClient();
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
private destroySocketClient = () => {
|
private destroySocketClient = (opts?: { isUnload: boolean }) => {
|
||||||
this.collaborators = new Map();
|
if (!opts?.isUnload) {
|
||||||
this.excalidrawAPI.updateScene({
|
this.collaborators = new Map();
|
||||||
collaborators: this.collaborators,
|
this.excalidrawAPI.updateScene({
|
||||||
});
|
collaborators: this.collaborators,
|
||||||
this.setState({
|
});
|
||||||
isCollaborating: false,
|
this.setState({
|
||||||
activeRoomLink: "",
|
activeRoomLink: "",
|
||||||
});
|
});
|
||||||
|
this.isCollaborating = false;
|
||||||
|
}
|
||||||
this.portal.close();
|
this.portal.close();
|
||||||
};
|
};
|
||||||
|
|
||||||
private initializeSocketClient = async (): Promise<ImportedDataState | null> => {
|
private initializeSocketClient = async (
|
||||||
|
existingRoomLinkData: null | { roomId: string; roomKey: string },
|
||||||
|
): Promise<ImportedDataState | null> => {
|
||||||
if (this.portal.socket) {
|
if (this.portal.socket) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const scenePromise = resolvablePromise<ImportedDataState | null>();
|
let roomId;
|
||||||
|
let roomKey;
|
||||||
|
|
||||||
const roomMatch = getCollaborationLinkData(window.location.href);
|
if (existingRoomLinkData) {
|
||||||
|
({ roomId, roomKey } = existingRoomLinkData);
|
||||||
if (roomMatch) {
|
} else {
|
||||||
const roomId = roomMatch[1];
|
({ roomId, roomKey } = await generateCollaborationLinkData());
|
||||||
const roomKey = roomMatch[2];
|
window.history.pushState(
|
||||||
|
{},
|
||||||
// fallback in case you're not alone in the room but still don't receive
|
APP_NAME,
|
||||||
// initial SCENE_UPDATE message
|
getCollaborationLink({ roomId, roomKey }),
|
||||||
this.socketInitializationTimer = setTimeout(() => {
|
|
||||||
this.initializeSocket();
|
|
||||||
scenePromise.resolve(null);
|
|
||||||
}, INITIAL_SCENE_UPDATE_TIMEOUT);
|
|
||||||
|
|
||||||
const { default: socketIOClient }: any = await import(
|
|
||||||
/* webpackChunkName: "socketIoClient" */ "socket.io-client"
|
|
||||||
);
|
);
|
||||||
|
|
||||||
this.portal.open(socketIOClient(SOCKET_SERVER), roomId, roomKey);
|
|
||||||
|
|
||||||
// All socket listeners are moving to Portal
|
|
||||||
this.portal.socket!.on(
|
|
||||||
"client-broadcast",
|
|
||||||
async (encryptedData: ArrayBuffer, iv: Uint8Array) => {
|
|
||||||
if (!this.portal.roomKey) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const decryptedData = await decryptAESGEM(
|
|
||||||
encryptedData,
|
|
||||||
this.portal.roomKey,
|
|
||||||
iv,
|
|
||||||
);
|
|
||||||
|
|
||||||
switch (decryptedData.type) {
|
|
||||||
case "INVALID_RESPONSE":
|
|
||||||
return;
|
|
||||||
case SCENE.INIT: {
|
|
||||||
if (!this.portal.socketInitialized) {
|
|
||||||
const remoteElements = decryptedData.payload.elements;
|
|
||||||
const reconciledElements = this.reconcileElements(
|
|
||||||
remoteElements,
|
|
||||||
);
|
|
||||||
this.handleRemoteSceneUpdate(reconciledElements, {
|
|
||||||
init: true,
|
|
||||||
});
|
|
||||||
this.initializeSocket();
|
|
||||||
scenePromise.resolve({ elements: reconciledElements });
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case SCENE.UPDATE:
|
|
||||||
this.handleRemoteSceneUpdate(
|
|
||||||
this.reconcileElements(decryptedData.payload.elements),
|
|
||||||
);
|
|
||||||
break;
|
|
||||||
case "MOUSE_LOCATION": {
|
|
||||||
const {
|
|
||||||
pointer,
|
|
||||||
button,
|
|
||||||
username,
|
|
||||||
selectedElementIds,
|
|
||||||
} = decryptedData.payload;
|
|
||||||
const socketId: SocketUpdateDataSource["MOUSE_LOCATION"]["payload"]["socketId"] =
|
|
||||||
decryptedData.payload.socketId ||
|
|
||||||
// @ts-ignore legacy, see #2094 (#2097)
|
|
||||||
decryptedData.payload.socketID;
|
|
||||||
|
|
||||||
const collaborators = new Map(this.collaborators);
|
|
||||||
const user = collaborators.get(socketId) || {}!;
|
|
||||||
user.pointer = pointer;
|
|
||||||
user.button = button;
|
|
||||||
user.selectedElementIds = selectedElementIds;
|
|
||||||
user.username = username;
|
|
||||||
collaborators.set(socketId, user);
|
|
||||||
this.excalidrawAPI.updateScene({
|
|
||||||
collaborators,
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
);
|
|
||||||
this.portal.socket!.on("first-in-room", () => {
|
|
||||||
if (this.portal.socket) {
|
|
||||||
this.portal.socket.off("first-in-room");
|
|
||||||
}
|
|
||||||
this.initializeSocket();
|
|
||||||
scenePromise.resolve(null);
|
|
||||||
});
|
|
||||||
|
|
||||||
this.setState({
|
|
||||||
isCollaborating: true,
|
|
||||||
activeRoomLink: window.location.href,
|
|
||||||
});
|
|
||||||
|
|
||||||
return scenePromise;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
const scenePromise = resolvablePromise<ImportedDataState | null>();
|
||||||
|
|
||||||
|
this.isCollaborating = true;
|
||||||
|
|
||||||
|
const { default: socketIOClient }: any = await import(
|
||||||
|
/* webpackChunkName: "socketIoClient" */ "socket.io-client"
|
||||||
|
);
|
||||||
|
|
||||||
|
this.portal.open(socketIOClient(SOCKET_SERVER), roomId, roomKey);
|
||||||
|
|
||||||
|
if (existingRoomLinkData) {
|
||||||
|
this.excalidrawAPI.resetScene();
|
||||||
|
|
||||||
|
try {
|
||||||
|
const elements = await loadFromFirebase(
|
||||||
|
roomId,
|
||||||
|
roomKey,
|
||||||
|
this.portal.socket,
|
||||||
|
);
|
||||||
|
if (elements) {
|
||||||
|
scenePromise.resolve({
|
||||||
|
elements,
|
||||||
|
scrollToCenter: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
// log the error and move on. other peers will sync us the scene.
|
||||||
|
console.error(error);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const elements = this.excalidrawAPI.getSceneElements();
|
||||||
|
// remove deleted elements from elements array & history to ensure we don't
|
||||||
|
// expose potentially sensitive user data in case user manually deletes
|
||||||
|
// existing elements (or clears scene), which would otherwise be persisted
|
||||||
|
// to database even if deleted before creating the room.
|
||||||
|
this.excalidrawAPI.history.clear();
|
||||||
|
this.excalidrawAPI.updateScene({
|
||||||
|
elements,
|
||||||
|
commitToHistory: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// fallback in case you're not alone in the room but still don't receive
|
||||||
|
// initial SCENE_UPDATE message
|
||||||
|
this.socketInitializationTimer = setTimeout(() => {
|
||||||
|
this.initializeSocket();
|
||||||
|
scenePromise.resolve(null);
|
||||||
|
}, INITIAL_SCENE_UPDATE_TIMEOUT);
|
||||||
|
|
||||||
|
// All socket listeners are moving to Portal
|
||||||
|
this.portal.socket!.on(
|
||||||
|
"client-broadcast",
|
||||||
|
async (encryptedData: ArrayBuffer, iv: Uint8Array) => {
|
||||||
|
if (!this.portal.roomKey) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const decryptedData = await decryptAESGEM(
|
||||||
|
encryptedData,
|
||||||
|
this.portal.roomKey,
|
||||||
|
iv,
|
||||||
|
);
|
||||||
|
|
||||||
|
switch (decryptedData.type) {
|
||||||
|
case "INVALID_RESPONSE":
|
||||||
|
return;
|
||||||
|
case SCENE.INIT: {
|
||||||
|
if (!this.portal.socketInitialized) {
|
||||||
|
this.initializeSocket();
|
||||||
|
const remoteElements = decryptedData.payload.elements;
|
||||||
|
const reconciledElements = this.reconcileElements(remoteElements);
|
||||||
|
this.handleRemoteSceneUpdate(reconciledElements, {
|
||||||
|
init: true,
|
||||||
|
});
|
||||||
|
// noop if already resolved via init from firebase
|
||||||
|
scenePromise.resolve({
|
||||||
|
elements: reconciledElements,
|
||||||
|
scrollToCenter: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case SCENE.UPDATE:
|
||||||
|
this.handleRemoteSceneUpdate(
|
||||||
|
this.reconcileElements(decryptedData.payload.elements),
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
case "MOUSE_LOCATION": {
|
||||||
|
const {
|
||||||
|
pointer,
|
||||||
|
button,
|
||||||
|
username,
|
||||||
|
selectedElementIds,
|
||||||
|
} = decryptedData.payload;
|
||||||
|
const socketId: SocketUpdateDataSource["MOUSE_LOCATION"]["payload"]["socketId"] =
|
||||||
|
decryptedData.payload.socketId ||
|
||||||
|
// @ts-ignore legacy, see #2094 (#2097)
|
||||||
|
decryptedData.payload.socketID;
|
||||||
|
|
||||||
|
const collaborators = new Map(this.collaborators);
|
||||||
|
const user = collaborators.get(socketId) || {}!;
|
||||||
|
user.pointer = pointer;
|
||||||
|
user.button = button;
|
||||||
|
user.selectedElementIds = selectedElementIds;
|
||||||
|
user.username = username;
|
||||||
|
collaborators.set(socketId, user);
|
||||||
|
this.excalidrawAPI.updateScene({
|
||||||
|
collaborators,
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "IDLE_STATUS": {
|
||||||
|
const { userState, socketId, username } = decryptedData.payload;
|
||||||
|
const collaborators = new Map(this.collaborators);
|
||||||
|
const user = collaborators.get(socketId) || {}!;
|
||||||
|
user.userState = userState;
|
||||||
|
user.username = username;
|
||||||
|
this.excalidrawAPI.updateScene({
|
||||||
|
collaborators,
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
this.portal.socket!.on("first-in-room", () => {
|
||||||
|
if (this.portal.socket) {
|
||||||
|
this.portal.socket.off("first-in-room");
|
||||||
|
}
|
||||||
|
this.initializeSocket();
|
||||||
|
scenePromise.resolve(null);
|
||||||
|
});
|
||||||
|
|
||||||
|
this.initializeIdleDetector();
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
activeRoomLink: window.location.href,
|
||||||
|
});
|
||||||
|
|
||||||
|
return scenePromise;
|
||||||
};
|
};
|
||||||
|
|
||||||
private initializeSocket = () => {
|
private initializeSocket = () => {
|
||||||
@@ -359,7 +437,7 @@ class CollabWrapper extends PureComponent<Props, CollabState> {
|
|||||||
// Avoid broadcasting to the rest of the collaborators the scene
|
// Avoid broadcasting to the rest of the collaborators the scene
|
||||||
// we just received!
|
// we just received!
|
||||||
// Note: this needs to be set before updating the scene as it
|
// Note: this needs to be set before updating the scene as it
|
||||||
// syncronously calls render.
|
// synchronously calls render.
|
||||||
this.setLastBroadcastedOrReceivedSceneVersion(getSceneVersion(newElements));
|
this.setLastBroadcastedOrReceivedSceneVersion(getSceneVersion(newElements));
|
||||||
|
|
||||||
return newElements as ReconciledElements;
|
return newElements as ReconciledElements;
|
||||||
@@ -388,6 +466,58 @@ class CollabWrapper extends PureComponent<Props, CollabState> {
|
|||||||
this.excalidrawAPI.history.clear();
|
this.excalidrawAPI.history.clear();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
private onPointerMove = () => {
|
||||||
|
if (this.idleTimeoutId) {
|
||||||
|
window.clearTimeout(this.idleTimeoutId);
|
||||||
|
this.idleTimeoutId = null;
|
||||||
|
}
|
||||||
|
this.idleTimeoutId = window.setTimeout(this.reportIdle, IDLE_THRESHOLD);
|
||||||
|
if (!this.activeIntervalId) {
|
||||||
|
this.activeIntervalId = window.setInterval(
|
||||||
|
this.reportActive,
|
||||||
|
ACTIVE_THRESHOLD,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
private onVisibilityChange = () => {
|
||||||
|
if (document.hidden) {
|
||||||
|
if (this.idleTimeoutId) {
|
||||||
|
window.clearTimeout(this.idleTimeoutId);
|
||||||
|
this.idleTimeoutId = null;
|
||||||
|
}
|
||||||
|
if (this.activeIntervalId) {
|
||||||
|
window.clearInterval(this.activeIntervalId);
|
||||||
|
this.activeIntervalId = null;
|
||||||
|
}
|
||||||
|
this.onIdleStateChange(UserIdleState.AWAY);
|
||||||
|
} else {
|
||||||
|
this.idleTimeoutId = window.setTimeout(this.reportIdle, IDLE_THRESHOLD);
|
||||||
|
this.activeIntervalId = window.setInterval(
|
||||||
|
this.reportActive,
|
||||||
|
ACTIVE_THRESHOLD,
|
||||||
|
);
|
||||||
|
this.onIdleStateChange(UserIdleState.ACTIVE);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
private reportIdle = () => {
|
||||||
|
this.onIdleStateChange(UserIdleState.IDLE);
|
||||||
|
if (this.activeIntervalId) {
|
||||||
|
window.clearInterval(this.activeIntervalId);
|
||||||
|
this.activeIntervalId = null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
private reportActive = () => {
|
||||||
|
this.onIdleStateChange(UserIdleState.ACTIVE);
|
||||||
|
};
|
||||||
|
|
||||||
|
private initializeIdleDetector = () => {
|
||||||
|
document.addEventListener(EVENT.POINTER_MOVE, this.onPointerMove);
|
||||||
|
document.addEventListener(EVENT.VISIBILITY_CHANGE, this.onVisibilityChange);
|
||||||
|
};
|
||||||
|
|
||||||
setCollaborators(sockets: string[]) {
|
setCollaborators(sockets: string[]) {
|
||||||
this.setState((state) => {
|
this.setState((state) => {
|
||||||
const collaborators: InstanceType<
|
const collaborators: InstanceType<
|
||||||
@@ -427,6 +557,11 @@ class CollabWrapper extends PureComponent<Props, CollabState> {
|
|||||||
this.portal.broadcastMouseLocation(payload);
|
this.portal.broadcastMouseLocation(payload);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
onIdleStateChange = (userState: UserIdleState) => {
|
||||||
|
this.setState({ userState });
|
||||||
|
this.portal.broadcastIdleChange(userState);
|
||||||
|
};
|
||||||
|
|
||||||
broadcastElements = (elements: readonly ExcalidrawElement[]) => {
|
broadcastElements = (elements: readonly ExcalidrawElement[]) => {
|
||||||
if (
|
if (
|
||||||
getSceneVersion(elements) >
|
getSceneVersion(elements) >
|
||||||
@@ -480,9 +615,11 @@ class CollabWrapper extends PureComponent<Props, CollabState> {
|
|||||||
|
|
||||||
/** Getter of context value. Returned object is stable. */
|
/** Getter of context value. Returned object is stable. */
|
||||||
getContextValue = (): CollabAPI => {
|
getContextValue = (): CollabAPI => {
|
||||||
this.contextValue = this.contextValue || ({} as CollabAPI);
|
if (!this.contextValue) {
|
||||||
|
this.contextValue = {} as CollabAPI;
|
||||||
|
}
|
||||||
|
|
||||||
this.contextValue.isCollaborating = this.state.isCollaborating;
|
this.contextValue.isCollaborating = () => this.isCollaborating;
|
||||||
this.contextValue.username = this.state.username;
|
this.contextValue.username = this.state.username;
|
||||||
this.contextValue.onPointerUpdate = this.onPointerUpdate;
|
this.contextValue.onPointerUpdate = this.onPointerUpdate;
|
||||||
this.contextValue.initializeSocketClient = this.initializeSocketClient;
|
this.contextValue.initializeSocketClient = this.initializeSocketClient;
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import CollabWrapper from "./CollabWrapper";
|
|||||||
import { getSyncableElements } from "../../packages/excalidraw/index";
|
import { getSyncableElements } from "../../packages/excalidraw/index";
|
||||||
import { ExcalidrawElement } from "../../element/types";
|
import { ExcalidrawElement } from "../../element/types";
|
||||||
import { BROADCAST, SCENE } from "../app_constants";
|
import { BROADCAST, SCENE } from "../app_constants";
|
||||||
|
import { UserIdleState } from "./types";
|
||||||
|
|
||||||
class Portal {
|
class Portal {
|
||||||
collab: CollabWrapper;
|
collab: CollabWrapper;
|
||||||
@@ -122,7 +123,7 @@ class Portal {
|
|||||||
data as SocketUpdateData,
|
data as SocketUpdateData,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (syncAll && this.collab.state.isCollaborating) {
|
if (syncAll && this.collab.isCollaborating) {
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
broadcastPromise,
|
broadcastPromise,
|
||||||
this.collab.saveCollabRoomToFirebase(syncableElements),
|
this.collab.saveCollabRoomToFirebase(syncableElements),
|
||||||
@@ -132,6 +133,23 @@ class Portal {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
broadcastIdleChange = (userState: UserIdleState) => {
|
||||||
|
if (this.socket?.id) {
|
||||||
|
const data: SocketUpdateDataSource["IDLE_STATUS"] = {
|
||||||
|
type: "IDLE_STATUS",
|
||||||
|
payload: {
|
||||||
|
socketId: this.socket.id,
|
||||||
|
userState,
|
||||||
|
username: this.collab.state.username,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
return this._broadcastSocketData(
|
||||||
|
data as SocketUpdateData,
|
||||||
|
true, // volatile
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
broadcastMouseLocation = (payload: {
|
broadcastMouseLocation = (payload: {
|
||||||
pointer: SocketUpdateDataSource["MOUSE_LOCATION"]["payload"]["pointer"];
|
pointer: SocketUpdateDataSource["MOUSE_LOCATION"]["payload"]["pointer"];
|
||||||
button: SocketUpdateDataSource["MOUSE_LOCATION"]["payload"]["button"];
|
button: SocketUpdateDataSource["MOUSE_LOCATION"]["payload"]["button"];
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.RoomDialog-link {
|
.RoomDialog-link {
|
||||||
color: var(--text-color-primary);
|
color: var(--text-primary-color);
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
flex: 1 1 auto;
|
flex: 1 1 auto;
|
||||||
margin-inline-start: 1em;
|
margin-inline-start: 1em;
|
||||||
@@ -32,15 +32,29 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
@media #{$is-mobile-query} {
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: stretch;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media #{$is-mobile-query} {
|
||||||
|
.RoomDialog-usernameLabel {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.RoomDialog-username {
|
.RoomDialog-username {
|
||||||
background-color: var(--input-background-color);
|
background-color: var(--input-bg-color);
|
||||||
border-color: var(--input-border-color);
|
border-color: var(--input-border-color);
|
||||||
appearance: none;
|
appearance: none;
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
flex: 1 1 auto;
|
flex: 1 1 auto;
|
||||||
margin-inline-start: 1em;
|
margin-inline-start: 1em;
|
||||||
|
@media #{$is-mobile-query} {
|
||||||
|
margin-top: 0.5em;
|
||||||
|
margin-inline-start: 0;
|
||||||
|
}
|
||||||
height: 2.5rem;
|
height: 2.5rem;
|
||||||
font-size: 1em;
|
font-size: 1em;
|
||||||
line-height: 1.5;
|
line-height: 1.5;
|
||||||
@@ -53,7 +67,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.Modal .RoomDialog-stopSession {
|
.Modal .RoomDialog-stopSession {
|
||||||
background-color: var(--button-destructive-background-color);
|
background-color: var(--button-destructive-bg-color);
|
||||||
|
|
||||||
.ToolIcon__label,
|
.ToolIcon__label,
|
||||||
.ToolIcon__icon svg {
|
.ToolIcon__icon svg {
|
||||||
|
|||||||
@@ -119,7 +119,11 @@ const RoomDialog = ({
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
return (
|
return (
|
||||||
<Dialog small onCloseRequest={handleClose} title={t("labels.createRoom")}>
|
<Dialog
|
||||||
|
small
|
||||||
|
onCloseRequest={handleClose}
|
||||||
|
title={t("labels.liveCollaboration")}
|
||||||
|
>
|
||||||
{renderRoomDialog()}
|
{renderRoomDialog()}
|
||||||
</Dialog>
|
</Dialog>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
export enum UserIdleState {
|
||||||
|
ACTIVE = "active",
|
||||||
|
AWAY = "away",
|
||||||
|
IDLE = "idle",
|
||||||
|
}
|
||||||
@@ -148,6 +148,7 @@ export const saveToFirebase = async (
|
|||||||
export const loadFromFirebase = async (
|
export const loadFromFirebase = async (
|
||||||
roomId: string,
|
roomId: string,
|
||||||
roomKey: string,
|
roomKey: string,
|
||||||
|
socket: SocketIOClient.Socket | null,
|
||||||
): Promise<readonly ExcalidrawElement[] | null> => {
|
): Promise<readonly ExcalidrawElement[] | null> => {
|
||||||
const firebase = await getFirebase();
|
const firebase = await getFirebase();
|
||||||
const db = firebase.firestore();
|
const db = firebase.firestore();
|
||||||
@@ -160,5 +161,12 @@ export const loadFromFirebase = async (
|
|||||||
const storedScene = doc.data() as FirebaseStoredScene;
|
const storedScene = doc.data() as FirebaseStoredScene;
|
||||||
const ciphertext = storedScene.ciphertext.toUint8Array();
|
const ciphertext = storedScene.ciphertext.toUint8Array();
|
||||||
const iv = storedScene.iv.toUint8Array();
|
const iv = storedScene.iv.toUint8Array();
|
||||||
return restoreElements(await decryptElements(roomKey, iv, ciphertext));
|
|
||||||
|
const elements = await decryptElements(roomKey, iv, ciphertext);
|
||||||
|
|
||||||
|
if (socket) {
|
||||||
|
firebaseSceneVersionCache.set(socket, getSceneVersion(elements));
|
||||||
|
}
|
||||||
|
|
||||||
|
return restoreElements(elements);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { ImportedDataState } from "../../data/types";
|
|||||||
import { ExcalidrawElement } from "../../element/types";
|
import { ExcalidrawElement } from "../../element/types";
|
||||||
import { t } from "../../i18n";
|
import { t } from "../../i18n";
|
||||||
import { AppState } from "../../types";
|
import { AppState } from "../../types";
|
||||||
|
import { UserIdleState } from "../collab/types";
|
||||||
|
|
||||||
const byteToHex = (byte: number): string => `0${byte.toString(16)}`.slice(-2);
|
const byteToHex = (byte: number): string => `0${byte.toString(16)}`.slice(-2);
|
||||||
|
|
||||||
@@ -59,6 +60,14 @@ export type SocketUpdateDataSource = {
|
|||||||
username: string;
|
username: string;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
IDLE_STATUS: {
|
||||||
|
type: "IDLE_STATUS";
|
||||||
|
payload: {
|
||||||
|
socketId: string;
|
||||||
|
userState: UserIdleState;
|
||||||
|
username: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export type SocketUpdateDataIncoming =
|
export type SocketUpdateDataIncoming =
|
||||||
@@ -125,17 +134,27 @@ export const decryptAESGEM = async (
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const getCollaborationLinkData = (link: string) => {
|
export const getCollaborationLinkData = (link: string) => {
|
||||||
if (link.length === 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const hash = new URL(link).hash;
|
const hash = new URL(link).hash;
|
||||||
return hash.match(/^#room=([a-zA-Z0-9_-]+),([a-zA-Z0-9_-]+)$/);
|
const match = hash.match(/^#room=([a-zA-Z0-9_-]+),([a-zA-Z0-9_-]+)$/);
|
||||||
|
return match ? { roomId: match[1], roomKey: match[2] } : null;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const generateCollaborationLink = async () => {
|
export const generateCollaborationLinkData = async () => {
|
||||||
const id = await generateRandomID();
|
const roomId = await generateRandomID();
|
||||||
const key = await generateEncryptionKey();
|
const roomKey = await generateEncryptionKey();
|
||||||
return `${window.location.origin}${window.location.pathname}#room=${id},${key}`;
|
|
||||||
|
if (!roomKey) {
|
||||||
|
throw new Error("Couldn't generate room key");
|
||||||
|
}
|
||||||
|
|
||||||
|
return { roomId, roomKey };
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getCollaborationLink = (data: {
|
||||||
|
roomId: string;
|
||||||
|
roomKey: string;
|
||||||
|
}) => {
|
||||||
|
return `${window.location.origin}${window.location.pathname}#room=${data.roomId},${data.roomKey}`;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getImportedKey = (key: string, usage: KeyUsage) =>
|
export const getImportedKey = (key: string, usage: KeyUsage) =>
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import { ExcalidrawImperativeAPI } from "../components/App";
|
|||||||
import { ErrorDialog } from "../components/ErrorDialog";
|
import { ErrorDialog } from "../components/ErrorDialog";
|
||||||
import { TopErrorBoundary } from "../components/TopErrorBoundary";
|
import { TopErrorBoundary } from "../components/TopErrorBoundary";
|
||||||
import { APP_NAME, EVENT, TITLE_TIMEOUT, VERSION_TIMEOUT } from "../constants";
|
import { APP_NAME, EVENT, TITLE_TIMEOUT, VERSION_TIMEOUT } from "../constants";
|
||||||
import { ImportedDataState } from "../data/types";
|
import { DataState, ImportedDataState } from "../data/types";
|
||||||
import {
|
import {
|
||||||
ExcalidrawElement,
|
ExcalidrawElement,
|
||||||
NonDeletedExcalidrawElement,
|
NonDeletedExcalidrawElement,
|
||||||
@@ -39,11 +39,9 @@ import CollabWrapper, {
|
|||||||
} from "./collab/CollabWrapper";
|
} from "./collab/CollabWrapper";
|
||||||
import { LanguageList } from "./components/LanguageList";
|
import { LanguageList } from "./components/LanguageList";
|
||||||
import { exportToBackend, getCollaborationLinkData, loadScene } from "./data";
|
import { exportToBackend, getCollaborationLinkData, loadScene } from "./data";
|
||||||
import { loadFromFirebase } from "./data/firebase";
|
|
||||||
import {
|
import {
|
||||||
importFromLocalStorage,
|
importFromLocalStorage,
|
||||||
saveToLocalStorage,
|
saveToLocalStorage,
|
||||||
STORAGE_KEYS,
|
|
||||||
} from "./data/localStorage";
|
} from "./data/localStorage";
|
||||||
|
|
||||||
const languageDetector = new LanguageDetector();
|
const languageDetector = new LanguageDetector();
|
||||||
@@ -66,50 +64,9 @@ const onBlur = () => {
|
|||||||
saveDebounced.flush();
|
saveDebounced.flush();
|
||||||
};
|
};
|
||||||
|
|
||||||
const shouldForceLoadScene = (
|
|
||||||
scene: ResolutionType<typeof loadScene>,
|
|
||||||
): boolean => {
|
|
||||||
if (!scene.elements.length) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
const roomMatch = getCollaborationLinkData(window.location.href);
|
|
||||||
|
|
||||||
if (!roomMatch) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const roomId = roomMatch[1];
|
|
||||||
|
|
||||||
let collabForceLoadFlag;
|
|
||||||
try {
|
|
||||||
collabForceLoadFlag = localStorage?.getItem(
|
|
||||||
STORAGE_KEYS.LOCAL_STORAGE_KEY_COLLAB_FORCE_FLAG,
|
|
||||||
);
|
|
||||||
} catch {}
|
|
||||||
|
|
||||||
if (collabForceLoadFlag) {
|
|
||||||
try {
|
|
||||||
const {
|
|
||||||
room: previousRoom,
|
|
||||||
timestamp,
|
|
||||||
}: { room: string; timestamp: number } = JSON.parse(collabForceLoadFlag);
|
|
||||||
// if loading same room as the one previously unloaded within 15sec
|
|
||||||
// force reload without prompting
|
|
||||||
if (previousRoom === roomId && Date.now() - timestamp < 15000) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
} catch {}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
};
|
|
||||||
|
|
||||||
type Scene = ImportedDataState & { commitToHistory: boolean };
|
|
||||||
|
|
||||||
const initializeScene = async (opts: {
|
const initializeScene = async (opts: {
|
||||||
resetScene: ExcalidrawImperativeAPI["resetScene"];
|
collabAPI: CollabAPI;
|
||||||
initializeSocketClient: CollabAPI["initializeSocketClient"];
|
}): Promise<ImportedDataState | 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");
|
||||||
const jsonMatch = window.location.hash.match(
|
const jsonMatch = window.location.hash.match(
|
||||||
@@ -118,13 +75,21 @@ const initializeScene = async (opts: {
|
|||||||
|
|
||||||
const initialData = importFromLocalStorage();
|
const initialData = importFromLocalStorage();
|
||||||
|
|
||||||
let scene = await loadScene(null, null, initialData);
|
let scene: DataState & { scrollToCenter?: boolean } = await loadScene(
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
initialData,
|
||||||
|
);
|
||||||
|
|
||||||
let isCollabScene = !!getCollaborationLinkData(window.location.href);
|
let roomLinkData = getCollaborationLinkData(window.location.href);
|
||||||
const isExternalScene = !!(id || jsonMatch || isCollabScene);
|
const isExternalScene = !!(id || jsonMatch || roomLinkData);
|
||||||
if (isExternalScene) {
|
if (isExternalScene) {
|
||||||
if (
|
if (
|
||||||
shouldForceLoadScene(scene) ||
|
// don't prompt if scene is empty
|
||||||
|
!scene.elements.length ||
|
||||||
|
// don't prompt for collab scenes because we don't override local storage
|
||||||
|
roomLinkData ||
|
||||||
|
// otherwise, prompt whether user wants to override current scene
|
||||||
window.confirm(t("alerts.loadSceneOverridePrompt"))
|
window.confirm(t("alerts.loadSceneOverridePrompt"))
|
||||||
) {
|
) {
|
||||||
// Backwards compatibility with legacy url format
|
// Backwards compatibility with legacy url format
|
||||||
@@ -133,7 +98,8 @@ const initializeScene = async (opts: {
|
|||||||
} else if (jsonMatch) {
|
} else if (jsonMatch) {
|
||||||
scene = await loadScene(jsonMatch[1], jsonMatch[2], initialData);
|
scene = await loadScene(jsonMatch[1], jsonMatch[2], initialData);
|
||||||
}
|
}
|
||||||
if (!isCollabScene) {
|
scene.scrollToCenter = true;
|
||||||
|
if (!roomLinkData) {
|
||||||
window.history.replaceState({}, APP_NAME, window.location.origin);
|
window.history.replaceState({}, APP_NAME, window.location.origin);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -150,38 +116,12 @@ const initializeScene = async (opts: {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
isCollabScene = false;
|
roomLinkData = null;
|
||||||
window.history.replaceState({}, APP_NAME, window.location.origin);
|
window.history.replaceState({}, APP_NAME, window.location.origin);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (isCollabScene) {
|
if (roomLinkData) {
|
||||||
// when joining a room we don't want user's local scene data to be merged
|
return opts.collabAPI.initializeSocketClient(roomLinkData);
|
||||||
// into the remote scene
|
|
||||||
opts.resetScene();
|
|
||||||
const scenePromise = opts.initializeSocketClient();
|
|
||||||
|
|
||||||
try {
|
|
||||||
const [, roomId, roomKey] = getCollaborationLinkData(
|
|
||||||
window.location.href,
|
|
||||||
)!;
|
|
||||||
const elements = await loadFromFirebase(roomId, roomKey);
|
|
||||||
if (elements) {
|
|
||||||
return {
|
|
||||||
elements,
|
|
||||||
commitToHistory: true,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
...(await scenePromise),
|
|
||||||
commitToHistory: true,
|
|
||||||
};
|
|
||||||
} catch (error) {
|
|
||||||
// log the error and move on. other peers will sync us the scene.
|
|
||||||
console.error(error);
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
} else if (scene) {
|
} else if (scene) {
|
||||||
return scene;
|
return scene;
|
||||||
}
|
}
|
||||||
@@ -242,24 +182,16 @@ function ExcalidrawWrapper() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
initializeScene({
|
initializeScene({ collabAPI }).then((scene) => {
|
||||||
resetScene: excalidrawAPI.resetScene,
|
|
||||||
initializeSocketClient: collabAPI.initializeSocketClient,
|
|
||||||
}).then((scene) => {
|
|
||||||
initialStatePromiseRef.current.promise.resolve(scene);
|
initialStatePromiseRef.current.promise.resolve(scene);
|
||||||
});
|
});
|
||||||
|
|
||||||
const onHashChange = (_: HashChangeEvent) => {
|
const onHashChange = (_: HashChangeEvent) => {
|
||||||
if (window.location.hash.length > 1) {
|
initializeScene({ collabAPI }).then((scene) => {
|
||||||
initializeScene({
|
if (scene) {
|
||||||
resetScene: excalidrawAPI.resetScene,
|
excalidrawAPI.updateScene(scene);
|
||||||
initializeSocketClient: collabAPI.initializeSocketClient,
|
}
|
||||||
}).then((scene) => {
|
});
|
||||||
if (scene) {
|
|
||||||
excalidrawAPI.updateScene(scene);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const titleTimeout = setTimeout(
|
const titleTimeout = setTimeout(
|
||||||
@@ -285,9 +217,13 @@ function ExcalidrawWrapper() {
|
|||||||
elements: readonly ExcalidrawElement[],
|
elements: readonly ExcalidrawElement[],
|
||||||
appState: AppState,
|
appState: AppState,
|
||||||
) => {
|
) => {
|
||||||
saveDebounced(elements, appState);
|
if (collabAPI?.isCollaborating()) {
|
||||||
if (collabAPI?.isCollaborating) {
|
|
||||||
collabAPI.broadcastElements(elements);
|
collabAPI.broadcastElements(elements);
|
||||||
|
} else {
|
||||||
|
// collab scenes are persisted to the server, so we don't have to persist
|
||||||
|
// them locally, which has the added benefit of not overwriting whatever
|
||||||
|
// the user was working on before joining
|
||||||
|
saveDebounced(elements, appState);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -350,9 +286,8 @@ function ExcalidrawWrapper() {
|
|||||||
width={dimensions.width}
|
width={dimensions.width}
|
||||||
height={dimensions.height}
|
height={dimensions.height}
|
||||||
initialData={initialStatePromiseRef.current.promise}
|
initialData={initialStatePromiseRef.current.promise}
|
||||||
user={{ name: collabAPI?.username }}
|
|
||||||
onCollabButtonClick={collabAPI?.onCollabButtonClick}
|
onCollabButtonClick={collabAPI?.onCollabButtonClick}
|
||||||
isCollaborating={collabAPI?.isCollaborating}
|
isCollaborating={collabAPI?.isCollaborating()}
|
||||||
onPointerUpdate={collabAPI?.onPointerUpdate}
|
onPointerUpdate={collabAPI?.onPointerUpdate}
|
||||||
onExportToBackend={onExportToBackend}
|
onExportToBackend={onExportToBackend}
|
||||||
renderFooter={renderFooter}
|
renderFooter={renderFooter}
|
||||||
|
|||||||
Vendored
+1
@@ -12,6 +12,7 @@ interface Document {
|
|||||||
interface Window {
|
interface Window {
|
||||||
ClipboardItem: any;
|
ClipboardItem: any;
|
||||||
__EXCALIDRAW_SHA__: string | undefined;
|
__EXCALIDRAW_SHA__: string | undefined;
|
||||||
|
EXCALIDRAW_ASSET_PATH: string | undefined;
|
||||||
gtag: Function;
|
gtag: Function;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+28
-12
@@ -1,5 +1,6 @@
|
|||||||
import fallbackLangData from "./locales/en.json";
|
import fallbackLangData from "./locales/en.json";
|
||||||
import percentages from "./locales/percentages.json";
|
import percentages from "./locales/percentages.json";
|
||||||
|
import { ENV } from "./constants";
|
||||||
|
|
||||||
const COMPLETION_THRESHOLD = 85;
|
const COMPLETION_THRESHOLD = 85;
|
||||||
|
|
||||||
@@ -55,25 +56,33 @@ export const languages: Language[] = allLanguages
|
|||||||
COMPLETION_THRESHOLD,
|
COMPLETION_THRESHOLD,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const TEST_LANG_CODE = "__test__";
|
||||||
|
if (process.env.NODE_ENV === ENV.DEVELOPMENT) {
|
||||||
|
languages.unshift(
|
||||||
|
{ code: TEST_LANG_CODE, label: "test language" },
|
||||||
|
{
|
||||||
|
code: `${TEST_LANG_CODE}.rtl`,
|
||||||
|
label: "\u{202a}test language (rtl)\u{202c}",
|
||||||
|
rtl: true,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
let currentLang: Language = defaultLang;
|
let currentLang: Language = defaultLang;
|
||||||
let currentLangData = {};
|
let currentLangData = {};
|
||||||
|
|
||||||
export const setLanguage = async (lang: Language) => {
|
export const setLanguage = async (lang: Language) => {
|
||||||
currentLang = lang;
|
currentLang = lang;
|
||||||
document.documentElement.dir = currentLang.rtl ? "rtl" : "ltr";
|
document.documentElement.dir = currentLang.rtl ? "rtl" : "ltr";
|
||||||
|
document.documentElement.lang = currentLang.code;
|
||||||
|
|
||||||
currentLangData = await import(
|
if (lang.code.startsWith(TEST_LANG_CODE)) {
|
||||||
/* webpackChunkName: "i18n-[request]" */ `./locales/${currentLang.code}.json`
|
currentLangData = {};
|
||||||
);
|
} else {
|
||||||
};
|
currentLangData = await import(
|
||||||
|
/* webpackChunkName: "i18n-[request]" */ `./locales/${currentLang.code}.json`
|
||||||
export const setLanguageFirstTime = async (lang: Language) => {
|
);
|
||||||
currentLang = lang;
|
}
|
||||||
document.documentElement.dir = currentLang.rtl ? "rtl" : "ltr";
|
|
||||||
|
|
||||||
currentLangData = await import(
|
|
||||||
/* webpackChunkName: "i18n-[request]" */ `./locales/${currentLang.code}.json`
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getLanguage = () => currentLang;
|
export const getLanguage = () => currentLang;
|
||||||
@@ -93,6 +102,13 @@ const findPartsForData = (data: any, parts: string[]) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const t = (path: string, replacement?: { [key: string]: string }) => {
|
export const t = (path: string, replacement?: { [key: string]: string }) => {
|
||||||
|
if (currentLang.code.startsWith(TEST_LANG_CODE)) {
|
||||||
|
const name = replacement
|
||||||
|
? `${path}(${JSON.stringify(replacement).slice(1, -1)})`
|
||||||
|
: path;
|
||||||
|
return `\u{202a}[[${name}]]\u{202c}`;
|
||||||
|
}
|
||||||
|
|
||||||
const parts = path.split(".");
|
const parts = path.split(".");
|
||||||
let translation =
|
let translation =
|
||||||
findPartsForData(currentLangData, parts) ||
|
findPartsForData(currentLangData, parts) ||
|
||||||
|
|||||||
+2
-2
@@ -54,8 +54,8 @@ const elements = [
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
registerFont("./public/FG_Virgil.ttf", { family: "Virgil" });
|
registerFont("./public/Virgil.woff2", { family: "Virgil" });
|
||||||
registerFont("./public/Cascadia.ttf", { family: "Cascadia" });
|
registerFont("./public/Cascadia.woff2", { family: "Cascadia" });
|
||||||
|
|
||||||
const canvas = exportToCanvas(
|
const canvas = exportToCanvas(
|
||||||
elements as any,
|
elements as any,
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import ExcalidrawApp from "./excalidraw-app";
|
|||||||
|
|
||||||
import "./excalidraw-app/pwa";
|
import "./excalidraw-app/pwa";
|
||||||
import "./excalidraw-app/sentry";
|
import "./excalidraw-app/sentry";
|
||||||
|
|
||||||
window.__EXCALIDRAW_SHA__ = process.env.REACT_APP_GIT_SHA;
|
window.__EXCALIDRAW_SHA__ = process.env.REACT_APP_GIT_SHA;
|
||||||
|
|
||||||
ReactDOM.render(<ExcalidrawApp />, document.getElementById("root"));
|
ReactDOM.render(<ExcalidrawApp />, document.getElementById("root"));
|
||||||
|
|||||||
+13
-5
@@ -68,7 +68,7 @@
|
|||||||
"layers": "الطبقات",
|
"layers": "الطبقات",
|
||||||
"actions": "الإجراءات",
|
"actions": "الإجراءات",
|
||||||
"language": "اللغة",
|
"language": "اللغة",
|
||||||
"createRoom": "مشاركة الجلسة مباشرة",
|
"liveCollaboration": "",
|
||||||
"duplicateSelection": "تكرار",
|
"duplicateSelection": "تكرار",
|
||||||
"untitled": "غير معنون",
|
"untitled": "غير معنون",
|
||||||
"name": "الاسم",
|
"name": "الاسم",
|
||||||
@@ -77,7 +77,7 @@
|
|||||||
"group": "تحديد مجموعة",
|
"group": "تحديد مجموعة",
|
||||||
"ungroup": "إلغاء تحديد مجموعة",
|
"ungroup": "إلغاء تحديد مجموعة",
|
||||||
"collaborators": "المتعاونون",
|
"collaborators": "المتعاونون",
|
||||||
"gridMode": "وضع الشبكة",
|
"showGrid": "",
|
||||||
"addToLibrary": "أضف إلى المكتبة",
|
"addToLibrary": "أضف إلى المكتبة",
|
||||||
"removeFromLibrary": "حذف من المكتبة",
|
"removeFromLibrary": "حذف من المكتبة",
|
||||||
"libraryLoadingMessage": "جارٍ تحميل المكتبة…",
|
"libraryLoadingMessage": "جارٍ تحميل المكتبة…",
|
||||||
@@ -117,7 +117,7 @@
|
|||||||
"edit": "تعديل",
|
"edit": "تعديل",
|
||||||
"undo": "تراجع",
|
"undo": "تراجع",
|
||||||
"redo": "إعادة تنفيذ",
|
"redo": "إعادة تنفيذ",
|
||||||
"roomDialog": "بدء المشاركة الحية",
|
"resetLibrary": "",
|
||||||
"createNewRoom": "إنشاء غرفة جديدة",
|
"createNewRoom": "إنشاء غرفة جديدة",
|
||||||
"fullScreen": "شاشة كاملة",
|
"fullScreen": "شاشة كاملة",
|
||||||
"darkMode": "الوضع المظلم",
|
"darkMode": "الوضع المظلم",
|
||||||
@@ -136,10 +136,12 @@
|
|||||||
"decryptFailed": "تعذر فك تشفير البيانات.",
|
"decryptFailed": "تعذر فك تشفير البيانات.",
|
||||||
"uploadedSecurly": "تم تأمين التحميل بتشفير النهاية إلى النهاية، مما يعني أن خادوم Excalidraw والأطراف الثالثة لا يمكنها قراءة المحتوى.",
|
"uploadedSecurly": "تم تأمين التحميل بتشفير النهاية إلى النهاية، مما يعني أن خادوم Excalidraw والأطراف الثالثة لا يمكنها قراءة المحتوى.",
|
||||||
"loadSceneOverridePrompt": "تحميل الرسم الخارجي سيحل محل المحتوى الموجود لديك. هل ترغب في المتابعة؟",
|
"loadSceneOverridePrompt": "تحميل الرسم الخارجي سيحل محل المحتوى الموجود لديك. هل ترغب في المتابعة؟",
|
||||||
|
"collabStopOverridePrompt": "",
|
||||||
"errorLoadingLibrary": "حصل خطأ أثناء تحميل مكتبة الطرف الثالث.",
|
"errorLoadingLibrary": "حصل خطأ أثناء تحميل مكتبة الطرف الثالث.",
|
||||||
"confirmAddLibrary": "هذا سيضيف {{numShapes}} شكل إلى مكتبتك. هل أنت متأكد؟",
|
"confirmAddLibrary": "هذا سيضيف {{numShapes}} شكل إلى مكتبتك. هل أنت متأكد؟",
|
||||||
"imageDoesNotContainScene": "استيراد الصور غير مدعوم في الوقت الراهن.\n\nهل تريد استيراد مشهد؟ لا يبدو أن هذه الصورة تحتوي على أي بيانات مشهد. هل قمت بسماح هذا أثناء التصدير؟",
|
"imageDoesNotContainScene": "استيراد الصور غير مدعوم في الوقت الراهن.\n\nهل تريد استيراد مشهد؟ لا يبدو أن هذه الصورة تحتوي على أي بيانات مشهد. هل قمت بسماح هذا أثناء التصدير؟",
|
||||||
"cannotRestoreFromImage": "تعذر استعادة المشهد من ملف الصورة"
|
"cannotRestoreFromImage": "تعذر استعادة المشهد من ملف الصورة",
|
||||||
|
"resetLibrary": ""
|
||||||
},
|
},
|
||||||
"toolBar": {
|
"toolBar": {
|
||||||
"selection": "تحديد",
|
"selection": "تحديد",
|
||||||
@@ -234,10 +236,16 @@
|
|||||||
"storage": "التخزين",
|
"storage": "التخزين",
|
||||||
"title": "إحصائيات للمهووسين",
|
"title": "إحصائيات للمهووسين",
|
||||||
"total": "المجموع",
|
"total": "المجموع",
|
||||||
|
"version": "",
|
||||||
|
"versionCopy": "",
|
||||||
|
"versionNotAvailable": "",
|
||||||
"width": "العرض"
|
"width": "العرض"
|
||||||
},
|
},
|
||||||
"toast": {
|
"toast": {
|
||||||
"copyStyles": "",
|
"copyStyles": "",
|
||||||
"copyToClipboardAsPng": ""
|
"copyToClipboard": "",
|
||||||
|
"copyToClipboardAsPng": "",
|
||||||
|
"fileSaved": "",
|
||||||
|
"fileSavedToFilename": ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+30
-22
@@ -8,11 +8,11 @@
|
|||||||
"cut": "Изрежи",
|
"cut": "Изрежи",
|
||||||
"copy": "Копирай",
|
"copy": "Копирай",
|
||||||
"copyAsPng": "Копиране в клипборда",
|
"copyAsPng": "Копиране в клипборда",
|
||||||
"copyAsSvg": "Копиране в клипборда",
|
"copyAsSvg": "Копирано в клипборда като SVG",
|
||||||
"bringForward": "Преместване на~пред",
|
"bringForward": "Преместване напред",
|
||||||
"sendToBack": "Изнасяне назад",
|
"sendToBack": "Изнасяне назад",
|
||||||
"bringToFront": "~Изнасяне отпред",
|
"bringToFront": "Изнасяне отпред",
|
||||||
"sendBackward": "Изпрати назад",
|
"sendBackward": "Изпрати отзад",
|
||||||
"delete": "Изтрий",
|
"delete": "Изтрий",
|
||||||
"copyStyles": "Копирайте стилове",
|
"copyStyles": "Копирайте стилове",
|
||||||
"pasteStyles": "Постави стилове",
|
"pasteStyles": "Постави стилове",
|
||||||
@@ -68,7 +68,7 @@
|
|||||||
"layers": "Слоеве",
|
"layers": "Слоеве",
|
||||||
"actions": "Действия",
|
"actions": "Действия",
|
||||||
"language": "Език",
|
"language": "Език",
|
||||||
"createRoom": "Споделете сесия за сътрудничество на живо",
|
"liveCollaboration": "",
|
||||||
"duplicateSelection": "Дублирай",
|
"duplicateSelection": "Дублирай",
|
||||||
"untitled": "Неозаглавено",
|
"untitled": "Неозаглавено",
|
||||||
"name": "Име",
|
"name": "Име",
|
||||||
@@ -77,7 +77,7 @@
|
|||||||
"group": "Групирай селекцията",
|
"group": "Групирай селекцията",
|
||||||
"ungroup": "Спри групирането на селекцията",
|
"ungroup": "Спри групирането на селекцията",
|
||||||
"collaborators": "Сътрудници",
|
"collaborators": "Сътрудници",
|
||||||
"gridMode": "Решетъчен режим",
|
"showGrid": "Показване на мрежа",
|
||||||
"addToLibrary": "Добавяне към библиотеката",
|
"addToLibrary": "Добавяне към библиотеката",
|
||||||
"removeFromLibrary": "Премахване от библиотеката",
|
"removeFromLibrary": "Премахване от библиотеката",
|
||||||
"libraryLoadingMessage": "Зареждане на библиотеката…",
|
"libraryLoadingMessage": "Зареждане на библиотеката…",
|
||||||
@@ -92,7 +92,7 @@
|
|||||||
"centerHorizontally": "Центрирай хоризонтално",
|
"centerHorizontally": "Центрирай хоризонтално",
|
||||||
"distributeHorizontally": "Разпредели хоризонтално",
|
"distributeHorizontally": "Разпредели хоризонтално",
|
||||||
"distributeVertically": "Разпредели вертикално",
|
"distributeVertically": "Разпредели вертикално",
|
||||||
"viewMode": ""
|
"viewMode": "Изглед"
|
||||||
},
|
},
|
||||||
"buttons": {
|
"buttons": {
|
||||||
"clearReset": "Нулиране на платно",
|
"clearReset": "Нулиране на платно",
|
||||||
@@ -117,7 +117,7 @@
|
|||||||
"edit": "Редактиране",
|
"edit": "Редактиране",
|
||||||
"undo": "Отмяна",
|
"undo": "Отмяна",
|
||||||
"redo": "Повтори",
|
"redo": "Повтори",
|
||||||
"roomDialog": "Започнете сътрудничество на живо",
|
"resetLibrary": "",
|
||||||
"createNewRoom": "Създай нова стая",
|
"createNewRoom": "Създай нова стая",
|
||||||
"fullScreen": "На цял екран",
|
"fullScreen": "На цял екран",
|
||||||
"darkMode": "Тъмен режим",
|
"darkMode": "Тъмен режим",
|
||||||
@@ -136,10 +136,12 @@
|
|||||||
"decryptFailed": "Данните не можаха да се дешифрират.",
|
"decryptFailed": "Данните не можаха да се дешифрират.",
|
||||||
"uploadedSecurly": "Качването е защитено с криптиране от край до край, което означава, че сървърът Excalidraw и трети страни не могат да четат съдържанието.",
|
"uploadedSecurly": "Качването е защитено с криптиране от край до край, което означава, че сървърът Excalidraw и трети страни не могат да четат съдържанието.",
|
||||||
"loadSceneOverridePrompt": "Зареждането на външна рисунка ще презапише настоящото ви съдържание. Желаете ли да продължите?",
|
"loadSceneOverridePrompt": "Зареждането на външна рисунка ще презапише настоящото ви съдържание. Желаете ли да продължите?",
|
||||||
|
"collabStopOverridePrompt": "Прекратяването на сесията ще презапише предишната, локално запазена, рисунка. Сигурни ли сте?\n\n(Ако искате да продължите с локалната рисунка, просто затворете таба на браузъра.)",
|
||||||
"errorLoadingLibrary": "Възникна грешка при зареждането на външна библиотека.",
|
"errorLoadingLibrary": "Възникна грешка при зареждането на външна библиотека.",
|
||||||
"confirmAddLibrary": "Ще се добавят {{numShapes}} фигура(и) във вашата библиотека. Сигурни ли сте?",
|
"confirmAddLibrary": "Ще се добавят {{numShapes}} фигура(и) във вашата библиотека. Сигурни ли сте?",
|
||||||
"imageDoesNotContainScene": "Импортирането на картинки не се поддържва в момента.\n\nИскате да импортнете сцена? Тази картинка не съдържа данни от сцена. Разрешили ли сте последното при експортирането?",
|
"imageDoesNotContainScene": "Импортирането на картинки не се поддържва в момента.\n\nИскате да импортнете сцена? Тази картинка не съдържа данни от сцена. Разрешили ли сте последното при експортирането?",
|
||||||
"cannotRestoreFromImage": "Не може да бъде възстановена сцена от този файл"
|
"cannotRestoreFromImage": "Не може да бъде възстановена сцена от този файл",
|
||||||
|
"resetLibrary": ""
|
||||||
},
|
},
|
||||||
"toolBar": {
|
"toolBar": {
|
||||||
"selection": "Селекция",
|
"selection": "Селекция",
|
||||||
@@ -201,24 +203,24 @@
|
|||||||
"title": "Грешка"
|
"title": "Грешка"
|
||||||
},
|
},
|
||||||
"helpDialog": {
|
"helpDialog": {
|
||||||
"blog": "",
|
"blog": "Прочетете нашия блог",
|
||||||
"click": "клик",
|
"click": "клик",
|
||||||
"curvedArrow": "",
|
"curvedArrow": "Извита стрелка",
|
||||||
"curvedLine": "",
|
"curvedLine": "Извита линия",
|
||||||
"documentation": "",
|
"documentation": "Документация",
|
||||||
"drag": "плъзнете",
|
"drag": "плъзнете",
|
||||||
"editor": "Редактор",
|
"editor": "Редактор",
|
||||||
"github": "",
|
"github": "Намерихте проблем? Изпратете",
|
||||||
"howto": "",
|
"howto": "Следвайте нашите ръководства",
|
||||||
"or": "или",
|
"or": "или",
|
||||||
"preventBinding": "",
|
"preventBinding": "Спри прилепяне на стрелките",
|
||||||
"shapes": "Фигури",
|
"shapes": "Фигури",
|
||||||
"shortcuts": "Клавиши за бърз достъп",
|
"shortcuts": "Клавиши за бърз достъп",
|
||||||
"textFinish": "",
|
"textFinish": "Завършете редактирането (текст)",
|
||||||
"textNewLine": "",
|
"textNewLine": "Добавяне на нов ред (текст)",
|
||||||
"title": "",
|
"title": "Помощ",
|
||||||
"view": "Преглед",
|
"view": "Преглед",
|
||||||
"zoomToFit": "",
|
"zoomToFit": "Приближи докато се виждат всички елементи",
|
||||||
"zoomToSelection": "Приближи селекцията"
|
"zoomToSelection": "Приближи селекцията"
|
||||||
},
|
},
|
||||||
"encrypted": {
|
"encrypted": {
|
||||||
@@ -234,10 +236,16 @@
|
|||||||
"storage": "Съхранение на данни",
|
"storage": "Съхранение на данни",
|
||||||
"title": "Статистика за хакери",
|
"title": "Статистика за хакери",
|
||||||
"total": "Общо",
|
"total": "Общо",
|
||||||
|
"version": "Версия",
|
||||||
|
"versionCopy": "Настисни за да копираш",
|
||||||
|
"versionNotAvailable": "Версията не е налична",
|
||||||
"width": "Широчина"
|
"width": "Широчина"
|
||||||
},
|
},
|
||||||
"toast": {
|
"toast": {
|
||||||
"copyStyles": "",
|
"copyStyles": "Копирани стилове.",
|
||||||
"copyToClipboardAsPng": ""
|
"copyToClipboard": "Копирано в клипборда.",
|
||||||
|
"copyToClipboardAsPng": "Копирано в клипборда като PNG.",
|
||||||
|
"fileSaved": "",
|
||||||
|
"fileSavedToFilename": ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+14
-6
@@ -68,7 +68,7 @@
|
|||||||
"layers": "Capes",
|
"layers": "Capes",
|
||||||
"actions": "Accions",
|
"actions": "Accions",
|
||||||
"language": "Llengua",
|
"language": "Llengua",
|
||||||
"createRoom": "Compartir una sessió de col·laboració en directe",
|
"liveCollaboration": "",
|
||||||
"duplicateSelection": "Duplicar",
|
"duplicateSelection": "Duplicar",
|
||||||
"untitled": "Sense títol",
|
"untitled": "Sense títol",
|
||||||
"name": "Nom",
|
"name": "Nom",
|
||||||
@@ -77,10 +77,10 @@
|
|||||||
"group": "Agrupar la selecció",
|
"group": "Agrupar la selecció",
|
||||||
"ungroup": "Desagrupar la selecció",
|
"ungroup": "Desagrupar la selecció",
|
||||||
"collaborators": "Col·laboradors",
|
"collaborators": "Col·laboradors",
|
||||||
"gridMode": "Mode quadrícula",
|
"showGrid": "",
|
||||||
"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": "Explorar biblioteques",
|
"libraries": "Explorar biblioteques",
|
||||||
"loadingScene": "Carregant escena…",
|
"loadingScene": "Carregant escena…",
|
||||||
"align": "Alinear",
|
"align": "Alinear",
|
||||||
@@ -117,7 +117,7 @@
|
|||||||
"edit": "Editar",
|
"edit": "Editar",
|
||||||
"undo": "Desfer",
|
"undo": "Desfer",
|
||||||
"redo": "Refer",
|
"redo": "Refer",
|
||||||
"roomDialog": "Començar col·laboració en directe",
|
"resetLibrary": "",
|
||||||
"createNewRoom": "Crear sala nova",
|
"createNewRoom": "Crear sala nova",
|
||||||
"fullScreen": "Pantalla completa",
|
"fullScreen": "Pantalla completa",
|
||||||
"darkMode": "Mode fosc",
|
"darkMode": "Mode fosc",
|
||||||
@@ -136,10 +136,12 @@
|
|||||||
"decryptFailed": "No s'ha pogut desencriptar.",
|
"decryptFailed": "No s'ha pogut desencriptar.",
|
||||||
"uploadedSecurly": "La càrrega s'ha assegurat amb xifratge punta a punta, cosa que significa que el servidor Excalidraw i tercers no poden llegir el contingut.",
|
"uploadedSecurly": "La càrrega s'ha assegurat amb xifratge punta a punta, cosa que significa que el servidor Excalidraw i tercers no poden llegir el contingut.",
|
||||||
"loadSceneOverridePrompt": "Si carregas aquest dibuix extern, substituirá el que tens. Vols continuar?",
|
"loadSceneOverridePrompt": "Si carregas aquest dibuix extern, substituirá el que tens. Vols continuar?",
|
||||||
|
"collabStopOverridePrompt": "",
|
||||||
"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": "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ó?",
|
"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": "L’escena no s’ha pogut restaurar des d’aquest fitxer d’imatge"
|
"cannotRestoreFromImage": "L’escena no s’ha pogut restaurar des d’aquest fitxer d’imatge",
|
||||||
|
"resetLibrary": ""
|
||||||
},
|
},
|
||||||
"toolBar": {
|
"toolBar": {
|
||||||
"selection": "Selecció",
|
"selection": "Selecció",
|
||||||
@@ -234,10 +236,16 @@
|
|||||||
"storage": "Emmagatzematge",
|
"storage": "Emmagatzematge",
|
||||||
"title": "Estadístiques per nerds",
|
"title": "Estadístiques per nerds",
|
||||||
"total": "Total",
|
"total": "Total",
|
||||||
|
"version": "",
|
||||||
|
"versionCopy": "",
|
||||||
|
"versionNotAvailable": "",
|
||||||
"width": "Amplada"
|
"width": "Amplada"
|
||||||
},
|
},
|
||||||
"toast": {
|
"toast": {
|
||||||
"copyStyles": "",
|
"copyStyles": "",
|
||||||
"copyToClipboardAsPng": ""
|
"copyToClipboard": "",
|
||||||
|
"copyToClipboardAsPng": "",
|
||||||
|
"fileSaved": "",
|
||||||
|
"fileSavedToFilename": ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+15
-7
@@ -68,7 +68,7 @@
|
|||||||
"layers": "Ebenen",
|
"layers": "Ebenen",
|
||||||
"actions": "Aktionen",
|
"actions": "Aktionen",
|
||||||
"language": "Sprache",
|
"language": "Sprache",
|
||||||
"createRoom": "Live-Kollaborationssitzung teilen",
|
"liveCollaboration": "Live-Zusammenarbeit",
|
||||||
"duplicateSelection": "Duplizieren",
|
"duplicateSelection": "Duplizieren",
|
||||||
"untitled": "Unbenannt",
|
"untitled": "Unbenannt",
|
||||||
"name": "Name",
|
"name": "Name",
|
||||||
@@ -76,8 +76,8 @@
|
|||||||
"madeWithExcalidraw": "Made with Excalidraw",
|
"madeWithExcalidraw": "Made with Excalidraw",
|
||||||
"group": "Auswahl gruppieren",
|
"group": "Auswahl gruppieren",
|
||||||
"ungroup": "Gruppierung aufheben",
|
"ungroup": "Gruppierung aufheben",
|
||||||
"collaborators": "Mitarbeitende",
|
"collaborators": "Kollaboratoren",
|
||||||
"gridMode": "Rastermodus",
|
"showGrid": "Raster anzeigen",
|
||||||
"addToLibrary": "Zur Bibliothek hinzufügen",
|
"addToLibrary": "Zur Bibliothek hinzufügen",
|
||||||
"removeFromLibrary": "Aus Bibliothek entfernen",
|
"removeFromLibrary": "Aus Bibliothek entfernen",
|
||||||
"libraryLoadingMessage": "Lade Bibliothek…",
|
"libraryLoadingMessage": "Lade Bibliothek…",
|
||||||
@@ -101,7 +101,7 @@
|
|||||||
"exportToSvg": "Als SVG exportieren",
|
"exportToSvg": "Als SVG exportieren",
|
||||||
"copyToClipboard": "In Zwischenablage kopieren",
|
"copyToClipboard": "In Zwischenablage kopieren",
|
||||||
"copyPngToClipboard": "PNG in die Zwischenablage kopieren",
|
"copyPngToClipboard": "PNG in die Zwischenablage kopieren",
|
||||||
"scale": "Skalieren",
|
"scale": "Skalierung",
|
||||||
"save": "Speichern",
|
"save": "Speichern",
|
||||||
"saveAs": "Speichern unter",
|
"saveAs": "Speichern unter",
|
||||||
"load": "Laden",
|
"load": "Laden",
|
||||||
@@ -117,7 +117,7 @@
|
|||||||
"edit": "Bearbeiten",
|
"edit": "Bearbeiten",
|
||||||
"undo": "Rückgängig machen",
|
"undo": "Rückgängig machen",
|
||||||
"redo": "Wiederholen",
|
"redo": "Wiederholen",
|
||||||
"roomDialog": "Live-Kollaborationssitzung starten",
|
"resetLibrary": "Bibliothek zurücksetzen",
|
||||||
"createNewRoom": "Neuen Raum erstellen",
|
"createNewRoom": "Neuen Raum erstellen",
|
||||||
"fullScreen": "Vollbildanzeige",
|
"fullScreen": "Vollbildanzeige",
|
||||||
"darkMode": "Dunkles Design",
|
"darkMode": "Dunkles Design",
|
||||||
@@ -136,10 +136,12 @@
|
|||||||
"decryptFailed": "Daten konnten nicht entschlüsselt werden.",
|
"decryptFailed": "Daten konnten nicht entschlüsselt werden.",
|
||||||
"uploadedSecurly": "Der Upload wurde mit Ende-zu-Ende-Verschlüsselung gespeichert. Weder Excalidraw noch Dritte können den Inhalt einsehen.",
|
"uploadedSecurly": "Der Upload wurde mit Ende-zu-Ende-Verschlüsselung gespeichert. Weder Excalidraw noch Dritte können den Inhalt einsehen.",
|
||||||
"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?",
|
||||||
|
"collabStopOverridePrompt": "Das Stoppen der Sitzung wird Deine vorherige, lokal gespeicherte Zeichnung überschreiben. Bist Du sicher?\n\n(Wenn Du Deine lokale Zeichnung behalten möchtest, schließe einfach stattdessen den Browser-Tab.)",
|
||||||
"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": "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?",
|
"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",
|
||||||
|
"resetLibrary": "Dieses löscht deine Bibliothek. Bist du sicher?"
|
||||||
},
|
},
|
||||||
"toolBar": {
|
"toolBar": {
|
||||||
"selection": "Auswahl",
|
"selection": "Auswahl",
|
||||||
@@ -234,10 +236,16 @@
|
|||||||
"storage": "Speicher",
|
"storage": "Speicher",
|
||||||
"title": "Statistiken für Nerds",
|
"title": "Statistiken für Nerds",
|
||||||
"total": "Gesamt",
|
"total": "Gesamt",
|
||||||
|
"version": "Version",
|
||||||
|
"versionCopy": "Zum Kopieren klicken",
|
||||||
|
"versionNotAvailable": "Version nicht verfügbar",
|
||||||
"width": "Breite"
|
"width": "Breite"
|
||||||
},
|
},
|
||||||
"toast": {
|
"toast": {
|
||||||
"copyStyles": "Formatierung kopiert.",
|
"copyStyles": "Formatierung kopiert.",
|
||||||
"copyToClipboardAsPng": "In die Zwischenablage als PNG kopiert."
|
"copyToClipboard": "In die Zwischenablage kopiert.",
|
||||||
|
"copyToClipboardAsPng": "In die Zwischenablage als PNG kopiert.",
|
||||||
|
"fileSaved": "Datei gespeichert.",
|
||||||
|
"fileSavedToFilename": "Als {filename} gespeichert"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+18
-10
@@ -68,7 +68,7 @@
|
|||||||
"layers": "Στρώματα",
|
"layers": "Στρώματα",
|
||||||
"actions": "Ενέργειες",
|
"actions": "Ενέργειες",
|
||||||
"language": "Γλώσσα",
|
"language": "Γλώσσα",
|
||||||
"createRoom": "Έναρξη ζωντανής συνεδρίας",
|
"liveCollaboration": "Ζωντανή συνεργασία",
|
||||||
"duplicateSelection": "Δημιουργία αντιγράφου",
|
"duplicateSelection": "Δημιουργία αντιγράφου",
|
||||||
"untitled": "Χωρίς τίτλο",
|
"untitled": "Χωρίς τίτλο",
|
||||||
"name": "Όνομα",
|
"name": "Όνομα",
|
||||||
@@ -77,7 +77,7 @@
|
|||||||
"group": "Δημιουργία ομάδας από επιλογή",
|
"group": "Δημιουργία ομάδας από επιλογή",
|
||||||
"ungroup": "Κατάργηση ομάδας από επιλογή",
|
"ungroup": "Κατάργηση ομάδας από επιλογή",
|
||||||
"collaborators": "Συνεργάτες",
|
"collaborators": "Συνεργάτες",
|
||||||
"gridMode": "Εμφάνιση σε πλέγμα",
|
"showGrid": "Προβολή πλέγματος",
|
||||||
"addToLibrary": "Προσθήκη στη βιβλιοθήκη",
|
"addToLibrary": "Προσθήκη στη βιβλιοθήκη",
|
||||||
"removeFromLibrary": "Αφαίρεση από τη βιβλιοθήκη",
|
"removeFromLibrary": "Αφαίρεση από τη βιβλιοθήκη",
|
||||||
"libraryLoadingMessage": "Φόρτωση βιβλιοθήκης…",
|
"libraryLoadingMessage": "Φόρτωση βιβλιοθήκης…",
|
||||||
@@ -117,13 +117,13 @@
|
|||||||
"edit": "Επεξεργασία",
|
"edit": "Επεξεργασία",
|
||||||
"undo": "Αναίρεση",
|
"undo": "Αναίρεση",
|
||||||
"redo": "Επαναφορά",
|
"redo": "Επαναφορά",
|
||||||
"roomDialog": "Έναρξη ζωντανής συνεργασίας",
|
"resetLibrary": "Καθαρισμός βιβλιοθήκης",
|
||||||
"createNewRoom": "Δημιουργία νέου χώρου",
|
"createNewRoom": "Δημιουργία νέου χώρου",
|
||||||
"fullScreen": "Πλήρης οθόνη",
|
"fullScreen": "Πλήρης οθόνη",
|
||||||
"darkMode": "Σκοτεινή λειτουργία",
|
"darkMode": "Σκοτεινή λειτουργία",
|
||||||
"lightMode": "Φωτεινή λειτουργία",
|
"lightMode": "Φωτεινή λειτουργία",
|
||||||
"zenMode": "Λειτουργία Zεν",
|
"zenMode": "Λειτουργία Zεν",
|
||||||
"exitZenMode": "Έξοδος απο την λειτουργία Zen"
|
"exitZenMode": "Έξοδος από την λειτουργία Zen"
|
||||||
},
|
},
|
||||||
"alerts": {
|
"alerts": {
|
||||||
"clearReset": "Αυτό θα σβήσει ολόκληρο τον καμβά. Είσαι σίγουρος;",
|
"clearReset": "Αυτό θα σβήσει ολόκληρο τον καμβά. Είσαι σίγουρος;",
|
||||||
@@ -136,10 +136,12 @@
|
|||||||
"decryptFailed": "Δεν ήταν δυνατή η αποκρυπτογράφηση δεδομένων.",
|
"decryptFailed": "Δεν ήταν δυνατή η αποκρυπτογράφηση δεδομένων.",
|
||||||
"uploadedSecurly": "Η μεταφόρτωση έχει εξασφαλιστεί με κρυπτογράφηση από άκρο σε άκρο, πράγμα που σημαίνει ότι ο διακομιστής Excalidraw και τρίτα μέρη δεν μπορούν να διαβάσουν το περιεχόμενο.",
|
"uploadedSecurly": "Η μεταφόρτωση έχει εξασφαλιστεί με κρυπτογράφηση από άκρο σε άκρο, πράγμα που σημαίνει ότι ο διακομιστής Excalidraw και τρίτα μέρη δεν μπορούν να διαβάσουν το περιεχόμενο.",
|
||||||
"loadSceneOverridePrompt": "Η φόρτωση εξωτερικού σχεδίου θα αντικαταστήσει το υπάρχον περιεχόμενο. Επιθυμείτε να συνεχίσετε;",
|
"loadSceneOverridePrompt": "Η φόρτωση εξωτερικού σχεδίου θα αντικαταστήσει το υπάρχον περιεχόμενο. Επιθυμείτε να συνεχίσετε;",
|
||||||
|
"collabStopOverridePrompt": "Η διακοπή της συνεδρίας θα αντικαταστήσει το προηγούμενο, τοπικά αποθηκευμένο σχέδιο. Είστε σίγουροι?\n\n(Αν θέλετε να διατηρήσετε το τοπικό σας σχέδιο, απλά κλείστε την καρτέλα του προγράμματος περιήγησης.)",
|
||||||
"errorLoadingLibrary": "Υπήρξε ένα σφάλμα κατά τη φόρτωση της βιβλιοθήκης τρίτου μέρους.",
|
"errorLoadingLibrary": "Υπήρξε ένα σφάλμα κατά τη φόρτωση της βιβλιοθήκης τρίτου μέρους.",
|
||||||
"confirmAddLibrary": "Αυτό θα προσθέσει {{numShapes}} σχήμα(τα) στη βιβιλιοθήκη σας. Είστε σίγουροι;",
|
"confirmAddLibrary": "Αυτό θα προσθέσει {{numShapes}} σχήμα(τα) στη βιβλιοθήκη σας. Είστε σίγουροι;",
|
||||||
"imageDoesNotContainScene": "Η εισαγωγή εικόνων δεν υποστηρίζεται αυτή τη στιγμή.\n\nΜήπως θέλετε να εισαγάγετε μια σκηνή; Αυτή η εικόνα δεν φαίνεται να περιέχει δεδομένα σκηνής. Έχετε ενεργοποιήσει αυτό κατά την εξαγωγή;",
|
"imageDoesNotContainScene": "Η εισαγωγή εικόνων δεν υποστηρίζεται αυτή τη στιγμή.\n\nΜήπως θέλετε να εισαγάγετε μια σκηνή; Αυτή η εικόνα δεν φαίνεται να περιέχει δεδομένα σκηνής. Έχετε ενεργοποιήσει αυτό κατά την εξαγωγή;",
|
||||||
"cannotRestoreFromImage": "Η σκηνή δεν ήταν δυνατό να αποκατασταθεί από αυτό το αρχείο εικόνας"
|
"cannotRestoreFromImage": "Η σκηνή δεν ήταν δυνατό να αποκατασταθεί από αυτό το αρχείο εικόνας",
|
||||||
|
"resetLibrary": "Αυτό θα καθαρίσει τη βιβλιοθήκη σας. Είστε σίγουροι;"
|
||||||
},
|
},
|
||||||
"toolBar": {
|
"toolBar": {
|
||||||
"selection": "Επιλογή",
|
"selection": "Επιλογή",
|
||||||
@@ -160,7 +162,7 @@
|
|||||||
},
|
},
|
||||||
"hints": {
|
"hints": {
|
||||||
"linearElement": "Κάνε κλικ για να ξεκινήσεις πολλαπλά σημεία, σύρε για μια γραμμή",
|
"linearElement": "Κάνε κλικ για να ξεκινήσεις πολλαπλά σημεία, σύρε για μια γραμμή",
|
||||||
"freeDraw": "Κάντε κλικ και σύρατε, απελευθερώσατε όταν έχετε τελειώσει",
|
"freeDraw": "Κάντε κλικ και σύρτε, απελευθερώσατε όταν έχετε τελειώσει",
|
||||||
"text": "Tip: μπορείτε επίσης να προσθέστε κείμενο με διπλό-κλικ οπουδήποτε με το εργαλείο επιλογών",
|
"text": "Tip: μπορείτε επίσης να προσθέστε κείμενο με διπλό-κλικ οπουδήποτε με το εργαλείο επιλογών",
|
||||||
"linearElementMulti": "Κάνε κλικ στο τελευταίο σημείο ή πάτησε Escape ή Enter για να τελειώσεις",
|
"linearElementMulti": "Κάνε κλικ στο τελευταίο σημείο ή πάτησε Escape ή Enter για να τελειώσεις",
|
||||||
"lockAngle": "Μπορείτε να περιορίσετε τη γωνία κρατώντας πατημένο το SHIFT",
|
"lockAngle": "Μπορείτε να περιορίσετε τη γωνία κρατώντας πατημένο το SHIFT",
|
||||||
@@ -183,7 +185,7 @@
|
|||||||
"clearCanvasCaveat": " Αυτό θα προκαλέσει απώλεια της δουλειάς σου ",
|
"clearCanvasCaveat": " Αυτό θα προκαλέσει απώλεια της δουλειάς σου ",
|
||||||
"trackedToSentry_pre": "Το σφάλμα με αναγνωριστικό ",
|
"trackedToSentry_pre": "Το σφάλμα με αναγνωριστικό ",
|
||||||
"trackedToSentry_post": " παρακολουθήθηκε στο σύστημά μας.",
|
"trackedToSentry_post": " παρακολουθήθηκε στο σύστημά μας.",
|
||||||
"openIssueMessage_pre": "Ήμασταν πολύ προσεκτικοί για να μην συμπεριλάβουμε τις πληροφορίες της σκηνής σου στο σφάλμα. Αν η σκηνή σου δεν είναι ιδιωτική, παρακαλώ σκέψουν να ακολουθήσεις το δικό μας ",
|
"openIssueMessage_pre": "Ήμασταν πολύ προσεκτικοί για να μην συμπεριλάβουμε τις πληροφορίες της σκηνής σου στο σφάλμα. Αν η σκηνή σου δεν είναι ιδιωτική, παρακαλώ σκέψου να ακολουθήσεις το δικό μας ",
|
||||||
"openIssueMessage_button": "ανιχνευτής σφαλμάτων.",
|
"openIssueMessage_button": "ανιχνευτής σφαλμάτων.",
|
||||||
"openIssueMessage_post": " Παρακαλώ να συμπεριλάβετε τις παρακάτω πληροφορίες, αντιγράφοντας και επικολλώντας το ζήτημα στο GitHub.",
|
"openIssueMessage_post": " Παρακαλώ να συμπεριλάβετε τις παρακάτω πληροφορίες, αντιγράφοντας και επικολλώντας το ζήτημα στο GitHub.",
|
||||||
"sceneContent": "Περιεχόμενο σκηνής:"
|
"sceneContent": "Περιεχόμενο σκηνής:"
|
||||||
@@ -222,7 +224,7 @@
|
|||||||
"zoomToSelection": "Ζουμ στην επιλογή"
|
"zoomToSelection": "Ζουμ στην επιλογή"
|
||||||
},
|
},
|
||||||
"encrypted": {
|
"encrypted": {
|
||||||
"tooltip": "Τα σχέδιά σου είναι κρυπτογραφημένα από άκρο σε άκρο, έτσι δεν θα έιναι ποτέ ορατά μέσα από τους διακομιστές του Excalidraw."
|
"tooltip": "Τα σχέδιά σου είναι κρυπτογραφημένα από άκρο σε άκρο, έτσι δεν θα είναι ποτέ ορατά μέσα από τους διακομιστές του Excalidraw."
|
||||||
},
|
},
|
||||||
"stats": {
|
"stats": {
|
||||||
"angle": "Γωνία",
|
"angle": "Γωνία",
|
||||||
@@ -234,10 +236,16 @@
|
|||||||
"storage": "Χώρος",
|
"storage": "Χώρος",
|
||||||
"title": "Στατιστικά για σπασίκλες",
|
"title": "Στατιστικά για σπασίκλες",
|
||||||
"total": "Σύνολο ",
|
"total": "Σύνολο ",
|
||||||
|
"version": "Έκδοση",
|
||||||
|
"versionCopy": "Κάνε κλικ για αντιγραφή",
|
||||||
|
"versionNotAvailable": "Έκδοση μη διαθέσιμη",
|
||||||
"width": "Πλάτος"
|
"width": "Πλάτος"
|
||||||
},
|
},
|
||||||
"toast": {
|
"toast": {
|
||||||
"copyStyles": "Αντιγράφηκαν στυλ.",
|
"copyStyles": "Αντιγράφηκαν στυλ.",
|
||||||
"copyToClipboardAsPng": "Αντιγράφτηκε στο πρόχειρο ως PNG."
|
"copyToClipboard": "Αντιγράφηκε στο πρόχειρο.",
|
||||||
|
"copyToClipboardAsPng": "Αντιγράφτηκε στο πρόχειρο ως PNG.",
|
||||||
|
"fileSaved": "Το αρχείο αποθηκεύτηκε.",
|
||||||
|
"fileSavedToFilename": "Αποθηκεύτηκε στο {filename}"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+15
-6
@@ -68,7 +68,7 @@
|
|||||||
"layers": "Layers",
|
"layers": "Layers",
|
||||||
"actions": "Actions",
|
"actions": "Actions",
|
||||||
"language": "Language",
|
"language": "Language",
|
||||||
"createRoom": "Share a live-collaboration session",
|
"liveCollaboration": "Live collaboration",
|
||||||
"duplicateSelection": "Duplicate",
|
"duplicateSelection": "Duplicate",
|
||||||
"untitled": "Untitled",
|
"untitled": "Untitled",
|
||||||
"name": "Name",
|
"name": "Name",
|
||||||
@@ -77,7 +77,7 @@
|
|||||||
"group": "Group selection",
|
"group": "Group selection",
|
||||||
"ungroup": "Ungroup selection",
|
"ungroup": "Ungroup selection",
|
||||||
"collaborators": "Collaborators",
|
"collaborators": "Collaborators",
|
||||||
"gridMode": "Grid mode",
|
"showGrid": "Show grid",
|
||||||
"addToLibrary": "Add to library",
|
"addToLibrary": "Add to library",
|
||||||
"removeFromLibrary": "Remove from library",
|
"removeFromLibrary": "Remove from library",
|
||||||
"libraryLoadingMessage": "Loading library…",
|
"libraryLoadingMessage": "Loading library…",
|
||||||
@@ -92,7 +92,8 @@
|
|||||||
"centerHorizontally": "Center horizontally",
|
"centerHorizontally": "Center horizontally",
|
||||||
"distributeHorizontally": "Distribute horizontally",
|
"distributeHorizontally": "Distribute horizontally",
|
||||||
"distributeVertically": "Distribute vertically",
|
"distributeVertically": "Distribute vertically",
|
||||||
"viewMode": "View mode"
|
"viewMode": "View mode",
|
||||||
|
"toggleExportColorScheme": "Toggle export color scheme"
|
||||||
},
|
},
|
||||||
"buttons": {
|
"buttons": {
|
||||||
"clearReset": "Reset the canvas",
|
"clearReset": "Reset the canvas",
|
||||||
@@ -117,7 +118,7 @@
|
|||||||
"edit": "Edit",
|
"edit": "Edit",
|
||||||
"undo": "Undo",
|
"undo": "Undo",
|
||||||
"redo": "Redo",
|
"redo": "Redo",
|
||||||
"roomDialog": "Start live collaboration",
|
"resetLibrary": "Reset library",
|
||||||
"createNewRoom": "Create new room",
|
"createNewRoom": "Create new room",
|
||||||
"fullScreen": "Full screen",
|
"fullScreen": "Full screen",
|
||||||
"darkMode": "Dark mode",
|
"darkMode": "Dark mode",
|
||||||
@@ -136,10 +137,12 @@
|
|||||||
"decryptFailed": "Couldn't decrypt data.",
|
"decryptFailed": "Couldn't decrypt data.",
|
||||||
"uploadedSecurly": "The upload has been secured with end-to-end encryption, which means that Excalidraw server and third parties can't read the content.",
|
"uploadedSecurly": "The upload has been secured with end-to-end encryption, which means that Excalidraw server and third parties can't read the content.",
|
||||||
"loadSceneOverridePrompt": "Loading external drawing will replace your existing content. Do you wish to continue?",
|
"loadSceneOverridePrompt": "Loading external drawing will replace your existing content. Do you wish to continue?",
|
||||||
|
"collabStopOverridePrompt": "Stopping the session will overwrite your previous, locally stored drawing. Are you sure?\n\n(If you want to keep your local drawing, simply close the browser tab instead.)",
|
||||||
"errorLoadingLibrary": "There was an error loading the third party library.",
|
"errorLoadingLibrary": "There was an error loading the third party library.",
|
||||||
"confirmAddLibrary": "This will add {{numShapes}} shape(s) to your library. Are you sure?",
|
"confirmAddLibrary": "This will add {{numShapes}} shape(s) to your library. Are you sure?",
|
||||||
"imageDoesNotContainScene": "Importing images isn't supported at the moment.\n\nDid you want to import a scene? This image does not seem to contain any scene data. Have you enabled this during export?",
|
"imageDoesNotContainScene": "Importing images isn't supported at the moment.\n\nDid you want to import a scene? This image does not seem to contain any scene data. Have you enabled this during export?",
|
||||||
"cannotRestoreFromImage": "Scene couldn't be restored from this image file"
|
"cannotRestoreFromImage": "Scene couldn't be restored from this image file",
|
||||||
|
"resetLibrary": "This will clear your library. Are you sure?"
|
||||||
},
|
},
|
||||||
"toolBar": {
|
"toolBar": {
|
||||||
"selection": "Selection",
|
"selection": "Selection",
|
||||||
@@ -234,10 +237,16 @@
|
|||||||
"storage": "Storage",
|
"storage": "Storage",
|
||||||
"title": "Stats for nerds",
|
"title": "Stats for nerds",
|
||||||
"total": "Total",
|
"total": "Total",
|
||||||
|
"version": "Version",
|
||||||
|
"versionCopy": "Click to copy",
|
||||||
|
"versionNotAvailable": "Version not available",
|
||||||
"width": "Width"
|
"width": "Width"
|
||||||
},
|
},
|
||||||
"toast": {
|
"toast": {
|
||||||
"copyStyles": "Copied styles.",
|
"copyStyles": "Copied styles.",
|
||||||
"copyToClipboardAsPng": "Copied to clipboard as PNG."
|
"copyToClipboard": "Copied to clipboard.",
|
||||||
|
"copyToClipboardAsPng": "Copied to clipboard as PNG.",
|
||||||
|
"fileSaved": "File saved.",
|
||||||
|
"fileSavedToFilename": "Saved to {filename}"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+15
-7
@@ -68,7 +68,7 @@
|
|||||||
"layers": "Capas",
|
"layers": "Capas",
|
||||||
"actions": "Acciones",
|
"actions": "Acciones",
|
||||||
"language": "Idioma",
|
"language": "Idioma",
|
||||||
"createRoom": "Compartir una sesión de colaboración en vivo",
|
"liveCollaboration": "Colaboración en directo",
|
||||||
"duplicateSelection": "Duplicar",
|
"duplicateSelection": "Duplicar",
|
||||||
"untitled": "Sin título",
|
"untitled": "Sin título",
|
||||||
"name": "Nombre",
|
"name": "Nombre",
|
||||||
@@ -77,12 +77,12 @@
|
|||||||
"group": "Agrupar selección",
|
"group": "Agrupar selección",
|
||||||
"ungroup": "Desagrupar selección",
|
"ungroup": "Desagrupar selección",
|
||||||
"collaborators": "Colaboradores",
|
"collaborators": "Colaboradores",
|
||||||
"gridMode": "Modo cuadrícula",
|
"showGrid": "Mostrar cuadrícula",
|
||||||
"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 librería…",
|
||||||
"libraries": "Explorar bibliotecas",
|
"libraries": "Explorar bibliotecas",
|
||||||
"loadingScene": "Cargando escena...",
|
"loadingScene": "Cargando escena…",
|
||||||
"align": "Alinear",
|
"align": "Alinear",
|
||||||
"alignTop": "Alineación superior",
|
"alignTop": "Alineación superior",
|
||||||
"alignBottom": "Alineación inferior",
|
"alignBottom": "Alineación inferior",
|
||||||
@@ -117,7 +117,7 @@
|
|||||||
"edit": "Editar",
|
"edit": "Editar",
|
||||||
"undo": "Deshacer",
|
"undo": "Deshacer",
|
||||||
"redo": "Rehacer",
|
"redo": "Rehacer",
|
||||||
"roomDialog": "Iniciar colaboración en vivo",
|
"resetLibrary": "Resetear librería",
|
||||||
"createNewRoom": "Crear nueva sala",
|
"createNewRoom": "Crear nueva sala",
|
||||||
"fullScreen": "Pantalla completa",
|
"fullScreen": "Pantalla completa",
|
||||||
"darkMode": "Modo oscuro",
|
"darkMode": "Modo oscuro",
|
||||||
@@ -136,10 +136,12 @@
|
|||||||
"decryptFailed": "No se pudieron descifrar los datos.",
|
"decryptFailed": "No se pudieron descifrar los datos.",
|
||||||
"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.",
|
"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?",
|
||||||
|
"collabStopOverridePrompt": "Detener la sesión sobrescribirá su dibujo anterior almacenado en local. ¿Estás seguro?\n\n(Si quieres mantener tu dibujo en local, simplemente cierre la pestaña del navegador en su lugar.)",
|
||||||
"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": "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?",
|
"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": "No se pudo restaurar la escena desde este archivo de imagen"
|
"cannotRestoreFromImage": "No se pudo restaurar la escena desde este archivo de imagen",
|
||||||
|
"resetLibrary": "Esto eliminará tu librería. ¿Estás seguro?"
|
||||||
},
|
},
|
||||||
"toolBar": {
|
"toolBar": {
|
||||||
"selection": "Selección",
|
"selection": "Selección",
|
||||||
@@ -234,10 +236,16 @@
|
|||||||
"storage": "Almacenamiento",
|
"storage": "Almacenamiento",
|
||||||
"title": "Estadísticas para nerds",
|
"title": "Estadísticas para nerds",
|
||||||
"total": "Total",
|
"total": "Total",
|
||||||
|
"version": "Versión",
|
||||||
|
"versionCopy": "Clic para copiar",
|
||||||
|
"versionNotAvailable": "Versión no disponible",
|
||||||
"width": "Ancho"
|
"width": "Ancho"
|
||||||
},
|
},
|
||||||
"toast": {
|
"toast": {
|
||||||
"copyStyles": "Estilos copiados.",
|
"copyStyles": "Estilos copiados.",
|
||||||
"copyToClipboardAsPng": "Copiado al portapapeles como PNG."
|
"copyToClipboard": "Copiado en el portapapeles.",
|
||||||
|
"copyToClipboardAsPng": "Copiado al portapapeles como PNG.",
|
||||||
|
"fileSaved": "Archivo guardado.",
|
||||||
|
"fileSavedToFilename": "Guardado en {filename}"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+13
-5
@@ -68,7 +68,7 @@
|
|||||||
"layers": "لایه ها",
|
"layers": "لایه ها",
|
||||||
"actions": "عملیات",
|
"actions": "عملیات",
|
||||||
"language": "زبان",
|
"language": "زبان",
|
||||||
"createRoom": "اشتراک گذاری جلسه همکاری زنده",
|
"liveCollaboration": "",
|
||||||
"duplicateSelection": "تکرار",
|
"duplicateSelection": "تکرار",
|
||||||
"untitled": "بدون عنوان",
|
"untitled": "بدون عنوان",
|
||||||
"name": "نام",
|
"name": "نام",
|
||||||
@@ -77,7 +77,7 @@
|
|||||||
"group": "گروهبندی انتخابها",
|
"group": "گروهبندی انتخابها",
|
||||||
"ungroup": "حذف گروهبندی انتخابها",
|
"ungroup": "حذف گروهبندی انتخابها",
|
||||||
"collaborators": "همکاران",
|
"collaborators": "همکاران",
|
||||||
"gridMode": "حالت شبکه ای",
|
"showGrid": "",
|
||||||
"addToLibrary": "افزودن به کتابخانه",
|
"addToLibrary": "افزودن به کتابخانه",
|
||||||
"removeFromLibrary": "حذف از کتابخانه",
|
"removeFromLibrary": "حذف از کتابخانه",
|
||||||
"libraryLoadingMessage": "بارگذاری کتابخانه…",
|
"libraryLoadingMessage": "بارگذاری کتابخانه…",
|
||||||
@@ -117,7 +117,7 @@
|
|||||||
"edit": "ویرایش",
|
"edit": "ویرایش",
|
||||||
"undo": "بازگرد",
|
"undo": "بازگرد",
|
||||||
"redo": "از سر",
|
"redo": "از سر",
|
||||||
"roomDialog": "همکاری آنلاین را شروع کنید",
|
"resetLibrary": "",
|
||||||
"createNewRoom": "ایجاد یک اتاق جدید",
|
"createNewRoom": "ایجاد یک اتاق جدید",
|
||||||
"fullScreen": "تمامصفحه",
|
"fullScreen": "تمامصفحه",
|
||||||
"darkMode": "حالت تیره",
|
"darkMode": "حالت تیره",
|
||||||
@@ -136,10 +136,12 @@
|
|||||||
"decryptFailed": "رمزگشایی داده ها امکان پذیر نیست.",
|
"decryptFailed": "رمزگشایی داده ها امکان پذیر نیست.",
|
||||||
"uploadedSecurly": "آپلود با رمزگذاری دو طرفه انجام میشود، به این معنی که سرور Excalidraw و اشخاص ثالث نمی توانند مطالب شما را بخوانند.",
|
"uploadedSecurly": "آپلود با رمزگذاری دو طرفه انجام میشود، به این معنی که سرور Excalidraw و اشخاص ثالث نمی توانند مطالب شما را بخوانند.",
|
||||||
"loadSceneOverridePrompt": "بارگزاری یک طرح خارجی محتوای فعلی رو از بین میبرد. آیا میخواهید ادامه دهید؟",
|
"loadSceneOverridePrompt": "بارگزاری یک طرح خارجی محتوای فعلی رو از بین میبرد. آیا میخواهید ادامه دهید؟",
|
||||||
|
"collabStopOverridePrompt": "",
|
||||||
"errorLoadingLibrary": "خطایی در بارگذاری کتابخانه ثالث وجود داشت.",
|
"errorLoadingLibrary": "خطایی در بارگذاری کتابخانه ثالث وجود داشت.",
|
||||||
"confirmAddLibrary": "{{numShapes}} از اشکال به کتابخانه شما اضافه خواهد شد. مطمئن هستید؟",
|
"confirmAddLibrary": "{{numShapes}} از اشکال به کتابخانه شما اضافه خواهد شد. مطمئن هستید؟",
|
||||||
"imageDoesNotContainScene": "وارد کردن تصویر در این لحظه امکان پذیر نمی باشد.\nآیا مایل به وارد کردن یک صحنه هستید؟ این تصویر به نظر می رسد که فاقد هرگونه اطلاعاتی مربوط به صحنه باشد. آیا این گزینه را در زمان وارد کردن تصویر فعال کرده اید؟",
|
"imageDoesNotContainScene": "وارد کردن تصویر در این لحظه امکان پذیر نمی باشد.\nآیا مایل به وارد کردن یک صحنه هستید؟ این تصویر به نظر می رسد که فاقد هرگونه اطلاعاتی مربوط به صحنه باشد. آیا این گزینه را در زمان وارد کردن تصویر فعال کرده اید؟",
|
||||||
"cannotRestoreFromImage": "صحنه را نمی توان از این فایل تصویری بازیابی کرد"
|
"cannotRestoreFromImage": "صحنه را نمی توان از این فایل تصویری بازیابی کرد",
|
||||||
|
"resetLibrary": ""
|
||||||
},
|
},
|
||||||
"toolBar": {
|
"toolBar": {
|
||||||
"selection": "گزینش",
|
"selection": "گزینش",
|
||||||
@@ -234,10 +236,16 @@
|
|||||||
"storage": "حافظه",
|
"storage": "حافظه",
|
||||||
"title": "آمار برای نردها",
|
"title": "آمار برای نردها",
|
||||||
"total": "مجموع",
|
"total": "مجموع",
|
||||||
|
"version": "",
|
||||||
|
"versionCopy": "",
|
||||||
|
"versionNotAvailable": "",
|
||||||
"width": "عرض"
|
"width": "عرض"
|
||||||
},
|
},
|
||||||
"toast": {
|
"toast": {
|
||||||
"copyStyles": "کپی سبک.",
|
"copyStyles": "کپی سبک.",
|
||||||
"copyToClipboardAsPng": "کپی در حافطه موقت به صورت PNG."
|
"copyToClipboard": "",
|
||||||
|
"copyToClipboardAsPng": "کپی در حافطه موقت به صورت PNG.",
|
||||||
|
"fileSaved": "",
|
||||||
|
"fileSavedToFilename": ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+13
-5
@@ -68,7 +68,7 @@
|
|||||||
"layers": "Tasot",
|
"layers": "Tasot",
|
||||||
"actions": "Toiminnot",
|
"actions": "Toiminnot",
|
||||||
"language": "Kieli",
|
"language": "Kieli",
|
||||||
"createRoom": "Jaa yhteistyöistunto",
|
"liveCollaboration": "Live-yhteistyö",
|
||||||
"duplicateSelection": "Monista",
|
"duplicateSelection": "Monista",
|
||||||
"untitled": "Nimetön",
|
"untitled": "Nimetön",
|
||||||
"name": "Nimi",
|
"name": "Nimi",
|
||||||
@@ -77,7 +77,7 @@
|
|||||||
"group": "Ryhmitä valinta",
|
"group": "Ryhmitä valinta",
|
||||||
"ungroup": "Pura valittu ryhmä",
|
"ungroup": "Pura valittu ryhmä",
|
||||||
"collaborators": "Yhteistyökumppanit",
|
"collaborators": "Yhteistyökumppanit",
|
||||||
"gridMode": "Ruudukkotila",
|
"showGrid": "Näytä ruudukko",
|
||||||
"addToLibrary": "Lisää kirjastoon",
|
"addToLibrary": "Lisää kirjastoon",
|
||||||
"removeFromLibrary": "Poista kirjastosta",
|
"removeFromLibrary": "Poista kirjastosta",
|
||||||
"libraryLoadingMessage": "Ladataan kirjastoa…",
|
"libraryLoadingMessage": "Ladataan kirjastoa…",
|
||||||
@@ -117,7 +117,7 @@
|
|||||||
"edit": "Muokkaa",
|
"edit": "Muokkaa",
|
||||||
"undo": "Kumoa",
|
"undo": "Kumoa",
|
||||||
"redo": "Tee uudelleen",
|
"redo": "Tee uudelleen",
|
||||||
"roomDialog": "Aloita live-yhteistyö",
|
"resetLibrary": "Tyhjennä kirjasto",
|
||||||
"createNewRoom": "Luo huone",
|
"createNewRoom": "Luo huone",
|
||||||
"fullScreen": "Koko näyttö",
|
"fullScreen": "Koko näyttö",
|
||||||
"darkMode": "Tumma tila",
|
"darkMode": "Tumma tila",
|
||||||
@@ -136,10 +136,12 @@
|
|||||||
"decryptFailed": "Salauksen purkaminen epäonnistui.",
|
"decryptFailed": "Salauksen purkaminen epäonnistui.",
|
||||||
"uploadedSecurly": "Lähetys on turvattu päästä päähän salauksella. Excalidrawin palvelin ja kolmannet osapuolet eivät voi lukea sisältöä.",
|
"uploadedSecurly": "Lähetys on turvattu päästä päähän salauksella. Excalidrawin palvelin ja kolmannet osapuolet eivät voi lukea sisältöä.",
|
||||||
"loadSceneOverridePrompt": "Ulkopuolisen piirroksen lataaminen korvaa nykyisen sisältösi. Haluatko jatkaa?",
|
"loadSceneOverridePrompt": "Ulkopuolisen piirroksen lataaminen korvaa nykyisen sisältösi. Haluatko jatkaa?",
|
||||||
|
"collabStopOverridePrompt": "Istunnon lopettaminen korvaa aiemman, paikallisesti tallennetun piirustuksen. Oletko varma?\n\n(Jos haluat pitää paikallisen piirustuksen, sulje selaimen välilehti sen sijaan.)",
|
||||||
"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": "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?",
|
"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",
|
||||||
|
"resetLibrary": "Tämä tyhjentää kirjastosi. Oletko varma?"
|
||||||
},
|
},
|
||||||
"toolBar": {
|
"toolBar": {
|
||||||
"selection": "Valinta",
|
"selection": "Valinta",
|
||||||
@@ -234,10 +236,16 @@
|
|||||||
"storage": "Tallennustila",
|
"storage": "Tallennustila",
|
||||||
"title": "Nörttien tilastot",
|
"title": "Nörttien tilastot",
|
||||||
"total": "Yhteensä",
|
"total": "Yhteensä",
|
||||||
|
"version": "Versio",
|
||||||
|
"versionCopy": "Klikkaa kopioidaksesi",
|
||||||
|
"versionNotAvailable": "Versio ei saatavilla",
|
||||||
"width": "Leveys"
|
"width": "Leveys"
|
||||||
},
|
},
|
||||||
"toast": {
|
"toast": {
|
||||||
"copyStyles": "Tyylit kopioitu.",
|
"copyStyles": "Tyylit kopioitu.",
|
||||||
"copyToClipboardAsPng": "Kopioitu leikepöydälle PNG-tiedostona."
|
"copyToClipboard": "Kopioitu leikepöydälle.",
|
||||||
|
"copyToClipboardAsPng": "Kopioitu leikepöydälle PNG-tiedostona.",
|
||||||
|
"fileSaved": "Tiedosto tallennettu.",
|
||||||
|
"fileSavedToFilename": "Tallennettu kohteeseen {filename}"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+15
-7
@@ -68,7 +68,7 @@
|
|||||||
"layers": "Calques",
|
"layers": "Calques",
|
||||||
"actions": "Actions",
|
"actions": "Actions",
|
||||||
"language": "Langue",
|
"language": "Langue",
|
||||||
"createRoom": "Partager une session de collaboration en direct",
|
"liveCollaboration": "Collaboration en direct",
|
||||||
"duplicateSelection": "Dupliquer",
|
"duplicateSelection": "Dupliquer",
|
||||||
"untitled": "Sans-titre",
|
"untitled": "Sans-titre",
|
||||||
"name": "Nom",
|
"name": "Nom",
|
||||||
@@ -77,12 +77,12 @@
|
|||||||
"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",
|
||||||
"gridMode": "Mode grille",
|
"showGrid": "Afficher la grille",
|
||||||
"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": "Parcourir 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",
|
||||||
"alignBottom": "Aligner en bas",
|
"alignBottom": "Aligner en bas",
|
||||||
@@ -117,7 +117,7 @@
|
|||||||
"edit": "Modifier",
|
"edit": "Modifier",
|
||||||
"undo": "Annuler",
|
"undo": "Annuler",
|
||||||
"redo": "Rétablir",
|
"redo": "Rétablir",
|
||||||
"roomDialog": "Démarrer la collaboration en direct",
|
"resetLibrary": "Réinitialiser la bibliothèque",
|
||||||
"createNewRoom": "Créer une nouvelle salle",
|
"createNewRoom": "Créer une nouvelle salle",
|
||||||
"fullScreen": "Plein écran",
|
"fullScreen": "Plein écran",
|
||||||
"darkMode": "Mode sombre",
|
"darkMode": "Mode sombre",
|
||||||
@@ -136,10 +136,12 @@
|
|||||||
"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 ?",
|
||||||
|
"collabStopOverridePrompt": "Arrêter la session écrasera votre précédent dessin stocké localement. Êtes-vous sûr·e ?\n\n(Si vous voulez garder votre dessin local, fermez simplement l'onglet du navigateur à la place.)",
|
||||||
"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": "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 ?",
|
"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",
|
||||||
|
"resetLibrary": "Cela va effacer votre bibliothèque. Êtes-vous sûr·e ?"
|
||||||
},
|
},
|
||||||
"toolBar": {
|
"toolBar": {
|
||||||
"selection": "Sélection",
|
"selection": "Sélection",
|
||||||
@@ -234,10 +236,16 @@
|
|||||||
"storage": "Stockage",
|
"storage": "Stockage",
|
||||||
"title": "Stats pour les nerds",
|
"title": "Stats pour les nerds",
|
||||||
"total": "Total",
|
"total": "Total",
|
||||||
|
"version": "Version",
|
||||||
|
"versionCopy": "Cliquer pour copier",
|
||||||
|
"versionNotAvailable": "Version non disponible",
|
||||||
"width": "Largeur"
|
"width": "Largeur"
|
||||||
},
|
},
|
||||||
"toast": {
|
"toast": {
|
||||||
"copyStyles": "Styles copiés.",
|
"copyStyles": "Styles copiés.",
|
||||||
"copyToClipboardAsPng": "Copié vers le presse-papier en PNG."
|
"copyToClipboard": "Copié vers le presse-papiers.",
|
||||||
|
"copyToClipboardAsPng": "Copié vers le presse-papier en PNG.",
|
||||||
|
"fileSaved": "Fichier enregistré.",
|
||||||
|
"fileSavedToFilename": "Enregistré sous {filename}"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+13
-5
@@ -68,7 +68,7 @@
|
|||||||
"layers": "שכבות",
|
"layers": "שכבות",
|
||||||
"actions": "פעולות",
|
"actions": "פעולות",
|
||||||
"language": "שפה",
|
"language": "שפה",
|
||||||
"createRoom": "התחל שיתוף פעולה חי",
|
"liveCollaboration": "",
|
||||||
"duplicateSelection": "שכפל",
|
"duplicateSelection": "שכפל",
|
||||||
"untitled": "ללא כותרת",
|
"untitled": "ללא כותרת",
|
||||||
"name": "שם",
|
"name": "שם",
|
||||||
@@ -77,7 +77,7 @@
|
|||||||
"group": "אחד לקבוצה",
|
"group": "אחד לקבוצה",
|
||||||
"ungroup": "פרק קבוצה",
|
"ungroup": "פרק קבוצה",
|
||||||
"collaborators": "שותפים",
|
"collaborators": "שותפים",
|
||||||
"gridMode": "מצב רשת",
|
"showGrid": "",
|
||||||
"addToLibrary": "הוסף לספריה",
|
"addToLibrary": "הוסף לספריה",
|
||||||
"removeFromLibrary": "הסר מספריה",
|
"removeFromLibrary": "הסר מספריה",
|
||||||
"libraryLoadingMessage": "טוען ספריה…",
|
"libraryLoadingMessage": "טוען ספריה…",
|
||||||
@@ -117,7 +117,7 @@
|
|||||||
"edit": "ערוך",
|
"edit": "ערוך",
|
||||||
"undo": "בטל",
|
"undo": "בטל",
|
||||||
"redo": "בצע מחדש",
|
"redo": "בצע מחדש",
|
||||||
"roomDialog": "התחל שיתוף חי",
|
"resetLibrary": "",
|
||||||
"createNewRoom": "צור חדר",
|
"createNewRoom": "צור חדר",
|
||||||
"fullScreen": "מסך מלא",
|
"fullScreen": "מסך מלא",
|
||||||
"darkMode": "מצב כהה",
|
"darkMode": "מצב כהה",
|
||||||
@@ -136,10 +136,12 @@
|
|||||||
"decryptFailed": "לא ניתן לפענח מידע.",
|
"decryptFailed": "לא ניתן לפענח מידע.",
|
||||||
"uploadedSecurly": "ההעלאה הוצפנה מקצה לקצה, ולכן שרת Excalidraw וצד שלישי לא יכולים לקרוא את התוכן.",
|
"uploadedSecurly": "ההעלאה הוצפנה מקצה לקצה, ולכן שרת Excalidraw וצד שלישי לא יכולים לקרוא את התוכן.",
|
||||||
"loadSceneOverridePrompt": "טעינה של ציור חיצוני תחליף את התוכן הקיים שלך. האם תרצה להמשיך?",
|
"loadSceneOverridePrompt": "טעינה של ציור חיצוני תחליף את התוכן הקיים שלך. האם תרצה להמשיך?",
|
||||||
|
"collabStopOverridePrompt": "",
|
||||||
"errorLoadingLibrary": "קרתה שגיאה בטעינת הספריה החיצונית.",
|
"errorLoadingLibrary": "קרתה שגיאה בטעינת הספריה החיצונית.",
|
||||||
"confirmAddLibrary": "הפעולה תוסיף {{numShapes}} צורה(ות) לספריה שלך. האם אתה בטוח?",
|
"confirmAddLibrary": "הפעולה תוסיף {{numShapes}} צורה(ות) לספריה שלך. האם אתה בטוח?",
|
||||||
"imageDoesNotContainScene": "אין תמיכה בייבוא תמונות כעת.\n\nהאם אתה רוצה לייבא תצוגה? התמונה הזאת אינה מכילה מידע על תצוגה. האם הפעלת את האפשרות הזאת בזמן הוצאת המידע?",
|
"imageDoesNotContainScene": "אין תמיכה בייבוא תמונות כעת.\n\nהאם אתה רוצה לייבא תצוגה? התמונה הזאת אינה מכילה מידע על תצוגה. האם הפעלת את האפשרות הזאת בזמן הוצאת המידע?",
|
||||||
"cannotRestoreFromImage": "לא הצלחנו לשחזר את התצוגה מקובץ התמונה"
|
"cannotRestoreFromImage": "לא הצלחנו לשחזר את התצוגה מקובץ התמונה",
|
||||||
|
"resetLibrary": ""
|
||||||
},
|
},
|
||||||
"toolBar": {
|
"toolBar": {
|
||||||
"selection": "בחירה",
|
"selection": "בחירה",
|
||||||
@@ -234,10 +236,16 @@
|
|||||||
"storage": "אחסון",
|
"storage": "אחסון",
|
||||||
"title": "סטטיסטיקות לחנונים",
|
"title": "סטטיסטיקות לחנונים",
|
||||||
"total": "סה״כ",
|
"total": "סה״כ",
|
||||||
|
"version": "",
|
||||||
|
"versionCopy": "",
|
||||||
|
"versionNotAvailable": "",
|
||||||
"width": "רוחב"
|
"width": "רוחב"
|
||||||
},
|
},
|
||||||
"toast": {
|
"toast": {
|
||||||
"copyStyles": "",
|
"copyStyles": "",
|
||||||
"copyToClipboardAsPng": ""
|
"copyToClipboard": "",
|
||||||
|
"copyToClipboardAsPng": "",
|
||||||
|
"fileSaved": "",
|
||||||
|
"fileSavedToFilename": ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+32
-24
@@ -68,7 +68,7 @@
|
|||||||
"layers": "परतें",
|
"layers": "परतें",
|
||||||
"actions": "कार्रवाई",
|
"actions": "कार्रवाई",
|
||||||
"language": "भाषा",
|
"language": "भाषा",
|
||||||
"createRoom": "अधिवेशन",
|
"liveCollaboration": "",
|
||||||
"duplicateSelection": "डुप्लिकेट",
|
"duplicateSelection": "डुप्लिकेट",
|
||||||
"untitled": "अशीर्षित",
|
"untitled": "अशीर्षित",
|
||||||
"name": "नाम",
|
"name": "नाम",
|
||||||
@@ -77,7 +77,7 @@
|
|||||||
"group": "समूह चयन",
|
"group": "समूह चयन",
|
||||||
"ungroup": "समूह चयन असमूहीकृत करें",
|
"ungroup": "समूह चयन असमूहीकृत करें",
|
||||||
"collaborators": "सहयोगी",
|
"collaborators": "सहयोगी",
|
||||||
"gridMode": "ग्रिड मॉड",
|
"showGrid": "",
|
||||||
"addToLibrary": "लाइब्रेरी से जोड़ें",
|
"addToLibrary": "लाइब्रेरी से जोड़ें",
|
||||||
"removeFromLibrary": "लाइब्रेरी से निकालें",
|
"removeFromLibrary": "लाइब्रेरी से निकालें",
|
||||||
"libraryLoadingMessage": "लाइब्रेरी खुल रही है",
|
"libraryLoadingMessage": "लाइब्रेरी खुल रही है",
|
||||||
@@ -117,7 +117,7 @@
|
|||||||
"edit": "संशोधन करें",
|
"edit": "संशोधन करें",
|
||||||
"undo": "पूर्ववत् करें",
|
"undo": "पूर्ववत् करें",
|
||||||
"redo": "फिर से करें",
|
"redo": "फिर से करें",
|
||||||
"roomDialog": "लाइव सहयोग शुरू करें",
|
"resetLibrary": "",
|
||||||
"createNewRoom": "एक नया कमरा बनाएं",
|
"createNewRoom": "एक नया कमरा बनाएं",
|
||||||
"fullScreen": "पूरी स्क्रीन",
|
"fullScreen": "पूरी स्क्रीन",
|
||||||
"darkMode": "डार्क मोड",
|
"darkMode": "डार्क मोड",
|
||||||
@@ -136,10 +136,12 @@
|
|||||||
"decryptFailed": "डेटा को डिक्रिप्ट नहीं किया जा सका।",
|
"decryptFailed": "डेटा को डिक्रिप्ट नहीं किया जा सका।",
|
||||||
"uploadedSecurly": "अपलोड को एंड-टू-एंड एन्क्रिप्शन के साथ सुरक्षित किया गया है, जिसका मतलब है कि एक्सक्लूसिव सर्वर और थर्ड पार्टी कंटेंट नहीं पढ़ सकते हैं।",
|
"uploadedSecurly": "अपलोड को एंड-टू-एंड एन्क्रिप्शन के साथ सुरक्षित किया गया है, जिसका मतलब है कि एक्सक्लूसिव सर्वर और थर्ड पार्टी कंटेंट नहीं पढ़ सकते हैं।",
|
||||||
"loadSceneOverridePrompt": "लोड हो रहा है बाहरी ड्राइंग आपके मौजूदा सामग्री को बदल देगा। क्या आप जारी रखना चाहते हैं?",
|
"loadSceneOverridePrompt": "लोड हो रहा है बाहरी ड्राइंग आपके मौजूदा सामग्री को बदल देगा। क्या आप जारी रखना चाहते हैं?",
|
||||||
|
"collabStopOverridePrompt": "",
|
||||||
"errorLoadingLibrary": "लाइब्रेरी लोड करने में त्रुटि",
|
"errorLoadingLibrary": "लाइब्रेरी लोड करने में त्रुटि",
|
||||||
"confirmAddLibrary": "लाइब्रेरी जोड़ें पुष्टि करें आकार संख्या",
|
"confirmAddLibrary": "लाइब्रेरी जोड़ें पुष्टि करें आकार संख्या",
|
||||||
"imageDoesNotContainScene": "दृश्य में छवि नहीं है",
|
"imageDoesNotContainScene": "दृश्य में छवि नहीं है",
|
||||||
"cannotRestoreFromImage": "छवि फ़ाइल बहाल दृश्य नहीं है"
|
"cannotRestoreFromImage": "छवि फ़ाइल बहाल दृश्य नहीं है",
|
||||||
|
"resetLibrary": ""
|
||||||
},
|
},
|
||||||
"toolBar": {
|
"toolBar": {
|
||||||
"selection": "चयन",
|
"selection": "चयन",
|
||||||
@@ -201,25 +203,25 @@
|
|||||||
"title": "गलती"
|
"title": "गलती"
|
||||||
},
|
},
|
||||||
"helpDialog": {
|
"helpDialog": {
|
||||||
"blog": "",
|
"blog": "हमारा ब्लॉग पढे",
|
||||||
"click": "",
|
"click": "क्लिक करें",
|
||||||
"curvedArrow": "",
|
"curvedArrow": "वक्र तीर",
|
||||||
"curvedLine": "",
|
"curvedLine": "वक्र रेखा",
|
||||||
"documentation": "",
|
"documentation": "",
|
||||||
"drag": "",
|
"drag": "खींचें",
|
||||||
"editor": "",
|
"editor": "संपादक",
|
||||||
"github": "",
|
"github": "मुद्दा मिला? प्रस्तुत करें",
|
||||||
"howto": "",
|
"howto": "हमारे गाइड का पालन करें",
|
||||||
"or": "",
|
"or": "या",
|
||||||
"preventBinding": "",
|
"preventBinding": "तीर बंधन रोकें",
|
||||||
"shapes": "",
|
"shapes": "आकृतियाँ",
|
||||||
"shortcuts": "",
|
"shortcuts": "कीबोर्ड के शॉर्टकट्स",
|
||||||
"textFinish": "",
|
"textFinish": "संपादन समाप्त करें (पाठ)",
|
||||||
"textNewLine": "",
|
"textNewLine": "नई पंक्ति जोड़ें (पाठ)",
|
||||||
"title": "",
|
"title": "मदद",
|
||||||
"view": "",
|
"view": "दृश्य",
|
||||||
"zoomToFit": "",
|
"zoomToFit": "सभी तत्वों को फिट करने के लिए ज़ूम करें",
|
||||||
"zoomToSelection": ""
|
"zoomToSelection": "चयन तक ज़ूम करे"
|
||||||
},
|
},
|
||||||
"encrypted": {
|
"encrypted": {
|
||||||
"tooltip": "आपके चित्र अंत-से-अंत एन्क्रिप्टेड हैं, इसलिए एक्सक्लूसिव्रॉव के सर्वर उन्हें कभी नहीं देखेंगे।"
|
"tooltip": "आपके चित्र अंत-से-अंत एन्क्रिप्टेड हैं, इसलिए एक्सक्लूसिव्रॉव के सर्वर उन्हें कभी नहीं देखेंगे।"
|
||||||
@@ -234,10 +236,16 @@
|
|||||||
"storage": "संग्रह",
|
"storage": "संग्रह",
|
||||||
"title": "बेवकूफ के लिए आँकड़े",
|
"title": "बेवकूफ के लिए आँकड़े",
|
||||||
"total": "कुल",
|
"total": "कुल",
|
||||||
|
"version": "संस्करण",
|
||||||
|
"versionCopy": "काॅपी करने के लिए क्लिक करें",
|
||||||
|
"versionNotAvailable": "संस्करण उपलब्ध नहीं है",
|
||||||
"width": "चौड़ाई"
|
"width": "चौड़ाई"
|
||||||
},
|
},
|
||||||
"toast": {
|
"toast": {
|
||||||
"copyStyles": "",
|
"copyStyles": "काॅपी कीए स्टाइल",
|
||||||
"copyToClipboardAsPng": ""
|
"copyToClipboard": "क्लिपबोर्ड में कॉपी कीए",
|
||||||
|
"copyToClipboardAsPng": "क्लिपबोर्ड में PNG के रूप में कॉपी किए",
|
||||||
|
"fileSaved": "",
|
||||||
|
"fileSavedToFilename": ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+13
-5
@@ -68,7 +68,7 @@
|
|||||||
"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",
|
"liveCollaboration": "",
|
||||||
"duplicateSelection": "Duplikálás",
|
"duplicateSelection": "Duplikálás",
|
||||||
"untitled": "Névtelen",
|
"untitled": "Névtelen",
|
||||||
"name": "Név",
|
"name": "Név",
|
||||||
@@ -77,7 +77,7 @@
|
|||||||
"group": "Csoportosítás",
|
"group": "Csoportosítás",
|
||||||
"ungroup": "Csoportbontás",
|
"ungroup": "Csoportbontás",
|
||||||
"collaborators": "Közreműködők",
|
"collaborators": "Közreműködők",
|
||||||
"gridMode": "Hálómód",
|
"showGrid": "",
|
||||||
"addToLibrary": "Hozzáadás a könyvtárhoz",
|
"addToLibrary": "Hozzáadás a könyvtárhoz",
|
||||||
"removeFromLibrary": "Eltávólítás a könyvtárból",
|
"removeFromLibrary": "Eltávólítás a könyvtárból",
|
||||||
"libraryLoadingMessage": "Könyvtár betöltése…",
|
"libraryLoadingMessage": "Könyvtár betöltése…",
|
||||||
@@ -117,7 +117,7 @@
|
|||||||
"edit": "Szerkesztés",
|
"edit": "Szerkesztés",
|
||||||
"undo": "Vissza",
|
"undo": "Vissza",
|
||||||
"redo": "Újra",
|
"redo": "Újra",
|
||||||
"roomDialog": "Élő együttműködés indítása",
|
"resetLibrary": "",
|
||||||
"createNewRoom": "Új szoba létrehozása",
|
"createNewRoom": "Új szoba létrehozása",
|
||||||
"fullScreen": "Teljes képernyő",
|
"fullScreen": "Teljes képernyő",
|
||||||
"darkMode": "Sötét mód",
|
"darkMode": "Sötét mód",
|
||||||
@@ -136,10 +136,12 @@
|
|||||||
"decryptFailed": "Nem sikerült visszafejteni a titkosított 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 egy harmadik fél nem tudja megnézni a tartalmát, beleértve az Excalidraw szervereit is.",
|
"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": "A betöltött külső rajz felül fogja írnia meglévőt. Szeretnéd folytatni?",
|
"loadSceneOverridePrompt": "A betöltött külső rajz felül fogja írnia meglévőt. Szeretnéd folytatni?",
|
||||||
|
"collabStopOverridePrompt": "",
|
||||||
"errorLoadingLibrary": "Hibába ütközött a harmarmadik féltől származó könyvtár betöltése.",
|
"errorLoadingLibrary": "Hibába ütközött a harmarmadik féltől származó könyvtár betöltése.",
|
||||||
"confirmAddLibrary": "Ez a művelet {{numShapes}} formát fog hozzáadni a könyvtáradhoz. Biztos vagy benne?",
|
"confirmAddLibrary": "Ez a művelet {{numShapes}} formát fog hozzáadni a könyvtáradhoz. Biztos vagy benne?",
|
||||||
"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.",
|
"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": "A jelenet visszaállítása nem sikerült ebből a kép fájlból"
|
"cannotRestoreFromImage": "A jelenet visszaállítása nem sikerült ebből a kép fájlból",
|
||||||
|
"resetLibrary": ""
|
||||||
},
|
},
|
||||||
"toolBar": {
|
"toolBar": {
|
||||||
"selection": "Kijelölés",
|
"selection": "Kijelölés",
|
||||||
@@ -234,10 +236,16 @@
|
|||||||
"storage": "Tárhely",
|
"storage": "Tárhely",
|
||||||
"title": "Statisztikák",
|
"title": "Statisztikák",
|
||||||
"total": "Összesen",
|
"total": "Összesen",
|
||||||
|
"version": "",
|
||||||
|
"versionCopy": "",
|
||||||
|
"versionNotAvailable": "",
|
||||||
"width": "Szélesség"
|
"width": "Szélesség"
|
||||||
},
|
},
|
||||||
"toast": {
|
"toast": {
|
||||||
"copyStyles": "",
|
"copyStyles": "",
|
||||||
"copyToClipboardAsPng": ""
|
"copyToClipboard": "",
|
||||||
|
"copyToClipboardAsPng": "",
|
||||||
|
"fileSaved": "",
|
||||||
|
"fileSavedToFilename": ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+13
-5
@@ -68,7 +68,7 @@
|
|||||||
"layers": "Lapisan",
|
"layers": "Lapisan",
|
||||||
"actions": "Aksi",
|
"actions": "Aksi",
|
||||||
"language": "Bahasa",
|
"language": "Bahasa",
|
||||||
"createRoom": "Bagikan sesi kolaborasi langsung",
|
"liveCollaboration": "",
|
||||||
"duplicateSelection": "Duplikat",
|
"duplicateSelection": "Duplikat",
|
||||||
"untitled": "Tanpa judul",
|
"untitled": "Tanpa judul",
|
||||||
"name": "Nama",
|
"name": "Nama",
|
||||||
@@ -77,7 +77,7 @@
|
|||||||
"group": "Kelompokan pilihan",
|
"group": "Kelompokan pilihan",
|
||||||
"ungroup": "Pisahkan pilihan",
|
"ungroup": "Pisahkan pilihan",
|
||||||
"collaborators": "Kolaborator",
|
"collaborators": "Kolaborator",
|
||||||
"gridMode": "Mode grid",
|
"showGrid": "Tampilkan grid",
|
||||||
"addToLibrary": "Tambahkan ke pustaka",
|
"addToLibrary": "Tambahkan ke pustaka",
|
||||||
"removeFromLibrary": "Hapus dari pustaka",
|
"removeFromLibrary": "Hapus dari pustaka",
|
||||||
"libraryLoadingMessage": "Memuat pustaka…",
|
"libraryLoadingMessage": "Memuat pustaka…",
|
||||||
@@ -117,7 +117,7 @@
|
|||||||
"edit": "Edit",
|
"edit": "Edit",
|
||||||
"undo": "Urungkan",
|
"undo": "Urungkan",
|
||||||
"redo": "Ulangi",
|
"redo": "Ulangi",
|
||||||
"roomDialog": "Mulai kolaborasi langsung",
|
"resetLibrary": "",
|
||||||
"createNewRoom": "Buat ruang baru",
|
"createNewRoom": "Buat ruang baru",
|
||||||
"fullScreen": "Layar penuh",
|
"fullScreen": "Layar penuh",
|
||||||
"darkMode": "Mode gelap",
|
"darkMode": "Mode gelap",
|
||||||
@@ -136,10 +136,12 @@
|
|||||||
"decryptFailed": "Tidak dapat mengdekripsi data.",
|
"decryptFailed": "Tidak dapat mengdekripsi data.",
|
||||||
"uploadedSecurly": "Pengunggahan ini telah diamankan menggunakan enkripsi end-to-end, artinya server Excalidraw dan pihak ketiga tidak data membaca nya",
|
"uploadedSecurly": "Pengunggahan ini telah diamankan menggunakan enkripsi end-to-end, artinya server Excalidraw dan pihak ketiga tidak data membaca nya",
|
||||||
"loadSceneOverridePrompt": "Memuat gambar external akan mengganti konten Anda yang ada. Apakah Anda ingin melanjutkan?",
|
"loadSceneOverridePrompt": "Memuat gambar external akan mengganti konten Anda yang ada. Apakah Anda ingin melanjutkan?",
|
||||||
|
"collabStopOverridePrompt": "Menghentikan sesi akan menimpa gambar Anda yang tersimpan secara lokal. Anda yakin?\n\n(Jika Anda ingin menyimpan gambar lokal Anda, gantinya cukup tutup tab browser.)",
|
||||||
"errorLoadingLibrary": "Terdapat kesalahan dalam memuat pustaka pihak ketiga.",
|
"errorLoadingLibrary": "Terdapat kesalahan dalam memuat pustaka pihak ketiga.",
|
||||||
"confirmAddLibrary": "Ini akan menambahkan {{numShapes}} bentuk ke pustaka Anda. Anda yakin?",
|
"confirmAddLibrary": "Ini akan menambahkan {{numShapes}} bentuk ke pustaka Anda. Anda yakin?",
|
||||||
"imageDoesNotContainScene": "Mengimpor gambar tidak didukung saat ini.\n\nApakah Anda ingin impor pemandangan? Gambar ini tidak berisi data pemandangan. Sudah ka Anda aktifkan ini ketika ekspor?",
|
"imageDoesNotContainScene": "Mengimpor gambar tidak didukung saat ini.\n\nApakah Anda ingin impor pemandangan? Gambar ini tidak berisi data pemandangan. Sudah ka Anda aktifkan ini ketika ekspor?",
|
||||||
"cannotRestoreFromImage": "Pemandangan tidak dapat dipulihkan dari file gambar ini"
|
"cannotRestoreFromImage": "Pemandangan tidak dapat dipulihkan dari file gambar ini",
|
||||||
|
"resetLibrary": ""
|
||||||
},
|
},
|
||||||
"toolBar": {
|
"toolBar": {
|
||||||
"selection": "Pilihan",
|
"selection": "Pilihan",
|
||||||
@@ -234,10 +236,16 @@
|
|||||||
"storage": "Penyimpanan",
|
"storage": "Penyimpanan",
|
||||||
"title": "Statistik untuk nerd",
|
"title": "Statistik untuk nerd",
|
||||||
"total": "Total",
|
"total": "Total",
|
||||||
|
"version": "Versi",
|
||||||
|
"versionCopy": "Klik untuk salin",
|
||||||
|
"versionNotAvailable": "Versi tidak tersedia",
|
||||||
"width": "Lebar"
|
"width": "Lebar"
|
||||||
},
|
},
|
||||||
"toast": {
|
"toast": {
|
||||||
"copyStyles": "Gaya tersalin.",
|
"copyStyles": "Gaya tersalin.",
|
||||||
"copyToClipboardAsPng": "Tersalin ke clipboard sebagai PNG."
|
"copyToClipboard": "Tersalin ke papan klip.",
|
||||||
|
"copyToClipboardAsPng": "Tersalin ke clipboard sebagai PNG.",
|
||||||
|
"fileSaved": "File tersimpan.",
|
||||||
|
"fileSavedToFilename": "Disimpan ke {filename}"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+14
-6
@@ -68,7 +68,7 @@
|
|||||||
"layers": "Livelli",
|
"layers": "Livelli",
|
||||||
"actions": "Azioni",
|
"actions": "Azioni",
|
||||||
"language": "Lingua",
|
"language": "Lingua",
|
||||||
"createRoom": "Condividi una sessione di collaborazione in diretta",
|
"liveCollaboration": "Collaborazione live",
|
||||||
"duplicateSelection": "Duplica",
|
"duplicateSelection": "Duplica",
|
||||||
"untitled": "Senza titolo",
|
"untitled": "Senza titolo",
|
||||||
"name": "Nome",
|
"name": "Nome",
|
||||||
@@ -77,10 +77,10 @@
|
|||||||
"group": "Crea gruppo da selezione",
|
"group": "Crea gruppo da selezione",
|
||||||
"ungroup": "Dividi gruppo da selezione",
|
"ungroup": "Dividi gruppo da selezione",
|
||||||
"collaborators": "Collaboratori",
|
"collaborators": "Collaboratori",
|
||||||
"gridMode": "Modalità griglia",
|
"showGrid": "Visualizza griglia",
|
||||||
"addToLibrary": "Aggiungi alla libreria",
|
"addToLibrary": "Aggiungi alla libreria",
|
||||||
"removeFromLibrary": "Rimuovi dalla libreria",
|
"removeFromLibrary": "Rimuovi dalla libreria",
|
||||||
"libraryLoadingMessage": "Caricamento della biblioteca…",
|
"libraryLoadingMessage": "Caricamento libreria…",
|
||||||
"libraries": "Sfoglia librerie",
|
"libraries": "Sfoglia librerie",
|
||||||
"loadingScene": "Caricamento della scena…",
|
"loadingScene": "Caricamento della scena…",
|
||||||
"align": "Allinea",
|
"align": "Allinea",
|
||||||
@@ -117,7 +117,7 @@
|
|||||||
"edit": "Modifica",
|
"edit": "Modifica",
|
||||||
"undo": "Annulla",
|
"undo": "Annulla",
|
||||||
"redo": "Ripeti",
|
"redo": "Ripeti",
|
||||||
"roomDialog": "Inizia collaborazione in diretta",
|
"resetLibrary": "Ripristina libreria",
|
||||||
"createNewRoom": "Crea nuova stanza",
|
"createNewRoom": "Crea nuova stanza",
|
||||||
"fullScreen": "Schermo intero",
|
"fullScreen": "Schermo intero",
|
||||||
"darkMode": "Tema scuro",
|
"darkMode": "Tema scuro",
|
||||||
@@ -136,10 +136,12 @@
|
|||||||
"decryptFailed": "Impossibile decriptare i dati.",
|
"decryptFailed": "Impossibile decriptare i dati.",
|
||||||
"uploadedSecurly": "L'upload è stato protetto con la crittografia end-to-end, il che significa che il server Excalidraw e terze parti non possono leggere il contenuto.",
|
"uploadedSecurly": "L'upload è stato protetto con la crittografia end-to-end, il che significa che il server Excalidraw e terze parti non possono leggere il contenuto.",
|
||||||
"loadSceneOverridePrompt": "Se carichi questo disegno esterno, sostituirà quello che hai. Vuoi continuare?",
|
"loadSceneOverridePrompt": "Se carichi questo disegno esterno, sostituirà quello che hai. Vuoi continuare?",
|
||||||
|
"collabStopOverridePrompt": "Interrompere la sessione sovrascriverà il precedente disegno memorizzato localmente. Sei sicuro?\n\n(Se vuoi mantenere il tuo disegno locale, chiudi semplicemente la scheda del browser.)",
|
||||||
"errorLoadingLibrary": "Si è verificato un errore nel caricamento della libreria di terze parti.",
|
"errorLoadingLibrary": "Si è verificato un errore nel caricamento della libreria di terze parti.",
|
||||||
"confirmAddLibrary": "Questo aggiungerà {{numShapes}} forma(e) alla tua libreria. Sei sicuro?",
|
"confirmAddLibrary": "Questo aggiungerà {{numShapes}} forma(e) alla tua libreria. Sei sicuro?",
|
||||||
"imageDoesNotContainScene": "L'importazione di immagini al momento non è supportata.\n\nVuoi importare una scena? Questa immagine non sembra contenere alcun dato di scena. Hai abilitato questa opzione durante l'esportazione?",
|
"imageDoesNotContainScene": "L'importazione di immagini al momento non è supportata.\n\nVuoi importare una scena? Questa immagine non sembra contenere alcun dato di scena. Hai abilitato questa opzione durante l'esportazione?",
|
||||||
"cannotRestoreFromImage": "Impossibile ripristinare la scena da questo file immagine"
|
"cannotRestoreFromImage": "Impossibile ripristinare la scena da questo file immagine",
|
||||||
|
"resetLibrary": "Questa azione cancellerà l'intera libreria. Sei sicuro?"
|
||||||
},
|
},
|
||||||
"toolBar": {
|
"toolBar": {
|
||||||
"selection": "Selezione",
|
"selection": "Selezione",
|
||||||
@@ -234,10 +236,16 @@
|
|||||||
"storage": "Memoria",
|
"storage": "Memoria",
|
||||||
"title": "Statistiche per nerd",
|
"title": "Statistiche per nerd",
|
||||||
"total": "Totale",
|
"total": "Totale",
|
||||||
|
"version": "Versione",
|
||||||
|
"versionCopy": "Clicca per copiare",
|
||||||
|
"versionNotAvailable": "Versione non disponibile",
|
||||||
"width": "Larghezza"
|
"width": "Larghezza"
|
||||||
},
|
},
|
||||||
"toast": {
|
"toast": {
|
||||||
"copyStyles": "Stili copiati.",
|
"copyStyles": "Stili copiati.",
|
||||||
"copyToClipboardAsPng": "Copiato negli appunti come PNG."
|
"copyToClipboard": "Copiato negli appunti.",
|
||||||
|
"copyToClipboardAsPng": "Copiato negli appunti come PNG.",
|
||||||
|
"fileSaved": "File salvato.",
|
||||||
|
"fileSavedToFilename": "Salvato in {filename}"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+51
-43
@@ -1,11 +1,11 @@
|
|||||||
{
|
{
|
||||||
"labels": {
|
"labels": {
|
||||||
"paste": "貼り付け",
|
"paste": "貼り付け",
|
||||||
"pasteCharts": "",
|
"pasteCharts": "チャートの貼り付け",
|
||||||
"selectAll": "すべて選択",
|
"selectAll": "すべて選択",
|
||||||
"multiSelect": "複数選択",
|
"multiSelect": "複数選択",
|
||||||
"moveCanvas": "キャンバスを移動",
|
"moveCanvas": "キャンバスを移動",
|
||||||
"cut": "",
|
"cut": "切り取り",
|
||||||
"copy": "コピー",
|
"copy": "コピー",
|
||||||
"copyAsPng": "PNGとしてクリップボードへコピー",
|
"copyAsPng": "PNGとしてクリップボードへコピー",
|
||||||
"copyAsSvg": "SVGとしてクリップボードへコピー",
|
"copyAsSvg": "SVGとしてクリップボードへコピー",
|
||||||
@@ -38,7 +38,7 @@
|
|||||||
"fontSize": "フォントの大きさ",
|
"fontSize": "フォントの大きさ",
|
||||||
"fontFamily": "フォントの種類",
|
"fontFamily": "フォントの種類",
|
||||||
"onlySelected": "選択中のみ",
|
"onlySelected": "選択中のみ",
|
||||||
"withBackground": "",
|
"withBackground": "背景を含める",
|
||||||
"exportEmbedScene": "エクスポートされたファイルにシーンを埋め込みます",
|
"exportEmbedScene": "エクスポートされたファイルにシーンを埋め込みます",
|
||||||
"exportEmbedScene_details": "シーンデータはエクスポートされたPNG/SVGファイルに保存され、シーンを復元することができます。\nエクスポートされたファイルのサイズは増加します。",
|
"exportEmbedScene_details": "シーンデータはエクスポートされたPNG/SVGファイルに保存され、シーンを復元することができます。\nエクスポートされたファイルのサイズは増加します。",
|
||||||
"addWatermark": "\"Made with Excalidraw\"と表示",
|
"addWatermark": "\"Made with Excalidraw\"と表示",
|
||||||
@@ -68,20 +68,20 @@
|
|||||||
"layers": "レイヤー",
|
"layers": "レイヤー",
|
||||||
"actions": "操作",
|
"actions": "操作",
|
||||||
"language": "言語",
|
"language": "言語",
|
||||||
"createRoom": "共同編集セッションの共有",
|
"liveCollaboration": "ライブ連携",
|
||||||
"duplicateSelection": "複製",
|
"duplicateSelection": "複製",
|
||||||
"untitled": "",
|
"untitled": "無題",
|
||||||
"name": "名前",
|
"name": "名前",
|
||||||
"yourName": "あなたの名前",
|
"yourName": "あなたの名前",
|
||||||
"madeWithExcalidraw": "Excalidrawで作成",
|
"madeWithExcalidraw": "Excalidrawで作成",
|
||||||
"group": "図形のグループ化",
|
"group": "図形のグループ化",
|
||||||
"ungroup": "グループ化を解除",
|
"ungroup": "グループ化を解除",
|
||||||
"collaborators": "共同編集者",
|
"collaborators": "共同編集者",
|
||||||
"gridMode": "",
|
"showGrid": "グリッドを表示",
|
||||||
"addToLibrary": "ライブラリに追加",
|
"addToLibrary": "ライブラリに追加",
|
||||||
"removeFromLibrary": "ライブラリから削除",
|
"removeFromLibrary": "ライブラリから削除",
|
||||||
"libraryLoadingMessage": "ライブラリを読み込み中…",
|
"libraryLoadingMessage": "ライブラリを読み込み中…",
|
||||||
"libraries": "",
|
"libraries": "ライブラリを参照する",
|
||||||
"loadingScene": "シーンを読み込み中…",
|
"loadingScene": "シーンを読み込み中…",
|
||||||
"align": "整列",
|
"align": "整列",
|
||||||
"alignTop": "上揃え",
|
"alignTop": "上揃え",
|
||||||
@@ -90,9 +90,9 @@
|
|||||||
"alignRight": "右揃え",
|
"alignRight": "右揃え",
|
||||||
"centerVertically": "縦方向に中央揃え",
|
"centerVertically": "縦方向に中央揃え",
|
||||||
"centerHorizontally": "横方向に中央揃え",
|
"centerHorizontally": "横方向に中央揃え",
|
||||||
"distributeHorizontally": "",
|
"distributeHorizontally": "水平方向に分散配置",
|
||||||
"distributeVertically": "",
|
"distributeVertically": "垂直方向に分散配置",
|
||||||
"viewMode": ""
|
"viewMode": "閲覧モード"
|
||||||
},
|
},
|
||||||
"buttons": {
|
"buttons": {
|
||||||
"clearReset": "キャンバスのリセット",
|
"clearReset": "キャンバスのリセット",
|
||||||
@@ -117,12 +117,12 @@
|
|||||||
"edit": "編集",
|
"edit": "編集",
|
||||||
"undo": "元に戻す",
|
"undo": "元に戻す",
|
||||||
"redo": "やり直し",
|
"redo": "やり直し",
|
||||||
"roomDialog": "共同編集を開始する",
|
"resetLibrary": "ライブラリをリセット",
|
||||||
"createNewRoom": "新しい部屋を作成する",
|
"createNewRoom": "新しい部屋を作成する",
|
||||||
"fullScreen": "全画面表示",
|
"fullScreen": "全画面表示",
|
||||||
"darkMode": "ダークモード",
|
"darkMode": "ダークモード",
|
||||||
"lightMode": "ライトモード",
|
"lightMode": "ライトモード",
|
||||||
"zenMode": "",
|
"zenMode": "Zenモード",
|
||||||
"exitZenMode": "集中モードをやめる"
|
"exitZenMode": "集中モードをやめる"
|
||||||
},
|
},
|
||||||
"alerts": {
|
"alerts": {
|
||||||
@@ -136,10 +136,12 @@
|
|||||||
"decryptFailed": "データを復号できませんでした。",
|
"decryptFailed": "データを復号できませんでした。",
|
||||||
"uploadedSecurly": "データのアップロードはエンドツーエンド暗号化によって保護されています。Excalidrawサーバーと第三者はデータの内容を見ることができません。",
|
"uploadedSecurly": "データのアップロードはエンドツーエンド暗号化によって保護されています。Excalidrawサーバーと第三者はデータの内容を見ることができません。",
|
||||||
"loadSceneOverridePrompt": "外部図面を読み込むと、既存のコンテンツが置き換わります。続行しますか?",
|
"loadSceneOverridePrompt": "外部図面を読み込むと、既存のコンテンツが置き換わります。続行しますか?",
|
||||||
|
"collabStopOverridePrompt": "セッションを停止すると、ローカルに保存されている図が上書きされます。 本当によろしいですか?\n\n(ローカルの図を保持したい場合は、セッションを停止せずにブラウザタブを閉じてください。)",
|
||||||
"errorLoadingLibrary": "サードパーティライブラリの読み込み中にエラーが発生しました。",
|
"errorLoadingLibrary": "サードパーティライブラリの読み込み中にエラーが発生しました。",
|
||||||
"confirmAddLibrary": "{{numShapes}} 個の図形をライブラリに追加します。よろしいですか?",
|
"confirmAddLibrary": "{{numShapes}} 個の図形をライブラリに追加します。よろしいですか?",
|
||||||
"imageDoesNotContainScene": "",
|
"imageDoesNotContainScene": "現在、画像のインポートはサポートされていません。\n\nシーンをインポートしようとしましたか?この画像にはシーンデータが含まれていないようです。エクスポート中に有効にしていましたか?",
|
||||||
"cannotRestoreFromImage": "このイメージファイルからシーンを復元できませんでした"
|
"cannotRestoreFromImage": "このイメージファイルからシーンを復元できませんでした",
|
||||||
|
"resetLibrary": "ライブラリを消去します。本当によろしいですか?"
|
||||||
},
|
},
|
||||||
"toolBar": {
|
"toolBar": {
|
||||||
"selection": "選択",
|
"selection": "選択",
|
||||||
@@ -163,7 +165,7 @@
|
|||||||
"freeDraw": "クリックしてドラッグします。離すと終了します",
|
"freeDraw": "クリックしてドラッグします。離すと終了します",
|
||||||
"text": "ヒント: 選択ツールを使用して任意の場所をダブルクリックしてテキストを追加することもできます",
|
"text": "ヒント: 選択ツールを使用して任意の場所をダブルクリックしてテキストを追加することもできます",
|
||||||
"linearElementMulti": "最後のポイントをクリックするか、エスケープまたはEnterを押して終了します",
|
"linearElementMulti": "最後のポイントをクリックするか、エスケープまたはEnterを押して終了します",
|
||||||
"lockAngle": "",
|
"lockAngle": "SHIFTを押したままにすると、角度を制限することができます",
|
||||||
"resize": "サイズを変更中にSHIFTを押しすと比率を制御できます。Altを押すと中央からサイズを変更できます。",
|
"resize": "サイズを変更中にSHIFTを押しすと比率を制御できます。Altを押すと中央からサイズを変更できます。",
|
||||||
"rotate": "回転中にSHIFT キーを押すと角度を制限することができます",
|
"rotate": "回転中にSHIFT キーを押すと角度を制限することができます",
|
||||||
"lineEditor_info": "ポイントを編集するには、ダブルクリックまたはEnterキーを押します",
|
"lineEditor_info": "ポイントを編集するには、ダブルクリックまたはEnterキーを押します",
|
||||||
@@ -201,43 +203,49 @@
|
|||||||
"title": "エラー"
|
"title": "エラー"
|
||||||
},
|
},
|
||||||
"helpDialog": {
|
"helpDialog": {
|
||||||
"blog": "",
|
"blog": "公式ブログを読む",
|
||||||
"click": "",
|
"click": "クリック",
|
||||||
"curvedArrow": "",
|
"curvedArrow": "カーブした矢印",
|
||||||
"curvedLine": "",
|
"curvedLine": "曲線",
|
||||||
"documentation": "",
|
"documentation": "ドキュメント",
|
||||||
"drag": "",
|
"drag": "ドラッグ",
|
||||||
"editor": "",
|
"editor": "エディタ",
|
||||||
"github": "",
|
"github": "不具合報告はこちら",
|
||||||
"howto": "",
|
"howto": "ヘルプ・マニュアル",
|
||||||
"or": "",
|
"or": "または",
|
||||||
"preventBinding": "",
|
"preventBinding": "矢印を結合しない",
|
||||||
"shapes": "",
|
"shapes": "図形",
|
||||||
"shortcuts": "",
|
"shortcuts": "キーボードショートカット",
|
||||||
"textFinish": "",
|
"textFinish": "編集を終了する (テキスト)",
|
||||||
"textNewLine": "",
|
"textNewLine": "新しい行を追加 (テキスト)",
|
||||||
"title": "",
|
"title": "ヘルプ",
|
||||||
"view": "",
|
"view": "表示",
|
||||||
"zoomToFit": "",
|
"zoomToFit": "すべての要素が収まるようにズーム",
|
||||||
"zoomToSelection": ""
|
"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": "合計",
|
||||||
|
"version": "バージョン",
|
||||||
|
"versionCopy": "クリックしてコピー",
|
||||||
|
"versionNotAvailable": "利用できないバージョン",
|
||||||
"width": "幅"
|
"width": "幅"
|
||||||
},
|
},
|
||||||
"toast": {
|
"toast": {
|
||||||
"copyStyles": "",
|
"copyStyles": "スタイルをコピー",
|
||||||
"copyToClipboardAsPng": ""
|
"copyToClipboard": "クリップボードにコピー",
|
||||||
|
"copyToClipboardAsPng": "PNG形式でクリップボードにコピー",
|
||||||
|
"fileSaved": "ファイルを保存しました",
|
||||||
|
"fileSavedToFilename": "{filename} に保存しました"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,7 +24,7 @@
|
|||||||
"strokeStyle_solid": "Aččuran",
|
"strokeStyle_solid": "Aččuran",
|
||||||
"strokeStyle_dashed": "S tjerriḍin",
|
"strokeStyle_dashed": "S tjerriḍin",
|
||||||
"strokeStyle_dotted": "S tenqiḍin",
|
"strokeStyle_dotted": "S tenqiḍin",
|
||||||
"sloppiness": "",
|
"sloppiness": "Astehzi",
|
||||||
"opacity": "Tiḍullest",
|
"opacity": "Tiḍullest",
|
||||||
"textAlign": "Areyyec n uḍris",
|
"textAlign": "Areyyec n uḍris",
|
||||||
"edges": "Leryuf",
|
"edges": "Leryuf",
|
||||||
@@ -68,7 +68,7 @@
|
|||||||
"layers": "Tissiyin",
|
"layers": "Tissiyin",
|
||||||
"actions": "Tigawin",
|
"actions": "Tigawin",
|
||||||
"language": "Tutlayt",
|
"language": "Tutlayt",
|
||||||
"createRoom": "Bḍu tiɣimit n umɛawen s srid",
|
"liveCollaboration": "",
|
||||||
"duplicateSelection": "Sisleg",
|
"duplicateSelection": "Sisleg",
|
||||||
"untitled": "War azwel",
|
"untitled": "War azwel",
|
||||||
"name": "Isem",
|
"name": "Isem",
|
||||||
@@ -77,7 +77,7 @@
|
|||||||
"group": "Segrew tafrayt",
|
"group": "Segrew tafrayt",
|
||||||
"ungroup": "Kkess asegrew i tefrayt",
|
"ungroup": "Kkess asegrew i tefrayt",
|
||||||
"collaborators": "Imɛiwnen",
|
"collaborators": "Imɛiwnen",
|
||||||
"gridMode": "Askar n uferrug",
|
"showGrid": "Beqqeḍ aferrug",
|
||||||
"addToLibrary": "Rnu ɣer temkarḍit",
|
"addToLibrary": "Rnu ɣer temkarḍit",
|
||||||
"removeFromLibrary": "Kkes si temkarḍit",
|
"removeFromLibrary": "Kkes si temkarḍit",
|
||||||
"libraryLoadingMessage": "Asali n temkarḍit…",
|
"libraryLoadingMessage": "Asali n temkarḍit…",
|
||||||
@@ -117,7 +117,7 @@
|
|||||||
"edit": "Ẓreg",
|
"edit": "Ẓreg",
|
||||||
"undo": "Sefsex",
|
"undo": "Sefsex",
|
||||||
"redo": "Err-d",
|
"redo": "Err-d",
|
||||||
"roomDialog": "Bdu amɛawen s srid",
|
"resetLibrary": "",
|
||||||
"createNewRoom": "Snulfu-d taxxamt tamaynutt",
|
"createNewRoom": "Snulfu-d taxxamt tamaynutt",
|
||||||
"fullScreen": "Agdil aččuran",
|
"fullScreen": "Agdil aččuran",
|
||||||
"darkMode": "Askar imsulles",
|
"darkMode": "Askar imsulles",
|
||||||
@@ -130,16 +130,18 @@
|
|||||||
"couldNotCreateShareableLink": "D awezɣi asnulfu n useɣwen n beṭṭu.",
|
"couldNotCreateShareableLink": "D awezɣi asnulfu n useɣwen n beṭṭu.",
|
||||||
"couldNotCreateShareableLinkTooBig": "D awezɣi asnulfu n useɣwen n beṭṭu. Asayes ɣezzif aṭas",
|
"couldNotCreateShareableLinkTooBig": "D awezɣi asnulfu n useɣwen n beṭṭu. Asayes ɣezzif aṭas",
|
||||||
"couldNotLoadInvalidFile": "D awezɣi asali n ufaylu armeɣtu",
|
"couldNotLoadInvalidFile": "D awezɣi asali n ufaylu armeɣtu",
|
||||||
"importBackendFailed": "",
|
"importBackendFailed": "Takterḍ seg uɣawas n deffir ur teddi ara.",
|
||||||
"cannotExportEmptyCanvas": "D awezɣi asifeḍ n teɣzut n usuneɣ tilemt.",
|
"cannotExportEmptyCanvas": "D awezɣi asifeḍ n teɣzut n usuneɣ tilemt.",
|
||||||
"couldNotCopyToClipboard": "D awezɣi anɣal ɣer tecfawit. Eɛreḍ ad tesqedceḍ iminig Chrome.",
|
"couldNotCopyToClipboard": "D awezɣi anɣal ɣer tecfawit. Eɛreḍ ad tesqedceḍ iminig Chrome.",
|
||||||
"decryptFailed": "D awezɣi tukksa n uwgelhen i yisefka.",
|
"decryptFailed": "D awezɣi tukksa n uwgelhen i yisefka.",
|
||||||
"uploadedSecurly": "Asili yettwasɣelles s uwgelhen ixef s ixef, ayagi yebɣa ad d-yini belli aqeddac n Excalidraw akked medden ur zmiren ara ad ɣren agbur.",
|
"uploadedSecurly": "Asili yettwasɣelles s uwgelhen ixef s ixef, ayagi yebɣa ad d-yini belli aqeddac n Excalidraw akked medden ur zmiren ara ad ɣren agbur.",
|
||||||
"loadSceneOverridePrompt": "Asali n wunuɣ uffiɣ ad isemselsi agbur-inek (m) yellan. Tebɣiḍ ad tkemmeleḍ?",
|
"loadSceneOverridePrompt": "Asali n wunuɣ uffiɣ ad isemselsi agbur-inek (m) yellan. Tebɣiḍ ad tkemmeleḍ?",
|
||||||
|
"collabStopOverridePrompt": "Aḥbas n tɣimit ad yesefsex unuɣ-inek (m) yettwaḥerzen yakan s wudem adigan. Tetḥeqqeḍ?\n(Ma tebɣiḍ ad teǧǧeḍ unuɣ-inek (m) adigan, mdel iccer n yiminig, deg umḍiq.)",
|
||||||
"errorLoadingLibrary": "Teḍra-d tuccḍa deg usali n temkarḍit n wis kraḍ.",
|
"errorLoadingLibrary": "Teḍra-d tuccḍa deg usali n temkarḍit n wis kraḍ.",
|
||||||
"confirmAddLibrary": "Ayagi adirnu talɣa (win) {{numShapes}} ɣer temkarḍit-inek (m). Tetḥeqqeḍ?",
|
"confirmAddLibrary": "Ayagi adirnu talɣa (win) {{numShapes}} ɣer temkarḍit-inek (m). Tetḥeqqeḍ?",
|
||||||
"imageDoesNotContainScene": "Taktert n tugniwin ur tettwadhel ara akka tura.\nTebɣiḍ ad tketreḍ asayes? Tugna-agi tettban-d ur tegbir ara isefka n usnas. Tesremdeḍ ayagi deg usifeḍ?",
|
"imageDoesNotContainScene": "Taktert n tugniwin ur tettwadhel ara akka tura.\nTebɣiḍ ad tketreḍ asayes? Tugna-agi tettban-d ur tegbir ara isefka n usnas. Tesremdeḍ ayagi deg usifeḍ?",
|
||||||
"cannotRestoreFromImage": "Asayes ulamek ara d-yettwarr seg ufaylu-agi n tugna"
|
"cannotRestoreFromImage": "Asayes ulamek ara d-yettwarr seg ufaylu-agi n tugna",
|
||||||
|
"resetLibrary": ""
|
||||||
},
|
},
|
||||||
"toolBar": {
|
"toolBar": {
|
||||||
"selection": "Tafrayt",
|
"selection": "Tafrayt",
|
||||||
@@ -183,7 +185,7 @@
|
|||||||
"clearCanvasCaveat": " Ayagi ad d-iglu s usṛuḥu n umahil ",
|
"clearCanvasCaveat": " Ayagi ad d-iglu s usṛuḥu n umahil ",
|
||||||
"trackedToSentry_pre": "Tuccḍa akked umesmagi ",
|
"trackedToSentry_pre": "Tuccḍa akked umesmagi ",
|
||||||
"trackedToSentry_post": " tettwasekles deg unagraw-nneɣ.",
|
"trackedToSentry_post": " tettwasekles deg unagraw-nneɣ.",
|
||||||
"openIssueMessage_pre": "",
|
"openIssueMessage_pre": "Nḥuder aṭas akken ur nseddu ara talɣut n usayes-inek (m) di tuccḍa. Ma yella asayes-inek (m) mačči d amaẓlay, ttxil-k (m) xemmem ad ḍefreḍ ",
|
||||||
"openIssueMessage_button": "afecku n weḍfar n yibugen.",
|
"openIssueMessage_button": "afecku n weḍfar n yibugen.",
|
||||||
"openIssueMessage_post": " Ma ulac uɣilif seddu talɣut ukessar-agi s wenɣal akked usenṭeḍ di GitHub issue.",
|
"openIssueMessage_post": " Ma ulac uɣilif seddu talɣut ukessar-agi s wenɣal akked usenṭeḍ di GitHub issue.",
|
||||||
"sceneContent": "Agbur n usayes:"
|
"sceneContent": "Agbur n usayes:"
|
||||||
@@ -211,7 +213,7 @@
|
|||||||
"github": "Tufiḍ-d ugur? Azen-aɣ-d",
|
"github": "Tufiḍ-d ugur? Azen-aɣ-d",
|
||||||
"howto": "Ḍfer imniren-nneɣ",
|
"howto": "Ḍfer imniren-nneɣ",
|
||||||
"or": "neɣ",
|
"or": "neɣ",
|
||||||
"preventBinding": "",
|
"preventBinding": "Seḥbes tuqqna n tneccabin",
|
||||||
"shapes": "Talɣiwin",
|
"shapes": "Talɣiwin",
|
||||||
"shortcuts": "Inegzumen n unasiw",
|
"shortcuts": "Inegzumen n unasiw",
|
||||||
"textFinish": "Fak asiẓreg (aḍris)",
|
"textFinish": "Fak asiẓreg (aḍris)",
|
||||||
@@ -234,10 +236,16 @@
|
|||||||
"storage": "Aḥraz",
|
"storage": "Aḥraz",
|
||||||
"title": "",
|
"title": "",
|
||||||
"total": "Aɣrud",
|
"total": "Aɣrud",
|
||||||
|
"version": "Alqem",
|
||||||
|
"versionCopy": "Sit ad tneɣleḍ",
|
||||||
|
"versionNotAvailable": "Ur inuḥ ulqem",
|
||||||
"width": "Tehri"
|
"width": "Tehri"
|
||||||
},
|
},
|
||||||
"toast": {
|
"toast": {
|
||||||
"copyStyles": "Iɣunab yettwaneɣlen.",
|
"copyStyles": "Iɣunab yettwaneɣlen.",
|
||||||
"copyToClipboardAsPng": "Yettwanɣel ɣer tecfawit am PNG."
|
"copyToClipboard": "Yettwaɣel ɣer tecfawit.",
|
||||||
|
"copyToClipboardAsPng": "Yettwanɣel ɣer tecfawit am PNG.",
|
||||||
|
"fileSaved": "",
|
||||||
|
"fileSavedToFilename": ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+36
-28
@@ -68,7 +68,7 @@
|
|||||||
"layers": "레이어",
|
"layers": "레이어",
|
||||||
"actions": "동작",
|
"actions": "동작",
|
||||||
"language": "언어",
|
"language": "언어",
|
||||||
"createRoom": "실시간 협업 세션 공유",
|
"liveCollaboration": "",
|
||||||
"duplicateSelection": "복제",
|
"duplicateSelection": "복제",
|
||||||
"untitled": "제목 없음",
|
"untitled": "제목 없음",
|
||||||
"name": "이름",
|
"name": "이름",
|
||||||
@@ -77,12 +77,12 @@
|
|||||||
"group": "그룹 생성",
|
"group": "그룹 생성",
|
||||||
"ungroup": "그룹 해제",
|
"ungroup": "그룹 해제",
|
||||||
"collaborators": "공동 작업자",
|
"collaborators": "공동 작업자",
|
||||||
"gridMode": "격자 방식",
|
"showGrid": "",
|
||||||
"addToLibrary": "라이브러리에 추가",
|
"addToLibrary": "라이브러리에 추가",
|
||||||
"removeFromLibrary": "라이브러리에서 제거",
|
"removeFromLibrary": "라이브러리에서 제거",
|
||||||
"libraryLoadingMessage": "라이브러리 불러오는 중...",
|
"libraryLoadingMessage": "라이브러리 불러오는 중…",
|
||||||
"libraries": "라이브러리 찾기",
|
"libraries": "라이브러리 찾기",
|
||||||
"loadingScene": "화면 불러오는 중...",
|
"loadingScene": "화면 불러오는 중…",
|
||||||
"align": "정렬",
|
"align": "정렬",
|
||||||
"alignTop": "상단 정렬",
|
"alignTop": "상단 정렬",
|
||||||
"alignBottom": "하단 정렬",
|
"alignBottom": "하단 정렬",
|
||||||
@@ -92,7 +92,7 @@
|
|||||||
"centerHorizontally": "수평으로 중앙 정렬",
|
"centerHorizontally": "수평으로 중앙 정렬",
|
||||||
"distributeHorizontally": "수평으로 분배",
|
"distributeHorizontally": "수평으로 분배",
|
||||||
"distributeVertically": "수직으로 분배",
|
"distributeVertically": "수직으로 분배",
|
||||||
"viewMode": ""
|
"viewMode": "보기 모드"
|
||||||
},
|
},
|
||||||
"buttons": {
|
"buttons": {
|
||||||
"clearReset": "캔버스 초기화",
|
"clearReset": "캔버스 초기화",
|
||||||
@@ -117,7 +117,7 @@
|
|||||||
"edit": "수정",
|
"edit": "수정",
|
||||||
"undo": "실행 취소",
|
"undo": "실행 취소",
|
||||||
"redo": "다시 실행",
|
"redo": "다시 실행",
|
||||||
"roomDialog": "실시간 협업 시작하기",
|
"resetLibrary": "",
|
||||||
"createNewRoom": "방 만들기",
|
"createNewRoom": "방 만들기",
|
||||||
"fullScreen": "전체화면",
|
"fullScreen": "전체화면",
|
||||||
"darkMode": "다크 모드",
|
"darkMode": "다크 모드",
|
||||||
@@ -136,10 +136,12 @@
|
|||||||
"decryptFailed": "데이터를 복호화하지 못했습니다.",
|
"decryptFailed": "데이터를 복호화하지 못했습니다.",
|
||||||
"uploadedSecurly": "업로드는 종단 간 암호화로 보호되므로 Excalidraw 서버 및 타사가 콘텐츠를 읽을 수 없습니다.",
|
"uploadedSecurly": "업로드는 종단 간 암호화로 보호되므로 Excalidraw 서버 및 타사가 콘텐츠를 읽을 수 없습니다.",
|
||||||
"loadSceneOverridePrompt": "외부 파일을 불러 오면 기존 콘텐츠가 대체됩니다. 계속 진행할까요?",
|
"loadSceneOverridePrompt": "외부 파일을 불러 오면 기존 콘텐츠가 대체됩니다. 계속 진행할까요?",
|
||||||
|
"collabStopOverridePrompt": "협업 세션을 종료하면 로컬 저장소에 있는 그림이 협업 세션의 그림으로 대체됩니다. 진행하겠습니까?\n\n(로컬 저장소에 있는 그림을 유지하려면 현재 브라우저 탭을 닫아주세요.)",
|
||||||
"errorLoadingLibrary": "외부 라이브러리를 불러오는 중에 문제가 발생했습니다.",
|
"errorLoadingLibrary": "외부 라이브러리를 불러오는 중에 문제가 발생했습니다.",
|
||||||
"confirmAddLibrary": "{{numShapes}}개의 모양이 라이브러리에 추가됩니다. 계속하시겠어요?",
|
"confirmAddLibrary": "{{numShapes}}개의 모양이 라이브러리에 추가됩니다. 계속하시겠어요?",
|
||||||
"imageDoesNotContainScene": "이미지에서 불러오기는 현재 지원되지 않습니다.\n\n화면을 불러오려고 하셨나요? 이미지에 화면 정보가 없는 것 같습니다. 내보낼 때 화면을 포함했나요?",
|
"imageDoesNotContainScene": "이미지에서 불러오기는 현재 지원되지 않습니다.\n\n화면을 불러오려고 하셨나요? 이미지에 화면 정보가 없는 것 같습니다. 내보낼 때 화면을 포함했나요?",
|
||||||
"cannotRestoreFromImage": "이미지 파일에서 화면을 복구할 수 없었습니다"
|
"cannotRestoreFromImage": "이미지 파일에서 화면을 복구할 수 없었습니다",
|
||||||
|
"resetLibrary": ""
|
||||||
},
|
},
|
||||||
"toolBar": {
|
"toolBar": {
|
||||||
"selection": "선택",
|
"selection": "선택",
|
||||||
@@ -201,25 +203,25 @@
|
|||||||
"title": "오류"
|
"title": "오류"
|
||||||
},
|
},
|
||||||
"helpDialog": {
|
"helpDialog": {
|
||||||
"blog": "",
|
"blog": "블로그 읽어보기",
|
||||||
"click": "",
|
"click": "클릭",
|
||||||
"curvedArrow": "",
|
"curvedArrow": "곡선 화살표",
|
||||||
"curvedLine": "",
|
"curvedLine": "곡선",
|
||||||
"documentation": "",
|
"documentation": "설명서",
|
||||||
"drag": "",
|
"drag": "드래그",
|
||||||
"editor": "",
|
"editor": "에디터",
|
||||||
"github": "",
|
"github": "문제 제보하기",
|
||||||
"howto": "",
|
"howto": "가이드 참고하기",
|
||||||
"or": "",
|
"or": "또는",
|
||||||
"preventBinding": "",
|
"preventBinding": "화살표가 붙지 않게 하기",
|
||||||
"shapes": "",
|
"shapes": "도형",
|
||||||
"shortcuts": "",
|
"shortcuts": "키보드 단축키",
|
||||||
"textFinish": "",
|
"textFinish": "편집 완료 (텍스트)",
|
||||||
"textNewLine": "",
|
"textNewLine": "줄바꿈 (텍스트)",
|
||||||
"title": "",
|
"title": "도움말",
|
||||||
"view": "",
|
"view": "보기",
|
||||||
"zoomToFit": "",
|
"zoomToFit": "모든 요소가 보이도록 확대/축소",
|
||||||
"zoomToSelection": ""
|
"zoomToSelection": "선택 영역으로 확대/축소"
|
||||||
},
|
},
|
||||||
"encrypted": {
|
"encrypted": {
|
||||||
"tooltip": "그림은 종단 간 암호화되므로 Excalidraw의 서버는 절대로 내용을 알 수 없습니다."
|
"tooltip": "그림은 종단 간 암호화되므로 Excalidraw의 서버는 절대로 내용을 알 수 없습니다."
|
||||||
@@ -234,10 +236,16 @@
|
|||||||
"storage": "저장공간",
|
"storage": "저장공간",
|
||||||
"title": "덕후들을 위한 통계",
|
"title": "덕후들을 위한 통계",
|
||||||
"total": "합계",
|
"total": "합계",
|
||||||
|
"version": "버전",
|
||||||
|
"versionCopy": "복사하려면 클릭",
|
||||||
|
"versionNotAvailable": "해당 버전 사용 불가능",
|
||||||
"width": "너비"
|
"width": "너비"
|
||||||
},
|
},
|
||||||
"toast": {
|
"toast": {
|
||||||
"copyStyles": "",
|
"copyStyles": "스타일 복사.",
|
||||||
"copyToClipboardAsPng": ""
|
"copyToClipboard": "클립보드로 복사.",
|
||||||
|
"copyToClipboardAsPng": "클립보드로 PNG 이미지 복사.",
|
||||||
|
"fileSaved": "",
|
||||||
|
"fileSavedToFilename": ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+13
-5
@@ -68,7 +68,7 @@
|
|||||||
"layers": "အလွှာများ",
|
"layers": "အလွှာများ",
|
||||||
"actions": "လုပ်ဆောင်ချက်များ",
|
"actions": "လုပ်ဆောင်ချက်များ",
|
||||||
"language": "ဘာသာစကား",
|
"language": "ဘာသာစကား",
|
||||||
"createRoom": "တိုက်ရိုက်ပူးပေါင်းဆောင်ရွက်ရန်အဖွဲ့ဖွဲ့",
|
"liveCollaboration": "",
|
||||||
"duplicateSelection": "ပွား",
|
"duplicateSelection": "ပွား",
|
||||||
"untitled": "အမည်မရှိ",
|
"untitled": "အမည်မရှိ",
|
||||||
"name": "အမည်",
|
"name": "အမည်",
|
||||||
@@ -77,7 +77,7 @@
|
|||||||
"group": "အုပ်စုဖွဲ့",
|
"group": "အုပ်စုဖွဲ့",
|
||||||
"ungroup": "အုပ်စုဖျက်သိမ်း",
|
"ungroup": "အုပ်စုဖျက်သိမ်း",
|
||||||
"collaborators": "ပူးပေါင်းပါဝင်သူများ",
|
"collaborators": "ပူးပေါင်းပါဝင်သူများ",
|
||||||
"gridMode": "",
|
"showGrid": "",
|
||||||
"addToLibrary": "မှတ်တမ်းတင်",
|
"addToLibrary": "မှတ်တမ်းတင်",
|
||||||
"removeFromLibrary": "မှတ်တမ်းမှထုတ်",
|
"removeFromLibrary": "မှတ်တမ်းမှထုတ်",
|
||||||
"libraryLoadingMessage": "မှတ်တမ်းအား တင်သွင်းနေသည်…",
|
"libraryLoadingMessage": "မှတ်တမ်းအား တင်သွင်းနေသည်…",
|
||||||
@@ -117,7 +117,7 @@
|
|||||||
"edit": "ပြင်ဆင်",
|
"edit": "ပြင်ဆင်",
|
||||||
"undo": "ပြန်ထား",
|
"undo": "ပြန်ထား",
|
||||||
"redo": "ထပ်လုပ်",
|
"redo": "ထပ်လုပ်",
|
||||||
"roomDialog": "တိုက်ရိုက်ပူးပေါင်းမှုစတင်",
|
"resetLibrary": "",
|
||||||
"createNewRoom": "အခန်းသစ်ဖွဲ့",
|
"createNewRoom": "အခန်းသစ်ဖွဲ့",
|
||||||
"fullScreen": "",
|
"fullScreen": "",
|
||||||
"darkMode": "",
|
"darkMode": "",
|
||||||
@@ -136,10 +136,12 @@
|
|||||||
"decryptFailed": "အချက်အလက်ဖော်ယူ၍မရပါ။",
|
"decryptFailed": "အချက်အလက်ဖော်ယူ၍မရပါ။",
|
||||||
"uploadedSecurly": "တင်သွင်းအချက်အလက်များအား နှစ်ဘက်စွန်းတိုင်လျှို့ဝှက်စနစ်အသုံးပြု၍လုံခြုံစွာထိန်းသိမ်းထားပါသဖြင့် Excalidraw ဆာဗာနှင့်ဆက်စပ်အဖွဲ့အစည်းများပင်လျှင်မဖတ်ရှုနိုင်ပါ။",
|
"uploadedSecurly": "တင်သွင်းအချက်အလက်များအား နှစ်ဘက်စွန်းတိုင်လျှို့ဝှက်စနစ်အသုံးပြု၍လုံခြုံစွာထိန်းသိမ်းထားပါသဖြင့် Excalidraw ဆာဗာနှင့်ဆက်စပ်အဖွဲ့အစည်းများပင်လျှင်မဖတ်ရှုနိုင်ပါ။",
|
||||||
"loadSceneOverridePrompt": "လက်ရှိရေးဆွဲထားသမျှအား ပြင်ပမှတင်သွင်းသောပုံနှင့်အစားထိုးပါမည်။ ဆက်လက်ဆောင်ရွက်လိုပါသလား။",
|
"loadSceneOverridePrompt": "လက်ရှိရေးဆွဲထားသမျှအား ပြင်ပမှတင်သွင်းသောပုံနှင့်အစားထိုးပါမည်။ ဆက်လက်ဆောင်ရွက်လိုပါသလား။",
|
||||||
|
"collabStopOverridePrompt": "",
|
||||||
"errorLoadingLibrary": "ပြင်ပမှမှတ်တမ်းအားတင်သွင်းရာတွင်အမှားအယွင်းရှိနေသည်။",
|
"errorLoadingLibrary": "ပြင်ပမှမှတ်တမ်းအားတင်သွင်းရာတွင်အမှားအယွင်းရှိနေသည်။",
|
||||||
"confirmAddLibrary": "{{numShapes}} ခုသောပုံသဏ္ဌာန်အားမှတ်တမ်းတင်ပါမည်။ အတည်ပြုပါ။",
|
"confirmAddLibrary": "{{numShapes}} ခုသောပုံသဏ္ဌာန်အားမှတ်တမ်းတင်ပါမည်။ အတည်ပြုပါ။",
|
||||||
"imageDoesNotContainScene": "",
|
"imageDoesNotContainScene": "",
|
||||||
"cannotRestoreFromImage": "ဤပုံဖြင့်မြင်ကွင်းပြန်လည်မရယူနိုင်ပါ။"
|
"cannotRestoreFromImage": "ဤပုံဖြင့်မြင်ကွင်းပြန်လည်မရယူနိုင်ပါ။",
|
||||||
|
"resetLibrary": ""
|
||||||
},
|
},
|
||||||
"toolBar": {
|
"toolBar": {
|
||||||
"selection": "ရွေးချယ်",
|
"selection": "ရွေးချယ်",
|
||||||
@@ -234,10 +236,16 @@
|
|||||||
"storage": "သိုလှောင်ခန်း",
|
"storage": "သိုလှောင်ခန်း",
|
||||||
"title": "အက္ခရာများအတွက်အချက်အလက်များ",
|
"title": "အက္ခရာများအတွက်အချက်အလက်များ",
|
||||||
"total": "စုစုပေါင်း",
|
"total": "စုစုပေါင်း",
|
||||||
|
"version": "",
|
||||||
|
"versionCopy": "",
|
||||||
|
"versionNotAvailable": "",
|
||||||
"width": "အကျယ်"
|
"width": "အကျယ်"
|
||||||
},
|
},
|
||||||
"toast": {
|
"toast": {
|
||||||
"copyStyles": "",
|
"copyStyles": "",
|
||||||
"copyToClipboardAsPng": ""
|
"copyToClipboard": "",
|
||||||
|
"copyToClipboardAsPng": "",
|
||||||
|
"fileSaved": "",
|
||||||
|
"fileSavedToFilename": ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+13
-5
@@ -68,7 +68,7 @@
|
|||||||
"layers": "Lag",
|
"layers": "Lag",
|
||||||
"actions": "Handlinger",
|
"actions": "Handlinger",
|
||||||
"language": "Språk",
|
"language": "Språk",
|
||||||
"createRoom": "Del en sanntids-samarbeidsøkt",
|
"liveCollaboration": "Sanntids-samarbeid",
|
||||||
"duplicateSelection": "Dupliser",
|
"duplicateSelection": "Dupliser",
|
||||||
"untitled": "Uten navn",
|
"untitled": "Uten navn",
|
||||||
"name": "Navn",
|
"name": "Navn",
|
||||||
@@ -77,7 +77,7 @@
|
|||||||
"group": "Gruppér utvalg",
|
"group": "Gruppér utvalg",
|
||||||
"ungroup": "Avgruppér utvalg",
|
"ungroup": "Avgruppér utvalg",
|
||||||
"collaborators": "Samarbeidspartnere",
|
"collaborators": "Samarbeidspartnere",
|
||||||
"gridMode": "Rutevisning",
|
"showGrid": "Vis rutenett",
|
||||||
"addToLibrary": "Legg til i bibliotek",
|
"addToLibrary": "Legg til i bibliotek",
|
||||||
"removeFromLibrary": "Fjern fra bibliotek",
|
"removeFromLibrary": "Fjern fra bibliotek",
|
||||||
"libraryLoadingMessage": "Laster bibliotek…",
|
"libraryLoadingMessage": "Laster bibliotek…",
|
||||||
@@ -117,7 +117,7 @@
|
|||||||
"edit": "Rediger",
|
"edit": "Rediger",
|
||||||
"undo": "Angre",
|
"undo": "Angre",
|
||||||
"redo": "Gjør om",
|
"redo": "Gjør om",
|
||||||
"roomDialog": "Start sanntids-samarbeid",
|
"resetLibrary": "Nullstill bibliotek",
|
||||||
"createNewRoom": "Opprett et nytt rom",
|
"createNewRoom": "Opprett et nytt rom",
|
||||||
"fullScreen": "Fullskjerm",
|
"fullScreen": "Fullskjerm",
|
||||||
"darkMode": "Mørk modus",
|
"darkMode": "Mørk modus",
|
||||||
@@ -136,10 +136,12 @@
|
|||||||
"decryptFailed": "Kunne ikke dekryptere data.",
|
"decryptFailed": "Kunne ikke dekryptere data.",
|
||||||
"uploadedSecurly": "Opplastingen er kryptert og kan ikke leses av Excalidraw-serveren eller tredjeparter.",
|
"uploadedSecurly": "Opplastingen er kryptert og kan ikke leses av Excalidraw-serveren eller tredjeparter.",
|
||||||
"loadSceneOverridePrompt": "Å laste inn ekstern tegning vil erstatte det eksisterende innholdet. Ønsker du å fortsette?",
|
"loadSceneOverridePrompt": "Å laste inn ekstern tegning vil erstatte det eksisterende innholdet. Ønsker du å fortsette?",
|
||||||
|
"collabStopOverridePrompt": "Hvis du slutter økten, overskrives din forrige, lokalt lagrede tegning. Er du sikker?\n\n(Hvis du ønsker å beholde din lokale tegning, bare lukk nettleserfanen i stedet.)",
|
||||||
"errorLoadingLibrary": "Det oppstod en feil under lasting av tredjepartsbiblioteket.",
|
"errorLoadingLibrary": "Det oppstod en feil under lasting av tredjepartsbiblioteket.",
|
||||||
"confirmAddLibrary": "Dette vil legge til {{numShapes}} figur(er) i biblioteket ditt. Er du sikker?",
|
"confirmAddLibrary": "Dette vil legge til {{numShapes}} figur(er) i biblioteket ditt. Er du sikker?",
|
||||||
"imageDoesNotContainScene": "Importering av bilder støttes ikke for øyeblikket.\n\nVil du importere en scene? Dette bildet ser ikke ut til å inneholde noen scene-data. Har du aktivert dette under eksporten?",
|
"imageDoesNotContainScene": "Importering av bilder støttes ikke for øyeblikket.\n\nVil du importere en scene? Dette bildet ser ikke ut til å inneholde noen scene-data. Har du aktivert dette under eksporten?",
|
||||||
"cannotRestoreFromImage": "Scenen kunne ikke gjenopprettes fra denne bildefilen"
|
"cannotRestoreFromImage": "Scenen kunne ikke gjenopprettes fra denne bildefilen",
|
||||||
|
"resetLibrary": "Dette vil tømme biblioteket ditt. Er du sikker?"
|
||||||
},
|
},
|
||||||
"toolBar": {
|
"toolBar": {
|
||||||
"selection": "Velg",
|
"selection": "Velg",
|
||||||
@@ -234,10 +236,16 @@
|
|||||||
"storage": "Lagring",
|
"storage": "Lagring",
|
||||||
"title": "Statistikk for nerder",
|
"title": "Statistikk for nerder",
|
||||||
"total": "Totalt",
|
"total": "Totalt",
|
||||||
|
"version": "Versjon",
|
||||||
|
"versionCopy": "Klikk for å kopiere",
|
||||||
|
"versionNotAvailable": "Versjon ikke tilgjengelig",
|
||||||
"width": "Bredde"
|
"width": "Bredde"
|
||||||
},
|
},
|
||||||
"toast": {
|
"toast": {
|
||||||
"copyStyles": "Kopierte stiler.",
|
"copyStyles": "Kopierte stiler.",
|
||||||
"copyToClipboardAsPng": "Kopiert til utklippstavlen som PNG."
|
"copyToClipboard": "Kopiert til utklippstavlen.",
|
||||||
|
"copyToClipboardAsPng": "Kopiert til utklippstavlen som PNG.",
|
||||||
|
"fileSaved": "Fil lagret.",
|
||||||
|
"fileSavedToFilename": "Lagret til {filename}"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+15
-7
@@ -68,7 +68,7 @@
|
|||||||
"layers": "Lagen",
|
"layers": "Lagen",
|
||||||
"actions": "Acties",
|
"actions": "Acties",
|
||||||
"language": "Taal",
|
"language": "Taal",
|
||||||
"createRoom": "Deel een live-samenwerkingssessie",
|
"liveCollaboration": "Live Samenwerking",
|
||||||
"duplicateSelection": "Dupliceer",
|
"duplicateSelection": "Dupliceer",
|
||||||
"untitled": "Naamloos",
|
"untitled": "Naamloos",
|
||||||
"name": "Naam",
|
"name": "Naam",
|
||||||
@@ -77,12 +77,12 @@
|
|||||||
"group": "Groeperen",
|
"group": "Groeperen",
|
||||||
"ungroup": "Groep opheffen",
|
"ungroup": "Groep opheffen",
|
||||||
"collaborators": "Deelnemers",
|
"collaborators": "Deelnemers",
|
||||||
"gridMode": "Rasterweergave",
|
"showGrid": "Raster weergeven",
|
||||||
"addToLibrary": "Voeg toe aan bibliotheek",
|
"addToLibrary": "Voeg toe aan bibliotheek",
|
||||||
"removeFromLibrary": "Verwijder uit bibliotheek",
|
"removeFromLibrary": "Verwijder uit bibliotheek",
|
||||||
"libraryLoadingMessage": "Bibliotheek laden...",
|
"libraryLoadingMessage": "Bibliotheek laden…",
|
||||||
"libraries": "Blader door bibliotheken",
|
"libraries": "Blader door bibliotheken",
|
||||||
"loadingScene": "Scène laden...",
|
"loadingScene": "Scène laden…",
|
||||||
"align": "Uitlijnen",
|
"align": "Uitlijnen",
|
||||||
"alignTop": "Boven uitlijnen",
|
"alignTop": "Boven uitlijnen",
|
||||||
"alignBottom": "Onder uitlijnen",
|
"alignBottom": "Onder uitlijnen",
|
||||||
@@ -117,7 +117,7 @@
|
|||||||
"edit": "Bewerken",
|
"edit": "Bewerken",
|
||||||
"undo": "Ongedaan maken",
|
"undo": "Ongedaan maken",
|
||||||
"redo": "Herstel ongedaan maken",
|
"redo": "Herstel ongedaan maken",
|
||||||
"roomDialog": "Live-samenwerkingssessie starten",
|
"resetLibrary": "Bibliotheek Resetten",
|
||||||
"createNewRoom": "Creëer live-samenwerkingssessie",
|
"createNewRoom": "Creëer live-samenwerkingssessie",
|
||||||
"fullScreen": "Volledig scherm",
|
"fullScreen": "Volledig scherm",
|
||||||
"darkMode": "Donkere modus",
|
"darkMode": "Donkere modus",
|
||||||
@@ -136,10 +136,12 @@
|
|||||||
"decryptFailed": "Kan gegevens niet decoderen.",
|
"decryptFailed": "Kan gegevens niet decoderen.",
|
||||||
"uploadedSecurly": "De upload is beveiligd met end-to-end encryptie, wat betekent dat de Excalidraw server en derden de inhoud niet kunnen lezen.",
|
"uploadedSecurly": "De upload is beveiligd met end-to-end encryptie, wat betekent dat de Excalidraw server en derden de inhoud niet kunnen lezen.",
|
||||||
"loadSceneOverridePrompt": "Het laden van externe tekening zal uw bestaande inhoud vervangen. Wil je doorgaan?",
|
"loadSceneOverridePrompt": "Het laden van externe tekening zal uw bestaande inhoud vervangen. Wil je doorgaan?",
|
||||||
|
"collabStopOverridePrompt": "",
|
||||||
"errorLoadingLibrary": "Bij het laden van de externe bibliotheek is een fout opgetreden.",
|
"errorLoadingLibrary": "Bij het laden van de externe bibliotheek is een fout opgetreden.",
|
||||||
"confirmAddLibrary": "Hiermee worden {{numShapes}} vorm(n) aan uw bibliotheek toegevoegd. Ben je het zeker?",
|
"confirmAddLibrary": "Hiermee worden {{numShapes}} vorm(n) aan uw bibliotheek toegevoegd. Ben je het zeker?",
|
||||||
"imageDoesNotContainScene": "Afbeeldingen importeren wordt op dit moment niet ondersteund.\n\nWil je een scène importeren? Deze afbeelding lijkt geen scène gegevens te bevatten. Heb je dit geactiveerd tijdens het exporteren?",
|
"imageDoesNotContainScene": "Afbeeldingen importeren wordt op dit moment niet ondersteund.\n\nWil je een scène importeren? Deze afbeelding lijkt geen scène gegevens te bevatten. Heb je dit geactiveerd tijdens het exporteren?",
|
||||||
"cannotRestoreFromImage": "Scène kan niet worden hersteld vanuit dit afbeeldingsbestand"
|
"cannotRestoreFromImage": "Scène kan niet worden hersteld vanuit dit afbeeldingsbestand",
|
||||||
|
"resetLibrary": "Dit zal je bibliotheek wissen. Weet je het zeker?"
|
||||||
},
|
},
|
||||||
"toolBar": {
|
"toolBar": {
|
||||||
"selection": "Selectie",
|
"selection": "Selectie",
|
||||||
@@ -234,10 +236,16 @@
|
|||||||
"storage": "Opslag",
|
"storage": "Opslag",
|
||||||
"title": "Statistieken voor nerds",
|
"title": "Statistieken voor nerds",
|
||||||
"total": "Totaal",
|
"total": "Totaal",
|
||||||
|
"version": "Versie",
|
||||||
|
"versionCopy": "Klik om te kopiëren",
|
||||||
|
"versionNotAvailable": "Versie niet beschikbaar",
|
||||||
"width": "Breedte"
|
"width": "Breedte"
|
||||||
},
|
},
|
||||||
"toast": {
|
"toast": {
|
||||||
"copyStyles": "Stijlen gekopieerd.",
|
"copyStyles": "Stijlen gekopieerd.",
|
||||||
"copyToClipboardAsPng": "Gekopieerd naar klembord als PNG."
|
"copyToClipboard": "Gekopieerd naar het klembord.",
|
||||||
|
"copyToClipboardAsPng": "Gekopieerd naar klembord als PNG.",
|
||||||
|
"fileSaved": "Bestand opgeslagen.",
|
||||||
|
"fileSavedToFilename": "Opgeslagen als {filename}"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+13
-5
@@ -68,7 +68,7 @@
|
|||||||
"layers": "Lag",
|
"layers": "Lag",
|
||||||
"actions": "Handlingar",
|
"actions": "Handlingar",
|
||||||
"language": "Språk",
|
"language": "Språk",
|
||||||
"createRoom": "Del ei sanntids-samarbeidsøkt",
|
"liveCollaboration": "",
|
||||||
"duplicateSelection": "Dupliser",
|
"duplicateSelection": "Dupliser",
|
||||||
"untitled": "Utan namn",
|
"untitled": "Utan namn",
|
||||||
"name": "Namn",
|
"name": "Namn",
|
||||||
@@ -77,7 +77,7 @@
|
|||||||
"group": "Grupper utval",
|
"group": "Grupper utval",
|
||||||
"ungroup": "Avgrupper utval",
|
"ungroup": "Avgrupper utval",
|
||||||
"collaborators": "Samarbeidarar",
|
"collaborators": "Samarbeidarar",
|
||||||
"gridMode": "Rutevisning",
|
"showGrid": "",
|
||||||
"addToLibrary": "Legg til i bibliotek",
|
"addToLibrary": "Legg til i bibliotek",
|
||||||
"removeFromLibrary": "Fjern frå bibliotek",
|
"removeFromLibrary": "Fjern frå bibliotek",
|
||||||
"libraryLoadingMessage": "Laster bibliotek…",
|
"libraryLoadingMessage": "Laster bibliotek…",
|
||||||
@@ -117,7 +117,7 @@
|
|||||||
"edit": "Rediger",
|
"edit": "Rediger",
|
||||||
"undo": "Angre",
|
"undo": "Angre",
|
||||||
"redo": "Gjer om",
|
"redo": "Gjer om",
|
||||||
"roomDialog": "Start sanntids-samarbeid",
|
"resetLibrary": "",
|
||||||
"createNewRoom": "Lag nytt rom",
|
"createNewRoom": "Lag nytt rom",
|
||||||
"fullScreen": "Fullskjerm",
|
"fullScreen": "Fullskjerm",
|
||||||
"darkMode": "Mørk modus",
|
"darkMode": "Mørk modus",
|
||||||
@@ -136,10 +136,12 @@
|
|||||||
"decryptFailed": "Kunne ikkje dekryptere data.",
|
"decryptFailed": "Kunne ikkje dekryptere data.",
|
||||||
"uploadedSecurly": "Opplastinga er kryptert og er ikkje mogleg å lese av Excalidraw-serveren eller tredjepartar.",
|
"uploadedSecurly": "Opplastinga er kryptert og er ikkje mogleg å lese av Excalidraw-serveren eller tredjepartar.",
|
||||||
"loadSceneOverridePrompt": "Innlasting av ekstern teikning erstattar ditt eksisterande innhald. Ynskjer du å fortsette?",
|
"loadSceneOverridePrompt": "Innlasting av ekstern teikning erstattar ditt eksisterande innhald. Ynskjer du å fortsette?",
|
||||||
|
"collabStopOverridePrompt": "",
|
||||||
"errorLoadingLibrary": "Det oppstod ein feil under lastinga av tredjepartsbibliotek.",
|
"errorLoadingLibrary": "Det oppstod ein feil under lastinga av tredjepartsbibliotek.",
|
||||||
"confirmAddLibrary": "Dette vil legge til {{numShapes}} form(er) i biblioteket ditt. Er du sikker?",
|
"confirmAddLibrary": "Dette vil legge til {{numShapes}} form(er) i biblioteket ditt. Er du sikker?",
|
||||||
"imageDoesNotContainScene": "Importering av bilder støttes ikkje for p. t.\n\nVil du importere ein scene? Dette bildet ser ikkje ut til å inneholde noen scene-data. Har du aktivert dette under eksporten?",
|
"imageDoesNotContainScene": "Importering av bilder støttes ikkje for p. t.\n\nVil du importere ein scene? Dette bildet ser ikkje ut til å inneholde noen scene-data. Har du aktivert dette under eksporten?",
|
||||||
"cannotRestoreFromImage": "Scena kunne ikkje gjenopprettast frå denne biletfila"
|
"cannotRestoreFromImage": "Scena kunne ikkje gjenopprettast frå denne biletfila",
|
||||||
|
"resetLibrary": ""
|
||||||
},
|
},
|
||||||
"toolBar": {
|
"toolBar": {
|
||||||
"selection": "Vel",
|
"selection": "Vel",
|
||||||
@@ -234,10 +236,16 @@
|
|||||||
"storage": "Lagring",
|
"storage": "Lagring",
|
||||||
"title": "Statistikk for nerdar",
|
"title": "Statistikk for nerdar",
|
||||||
"total": "Totalt",
|
"total": "Totalt",
|
||||||
|
"version": "",
|
||||||
|
"versionCopy": "",
|
||||||
|
"versionNotAvailable": "",
|
||||||
"width": "Breidde"
|
"width": "Breidde"
|
||||||
},
|
},
|
||||||
"toast": {
|
"toast": {
|
||||||
"copyStyles": "",
|
"copyStyles": "",
|
||||||
"copyToClipboardAsPng": ""
|
"copyToClipboard": "",
|
||||||
|
"copyToClipboardAsPng": "",
|
||||||
|
"fileSaved": "",
|
||||||
|
"fileSavedToFilename": ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user