Compare commits

...

19 Commits

Author SHA1 Message Date
dwelle 5dbdbb0b6b docs: add excalidraw codebase section 2023-02-18 11:47:37 +01:00
Aakansha Doshi b9ba407f96 feat: Bind text to container if double clicked on filled shape or stroke (#6250)
* feat: bind text to container when clicked on filled shape or element stroke

* Bind if double clicked on stroke as well

* remove

* specs

* remove

* shuffle

* fix

* back to normal
2023-02-16 20:46:51 +05:30
Aakansha Doshi 5acb99777a docs: fix typo (#6252) 2023-02-16 19:45:41 +05:30
David Luzar b107c9af2a docs: fix next.js example (#6241) 2023-02-15 15:14:15 +01:00
Milos Vetesnik c587b85b4e docs: new readme (#6240)
Co-authored-by: David Luzar <luzar.david@gmail.com>
2023-02-15 14:45:06 +01:00
Luka Hietala 9686141113 docs: Fixed broken codesandbox link in the dev-docs (#6229)
fixed broken link
2023-02-15 05:31:07 +00:00
Aakansha Doshi 0d7ee891e0 feat: Make repair and refreshDimensions configurable in restoreElements (#6238)
* fix: don't repair during reconcilation

* Add opts to restoreElement and enable refreshDimensions and repair via config

* remove

* update changelog

* fix tests

* rename to repairBindings
2023-02-15 10:41:11 +05:30
Aakansha Doshi 71fb60394a docs: enable Algolia for search (#6230) 2023-02-13 17:39:11 +05:30
Aakansha Doshi c9d18ecab6 fix: don't allow blank space in collab name (#6211)
* don't allow blank space in collab name

* add spec

* prevent blank
2023-02-09 15:51:49 +05:30
DanielJGeiger 8c1168ef33 refactor: Make the example React app reusable without duplication (#6188) 2023-02-07 12:41:20 +05:30
Dejavu Moe c3c45a8c37 fix: docker build architecture:linux/amd64 error occur on linux/arm64 instance (#6197)
fix docker build
when in linux/arm64 use docker buildx plugin to build linux/amd64 image, a build error will occur causing the build to break
2023-02-07 11:44:31 +05:30
Matthieu Rossignon a8e6028c33 feat: show error message when not connected to internet while collabo… (#6165)
Co-authored-by: dwelle <luzar.david@gmail.com>
Resolves https://github.com/excalidraw/excalidraw/issues/5994
2023-02-04 15:03:39 +01:00
Jang Min 11e2f90ca1 feat: shortcut for clearCanvas confirmDialog (#6114)
Co-authored-by: dwelle <luzar.david@gmail.com>
resolve https://github.com/excalidraw/excalidraw/issues/5818
2023-02-04 13:33:40 +01:00
Excalidraw Bot 4db87a0b6a chore: Update translations from Crowdin (#6150) 2023-02-04 10:04:15 +01:00
David Luzar 4414069617 feat: disable canvas smoothing (antialiasing) for right-angled elements (#6186)Co-authored-by: Ignacio Cuadra <67276174+ignacio-cuadra@users.noreply.github.com>
* feat: disable canvas smoothing for text and other types

* disable smoothing for all right-angled elements

* Update src/renderer/renderElement.ts

Co-authored-by: Ignacio Cuadra <67276174+ignacio-cuadra@users.noreply.github.com>

* Update src/renderer/renderElement.ts

Co-authored-by: Ignacio Cuadra <67276174+ignacio-cuadra@users.noreply.github.com>

* fix lint

* always enable smoothing while zooming

---------

Co-authored-by: Ignacio Cuadra <67276174+ignacio-cuadra@users.noreply.github.com>
2023-02-03 17:07:14 +01:00
Ryan Di a9c5bdb878 fix: sort bound text elements to fix text duplication z-index error (#5130)
* fix: sort bound text elements to fix text duplication z-index error

* improve & sort groups & add tests

* fix backtracking and discontiguous groups

---------

Co-authored-by: dwelle <luzar.david@gmail.com>
2023-02-02 16:23:39 +08:00
Aakansha Doshi 5a0334f37f fix: hide welcome screen on mobile once user interacts (#6185)
* fix: hide welcome screen on mobile once started drawing

* Add specs
2023-02-02 12:58:45 +05:30
Aakansha Doshi d8a4ca6911 docs: show last updated time and author (#6183)
docs:show last updated time and author
2023-02-01 21:09:23 +05:30
Aakansha Doshi eb9eeefc63 fix: edit link in docs (#6182) 2023-02-01 20:27:31 +05:30
97 changed files with 1867 additions and 464 deletions
+1 -1
View File
@@ -3,7 +3,7 @@ FROM node:14-alpine AS build
WORKDIR /opt/node_app WORKDIR /opt/node_app
COPY package.json yarn.lock ./ COPY package.json yarn.lock ./
RUN yarn --ignore-optional RUN yarn --ignore-optional --network-timeout 600000
ARG NODE_ENV=production ARG NODE_ENV=production
+111 -29
View File
@@ -1,29 +1,121 @@
<div align="center" style="display:flex;flex-direction:column;"}> <a href="https://excalidraw.com/" target="_blank" rel="noopener">
<a href="https://excalidraw.com"> <picture>
<img width="540" src="./public/og-image-sm.png" alt="Excalidraw logo: Sketch handrawn like diagrams."/> <source media="(prefers-color-scheme: dark)" alt="Excalidraw" srcset="https://excalidraw.nyc3.cdn.digitaloceanspaces.com/github%2FExcalidraw_Github_cover_dark.png" />
</a> <img alt="Excalidraw" src="https://excalidraw.nyc3.cdn.digitaloceanspaces.com/github%2FExcalidraw_Github_cover.png" />
<h3>Virtual whiteboard for sketching hand-drawn like diagrams.<br/>Collaborative and end-to-end encrypted.</h3> </picture>
<p> </a>
<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"/> <h4 align="center">
</a> <a href="https://excalidraw.com">Excalidraw Editor</a> |
<a href="https://discord.gg/UexuTaE"> <a href="https://blog.excalidraw.com">Blog</a> |
<img alt="Chat with us on Discord" src="https://img.shields.io/discord/723672430744174682?color=738ad6&label=Chat%20on%20Discord&logo=discord&logoColor=ffffff&widge=false"/> <a href="https://docs.excalidraw.com">Documentation</a> |
</a> <a href="https://plus.excalidraw.com">Excalidraw+</a>
</p> </h4>
<div align="center">
<h2>
An open source virtual hand-drawn style whiteboard. </br>
Collaborative and end-to-end encrypted. </br>
<br />
</h3>
</div> </div>
## Try now <br />
<p align="center">
<a href="https://github.com/excalidraw/excalidraw/blob/master/LICENSE">
<img alt="Excalidraw is released under the MIT license." src="https://img.shields.io/badge/license-MIT-blue.svg" />
</a>
<a href="https://docs.excalidraw.com/docs/introduction/contributing">
<img alt="PRs welcome!" src="https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat" />
</a>
<a href="https://discord.gg/UexuTaE">
<img alt="Chat on Discord" src="https://img.shields.io/discord/723672430744174682?color=738ad6&label=Chat%20on%20Discord&logo=discord&logoColor=ffffff&widge=false"/>
</a>
<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"/>
</a>
</p>
Visit [excalidraw.com](https://excalidraw.com) to start sketching. <div align="center">
<figure>
<a href="https://excalidraw.com" target="_blank" rel="noopener">
<img src="https://excalidraw.nyc3.cdn.digitaloceanspaces.com/github%2Fproduct_showcase.png" alt="Product showcase" />
</a>
<figcaption>
<p align="center">
Create beautiful hand-drawn like diagrams, wireframes, or whatever you like.
</p>
</figcaption>
</figure>
</div>
## Community ## Features
For latest updates, follow us on [twitter](https://twitter.com/excalidraw). If you need help or want to chat, join us on [Discord](https://discord.gg/UexuTaE). For releases and deep dives, check out our [blog](https://blog.excalidraw.com). Report bugs on [GitHub](https://github.com/excalidraw/excalidraw/issues). The Excalidraw editor (npm package) supports:
## Supporting Excalidraw - 💯&nbsp;Free & open-source.
- 🎨&nbsp;Infinite, canvas-based whiteboard.
- ✍️&nbsp;Hand-drawn like style.
- 🌓&nbsp;Dark mode.
- 🏗️&nbsp;Customizable.
- 📷&nbsp;Image support.
- 😀&nbsp;Shape libraries support.
- 👅&nbsp;Localization (i18n) support.
- 🖼️&nbsp;Export to PNG, SVG & clipboard.
- 💾&nbsp;Open format - export drawings as an `.excalidraw` json file.
- ⚒️&nbsp;Wide range of tools - rectangle, circle, diamond, arrow, line, free-draw, eraser...
- ➡️&nbsp;Arrow-binding & labeled arrows.
- 🔙&nbsp;Undo / Redo.
- 🔍&nbsp;Zoom and panning support.
If you like the project, you can become a sponsor at [Open Collective](https://opencollective.com/excalidraw). ## Excalidraw.com
The app hosted at [excalidraw.com](https://excalidraw.com) is a minimal showcase of what you can build with Excalidraw. Its [source code](https://github.com/excalidraw/excalidraw/tree/maielo/new-readme/src/excalidraw-app) is part of this repository as well, and the app features:
- 📡&nbsp;PWA support (works offline).
- 🤼&nbsp;Real-time collaboration.
- 🔒&nbsp;End-to-end encryption.
- 💾&nbsp;Local-first support (autosaves to the browser).
- 🔗&nbsp;Shareable links (export to a readonly link you can share with others).
We'll be adding these features as drop-in plugins for the npm package in the future.
## Quick start
Install the [Excalidraw npm package](https://www.npmjs.com/package/@excalidraw/excalidraw):
```
npm install react react-dom @excalidraw/excalidraw
```
or via yarn
```
yarn add react react-dom @excalidraw/excalidraw
```
Don't forget to check out our [Documentation](https://docs.excalidraw.com)!
## Contributing
- Missing something or found a bug? [Report here](https://github.com/excalidraw/excalidraw/issues).
- Want to contribute? Check out our [contribution guide](https://docs.excalidraw.com/docs/introduction/contributing) or let us know on [Discord](https://discord.gg/UexuTaE).
- Want to help with translations? See the [translation guide](https://docs.excalidraw.com/docs/introduction/contributing#translating).
## Integrations
- [VScode extension](https://marketplace.visualstudio.com/items?itemName=pomdtr.excalidraw-editor)
- [npm package](https://www.npmjs.com/package/@excalidraw/excalidraw)
## Who's integrating Excalidraw
[Google Cloud](https://googlecloudcheatsheet.withgoogle.com/architecture) • [Meta](https://meta.com/) • [CodeSandbox](https://codesandbox.io/) • [Obsidian Excalidraw](https://github.com/zsviczian/obsidian-excalidraw-plugin) • [Replit](https://replit.com/) • [Slite](https://slite.com/) • [Notion](https://notion.so/) • [HackerRank](https://www.hackerrank.com/) • and many others
## Sponsors & support
If you like the project, you can become a sponsor at [Open Collective](https://opencollective.com/excalidraw) or use [Excalidraw+](https://plus.excalidraw.com/).
## Thank you for supporting 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) [<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)
@@ -32,13 +124,3 @@ If you like the project, you can become a sponsor at [Open Collective](https://o
Last but not least, we're thankful to these companies for offering their services for free: Last but not least, we're thankful to these companies for offering their services for free:
[![Vercel](./.github/assets/vercel.svg)](https://vercel.com) [![Sentry](./.github/assets/sentry.svg)](https://sentry.io) [![Crowdin](./.github/assets/crowdin.svg)](https://crowdin.com) [![Vercel](./.github/assets/vercel.svg)](https://vercel.com) [![Sentry](./.github/assets/sentry.svg)](https://sentry.io) [![Crowdin](./.github/assets/crowdin.svg)](https://crowdin.com)
## Developers
You can integrate Excalidraw into your app by installing our [npm component](https://npmjs.com/package/@excalidraw/excalidraw).
Visit our documentation on [https://docs.excalidraw.com](https://docs.excalidraw.com).
## Who's integrating Excalidraw
[Google Cloud](https://googlecloudcheatsheet.withgoogle.com/architecture) • [Meta](https://meta.com/) • [CodeSandbox](https://codesandbox.io/) • [Obsidian Excalidraw](https://github.com/zsviczian/obsidian-excalidraw-plugin) • [Replit](https://replit.com/) • [Slite](https://slite.com/) • [Notion](https://notion.so/) • [HackerRank](https://www.hackerrank.com/)
@@ -53,7 +53,7 @@ Parameter `refreshDimensions` indicates whether we should also `recalculate` tex
**_Signature_** **_Signature_**
<pre> <pre>
restoreElements( restore(
data: <a href="https://github.com/excalidraw/excalidraw/blob/master/src/data/types.ts#L34">ImportedDataState</a>,<br/>&nbsp; data: <a href="https://github.com/excalidraw/excalidraw/blob/master/src/data/types.ts#L34">ImportedDataState</a>,<br/>&nbsp;
localAppState: Partial&lt;<a href="https://github.com/excalidraw/excalidraw/blob/master/src/types.ts#L95">AppState</a>> | null | undefined,<br/>&nbsp; localAppState: Partial&lt;<a href="https://github.com/excalidraw/excalidraw/blob/master/src/types.ts#L95">AppState</a>> | null | undefined,<br/>&nbsp;
localElements: <a href="https://github.com/excalidraw/excalidraw/blob/master/src/element/types.ts#L114">ExcalidrawElement[]</a> | null | undefined<br/>): <a href="https://github.com/excalidraw/excalidraw/blob/master/src/data/types.ts#L4">DataState</a> localElements: <a href="https://github.com/excalidraw/excalidraw/blob/master/src/element/types.ts#L114">ExcalidrawElement[]</a> | null | undefined<br/>): <a href="https://github.com/excalidraw/excalidraw/blob/master/src/data/types.ts#L4">DataState</a>
@@ -34,14 +34,16 @@ function App() {
Since _Excalidraw_ doesn't support server side rendering, you should render the component once the host is `mounted`. Since _Excalidraw_ doesn't support server side rendering, you should render the component once the host is `mounted`.
The following worfklow shows one way how to render Excalidraw on Next.js. We'll add more detailed and alternative Next.js examples, soon.
```jsx showLineNumbers ```jsx showLineNumbers
import { useState, useEffect } from "react"; import { useState, useEffect } from "react";
export default function App() { export default function App() {
const [Comp, setComp] = useState(null); const [Excalidraw, setExcalidraw] = useState(null);
useEffect(() => { useEffect(() => {
import("@excalidraw/excalidraw").then((comp) => setComp(comp.default)); import("@excalidraw/excalidraw").then((comp) => setExcalidraw(comp.Excalidraw));
}, []); }, []);
return <>{Comp && <Comp />}</>; return <>{Excalidraw && <Excalidraw />}</>;
} }
``` ```
@@ -0,0 +1,6 @@
---
title: Introduction to the codebase
slug: ../
---
This section is documenting the Excalidraw codebase itself for developers who want to contribute to the project.
+1 -1
View File
@@ -20,7 +20,7 @@ Pull requests are welcome. For major changes, please [open an issue](https://git
### Option 2 - CodeSandbox ### Option 2 - CodeSandbox
1. Go to https://codesandbox.io/s/github/excalidraw/excalidraw 1. Go to https://codesandbox.io/p/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`
+9 -1
View File
@@ -30,7 +30,10 @@ const config = {
docs: { docs: {
sidebarPath: require.resolve("./sidebars.js"), sidebarPath: require.resolve("./sidebars.js"),
// Please change this to your repo. // Please change this to your repo.
editUrl: "https://github.com/excalidraw/docs/tree/master/", editUrl:
"https://github.com/excalidraw/excalidraw/tree/master/dev-docs/",
showLastUpdateAuthor: true,
showLastUpdateTime: true,
}, },
theme: { theme: {
customCss: [ customCss: [
@@ -129,6 +132,11 @@ const config = {
tableOfContents: { tableOfContents: {
maxHeadingLevel: 4, maxHeadingLevel: 4,
}, },
algolia: {
appId: "8FEAOD28DI",
apiKey: "4b07cca33ff2d2919bc95ff98f148e9e",
indexName: "excalidraw",
},
}), }),
themes: ["@docusaurus/theme-live-codeblock"], themes: ["@docusaurus/theme-live-codeblock"],
plugins: ["docusaurus-plugin-sass"], plugins: ["docusaurus-plugin-sass"],
+10
View File
@@ -92,6 +92,16 @@ const sidebars = {
"@excalidraw/excalidraw/development", "@excalidraw/excalidraw/development",
], ],
}, },
{
type: "category",
label: "Excalidraw codebase",
link: {
type: "doc",
id: "codebase/introduction-to-the-codebase",
},
items: [],
},
], ],
}; };
+3 -1
View File
@@ -154,7 +154,9 @@ export const actionDeleteSelected = register({
}; };
}, },
contextItemLabel: "labels.delete", contextItemLabel: "labels.delete",
keyTest: (event) => event.key === KEYS.BACKSPACE || event.key === KEYS.DELETE, keyTest: (event, appState, elements) =>
(event.key === KEYS.BACKSPACE || event.key === KEYS.DELETE) &&
!event[KEYS.CTRL_OR_CMD],
PanelComponent: ({ elements, appState, updateData }) => ( PanelComponent: ({ elements, appState, updateData }) => (
<ToolButton <ToolButton
type="button" type="button"
+100 -21
View File
@@ -16,8 +16,12 @@ import { AppState } from "../types";
import { fixBindingsAfterDuplication } from "../element/binding"; import { fixBindingsAfterDuplication } from "../element/binding";
import { ActionResult } from "./types"; import { ActionResult } from "./types";
import { GRID_SIZE } from "../constants"; import { GRID_SIZE } from "../constants";
import { bindTextToShapeAfterDuplication } from "../element/textElement"; import {
bindTextToShapeAfterDuplication,
getBoundTextElement,
} from "../element/textElement";
import { isBoundToContainer } from "../element/typeChecks"; import { isBoundToContainer } from "../element/typeChecks";
import { normalizeElementOrder } from "../element/sortElements";
import { DuplicateIcon } from "../components/icons"; import { DuplicateIcon } from "../components/icons";
export const actionDuplicateSelection = register({ export const actionDuplicateSelection = register({
@@ -64,6 +68,11 @@ const duplicateElements = (
elements: readonly ExcalidrawElement[], elements: readonly ExcalidrawElement[],
appState: AppState, appState: AppState,
): Partial<ActionResult> => { ): Partial<ActionResult> => {
// ---------------------------------------------------------------------------
// step (1)
const sortedElements = normalizeElementOrder(elements);
const groupIdMap = new Map(); const groupIdMap = new Map();
const newElements: ExcalidrawElement[] = []; const newElements: ExcalidrawElement[] = [];
const oldElements: ExcalidrawElement[] = []; const oldElements: ExcalidrawElement[] = [];
@@ -85,42 +94,112 @@ const duplicateElements = (
return newElement; return newElement;
}; };
const finalElements: ExcalidrawElement[] = [];
let index = 0;
const selectedElementIds = arrayToMap( const selectedElementIds = arrayToMap(
getSelectedElements(elements, appState, true), getSelectedElements(sortedElements, appState, true),
); );
while (index < elements.length) {
const element = elements[index]; // Ids of elements that have already been processed so we don't push them
// into the array twice if we end up backtracking when retrieving
// discontiguous group of elements (can happen due to a bug, or in edge
// cases such as a group containing deleted elements which were not selected).
//
// This is not enough to prevent duplicates, so we do a second loop afterwards
// to remove them.
//
// For convenience we mark even the newly created ones even though we don't
// loop over them.
const processedIds = new Map<ExcalidrawElement["id"], true>();
const markAsProcessed = (elements: ExcalidrawElement[]) => {
for (const element of elements) {
processedIds.set(element.id, true);
}
return elements;
};
const elementsWithClones: ExcalidrawElement[] = [];
let index = -1;
while (++index < sortedElements.length) {
const element = sortedElements[index];
if (processedIds.get(element.id)) {
continue;
}
const boundTextElement = getBoundTextElement(element);
if (selectedElementIds.get(element.id)) { if (selectedElementIds.get(element.id)) {
if (element.groupIds.length) { // if a group or a container/bound-text, duplicate atomically
if (element.groupIds.length || boundTextElement) {
const groupId = getSelectedGroupForElement(appState, element); const groupId = getSelectedGroupForElement(appState, element);
// if group selected, duplicate it atomically
if (groupId) { if (groupId) {
const groupElements = getElementsInGroup(elements, groupId); const groupElements = getElementsInGroup(sortedElements, groupId);
finalElements.push( elementsWithClones.push(
...groupElements, ...markAsProcessed([
...groupElements.map((element) => ...groupElements,
duplicateAndOffsetElement(element), ...groupElements.map((element) =>
), duplicateAndOffsetElement(element),
),
]),
);
continue;
}
if (boundTextElement) {
elementsWithClones.push(
...markAsProcessed([
element,
boundTextElement,
duplicateAndOffsetElement(element),
duplicateAndOffsetElement(boundTextElement),
]),
); );
index = index + groupElements.length;
continue; continue;
} }
} }
finalElements.push(element, duplicateAndOffsetElement(element)); elementsWithClones.push(
...markAsProcessed([element, duplicateAndOffsetElement(element)]),
);
} else { } else {
finalElements.push(element); elementsWithClones.push(...markAsProcessed([element]));
} }
index++;
} }
// step (2)
// second pass to remove duplicates. We loop from the end as it's likelier
// that the last elements are in the correct order (contiguous or otherwise).
// Thus we need to reverse as the last step (3).
const finalElementsReversed: ExcalidrawElement[] = [];
const finalElementIds = new Map<ExcalidrawElement["id"], true>();
index = elementsWithClones.length;
while (--index >= 0) {
const element = elementsWithClones[index];
if (!finalElementIds.get(element.id)) {
finalElementIds.set(element.id, true);
finalElementsReversed.push(element);
}
}
// step (3)
const finalElements = finalElementsReversed.reverse();
// ---------------------------------------------------------------------------
bindTextToShapeAfterDuplication( bindTextToShapeAfterDuplication(
finalElements, elementsWithClones,
oldElements,
oldIdToDuplicatedId,
);
fixBindingsAfterDuplication(
elementsWithClones,
oldElements, oldElements,
oldIdToDuplicatedId, oldIdToDuplicatedId,
); );
fixBindingsAfterDuplication(finalElements, oldElements, oldIdToDuplicatedId);
return { return {
elements: finalElements, elements: finalElements,
+2
View File
@@ -8,6 +8,7 @@ export type ShortcutName =
ActionName, ActionName,
| "toggleTheme" | "toggleTheme"
| "loadScene" | "loadScene"
| "clearCanvas"
| "cut" | "cut"
| "copy" | "copy"
| "paste" | "paste"
@@ -41,6 +42,7 @@ const shortcutMap: Record<ShortcutName, string[]> = {
toggleTheme: [getShortcutKey("Shift+Alt+D")], toggleTheme: [getShortcutKey("Shift+Alt+D")],
saveScene: [getShortcutKey("CtrlOrCmd+S")], saveScene: [getShortcutKey("CtrlOrCmd+S")],
loadScene: [getShortcutKey("CtrlOrCmd+O")], loadScene: [getShortcutKey("CtrlOrCmd+O")],
clearCanvas: [getShortcutKey("CtrlOrCmd+Delete")],
imageExport: [getShortcutKey("CtrlOrCmd+Shift+E")], imageExport: [getShortcutKey("CtrlOrCmd+Shift+E")],
cut: [getShortcutKey("CtrlOrCmd+X")], cut: [getShortcutKey("CtrlOrCmd+X")],
copy: [getShortcutKey("CtrlOrCmd+C")], copy: [getShortcutKey("CtrlOrCmd+C")],
+1 -1
View File
@@ -21,7 +21,7 @@ export const getClientColors = (clientId: string, appState: AppState) => {
}; };
export const getClientInitials = (userName?: string | null) => { export const getClientInitials = (userName?: string | null) => {
if (!userName) { if (!userName?.trim()) {
return "?"; return "?";
} }
return userName.trim()[0].toUpperCase(); return userName.trim()[0].toUpperCase();
+20 -3
View File
@@ -226,6 +226,7 @@ import {
setEraserCursor, setEraserCursor,
updateActiveTool, updateActiveTool,
getShortcutKey, getShortcutKey,
isTransparent,
} from "../utils"; } from "../utils";
import { import {
ContextMenu, ContextMenu,
@@ -279,6 +280,8 @@ import { shouldShowBoundingBox } from "../element/transformHandles";
import { Fonts } from "../scene/Fonts"; import { Fonts } from "../scene/Fonts";
import { actionPaste } from "../actions/actionClipboard"; import { actionPaste } from "../actions/actionClipboard";
import { actionToggleHandTool } from "../actions/actionCanvas"; import { actionToggleHandTool } from "../actions/actionCanvas";
import { jotaiStore } from "../jotai";
import { activeConfirmDialogAtom } from "./ActiveConfirmDialog";
const deviceContextInitialValue = { const deviceContextInitialValue = {
isSmScreen: false, isSmScreen: false,
@@ -834,7 +837,7 @@ class App extends React.Component<AppProps, AppState> {
}, },
}; };
} }
const scene = restore(initialData, null, null); const scene = restore(initialData, null, null, { repairBindings: true });
scene.appState = { scene.appState = {
...scene.appState, ...scene.appState,
theme: this.props.theme || scene.appState.theme, theme: this.props.theme || scene.appState.theme,
@@ -1952,7 +1955,6 @@ class App extends React.Component<AppProps, AppState> {
); );
// Input handling // Input handling
private onKeyDown = withBatchedUpdates( private onKeyDown = withBatchedUpdates(
(event: React.KeyboardEvent | KeyboardEvent) => { (event: React.KeyboardEvent | KeyboardEvent) => {
// normalize `event.key` when CapsLock is pressed #2372 // normalize `event.key` when CapsLock is pressed #2372
@@ -2194,6 +2196,13 @@ class App extends React.Component<AppProps, AppState> {
event.stopPropagation(); event.stopPropagation();
} }
} }
if (
event[KEYS.CTRL_OR_CMD] &&
(event.key === KEYS.BACKSPACE || event.key === KEYS.DELETE)
) {
jotaiStore.set(activeConfirmDialogAtom, "clearCanvas");
}
}, },
); );
@@ -2754,7 +2763,15 @@ class App extends React.Component<AppProps, AppState> {
sceneY, sceneY,
); );
if (container) { if (container) {
if (isArrowElement(container) || hasBoundTextElement(container)) { if (
isArrowElement(container) ||
hasBoundTextElement(container) ||
!isTransparent(container.backgroundColor) ||
isHittingElementNotConsideringBoundingBox(container, this.state, [
sceneX,
sceneY,
])
) {
const midPoint = getContainerCenter(container, this.state); const midPoint = getContainerCenter(container, this.state);
sceneX = midPoint.x; sceneX = midPoint.x;
+24 -20
View File
@@ -273,22 +273,6 @@ export const HelpDialog = ({ onClose }: { onClose?: () => void }) => {
className="HelpDialog__island--editor" className="HelpDialog__island--editor"
caption={t("helpDialog.editor")} caption={t("helpDialog.editor")}
> >
<Shortcut
label={t("labels.selectAll")}
shortcuts={[getShortcutKey("CtrlOrCmd+A")]}
/>
<Shortcut
label={t("labels.multiSelect")}
shortcuts={[getShortcutKey(`Shift+${t("helpDialog.click")}`)]}
/>
<Shortcut
label={t("helpDialog.deepSelect")}
shortcuts={[getShortcutKey(`CtrlOrCmd+${t("helpDialog.click")}`)]}
/>
<Shortcut
label={t("helpDialog.deepBoxSelect")}
shortcuts={[getShortcutKey(`CtrlOrCmd+${t("helpDialog.drag")}`)]}
/>
<Shortcut <Shortcut
label={t("labels.moveCanvas")} label={t("labels.moveCanvas")}
shortcuts={[ shortcuts={[
@@ -297,6 +281,14 @@ export const HelpDialog = ({ onClose }: { onClose?: () => void }) => {
]} ]}
isOr={true} isOr={true}
/> />
<Shortcut
label={t("buttons.clearReset")}
shortcuts={[getShortcutKey("CtrlOrCmd+Delete")]}
/>
<Shortcut
label={t("labels.delete")}
shortcuts={[getShortcutKey("Delete")]}
/>
<Shortcut <Shortcut
label={t("labels.cut")} label={t("labels.cut")}
shortcuts={[getShortcutKey("CtrlOrCmd+X")]} shortcuts={[getShortcutKey("CtrlOrCmd+X")]}
@@ -313,6 +305,22 @@ export const HelpDialog = ({ onClose }: { onClose?: () => void }) => {
label={t("labels.pasteAsPlaintext")} label={t("labels.pasteAsPlaintext")}
shortcuts={[getShortcutKey("CtrlOrCmd+Shift+V")]} shortcuts={[getShortcutKey("CtrlOrCmd+Shift+V")]}
/> />
<Shortcut
label={t("labels.selectAll")}
shortcuts={[getShortcutKey("CtrlOrCmd+A")]}
/>
<Shortcut
label={t("labels.multiSelect")}
shortcuts={[getShortcutKey(`Shift+${t("helpDialog.click")}`)]}
/>
<Shortcut
label={t("helpDialog.deepSelect")}
shortcuts={[getShortcutKey(`CtrlOrCmd+${t("helpDialog.click")}`)]}
/>
<Shortcut
label={t("helpDialog.deepBoxSelect")}
shortcuts={[getShortcutKey(`CtrlOrCmd+${t("helpDialog.drag")}`)]}
/>
{/* firefox supports clipboard API under a flag, so we'll {/* firefox supports clipboard API under a flag, so we'll
show users what they can do in the error message */} show users what they can do in the error message */}
{(probablySupportsClipboardBlob || isFirefox) && ( {(probablySupportsClipboardBlob || isFirefox) && (
@@ -329,10 +337,6 @@ export const HelpDialog = ({ onClose }: { onClose?: () => void }) => {
label={t("labels.pasteStyles")} label={t("labels.pasteStyles")}
shortcuts={[getShortcutKey("CtrlOrCmd+Alt+V")]} shortcuts={[getShortcutKey("CtrlOrCmd+Alt+V")]}
/> />
<Shortcut
label={t("labels.delete")}
shortcuts={[getShortcutKey("Delete")]}
/>
<Shortcut <Shortcut
label={t("labels.sendToBack")} label={t("labels.sendToBack")}
shortcuts={[ shortcuts={[
+1 -1
View File
@@ -124,7 +124,6 @@ const LayerUI = ({
children, children,
}: LayerUIProps) => { }: LayerUIProps) => {
const device = useDevice(); const device = useDevice();
const tunnels = useInitializeTunnels(); const tunnels = useInitializeTunnels();
const renderJSONExportDialog = () => { const renderJSONExportDialog = () => {
@@ -409,6 +408,7 @@ const LayerUI = ({
renderCustomStats={renderCustomStats} renderCustomStats={renderCustomStats}
renderSidebars={renderSidebars} renderSidebars={renderSidebars}
device={device} device={device}
renderWelcomeScreen={renderWelcomeScreen}
/> />
)} )}
+3 -1
View File
@@ -41,6 +41,7 @@ type MobileMenuProps = {
renderCustomStats?: ExcalidrawProps["renderCustomStats"]; renderCustomStats?: ExcalidrawProps["renderCustomStats"];
renderSidebars: () => JSX.Element | null; renderSidebars: () => JSX.Element | null;
device: Device; device: Device;
renderWelcomeScreen: boolean;
}; };
export const MobileMenu = ({ export const MobileMenu = ({
@@ -57,12 +58,13 @@ export const MobileMenu = ({
renderCustomStats, renderCustomStats,
renderSidebars, renderSidebars,
device, device,
renderWelcomeScreen,
}: MobileMenuProps) => { }: MobileMenuProps) => {
const { welcomeScreenCenterTunnel, mainMenuTunnel } = useTunnels(); const { welcomeScreenCenterTunnel, mainMenuTunnel } = useTunnels();
const renderToolbar = () => { const renderToolbar = () => {
return ( return (
<FixedSideContainer side="top" className="App-top-bar"> <FixedSideContainer side="top" className="App-top-bar">
<welcomeScreenCenterTunnel.Out /> {renderWelcomeScreen && <welcomeScreenCenterTunnel.Out />}
<Section heading="shapes"> <Section heading="shapes">
{(heading: React.ReactNode) => ( {(heading: React.ReactNode) => (
<Stack.Col gap={4} align="center"> <Stack.Col gap={4} align="center">
+5
View File
@@ -95,6 +95,9 @@
--color-gray-90: #1e1e1e; --color-gray-90: #1e1e1e;
--color-gray-100: #121212; --color-gray-100: #121212;
--color-warning: #fceeca;
--color-text-warning: var(--text-primary-color);
--color-danger: #db6965; --color-danger: #db6965;
--color-promo: #e70078; --color-promo: #e70078;
@@ -163,6 +166,8 @@
--color-primary-darkest: #beb9ff; --color-primary-darkest: #beb9ff;
--color-primary-light: #4f4d6f; --color-primary-light: #4f4d6f;
--color-text-warning: var(--color-gray-80);
--color-danger: #ffa8a5; --color-danger: #ffa8a5;
--color-promo: #d297ff; --color-promo: #d297ff;
} }
+1
View File
@@ -156,6 +156,7 @@ export const loadSceneOrLibraryFromBlob = async (
}, },
localAppState, localAppState,
localElements, localElements,
{ repairBindings: true },
), ),
}; };
} else if (isValidLibrary(data)) { } else if (isValidLibrary(data)) {
+8 -3
View File
@@ -339,7 +339,7 @@ export const restoreElements = (
elements: ImportedDataState["elements"], elements: ImportedDataState["elements"],
/** NOTE doesn't serve for reconciliation */ /** NOTE doesn't serve for reconciliation */
localElements: readonly ExcalidrawElement[] | null | undefined, localElements: readonly ExcalidrawElement[] | null | undefined,
refreshDimensions = false, opts?: { refreshDimensions?: boolean; repairBindings?: boolean } | undefined,
): ExcalidrawElement[] => { ): ExcalidrawElement[] => {
const localElementsMap = localElements ? arrayToMap(localElements) : null; const localElementsMap = localElements ? arrayToMap(localElements) : null;
const restoredElements = (elements || []).reduce((elements, element) => { const restoredElements = (elements || []).reduce((elements, element) => {
@@ -348,7 +348,7 @@ export const restoreElements = (
if (element.type !== "selection" && !isInvisiblySmallElement(element)) { if (element.type !== "selection" && !isInvisiblySmallElement(element)) {
let migratedElement: ExcalidrawElement | null = restoreElement( let migratedElement: ExcalidrawElement | null = restoreElement(
element, element,
refreshDimensions, opts?.refreshDimensions,
); );
if (migratedElement) { if (migratedElement) {
const localElement = localElementsMap?.get(element.id); const localElement = localElementsMap?.get(element.id);
@@ -361,6 +361,10 @@ export const restoreElements = (
return elements; return elements;
}, [] as ExcalidrawElement[]); }, [] as ExcalidrawElement[]);
if (!opts?.repairBindings) {
return restoredElements;
}
// repair binding. Mutates elements. // repair binding. Mutates elements.
const restoredElementsMap = arrayToMap(restoredElements); const restoredElementsMap = arrayToMap(restoredElements);
for (const element of restoredElements) { for (const element of restoredElements) {
@@ -497,9 +501,10 @@ export const restore = (
*/ */
localAppState: Partial<AppState> | null | undefined, localAppState: Partial<AppState> | null | undefined,
localElements: readonly ExcalidrawElement[] | null | undefined, localElements: readonly ExcalidrawElement[] | null | undefined,
elementsConfig?: { refreshDimensions?: boolean; repairBindings?: boolean },
): RestoredDataState => { ): RestoredDataState => {
return { return {
elements: restoreElements(data?.elements, localElements), elements: restoreElements(data?.elements, localElements, elementsConfig),
appState: restoreAppState(data?.appState, localAppState || null), appState: restoreAppState(data?.appState, localAppState || null),
files: data?.files || {}, files: data?.files || {},
}; };
+402
View File
@@ -0,0 +1,402 @@
import { API } from "../tests/helpers/api";
import { mutateElement } from "./mutateElement";
import { normalizeElementOrder } from "./sortElements";
import { ExcalidrawElement } from "./types";
const assertOrder = (
elements: readonly ExcalidrawElement[],
expectedOrder: string[],
) => {
const actualOrder = elements.map((element) => element.id);
expect(actualOrder).toEqual(expectedOrder);
};
describe("normalizeElementsOrder", () => {
it("sort bound-text elements", () => {
const container = API.createElement({
id: "container",
type: "rectangle",
});
const boundText = API.createElement({
id: "boundText",
type: "text",
containerId: container.id,
});
const otherElement = API.createElement({
id: "otherElement",
type: "rectangle",
boundElements: [],
});
const otherElement2 = API.createElement({
id: "otherElement2",
type: "rectangle",
boundElements: [],
});
mutateElement(container, {
boundElements: [{ type: "text", id: boundText.id }],
});
assertOrder(normalizeElementOrder([container, boundText]), [
"container",
"boundText",
]);
assertOrder(normalizeElementOrder([boundText, container]), [
"container",
"boundText",
]);
assertOrder(
normalizeElementOrder([
boundText,
container,
otherElement,
otherElement2,
]),
["container", "boundText", "otherElement", "otherElement2"],
);
assertOrder(normalizeElementOrder([container, otherElement, boundText]), [
"container",
"boundText",
"otherElement",
]);
assertOrder(
normalizeElementOrder([
container,
otherElement,
otherElement2,
boundText,
]),
["container", "boundText", "otherElement", "otherElement2"],
);
assertOrder(
normalizeElementOrder([
boundText,
otherElement,
container,
otherElement2,
]),
["otherElement", "container", "boundText", "otherElement2"],
);
// noop
assertOrder(
normalizeElementOrder([
otherElement,
container,
boundText,
otherElement2,
]),
["otherElement", "container", "boundText", "otherElement2"],
);
// text has existing containerId, but container doesn't list is
// as a boundElement
assertOrder(
normalizeElementOrder([
API.createElement({
id: "boundText",
type: "text",
containerId: "container",
}),
API.createElement({
id: "container",
type: "rectangle",
}),
]),
["boundText", "container"],
);
assertOrder(
normalizeElementOrder([
API.createElement({
id: "boundText",
type: "text",
containerId: "container",
}),
]),
["boundText"],
);
assertOrder(
normalizeElementOrder([
API.createElement({
id: "container",
type: "rectangle",
boundElements: [],
}),
]),
["container"],
);
assertOrder(
normalizeElementOrder([
API.createElement({
id: "container",
type: "rectangle",
boundElements: [{ id: "x", type: "text" }],
}),
]),
["container"],
);
assertOrder(
normalizeElementOrder([
API.createElement({
id: "arrow",
type: "arrow",
}),
API.createElement({
id: "container",
type: "rectangle",
boundElements: [{ id: "arrow", type: "arrow" }],
}),
]),
["arrow", "container"],
);
});
it("normalize group order", () => {
assertOrder(
normalizeElementOrder([
API.createElement({
id: "A_rect1",
type: "rectangle",
groupIds: ["A"],
}),
API.createElement({
id: "rect2",
type: "rectangle",
}),
API.createElement({
id: "rect3",
type: "rectangle",
}),
API.createElement({
id: "A_rect4",
type: "rectangle",
groupIds: ["A"],
}),
API.createElement({
id: "A_rect5",
type: "rectangle",
groupIds: ["A"],
}),
API.createElement({
id: "rect6",
type: "rectangle",
}),
API.createElement({
id: "A_rect7",
type: "rectangle",
groupIds: ["A"],
}),
]),
["A_rect1", "A_rect4", "A_rect5", "A_rect7", "rect2", "rect3", "rect6"],
);
assertOrder(
normalizeElementOrder([
API.createElement({
id: "A_rect1",
type: "rectangle",
groupIds: ["A"],
}),
API.createElement({
id: "rect2",
type: "rectangle",
}),
API.createElement({
id: "B_rect3",
type: "rectangle",
groupIds: ["B"],
}),
API.createElement({
id: "A_rect4",
type: "rectangle",
groupIds: ["A"],
}),
API.createElement({
id: "B_rect5",
type: "rectangle",
groupIds: ["B"],
}),
API.createElement({
id: "rect6",
type: "rectangle",
}),
API.createElement({
id: "A_rect7",
type: "rectangle",
groupIds: ["A"],
}),
]),
["A_rect1", "A_rect4", "A_rect7", "rect2", "B_rect3", "B_rect5", "rect6"],
);
// nested groups
assertOrder(
normalizeElementOrder([
API.createElement({
id: "A_rect1",
type: "rectangle",
groupIds: ["A"],
}),
API.createElement({
id: "BA_rect2",
type: "rectangle",
groupIds: ["B", "A"],
}),
]),
["A_rect1", "BA_rect2"],
);
assertOrder(
normalizeElementOrder([
API.createElement({
id: "BA_rect1",
type: "rectangle",
groupIds: ["B", "A"],
}),
API.createElement({
id: "A_rect2",
type: "rectangle",
groupIds: ["A"],
}),
]),
["BA_rect1", "A_rect2"],
);
assertOrder(
normalizeElementOrder([
API.createElement({
id: "BA_rect1",
type: "rectangle",
groupIds: ["B", "A"],
}),
API.createElement({
id: "A_rect2",
type: "rectangle",
groupIds: ["A"],
}),
API.createElement({
id: "CBA_rect3",
type: "rectangle",
groupIds: ["C", "B", "A"],
}),
API.createElement({
id: "rect4",
type: "rectangle",
}),
API.createElement({
id: "A_rect5",
type: "rectangle",
groupIds: ["A"],
}),
API.createElement({
id: "BA_rect5",
type: "rectangle",
groupIds: ["B", "A"],
}),
API.createElement({
id: "BA_rect6",
type: "rectangle",
groupIds: ["B", "A"],
}),
API.createElement({
id: "CBA_rect7",
type: "rectangle",
groupIds: ["C", "B", "A"],
}),
API.createElement({
id: "X_rect8",
type: "rectangle",
groupIds: ["X"],
}),
API.createElement({
id: "rect9",
type: "rectangle",
}),
API.createElement({
id: "YX_rect10",
type: "rectangle",
groupIds: ["Y", "X"],
}),
API.createElement({
id: "X_rect11",
type: "rectangle",
groupIds: ["X"],
}),
]),
[
"BA_rect1",
"BA_rect5",
"BA_rect6",
"A_rect2",
"A_rect5",
"CBA_rect3",
"CBA_rect7",
"rect4",
"X_rect8",
"X_rect11",
"YX_rect10",
"rect9",
],
);
});
// TODO
it.skip("normalize boundElements array", () => {
const container = API.createElement({
id: "container",
type: "rectangle",
boundElements: [],
});
const boundText = API.createElement({
id: "boundText",
type: "text",
containerId: container.id,
});
mutateElement(container, {
boundElements: [
{ type: "text", id: boundText.id },
{ type: "text", id: "xxx" },
],
});
expect(normalizeElementOrder([container, boundText])).toEqual([
expect.objectContaining({
id: container.id,
}),
expect.objectContaining({ id: boundText.id }),
]);
});
// should take around <100ms for 10K iterations (@dwelle's PC 22-05-25)
it.skip("normalizeElementsOrder() perf", () => {
const makeElements = (iterations: number) => {
const elements: ExcalidrawElement[] = [];
while (iterations--) {
const container = API.createElement({
type: "rectangle",
boundElements: [],
groupIds: ["B", "A"],
});
const boundText = API.createElement({
type: "text",
containerId: container.id,
groupIds: ["A"],
});
const otherElement = API.createElement({
type: "rectangle",
boundElements: [],
groupIds: ["C", "A"],
});
mutateElement(container, {
boundElements: [{ type: "text", id: boundText.id }],
});
elements.push(boundText, otherElement, container);
}
return elements;
};
const elements = makeElements(10000);
const t0 = Date.now();
normalizeElementOrder(elements);
console.info(`${Date.now() - t0}ms`);
});
});
+123
View File
@@ -0,0 +1,123 @@
import { arrayToMapWithIndex } from "../utils";
import { ExcalidrawElement } from "./types";
const normalizeGroupElementOrder = (elements: readonly ExcalidrawElement[]) => {
const origElements: ExcalidrawElement[] = elements.slice();
const sortedElements = new Set<ExcalidrawElement>();
const orderInnerGroups = (
elements: readonly ExcalidrawElement[],
): ExcalidrawElement[] => {
const firstGroupSig = elements[0]?.groupIds?.join("");
const aGroup: ExcalidrawElement[] = [elements[0]];
const bGroup: ExcalidrawElement[] = [];
for (const element of elements.slice(1)) {
if (element.groupIds?.join("") === firstGroupSig) {
aGroup.push(element);
} else {
bGroup.push(element);
}
}
return bGroup.length ? [...aGroup, ...orderInnerGroups(bGroup)] : aGroup;
};
const groupHandledElements = new Map<string, true>();
origElements.forEach((element, idx) => {
if (groupHandledElements.has(element.id)) {
return;
}
if (element.groupIds?.length) {
const topGroup = element.groupIds[element.groupIds.length - 1];
const groupElements = origElements.slice(idx).filter((element) => {
const ret = element?.groupIds?.some((id) => id === topGroup);
if (ret) {
groupHandledElements.set(element!.id, true);
}
return ret;
});
for (const elem of orderInnerGroups(groupElements)) {
sortedElements.add(elem);
}
} else {
sortedElements.add(element);
}
});
// if there's a bug which resulted in losing some of the elements, return
// original instead as that's better than losing data
if (sortedElements.size !== elements.length) {
console.error("normalizeGroupElementOrder: lost some elements... bailing!");
return elements;
}
return [...sortedElements];
};
/**
* In theory, when we have text elements bound to a container, they
* should be right after the container element in the elements array.
* However, this is not guaranteed due to old and potential future bugs.
*
* This function sorts containers and their bound texts together. It prefers
* original z-index of container (i.e. it moves bound text elements after
* containers).
*/
const normalizeBoundElementsOrder = (
elements: readonly ExcalidrawElement[],
) => {
const elementsMap = arrayToMapWithIndex(elements);
const origElements: (ExcalidrawElement | null)[] = elements.slice();
const sortedElements = new Set<ExcalidrawElement>();
origElements.forEach((element, idx) => {
if (!element) {
return;
}
if (element.boundElements?.length) {
sortedElements.add(element);
origElements[idx] = null;
element.boundElements.forEach((boundElement) => {
const child = elementsMap.get(boundElement.id);
if (child && boundElement.type === "text") {
sortedElements.add(child[0]);
origElements[child[1]] = null;
}
});
} else if (element.type === "text" && element.containerId) {
const parent = elementsMap.get(element.containerId);
if (!parent?.[0].boundElements?.find((x) => x.id === element.id)) {
sortedElements.add(element);
origElements[idx] = null;
// if element has a container and container lists it, skip this element
// as it'll be taken care of by the container
}
} else {
sortedElements.add(element);
origElements[idx] = null;
}
});
// if there's a bug which resulted in losing some of the elements, return
// original instead as that's better than losing data
if (sortedElements.size !== elements.length) {
console.error(
"normalizeBoundElementsOrder: lost some elements... bailing!",
);
return elements;
}
return [...sortedElements];
};
export const normalizeElementOrder = (
elements: readonly ExcalidrawElement[],
) => {
// console.time();
const ret = normalizeBoundElementsOrder(normalizeGroupElementOrder(elements));
// console.timeEnd();
return ret;
};
+10 -4
View File
@@ -127,10 +127,16 @@ export const bindTextToShapeAfterDuplication = (
const newContainer = sceneElementMap.get(newElementId); const newContainer = sceneElementMap.get(newElementId);
if (newContainer) { if (newContainer) {
mutateElement(newContainer, { mutateElement(newContainer, {
boundElements: (newContainer.boundElements || []).concat({ boundElements: (element.boundElements || [])
type: "text", .filter(
id: newTextElementId, (boundElement) =>
}), boundElement.id !== newTextElementId &&
boundElement.id !== boundTextElementId,
)
.concat({
type: "text",
id: newTextElementId,
}),
}); });
} }
const newTextElement = sceneElementMap.get(newTextElementId); const newTextElement = sceneElementMap.get(newTextElementId);
+66 -9
View File
@@ -463,14 +463,21 @@ describe("textWysiwyg", () => {
}); });
}); });
it("should bind text to container when double clicked on center of filled container", async () => { it("should bind text to container when double clicked inside filled container", async () => {
const rectangle = API.createElement({
type: "rectangle",
x: 10,
y: 20,
width: 90,
height: 75,
backgroundColor: "red",
});
h.elements = [rectangle];
expect(h.elements.length).toBe(1); expect(h.elements.length).toBe(1);
expect(h.elements[0].id).toBe(rectangle.id); expect(h.elements[0].id).toBe(rectangle.id);
mouse.doubleClickAt( mouse.doubleClickAt(rectangle.x + 10, rectangle.y + 10);
rectangle.x + rectangle.width / 2,
rectangle.y + rectangle.height / 2,
);
expect(h.elements.length).toBe(2); expect(h.elements.length).toBe(2);
const text = h.elements[1] as ExcalidrawTextElementWithContainer; const text = h.elements[1] as ExcalidrawTextElementWithContainer;
@@ -504,24 +511,37 @@ describe("textWysiwyg", () => {
}); });
h.elements = [rectangle]; h.elements = [rectangle];
mouse.doubleClickAt(rectangle.x + 10, rectangle.y + 10);
expect(h.elements.length).toBe(2);
let text = h.elements[1] as ExcalidrawTextElementWithContainer;
expect(text.type).toBe("text");
expect(text.containerId).toBe(null);
mouse.down();
let editor = document.querySelector(
".excalidraw-textEditorContainer > textarea",
) as HTMLTextAreaElement;
await new Promise((r) => setTimeout(r, 0));
editor.blur();
mouse.doubleClickAt( mouse.doubleClickAt(
rectangle.x + rectangle.width / 2, rectangle.x + rectangle.width / 2,
rectangle.y + rectangle.height / 2, rectangle.y + rectangle.height / 2,
); );
expect(h.elements.length).toBe(2); expect(h.elements.length).toBe(3);
const text = h.elements[1] as ExcalidrawTextElementWithContainer; text = h.elements[1] as ExcalidrawTextElementWithContainer;
expect(text.type).toBe("text"); expect(text.type).toBe("text");
expect(text.containerId).toBe(rectangle.id); expect(text.containerId).toBe(rectangle.id);
mouse.down(); mouse.down();
const editor = document.querySelector( editor = document.querySelector(
".excalidraw-textEditorContainer > textarea", ".excalidraw-textEditorContainer > textarea",
) as HTMLTextAreaElement; ) as HTMLTextAreaElement;
fireEvent.change(editor, { target: { value: "Hello World!" } }); fireEvent.change(editor, { target: { value: "Hello World!" } });
await new Promise((r) => setTimeout(r, 0)); await new Promise((r) => setTimeout(r, 0));
editor.blur(); editor.blur();
expect(rectangle.boundElements).toStrictEqual([ expect(rectangle.boundElements).toStrictEqual([
{ id: text.id, type: "text" }, { id: text.id, type: "text" },
]); ]);
@@ -551,6 +571,43 @@ describe("textWysiwyg", () => {
]); ]);
}); });
it("should bind text to container when double clicked on container stroke", async () => {
const rectangle = API.createElement({
type: "rectangle",
x: 10,
y: 20,
width: 90,
height: 75,
strokeWidth: 4,
});
h.elements = [rectangle];
expect(h.elements.length).toBe(1);
expect(h.elements[0].id).toBe(rectangle.id);
mouse.doubleClickAt(rectangle.x + 2, rectangle.y + 2);
expect(h.elements.length).toBe(2);
const text = h.elements[1] as ExcalidrawTextElementWithContainer;
expect(text.type).toBe("text");
expect(text.containerId).toBe(rectangle.id);
expect(rectangle.boundElements).toStrictEqual([
{ id: text.id, type: "text" },
]);
mouse.down();
const editor = document.querySelector(
".excalidraw-textEditorContainer > textarea",
) as HTMLTextAreaElement;
fireEvent.change(editor, { target: { value: "Hello World!" } });
await new Promise((r) => setTimeout(r, 0));
editor.blur();
expect(rectangle.boundElements).toStrictEqual([
{ id: text.id, type: "text" },
]);
});
it("shouldn't bind to non-text-bindable containers", async () => { it("shouldn't bind to non-text-bindable containers", async () => {
const freedraw = API.createElement({ const freedraw = API.createElement({
type: "freedraw", type: "freedraw",
+1 -1
View File
@@ -137,7 +137,7 @@ export const isExcalidrawElement = (element: any): boolean => {
export const hasBoundTextElement = ( export const hasBoundTextElement = (
element: ExcalidrawElement | null, element: ExcalidrawElement | null,
): element is ExcalidrawBindableElement => { ): element is MarkNonNullable<ExcalidrawBindableElement, "boundElements"> => {
return ( return (
isBindableElement(element) && isBindableElement(element) &&
!!element.boundElements?.some(({ type }) => type === "text") !!element.boundElements?.some(({ type }) => type === "text")
+11 -1
View File
@@ -75,6 +75,7 @@ import { jotaiStore } from "../../jotai";
export const collabAPIAtom = atom<CollabAPI | null>(null); export const collabAPIAtom = atom<CollabAPI | null>(null);
export const collabDialogShownAtom = atom(false); export const collabDialogShownAtom = atom(false);
export const isCollaboratingAtom = atom(false); export const isCollaboratingAtom = atom(false);
export const isOfflineAtom = atom(false);
interface CollabState { interface CollabState {
errorMessage: string; errorMessage: string;
@@ -152,6 +153,8 @@ class Collab extends PureComponent<Props, CollabState> {
componentDidMount() { componentDidMount() {
window.addEventListener(EVENT.BEFORE_UNLOAD, this.beforeUnload); window.addEventListener(EVENT.BEFORE_UNLOAD, this.beforeUnload);
window.addEventListener("online", this.onOfflineStatusToggle);
window.addEventListener("offline", this.onOfflineStatusToggle);
window.addEventListener(EVENT.UNLOAD, this.onUnload); window.addEventListener(EVENT.UNLOAD, this.onUnload);
const collabAPI: CollabAPI = { const collabAPI: CollabAPI = {
@@ -165,6 +168,7 @@ class Collab extends PureComponent<Props, CollabState> {
}; };
jotaiStore.set(collabAPIAtom, collabAPI); jotaiStore.set(collabAPIAtom, collabAPI);
this.onOfflineStatusToggle();
if ( if (
process.env.NODE_ENV === ENV.TEST || process.env.NODE_ENV === ENV.TEST ||
@@ -180,7 +184,13 @@ class Collab extends PureComponent<Props, CollabState> {
} }
} }
onOfflineStatusToggle = () => {
jotaiStore.set(isOfflineAtom, !window.navigator.onLine);
};
componentWillUnmount() { componentWillUnmount() {
window.removeEventListener("online", this.onOfflineStatusToggle);
window.removeEventListener("offline", this.onOfflineStatusToggle);
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.POINTER_MOVE, this.onPointerMove);
@@ -600,7 +610,7 @@ class Collab extends PureComponent<Props, CollabState> {
const localElements = this.getSceneElementsIncludingDeleted(); const localElements = this.getSceneElementsIncludingDeleted();
const appState = this.excalidrawAPI.getAppState(); const appState = this.excalidrawAPI.getAppState();
remoteElements = restoreElements(remoteElements, null, false); remoteElements = restoreElements(remoteElements, null);
const reconciledElements = _reconcileElements( const reconciledElements = _reconcileElements(
localElements, localElements,
+1 -1
View File
@@ -144,7 +144,7 @@ const RoomDialog = ({
<input <input
type="text" type="text"
id="username" id="username"
value={username || ""} value={username.trim() || ""}
className="RoomDialog-username TextInput" className="RoomDialog-username TextInput"
onChange={(event) => onUsernameChange(event.target.value)} onChange={(event) => onUsernameChange(event.target.value)}
onKeyPress={(event) => event.key === "Enter" && handleClose()} onKeyPress={(event) => event.key === "Enter" && handleClose()}
+18 -34
View File
@@ -1,6 +1,7 @@
import { PRECEDING_ELEMENT_KEY } from "../../constants"; import { PRECEDING_ELEMENT_KEY } from "../../constants";
import { ExcalidrawElement } from "../../element/types"; import { ExcalidrawElement } from "../../element/types";
import { AppState } from "../../types"; import { AppState } from "../../types";
import { arrayToMapWithIndex } from "../../utils";
export type ReconciledElements = readonly ExcalidrawElement[] & { export type ReconciledElements = readonly ExcalidrawElement[] & {
_brand: "reconciledElements"; _brand: "reconciledElements";
@@ -33,30 +34,13 @@ const shouldDiscardRemoteElement = (
return false; return false;
}; };
const getElementsMapWithIndex = <T extends ExcalidrawElement>(
elements: readonly T[],
) =>
elements.reduce(
(
acc: {
[key: string]: [element: T, index: number] | undefined;
},
element: T,
idx,
) => {
acc[element.id] = [element, idx];
return acc;
},
{},
);
export const reconcileElements = ( export const reconcileElements = (
localElements: readonly ExcalidrawElement[], localElements: readonly ExcalidrawElement[],
remoteElements: readonly BroadcastedExcalidrawElement[], remoteElements: readonly BroadcastedExcalidrawElement[],
localAppState: AppState, localAppState: AppState,
): ReconciledElements => { ): ReconciledElements => {
const localElementsData = const localElementsData =
getElementsMapWithIndex<ExcalidrawElement>(localElements); arrayToMapWithIndex<ExcalidrawElement>(localElements);
const reconciledElements: ExcalidrawElement[] = localElements.slice(); const reconciledElements: ExcalidrawElement[] = localElements.slice();
@@ -69,7 +53,7 @@ export const reconcileElements = (
for (const remoteElement of remoteElements) { for (const remoteElement of remoteElements) {
remoteElementIdx++; remoteElementIdx++;
const local = localElementsData[remoteElement.id]; const local = localElementsData.get(remoteElement.id);
if (shouldDiscardRemoteElement(localAppState, local?.[0], remoteElement)) { if (shouldDiscardRemoteElement(localAppState, local?.[0], remoteElement)) {
if (remoteElement[PRECEDING_ELEMENT_KEY]) { if (remoteElement[PRECEDING_ELEMENT_KEY]) {
@@ -105,21 +89,21 @@ export const reconcileElements = (
offset++; offset++;
if (cursor === 0) { if (cursor === 0) {
reconciledElements.unshift(remoteElement); reconciledElements.unshift(remoteElement);
localElementsData[remoteElement.id] = [ localElementsData.set(remoteElement.id, [
remoteElement, remoteElement,
cursor - offset, cursor - offset,
]; ]);
} else { } else {
reconciledElements.splice(cursor + 1, 0, remoteElement); reconciledElements.splice(cursor + 1, 0, remoteElement);
localElementsData[remoteElement.id] = [ localElementsData.set(remoteElement.id, [
remoteElement, remoteElement,
cursor + 1 - offset, cursor + 1 - offset,
]; ]);
cursor++; cursor++;
} }
} else { } else {
let idx = localElementsData[parent] let idx = localElementsData.has(parent)
? localElementsData[parent]![1] ? localElementsData.get(parent)![1]
: null; : null;
if (idx != null) { if (idx != null) {
idx += offset; idx += offset;
@@ -127,38 +111,38 @@ export const reconcileElements = (
if (idx != null && idx >= cursor) { if (idx != null && idx >= cursor) {
reconciledElements.splice(idx + 1, 0, remoteElement); reconciledElements.splice(idx + 1, 0, remoteElement);
offset++; offset++;
localElementsData[remoteElement.id] = [ localElementsData.set(remoteElement.id, [
remoteElement, remoteElement,
idx + 1 - offset, idx + 1 - offset,
]; ]);
cursor = idx + 1; cursor = idx + 1;
} else if (idx != null) { } else if (idx != null) {
reconciledElements.splice(cursor + 1, 0, remoteElement); reconciledElements.splice(cursor + 1, 0, remoteElement);
offset++; offset++;
localElementsData[remoteElement.id] = [ localElementsData.set(remoteElement.id, [
remoteElement, remoteElement,
cursor + 1 - offset, cursor + 1 - offset,
]; ]);
cursor++; cursor++;
} else { } else {
reconciledElements.push(remoteElement); reconciledElements.push(remoteElement);
localElementsData[remoteElement.id] = [ localElementsData.set(remoteElement.id, [
remoteElement, remoteElement,
reconciledElements.length - 1 - offset, reconciledElements.length - 1 - offset,
]; ]);
} }
} }
// no parent z-index information, local element exists → replace in place // no parent z-index information, local element exists → replace in place
} else if (local) { } else if (local) {
reconciledElements[local[1]] = remoteElement; reconciledElements[local[1]] = remoteElement;
localElementsData[remoteElement.id] = [remoteElement, local[1]]; localElementsData.set(remoteElement.id, [remoteElement, local[1]]);
// otherwise push to the end // otherwise push to the end
} else { } else {
reconciledElements.push(remoteElement); reconciledElements.push(remoteElement);
localElementsData[remoteElement.id] = [ localElementsData.set(remoteElement.id, [
remoteElement, remoteElement,
reconciledElements.length - 1 - offset, reconciledElements.length - 1 - offset,
]; ]);
} }
} }
+4 -1
View File
@@ -263,9 +263,12 @@ export const loadScene = async (
await importFromBackend(id, privateKey), await importFromBackend(id, privateKey),
localDataState?.appState, localDataState?.appState,
localDataState?.elements, localDataState?.elements,
{ repairBindings: true },
); );
} else { } else {
data = restore(localDataState || null, null, null); data = restore(localDataState || null, null, null, {
repairBindings: true,
});
} }
return { return {
+17
View File
@@ -45,6 +45,23 @@
} }
} }
} }
.collab-offline-warning {
pointer-events: none;
position: absolute;
top: 6.5rem;
left: 50%;
transform: translateX(-50%);
padding: 0.5rem 1rem;
font-size: 0.875rem;
text-align: center;
line-height: 1.5;
border-radius: var(--border-radius-md);
background-color: var(--color-warning);
color: var(--color-text-warning);
z-index: 6;
white-space: pre;
}
} }
.excalidraw-app.is-collaborating { .excalidraw-app.is-collaborating {
+12 -5
View File
@@ -52,6 +52,7 @@ import Collab, {
collabAPIAtom, collabAPIAtom,
collabDialogShownAtom, collabDialogShownAtom,
isCollaboratingAtom, isCollaboratingAtom,
isOfflineAtom,
} from "./collab/Collab"; } from "./collab/Collab";
import { import {
exportToBackend, exportToBackend,
@@ -66,10 +67,7 @@ import {
} from "./data/localStorage"; } from "./data/localStorage";
import CustomStats from "./CustomStats"; import CustomStats from "./CustomStats";
import { restore, restoreAppState, RestoredDataState } from "../data/restore"; import { restore, restoreAppState, RestoredDataState } from "../data/restore";
import "./index.scss";
import { ExportToExcalidrawPlus } from "./components/ExportToExcalidrawPlus"; import { ExportToExcalidrawPlus } from "./components/ExportToExcalidrawPlus";
import { updateStaleImageStatuses } from "./data/FileManager"; import { updateStaleImageStatuses } from "./data/FileManager";
import { newElementWith } from "../element/mutateElement"; import { newElementWith } from "../element/mutateElement";
import { isInitializedImageElement } from "../element/typeChecks"; import { isInitializedImageElement } from "../element/typeChecks";
@@ -77,7 +75,7 @@ import { loadFilesFromFirebase } from "./data/firebase";
import { LocalData } from "./data/LocalData"; import { LocalData } from "./data/LocalData";
import { isBrowserStorageStateNewer } from "./data/tabSync"; import { isBrowserStorageStateNewer } from "./data/tabSync";
import clsx from "clsx"; import clsx from "clsx";
import { atom, Provider, useAtom } from "jotai"; import { atom, Provider, useAtom, useAtomValue } from "jotai";
import { jotaiStore, useAtomWithInitialValue } from "../jotai"; import { jotaiStore, useAtomWithInitialValue } from "../jotai";
import { reconcileElements } from "./collab/reconciliation"; import { reconcileElements } from "./collab/reconciliation";
import { parseLibraryTokensFromUrl, useHandleLibrary } from "../data/library"; import { parseLibraryTokensFromUrl, useHandleLibrary } from "../data/library";
@@ -85,6 +83,8 @@ import { AppMainMenu } from "./components/AppMainMenu";
import { AppWelcomeScreen } from "./components/AppWelcomeScreen"; import { AppWelcomeScreen } from "./components/AppWelcomeScreen";
import { AppFooter } from "./components/AppFooter"; import { AppFooter } from "./components/AppFooter";
import "./index.scss";
polyfill(); polyfill();
window.EXCALIDRAW_THROTTLE_RENDER = true; window.EXCALIDRAW_THROTTLE_RENDER = true;
@@ -362,7 +362,7 @@ const ExcalidrawWrapper = () => {
if (data.scene) { if (data.scene) {
excalidrawAPI.updateScene({ excalidrawAPI.updateScene({
...data.scene, ...data.scene,
...restore(data.scene, null, null), ...restore(data.scene, null, null, { repairBindings: true }),
commitToHistory: true, commitToHistory: true,
}); });
} }
@@ -599,6 +599,8 @@ const ExcalidrawWrapper = () => {
localStorage.setItem(STORAGE_KEYS.LOCAL_STORAGE_LIBRARY, serializedItems); localStorage.setItem(STORAGE_KEYS.LOCAL_STORAGE_LIBRARY, serializedItems);
}; };
const isOffline = useAtomValue(isOfflineAtom);
return ( return (
<div <div
style={{ height: "100%" }} style={{ height: "100%" }}
@@ -661,6 +663,11 @@ const ExcalidrawWrapper = () => {
/> />
<AppWelcomeScreen setCollabDialogShown={setCollabDialogShown} /> <AppWelcomeScreen setCollabDialogShown={setCollabDialogShown} />
<AppFooter /> <AppFooter />
{isCollaborating && isOffline && (
<div className="collab-offline-warning">
{t("alerts.collabOfflineWarning")}
</div>
)}
</Excalidraw> </Excalidraw>
{excalidrawAPI && <Collab excalidrawAPI={excalidrawAPI} />} {excalidrawAPI && <Collab excalidrawAPI={excalidrawAPI} />}
{errorMessage && ( {errorMessage && (
+27 -25
View File
@@ -109,35 +109,35 @@
"decreaseFontSize": "تصغير حجم الخط", "decreaseFontSize": "تصغير حجم الخط",
"increaseFontSize": "تكبير حجم الخط", "increaseFontSize": "تكبير حجم الخط",
"unbindText": "فك ربط النص", "unbindText": "فك ربط النص",
"bindText": "", "bindText": "ربط النص بالحاوية",
"link": { "link": {
"edit": "تعديل الرابط", "edit": "تعديل الرابط",
"create": "إنشاء رابط", "create": "إنشاء رابط",
"label": "رابط" "label": "رابط"
}, },
"lineEditor": { "lineEditor": {
"edit": "", "edit": "تحرير السطر",
"exit": "" "exit": "الخروج من المُحرر"
}, },
"elementLock": { "elementLock": {
"lock": "", "lock": "قفل",
"unlock": "", "unlock": "فتح",
"lockAll": "", "lockAll": "قفل الكل",
"unlockAll": "" "unlockAll": "فتح الكل"
}, },
"statusPublished": "", "statusPublished": "نُشر",
"sidebarLock": "" "sidebarLock": "إبقاء الشريط الجانبي مفتوح"
}, },
"library": { "library": {
"noItems": "", "noItems": "لا توجد عناصر أضيفت بعد...",
"hint_emptyLibrary": "", "hint_emptyLibrary": "حدد عنصر على القماش لإضافته هنا، أو تثبيت مكتبة من المستودع العام أدناه.",
"hint_emptyPrivateLibrary": "" "hint_emptyPrivateLibrary": "حدد عنصر على القماش لإضافته هنا."
}, },
"buttons": { "buttons": {
"clearReset": "إعادة تعيين اللوحة", "clearReset": "إعادة تعيين اللوحة",
"exportJSON": "صدر الملف", "exportJSON": "صدر الملف",
"exportImage": "", "exportImage": "تصدير الصورة...",
"export": "", "export": "حفظ إلى...",
"exportToPng": "تصدير بصيغة PNG", "exportToPng": "تصدير بصيغة PNG",
"exportToSvg": "تصدير بصيغة SVG", "exportToSvg": "تصدير بصيغة SVG",
"copyToClipboard": "نسخ إلى الحافظة", "copyToClipboard": "نسخ إلى الحافظة",
@@ -179,7 +179,7 @@
"couldNotLoadInvalidFile": "تعذر التحميل، الملف غير صالح", "couldNotLoadInvalidFile": "تعذر التحميل، الملف غير صالح",
"importBackendFailed": "فشل الاستيراد من الخادوم.", "importBackendFailed": "فشل الاستيراد من الخادوم.",
"cannotExportEmptyCanvas": "لا يمكن تصدير لوحة فارغة.", "cannotExportEmptyCanvas": "لا يمكن تصدير لوحة فارغة.",
"couldNotCopyToClipboard": "", "couldNotCopyToClipboard": "تعذر النسخ إلى الحافظة.",
"decryptFailed": "تعذر فك تشفير البيانات.", "decryptFailed": "تعذر فك تشفير البيانات.",
"uploadedSecurly": "تم تأمين التحميل بتشفير النهاية إلى النهاية، مما يعني أن خادوم Excalidraw والأطراف الثالثة لا يمكنها قراءة المحتوى.", "uploadedSecurly": "تم تأمين التحميل بتشفير النهاية إلى النهاية، مما يعني أن خادوم Excalidraw والأطراف الثالثة لا يمكنها قراءة المحتوى.",
"loadSceneOverridePrompt": "تحميل الرسم الخارجي سيحل محل المحتوى الموجود لديك. هل ترغب في المتابعة؟", "loadSceneOverridePrompt": "تحميل الرسم الخارجي سيحل محل المحتوى الموجود لديك. هل ترغب في المتابعة؟",
@@ -200,10 +200,10 @@
"fileTooBig": "الملف كبير جداً. الحد الأقصى المسموح به للحجم هو {{maxSize}}.", "fileTooBig": "الملف كبير جداً. الحد الأقصى المسموح به للحجم هو {{maxSize}}.",
"svgImageInsertError": "تعذر إدراج صورة SVG. يبدو أن ترميز SVG غير صحيح.", "svgImageInsertError": "تعذر إدراج صورة SVG. يبدو أن ترميز SVG غير صحيح.",
"invalidSVGString": "SVG غير صالح.", "invalidSVGString": "SVG غير صالح.",
"cannotResolveCollabServer": "", "cannotResolveCollabServer": "تعذر الاتصال بخادم التعاون. الرجاء إعادة تحميل الصفحة والمحاولة مرة أخرى.",
"importLibraryError": "", "importLibraryError": "تعذر تحميل المكتبة",
"collabSaveFailed": "", "collabSaveFailed": "تعذر الحفظ في قاعدة البيانات. إذا استمرت المشاكل، يفضل أن تحفظ ملفك محليا كي لا تفقد عملك.",
"collabSaveFailed_sizeExceeded": "" "collabSaveFailed_sizeExceeded": "تعذر الحفظ في قاعدة البيانات، يبدو أن القماش كبير للغاية، يفضّل حفظ الملف محليا كي لا تفقد عملك."
}, },
"toolBar": { "toolBar": {
"selection": "تحديد", "selection": "تحديد",
@@ -217,9 +217,10 @@
"text": "نص", "text": "نص",
"library": "مكتبة", "library": "مكتبة",
"lock": "الحفاظ على أداة التحديد نشطة بعد الرسم", "lock": "الحفاظ على أداة التحديد نشطة بعد الرسم",
"penMode": "", "penMode": "وضع القلم - امنع اللمس",
"link": "", "link": "إضافة/تحديث الرابط للشكل المحدد",
"eraser": "ممحاة" "eraser": "ممحاة",
"hand": ""
}, },
"headings": { "headings": {
"canvasActions": "إجراءات اللوحة", "canvasActions": "إجراءات اللوحة",
@@ -227,7 +228,7 @@
"shapes": "الأشكال" "shapes": "الأشكال"
}, },
"hints": { "hints": {
"canvasPanning": "لتحريك لوحة الرسم ، استمر في الضغط على عجلة الماوس أو مفتاح المسافة أثناء السحب", "canvasPanning": "لتحريك القماش، اضغط على عجلة الفأرة أو مفتاح المسافة أثناء السحب، أو استخدم أداة اليد",
"linearElement": "انقر لبدء نقاط متعددة، اسحب لخط واحد", "linearElement": "انقر لبدء نقاط متعددة، اسحب لخط واحد",
"freeDraw": "انقر واسحب، افرج عند الانتهاء", "freeDraw": "انقر واسحب، افرج عند الانتهاء",
"text": "نصيحة: يمكنك أيضًا إضافة نص بالنقر المزدوج في أي مكان بأداة الاختيار", "text": "نصيحة: يمكنك أيضًا إضافة نص بالنقر المزدوج في أي مكان بأداة الاختيار",
@@ -238,14 +239,15 @@
"resize": "يمكنك تقييد النسب بالضغط على SHIFT أثناء تغيير الحجم،\nاضغط على ALT لتغيير الحجم من المركز", "resize": "يمكنك تقييد النسب بالضغط على SHIFT أثناء تغيير الحجم،\nاضغط على ALT لتغيير الحجم من المركز",
"resizeImage": "يمكنك تغيير الحجم بحرية بالضغط بأستمرار على SHIFT،\nاضغط بأستمرار على ALT أيضا لتغيير الحجم من المركز", "resizeImage": "يمكنك تغيير الحجم بحرية بالضغط بأستمرار على SHIFT،\nاضغط بأستمرار على ALT أيضا لتغيير الحجم من المركز",
"rotate": "يمكنك تقييد الزوايا من خلال الضغط على SHIFT أثناء الدوران", "rotate": "يمكنك تقييد الزوايا من خلال الضغط على SHIFT أثناء الدوران",
"lineEditor_info": "", "lineEditor_info": "اضغط على مفتاح (Ctrl أو Cmd) و انقر بشكل مزدوج، أو اضغط على مفتاحي (Ctrl أو Cmd) و (Enter) لتعديل النقاط",
"lineEditor_pointSelected": "", "lineEditor_pointSelected": "",
"lineEditor_nothingSelected": "", "lineEditor_nothingSelected": "",
"placeImage": "", "placeImage": "",
"publishLibrary": "نشر مكتبتك", "publishLibrary": "نشر مكتبتك",
"bindTextToElement": "", "bindTextToElement": "",
"deepBoxSelect": "", "deepBoxSelect": "",
"eraserRevert": "" "eraserRevert": "",
"firefox_clipboard_write": ""
}, },
"canvasError": { "canvasError": {
"cannotShowPreview": "تعذر عرض المعاينة", "cannotShowPreview": "تعذر عرض المعاينة",
+4 -2
View File
@@ -219,7 +219,8 @@
"lock": "Поддържайте избрания инструмент активен след рисуване", "lock": "Поддържайте избрания инструмент активен след рисуване",
"penMode": "", "penMode": "",
"link": "", "link": "",
"eraser": "" "eraser": "",
"hand": ""
}, },
"headings": { "headings": {
"canvasActions": "Действия по платното", "canvasActions": "Действия по платното",
@@ -245,7 +246,8 @@
"publishLibrary": "", "publishLibrary": "",
"bindTextToElement": "Натиснете Enter, за да добавите", "bindTextToElement": "Натиснете Enter, за да добавите",
"deepBoxSelect": "", "deepBoxSelect": "",
"eraserRevert": "" "eraserRevert": "",
"firefox_clipboard_write": ""
}, },
"canvasError": { "canvasError": {
"cannotShowPreview": "Невъзможност за показване на preview", "cannotShowPreview": "Невъзможност за показване на preview",
+5 -3
View File
@@ -219,7 +219,8 @@
"lock": "আঁকার পরে নির্বাচিত টুল সক্রিয় রাখুন", "lock": "আঁকার পরে নির্বাচিত টুল সক্রিয় রাখুন",
"penMode": "", "penMode": "",
"link": "একটি নির্বাচিত আকৃতির জন্য লিঙ্ক যোগ বা আপডেট করুন", "link": "একটি নির্বাচিত আকৃতির জন্য লিঙ্ক যোগ বা আপডেট করুন",
"eraser": "ঝাড়ন" "eraser": "ঝাড়ন",
"hand": ""
}, },
"headings": { "headings": {
"canvasActions": "ক্যানভাস কার্যকলাপ", "canvasActions": "ক্যানভাস কার্যকলাপ",
@@ -227,7 +228,7 @@
"shapes": "আকার(গুলি)" "shapes": "আকার(গুলি)"
}, },
"hints": { "hints": {
"canvasPanning": "ক্যানভাস সরানোর জন্য মাউস হুইল বা স্পেসবার ধরে টানুন", "canvasPanning": "",
"linearElement": "একাধিক বিন্দু শুরু করতে ক্লিক করুন, একক লাইনের জন্য টেনে আনুন", "linearElement": "একাধিক বিন্দু শুরু করতে ক্লিক করুন, একক লাইনের জন্য টেনে আনুন",
"freeDraw": "ক্লিক করুন এবং টেনে আনুন, আপনার কাজ শেষ হলে ছেড়ে দিন", "freeDraw": "ক্লিক করুন এবং টেনে আনুন, আপনার কাজ শেষ হলে ছেড়ে দিন",
"text": "বিশেষ্য: আপনি নির্বাচন টুলের সাথে যে কোনো জায়গায় ডাবল-ক্লিক করে পাঠ্য যোগ করতে পারেন", "text": "বিশেষ্য: আপনি নির্বাচন টুলের সাথে যে কোনো জায়গায় ডাবল-ক্লিক করে পাঠ্য যোগ করতে পারেন",
@@ -245,7 +246,8 @@
"publishLibrary": "আপনার নিজস্ব সংগ্রহ প্রকাশ করুন", "publishLibrary": "আপনার নিজস্ব সংগ্রহ প্রকাশ করুন",
"bindTextToElement": "লেখা যোগ করতে এন্টার টিপুন", "bindTextToElement": "লেখা যোগ করতে এন্টার টিপুন",
"deepBoxSelect": "", "deepBoxSelect": "",
"eraserRevert": "মুছে ফেলার জন্য চিহ্নিত উপাদানগুলিকে ফিরিয়ে আনতে অল্ট ধরে রাখুন" "eraserRevert": "মুছে ফেলার জন্য চিহ্নিত উপাদানগুলিকে ফিরিয়ে আনতে অল্ট ধরে রাখুন",
"firefox_clipboard_write": ""
}, },
"canvasError": { "canvasError": {
"cannotShowPreview": "প্রিভিউ দেখাতে অপারগ", "cannotShowPreview": "প্রিভিউ দেখাতে অপারগ",
+5 -3
View File
@@ -219,7 +219,8 @@
"lock": "Mantenir activa l'eina seleccionada desprès de dibuixar", "lock": "Mantenir activa l'eina seleccionada desprès de dibuixar",
"penMode": "", "penMode": "",
"link": "Afegeix / actualitza l'enllaç per a la forma seleccionada", "link": "Afegeix / actualitza l'enllaç per a la forma seleccionada",
"eraser": "Esborrador" "eraser": "Esborrador",
"hand": ""
}, },
"headings": { "headings": {
"canvasActions": "Accions del llenç", "canvasActions": "Accions del llenç",
@@ -227,7 +228,7 @@
"shapes": "Formes" "shapes": "Formes"
}, },
"hints": { "hints": {
"canvasPanning": "Per a moure el llenç, mantingueu premuda la roda del ratolí o la tecla espai mentre l'arrossegueu", "canvasPanning": "",
"linearElement": "Feu clic per a dibuixar múltiples punts; arrossegueu per a una sola línia", "linearElement": "Feu clic per a dibuixar múltiples punts; arrossegueu per a una sola línia",
"freeDraw": "Feu clic i arrossegueu, deixeu anar per a finalitzar", "freeDraw": "Feu clic i arrossegueu, deixeu anar per a finalitzar",
"text": "Consell: també podeu afegir text fent doble clic en qualsevol lloc amb l'eina de selecció", "text": "Consell: també podeu afegir text fent doble clic en qualsevol lloc amb l'eina de selecció",
@@ -245,7 +246,8 @@
"publishLibrary": "Publiqueu la vostra pròpia llibreria", "publishLibrary": "Publiqueu la vostra pròpia llibreria",
"bindTextToElement": "Premeu enter per a afegir-hi text", "bindTextToElement": "Premeu enter per a afegir-hi text",
"deepBoxSelect": "Manteniu CtrlOrCmd per a selecció profunda, i per a evitar l'arrossegament", "deepBoxSelect": "Manteniu CtrlOrCmd per a selecció profunda, i per a evitar l'arrossegament",
"eraserRevert": "Mantingueu premuda Alt per a revertir els elements seleccionats per a esborrar" "eraserRevert": "Mantingueu premuda Alt per a revertir els elements seleccionats per a esborrar",
"firefox_clipboard_write": ""
}, },
"canvasError": { "canvasError": {
"cannotShowPreview": "No es pot mostrar la previsualització", "cannotShowPreview": "No es pot mostrar la previsualització",
+4 -2
View File
@@ -219,7 +219,8 @@
"lock": "", "lock": "",
"penMode": "", "penMode": "",
"link": "", "link": "",
"eraser": "Guma" "eraser": "Guma",
"hand": ""
}, },
"headings": { "headings": {
"canvasActions": "", "canvasActions": "",
@@ -245,7 +246,8 @@
"publishLibrary": "", "publishLibrary": "",
"bindTextToElement": "", "bindTextToElement": "",
"deepBoxSelect": "", "deepBoxSelect": "",
"eraserRevert": "" "eraserRevert": "",
"firefox_clipboard_write": ""
}, },
"canvasError": { "canvasError": {
"cannotShowPreview": "", "cannotShowPreview": "",
+4 -2
View File
@@ -219,7 +219,8 @@
"lock": "", "lock": "",
"penMode": "", "penMode": "",
"link": "", "link": "",
"eraser": "" "eraser": "",
"hand": ""
}, },
"headings": { "headings": {
"canvasActions": "", "canvasActions": "",
@@ -245,7 +246,8 @@
"publishLibrary": "", "publishLibrary": "",
"bindTextToElement": "", "bindTextToElement": "",
"deepBoxSelect": "", "deepBoxSelect": "",
"eraserRevert": "" "eraserRevert": "",
"firefox_clipboard_write": ""
}, },
"canvasError": { "canvasError": {
"cannotShowPreview": "", "cannotShowPreview": "",
+5 -3
View File
@@ -219,7 +219,8 @@
"lock": "Ausgewähltes Werkzeug nach Zeichnen aktiv lassen", "lock": "Ausgewähltes Werkzeug nach Zeichnen aktiv lassen",
"penMode": "Stift-Modus - Berührung verhindern", "penMode": "Stift-Modus - Berührung verhindern",
"link": "Link für ausgewählte Form hinzufügen / aktualisieren", "link": "Link für ausgewählte Form hinzufügen / aktualisieren",
"eraser": "Radierer" "eraser": "Radierer",
"hand": "Hand (Schwenkwerkzeug)"
}, },
"headings": { "headings": {
"canvasActions": "Aktionen für Zeichenfläche", "canvasActions": "Aktionen für Zeichenfläche",
@@ -227,7 +228,7 @@
"shapes": "Formen" "shapes": "Formen"
}, },
"hints": { "hints": {
"canvasPanning": "Um die Zeichenfläche zu verschieben, halte das Mausrad oder die Leertaste während des Ziehens", "canvasPanning": "Um die Zeichenfläche zu verschieben, halte das Mausrad oder die Leertaste während des Ziehens, oder verwende das Hand-Werkzeug",
"linearElement": "Klicken für Linie mit mehreren Punkten, Ziehen für einzelne Linie", "linearElement": "Klicken für Linie mit mehreren Punkten, Ziehen für einzelne Linie",
"freeDraw": "Klicke und ziehe. Lass los, wenn du fertig bist", "freeDraw": "Klicke und ziehe. Lass los, wenn du fertig bist",
"text": "Tipp: Du kannst auch Text hinzufügen, indem du mit dem Auswahlwerkzeug auf eine beliebige Stelle doppelklickst", "text": "Tipp: Du kannst auch Text hinzufügen, indem du mit dem Auswahlwerkzeug auf eine beliebige Stelle doppelklickst",
@@ -245,7 +246,8 @@
"publishLibrary": "Veröffentliche deine eigene Bibliothek", "publishLibrary": "Veröffentliche deine eigene Bibliothek",
"bindTextToElement": "Zum Hinzufügen Eingabetaste drücken", "bindTextToElement": "Zum Hinzufügen Eingabetaste drücken",
"deepBoxSelect": "Halte CtrlOrCmd gedrückt, um innerhalb der Gruppe auszuwählen, und um Ziehen zu vermeiden", "deepBoxSelect": "Halte CtrlOrCmd gedrückt, um innerhalb der Gruppe auszuwählen, und um Ziehen zu vermeiden",
"eraserRevert": "Halte Alt gedrückt, um die zum Löschen markierten Elemente zurückzusetzen" "eraserRevert": "Halte Alt gedrückt, um die zum Löschen markierten Elemente zurückzusetzen",
"firefox_clipboard_write": "Diese Funktion kann wahrscheinlich aktiviert werden, indem die Einstellung \"dom.events.asyncClipboard.clipboardItem\" auf \"true\" gesetzt wird. Um die Browsereinstellungen in Firefox zu ändern, besuche die Seite \"about:config\"."
}, },
"canvasError": { "canvasError": {
"cannotShowPreview": "Vorschau kann nicht angezeigt werden", "cannotShowPreview": "Vorschau kann nicht angezeigt werden",
+5 -3
View File
@@ -219,7 +219,8 @@
"lock": "Κράτησε επιλεγμένο το εργαλείο μετά το σχέδιο", "lock": "Κράτησε επιλεγμένο το εργαλείο μετά το σχέδιο",
"penMode": "Λειτουργία μολυβιού - αποτροπή αφής", "penMode": "Λειτουργία μολυβιού - αποτροπή αφής",
"link": "Προσθήκη/ Ενημέρωση συνδέσμου για ένα επιλεγμένο σχήμα", "link": "Προσθήκη/ Ενημέρωση συνδέσμου για ένα επιλεγμένο σχήμα",
"eraser": "Γόμα" "eraser": "Γόμα",
"hand": ""
}, },
"headings": { "headings": {
"canvasActions": "Ενέργειες καμβά", "canvasActions": "Ενέργειες καμβά",
@@ -227,7 +228,7 @@
"shapes": "Σχήματα" "shapes": "Σχήματα"
}, },
"hints": { "hints": {
"canvasPanning": "Για να μετακινήσετε καμβά, κρατήστε πατημένο τον τροχό του ποντικιού ή το πλήκτρο διαστήματος ενώ σύρετε", "canvasPanning": "",
"linearElement": "Κάνε κλικ για να ξεκινήσεις πολλαπλά σημεία, σύρε για μια γραμμή", "linearElement": "Κάνε κλικ για να ξεκινήσεις πολλαπλά σημεία, σύρε για μια γραμμή",
"freeDraw": "Κάντε κλικ και σύρτε, απελευθερώσατε όταν έχετε τελειώσει", "freeDraw": "Κάντε κλικ και σύρτε, απελευθερώσατε όταν έχετε τελειώσει",
"text": "Tip: μπορείτε επίσης να προσθέστε κείμενο με διπλό-κλικ οπουδήποτε με το εργαλείο επιλογών", "text": "Tip: μπορείτε επίσης να προσθέστε κείμενο με διπλό-κλικ οπουδήποτε με το εργαλείο επιλογών",
@@ -245,7 +246,8 @@
"publishLibrary": "Δημοσιεύστε τη δική σας βιβλιοθήκη", "publishLibrary": "Δημοσιεύστε τη δική σας βιβλιοθήκη",
"bindTextToElement": "Πατήστε Enter για προσθήκη κειμένου", "bindTextToElement": "Πατήστε Enter για προσθήκη κειμένου",
"deepBoxSelect": "Κρατήστε πατημένο το CtrlOrCmd για να επιλέξετε βαθιά, και να αποτρέψετε τη μεταφορά", "deepBoxSelect": "Κρατήστε πατημένο το CtrlOrCmd για να επιλέξετε βαθιά, και να αποτρέψετε τη μεταφορά",
"eraserRevert": "Κρατήστε πατημένο το Alt για να επαναφέρετε τα στοιχεία που σημειώθηκαν για διαγραφή" "eraserRevert": "Κρατήστε πατημένο το Alt για να επαναφέρετε τα στοιχεία που σημειώθηκαν για διαγραφή",
"firefox_clipboard_write": "Αυτή η επιλογή μπορεί πιθανώς να ενεργοποιηθεί αλλάζοντας την ρύθμιση \"dom.events.asyncClipboard.clipboardItem\" σε \"true\". Για να αλλάξετε τις ρυθμίσεις του προγράμματος περιήγησης στο Firefox, επισκεφθείτε τη σελίδα \"about:config\"."
}, },
"canvasError": { "canvasError": {
"cannotShowPreview": "Αδυναμία εμφάνισης προεπισκόπησης", "cannotShowPreview": "Αδυναμία εμφάνισης προεπισκόπησης",
+2 -1
View File
@@ -193,7 +193,8 @@
"invalidSceneUrl": "Couldn't import scene from the supplied URL. It's either malformed, or doesn't contain valid Excalidraw JSON data.", "invalidSceneUrl": "Couldn't import scene from the supplied URL. It's either malformed, or doesn't contain valid Excalidraw JSON data.",
"resetLibrary": "This will clear your library. Are you sure?", "resetLibrary": "This will clear your library. Are you sure?",
"removeItemsFromsLibrary": "Delete {{count}} item(s) from library?", "removeItemsFromsLibrary": "Delete {{count}} item(s) from library?",
"invalidEncryptionKey": "Encryption key must be of 22 characters. Live collaboration is disabled." "invalidEncryptionKey": "Encryption key must be of 22 characters. Live collaboration is disabled.",
"collabOfflineWarning": "No internet connection available.\nYour changes will not be saved!"
}, },
"errors": { "errors": {
"unsupportedFileType": "Unsupported file type.", "unsupportedFileType": "Unsupported file type.",
+5 -3
View File
@@ -219,7 +219,8 @@
"lock": "Mantener la herramienta seleccionada activa después de dibujar", "lock": "Mantener la herramienta seleccionada activa después de dibujar",
"penMode": "Modo Lápiz - previene toque", "penMode": "Modo Lápiz - previene toque",
"link": "Añadir/Actualizar enlace para una forma seleccionada", "link": "Añadir/Actualizar enlace para una forma seleccionada",
"eraser": "Borrar" "eraser": "Borrar",
"hand": "Mano (herramienta de panoramización)"
}, },
"headings": { "headings": {
"canvasActions": "Acciones del lienzo", "canvasActions": "Acciones del lienzo",
@@ -227,7 +228,7 @@
"shapes": "Formas" "shapes": "Formas"
}, },
"hints": { "hints": {
"canvasPanning": "Para mover el lienzo, mantenga la rueda del ratón o la barra de espacio mientras arrastra", "canvasPanning": "Para mover el lienzo, mantenga la rueda del ratón o la barra espaciadora mientras arrastra o utilice la herramienta de mano",
"linearElement": "Haz clic para dibujar múltiples puntos, arrastrar para solo una línea", "linearElement": "Haz clic para dibujar múltiples puntos, arrastrar para solo una línea",
"freeDraw": "Haz clic y arrastra, suelta al terminar", "freeDraw": "Haz clic y arrastra, suelta al terminar",
"text": "Consejo: también puedes añadir texto haciendo doble clic en cualquier lugar con la herramienta de selección", "text": "Consejo: también puedes añadir texto haciendo doble clic en cualquier lugar con la herramienta de selección",
@@ -245,7 +246,8 @@
"publishLibrary": "Publica tu propia biblioteca", "publishLibrary": "Publica tu propia biblioteca",
"bindTextToElement": "Presione Entrar para agregar", "bindTextToElement": "Presione Entrar para agregar",
"deepBoxSelect": "Mantén CtrlOrCmd para seleccionar en profundidad, y para evitar arrastrar", "deepBoxSelect": "Mantén CtrlOrCmd para seleccionar en profundidad, y para evitar arrastrar",
"eraserRevert": "Mantenga pulsado Alt para revertir los elementos marcados para su eliminación" "eraserRevert": "Mantenga pulsado Alt para revertir los elementos marcados para su eliminación",
"firefox_clipboard_write": "Esta característica puede ser habilitada estableciendo la bandera \"dom.events.asyncClipboard.clipboardItem\" a \"true\". Para cambiar las banderas del navegador en Firefox, visite la página \"about:config\"."
}, },
"canvasError": { "canvasError": {
"cannotShowPreview": "No se puede mostrar la vista previa", "cannotShowPreview": "No se puede mostrar la vista previa",
+5 -3
View File
@@ -219,7 +219,8 @@
"lock": "Mantendu aktibo hautatutako tresna marraztu ondoren", "lock": "Mantendu aktibo hautatutako tresna marraztu ondoren",
"penMode": "Luma modua - ukipena saihestu", "penMode": "Luma modua - ukipena saihestu",
"link": "Gehitu / Eguneratu esteka hautatutako forma baterako", "link": "Gehitu / Eguneratu esteka hautatutako forma baterako",
"eraser": "Borragoma" "eraser": "Borragoma",
"hand": ""
}, },
"headings": { "headings": {
"canvasActions": "Canvas ekintzak", "canvasActions": "Canvas ekintzak",
@@ -227,7 +228,7 @@
"shapes": "Formak" "shapes": "Formak"
}, },
"hints": { "hints": {
"canvasPanning": "Oihala mugitzeko, sakatu saguaren gurpila edo zuriune-barra arrastatzean", "canvasPanning": "",
"linearElement": "Egin klik hainbat puntu hasteko, arrastatu lerro bakarrerako", "linearElement": "Egin klik hainbat puntu hasteko, arrastatu lerro bakarrerako",
"freeDraw": "Egin klik eta arrastatu, askatu amaitutakoan", "freeDraw": "Egin klik eta arrastatu, askatu amaitutakoan",
"text": "Aholkua: testua gehitu dezakezu edozein lekutan klik bikoitza eginez hautapen tresnarekin", "text": "Aholkua: testua gehitu dezakezu edozein lekutan klik bikoitza eginez hautapen tresnarekin",
@@ -245,7 +246,8 @@
"publishLibrary": "Argitaratu zure liburutegia", "publishLibrary": "Argitaratu zure liburutegia",
"bindTextToElement": "Sakatu Sartu testua gehitzeko", "bindTextToElement": "Sakatu Sartu testua gehitzeko",
"deepBoxSelect": "Eutsi Ctrl edo Cmd sakatuta aukeraketa sakona egiteko eta arrastatzea saihesteko", "deepBoxSelect": "Eutsi Ctrl edo Cmd sakatuta aukeraketa sakona egiteko eta arrastatzea saihesteko",
"eraserRevert": "Eduki Alt sakatuta ezabatzeko markatutako elementuak leheneratzeko" "eraserRevert": "Eduki Alt sakatuta ezabatzeko markatutako elementuak leheneratzeko",
"firefox_clipboard_write": ""
}, },
"canvasError": { "canvasError": {
"cannotShowPreview": "Ezin da oihala aurreikusi", "cannotShowPreview": "Ezin da oihala aurreikusi",
+5 -3
View File
@@ -219,7 +219,8 @@
"lock": "ابزار انتخاب شده را بعد از کشیدن نگه دار", "lock": "ابزار انتخاب شده را بعد از کشیدن نگه دار",
"penMode": "حالت قلم - جلوگیری از تماس", "penMode": "حالت قلم - جلوگیری از تماس",
"link": "افزودن/به‌روزرسانی پیوند برای شکل انتخابی", "link": "افزودن/به‌روزرسانی پیوند برای شکل انتخابی",
"eraser": "پاک کن" "eraser": "پاک کن",
"hand": ""
}, },
"headings": { "headings": {
"canvasActions": "عملیات روی بوم", "canvasActions": "عملیات روی بوم",
@@ -227,7 +228,7 @@
"shapes": "شکل‌ها" "shapes": "شکل‌ها"
}, },
"hints": { "hints": {
"canvasPanning": "برای حرکت دادن بوم، چرخ ماوس یا فاصله را در حین کشیدن نگه دارید", "canvasPanning": "",
"linearElement": "برای چند نقطه کلیک و برای یک خط بکشید", "linearElement": "برای چند نقطه کلیک و برای یک خط بکشید",
"freeDraw": "کلیک کنید و بکشید و وقتی کار تمام شد رها کنید", "freeDraw": "کلیک کنید و بکشید و وقتی کار تمام شد رها کنید",
"text": "نکته: با برنامه انتخاب شده شما میتوانید با دوبار کلیک کردن هرکجا میخواید متن اظاف کنید", "text": "نکته: با برنامه انتخاب شده شما میتوانید با دوبار کلیک کردن هرکجا میخواید متن اظاف کنید",
@@ -245,7 +246,8 @@
"publishLibrary": "کتابخانه خود را منتشر کنید", "publishLibrary": "کتابخانه خود را منتشر کنید",
"bindTextToElement": "برای افزودن اینتر را بزنید", "bindTextToElement": "برای افزودن اینتر را بزنید",
"deepBoxSelect": "CtrlOrCmd را برای انتخاب عمیق و جلوگیری از کشیدن نگه دارید", "deepBoxSelect": "CtrlOrCmd را برای انتخاب عمیق و جلوگیری از کشیدن نگه دارید",
"eraserRevert": "Alt را نگه دارید تا عناصر علامت گذاری شده برای حذف برگردند" "eraserRevert": "Alt را نگه دارید تا عناصر علامت گذاری شده برای حذف برگردند",
"firefox_clipboard_write": ""
}, },
"canvasError": { "canvasError": {
"cannotShowPreview": "پیش نمایش نشان داده نمی شود", "cannotShowPreview": "پیش نمایش نشان داده نمی شود",
+5 -3
View File
@@ -219,7 +219,8 @@
"lock": "Pidä valittu työkalu aktiivisena piirron jälkeen", "lock": "Pidä valittu työkalu aktiivisena piirron jälkeen",
"penMode": "", "penMode": "",
"link": "Lisää/päivitä linkki valitulle muodolle", "link": "Lisää/päivitä linkki valitulle muodolle",
"eraser": "Poistotyökalu" "eraser": "Poistotyökalu",
"hand": ""
}, },
"headings": { "headings": {
"canvasActions": "Piirtoalueen toiminnot", "canvasActions": "Piirtoalueen toiminnot",
@@ -227,7 +228,7 @@
"shapes": "Muodot" "shapes": "Muodot"
}, },
"hints": { "hints": {
"canvasPanning": "Liikuttaaksesi piirtoaluetta, raahaa hiiren vieritysrulla tai välilyöntinäppäin alaspainettuna", "canvasPanning": "",
"linearElement": "Klikkaa piirtääksesi useampi piste, raahaa piirtääksesi yksittäinen viiva", "linearElement": "Klikkaa piirtääksesi useampi piste, raahaa piirtääksesi yksittäinen viiva",
"freeDraw": "Paina ja raahaa, päästä irti kun olet valmis", "freeDraw": "Paina ja raahaa, päästä irti kun olet valmis",
"text": "Vinkki: voit myös lisätä tekstiä kaksoisnapsauttamalla mihin tahansa valintatyökalulla", "text": "Vinkki: voit myös lisätä tekstiä kaksoisnapsauttamalla mihin tahansa valintatyökalulla",
@@ -245,7 +246,8 @@
"publishLibrary": "Julkaise oma kirjasto", "publishLibrary": "Julkaise oma kirjasto",
"bindTextToElement": "Lisää tekstiä painamalla enter", "bindTextToElement": "Lisää tekstiä painamalla enter",
"deepBoxSelect": "Käytä syvävalintaa ja estä raahaus painamalla CtrlOrCmd", "deepBoxSelect": "Käytä syvävalintaa ja estä raahaus painamalla CtrlOrCmd",
"eraserRevert": "Pidä Alt alaspainettuna, kumotaksesi merkittyjen elementtien poistamisen" "eraserRevert": "Pidä Alt alaspainettuna, kumotaksesi merkittyjen elementtien poistamisen",
"firefox_clipboard_write": ""
}, },
"canvasError": { "canvasError": {
"cannotShowPreview": "Esikatselua ei voitu näyttää", "cannotShowPreview": "Esikatselua ei voitu näyttää",
+6 -4
View File
@@ -45,7 +45,7 @@
"exportEmbedScene": "Intégrer la scène", "exportEmbedScene": "Intégrer la scène",
"exportEmbedScene_details": "Les données de scène seront enregistrées dans le fichier PNG/SVG exporté, afin que la scène puisse être restaurée à partir de celui-ci.\nCela augmentera la taille du fichier exporté.", "exportEmbedScene_details": "Les données de scène seront enregistrées dans le fichier PNG/SVG exporté, afin que la scène puisse être restaurée à partir de celui-ci.\nCela augmentera la taille du fichier exporté.",
"addWatermark": "Ajouter \"Réalisé avec Excalidraw\"", "addWatermark": "Ajouter \"Réalisé avec Excalidraw\"",
"handDrawn": "Manuscrit", "handDrawn": "À main levée",
"normal": "Normale", "normal": "Normale",
"code": "Code", "code": "Code",
"small": "Petite", "small": "Petite",
@@ -219,7 +219,8 @@
"lock": "Garder l'outil sélectionné actif après le dessin", "lock": "Garder l'outil sélectionné actif après le dessin",
"penMode": "Mode stylo - évite le toucher", "penMode": "Mode stylo - évite le toucher",
"link": "Ajouter/mettre à jour le lien pour une forme sélectionnée", "link": "Ajouter/mettre à jour le lien pour une forme sélectionnée",
"eraser": "Gomme" "eraser": "Gomme",
"hand": "Mains (outil de déplacement de la vue)"
}, },
"headings": { "headings": {
"canvasActions": "Actions du canevas", "canvasActions": "Actions du canevas",
@@ -227,7 +228,7 @@
"shapes": "Formes" "shapes": "Formes"
}, },
"hints": { "hints": {
"canvasPanning": "Pour déplacer la zone de dessin, maintenez la molette de la souris enfoncée ou la barre d'espace tout en faisant glisser", "canvasPanning": "Pour déplacer la zone de dessin, maintenez la molette de la souris enfoncée ou la barre d'espace tout en faisant glisser, ou utiliser l'outil main.",
"linearElement": "Cliquez pour démarrer plusieurs points, faites glisser pour une seule ligne", "linearElement": "Cliquez pour démarrer plusieurs points, faites glisser pour une seule ligne",
"freeDraw": "Cliquez et faites glissez, relâchez quand vous avez terminé", "freeDraw": "Cliquez et faites glissez, relâchez quand vous avez terminé",
"text": "Astuce : vous pouvez aussi ajouter du texte en double-cliquant n'importe où avec l'outil de sélection", "text": "Astuce : vous pouvez aussi ajouter du texte en double-cliquant n'importe où avec l'outil de sélection",
@@ -245,7 +246,8 @@
"publishLibrary": "Publier votre propre bibliothèque", "publishLibrary": "Publier votre propre bibliothèque",
"bindTextToElement": "Appuyer sur Entrée pour ajouter du texte", "bindTextToElement": "Appuyer sur Entrée pour ajouter du texte",
"deepBoxSelect": "Maintenir Ctrl ou Cmd pour sélectionner dans les groupes et empêcher le déplacement", "deepBoxSelect": "Maintenir Ctrl ou Cmd pour sélectionner dans les groupes et empêcher le déplacement",
"eraserRevert": "Maintenez Alt enfoncé pour annuler les éléments marqués pour suppression" "eraserRevert": "Maintenez Alt enfoncé pour annuler les éléments marqués pour suppression",
"firefox_clipboard_write": "Cette fonctionnalité devrait pouvoir être activée en définissant l'option \"dom.events.asyncClipboard.clipboard.clipboardItem\" à \"true\". Pour modifier les paramètres du navigateur dans Firefox, visitez la page \"about:config\"."
}, },
"canvasError": { "canvasError": {
"cannotShowPreview": "Impossible dafficher laperçu", "cannotShowPreview": "Impossible dafficher laperçu",
+5 -3
View File
@@ -219,7 +219,8 @@
"lock": "Manter a ferramenta seleccionada activa despois de debuxar", "lock": "Manter a ferramenta seleccionada activa despois de debuxar",
"penMode": "Modo lapis - evitar o contacto", "penMode": "Modo lapis - evitar o contacto",
"link": "Engadir/ Actualizar ligazón para a forma seleccionada", "link": "Engadir/ Actualizar ligazón para a forma seleccionada",
"eraser": "Goma de borrar" "eraser": "Goma de borrar",
"hand": "Man (ferramenta de desprazamento)"
}, },
"headings": { "headings": {
"canvasActions": "Accións do lenzo", "canvasActions": "Accións do lenzo",
@@ -227,7 +228,7 @@
"shapes": "Formas" "shapes": "Formas"
}, },
"hints": { "hints": {
"canvasPanning": "Para mover o lenzo, manteña a roda do rato ou a barra de espazo mentres arrastra", "canvasPanning": "Para mover o lenzo, manteña pulsada a roda do rato ou a barra de espazo mentres arrastra, ou utilice a ferramenta da man",
"linearElement": "Faga clic para iniciar varios puntos, arrastre para unha sola liña", "linearElement": "Faga clic para iniciar varios puntos, arrastre para unha sola liña",
"freeDraw": "Fai clic e arrastra, solta cando acabes", "freeDraw": "Fai clic e arrastra, solta cando acabes",
"text": "Consello: tamén podes engadir texto facendo dobre-clic en calquera lugar coa ferramenta de selección", "text": "Consello: tamén podes engadir texto facendo dobre-clic en calquera lugar coa ferramenta de selección",
@@ -245,7 +246,8 @@
"publishLibrary": "Publica a túa propia biblioteca", "publishLibrary": "Publica a túa propia biblioteca",
"bindTextToElement": "Prema a tecla enter para engadir texto", "bindTextToElement": "Prema a tecla enter para engadir texto",
"deepBoxSelect": "Manteña pulsado CtrlOrCmd para seleccionar en profundidade e evitar o arrastre", "deepBoxSelect": "Manteña pulsado CtrlOrCmd para seleccionar en profundidade e evitar o arrastre",
"eraserRevert": "Manteña pulsado Alt para reverter os elementos marcados para a súa eliminación" "eraserRevert": "Manteña pulsado Alt para reverter os elementos marcados para a súa eliminación",
"firefox_clipboard_write": "Esta función pódese activar establecendo a opción \"dom.events.asyncClipboard.clipboardItem\" a \"true\". Para cambiar as opcións do navegador en Firefox, visita a páxina \"about:config\"."
}, },
"canvasError": { "canvasError": {
"cannotShowPreview": "Non se pode mostrar a vista previa", "cannotShowPreview": "Non se pode mostrar a vista previa",
+5 -3
View File
@@ -219,7 +219,8 @@
"lock": "השאר את הכלי הנבחר פעיל גם לאחר סיום הציור", "lock": "השאר את הכלי הנבחר פעיל גם לאחר סיום הציור",
"penMode": "", "penMode": "",
"link": "הוספה/עדכון של קישור עבור הצורה הנבחרת", "link": "הוספה/עדכון של קישור עבור הצורה הנבחרת",
"eraser": "מחק" "eraser": "מחק",
"hand": ""
}, },
"headings": { "headings": {
"canvasActions": "פעולות הלוח", "canvasActions": "פעולות הלוח",
@@ -227,7 +228,7 @@
"shapes": "צורות" "shapes": "צורות"
}, },
"hints": { "hints": {
"canvasPanning": "כדי להזיז את הקנבס לחצו על גלגל העכבר או על מקש הרווח תוך כדי גרירה", "canvasPanning": "",
"linearElement": "הקלק בשביל לבחור נקודות מרובות, גרור בשביל קו בודד", "linearElement": "הקלק בשביל לבחור נקודות מרובות, גרור בשביל קו בודד",
"freeDraw": "לחץ וגרור, שחרר כשסיימת", "freeDraw": "לחץ וגרור, שחרר כשסיימת",
"text": "טיפ: אפשר להוסיף טקסט על ידי לחיצה כפולה בכל מקום עם כלי הבחירה", "text": "טיפ: אפשר להוסיף טקסט על ידי לחיצה כפולה בכל מקום עם כלי הבחירה",
@@ -245,7 +246,8 @@
"publishLibrary": "פירסום ספריה אישית", "publishLibrary": "פירסום ספריה אישית",
"bindTextToElement": "יש להקיש Enter כדי להוסיף טקסט", "bindTextToElement": "יש להקיש Enter כדי להוסיף טקסט",
"deepBoxSelect": "", "deepBoxSelect": "",
"eraserRevert": "" "eraserRevert": "",
"firefox_clipboard_write": ""
}, },
"canvasError": { "canvasError": {
"cannotShowPreview": "לא הצלחנו להציג את התצוגה המקדימה", "cannotShowPreview": "לא הצלחנו להציג את התצוגה המקדימה",
+12 -10
View File
@@ -219,7 +219,8 @@
"lock": "ड्राइंग के बाद चयनित टूल को सक्रिय रखें", "lock": "ड्राइंग के बाद चयनित टूल को सक्रिय रखें",
"penMode": "पेन का मोड - स्पर्श टाले", "penMode": "पेन का मोड - स्पर्श टाले",
"link": "", "link": "",
"eraser": "रबड़" "eraser": "रबड़",
"hand": "हाथ ( खिसकाने का औज़ार)"
}, },
"headings": { "headings": {
"canvasActions": "कैनवास क्रिया", "canvasActions": "कैनवास क्रिया",
@@ -227,7 +228,7 @@
"shapes": "आकृतियाँ" "shapes": "आकृतियाँ"
}, },
"hints": { "hints": {
"canvasPanning": "", "canvasPanning": "कैनवास को सरकाने के लिए, ड्रैग करते समय माउस व्हील को पकड़े रखे या स्पेसबार को दबाए रखे, अथवा हाथ वाले औज़ार का उपयोग करें",
"linearElement": "कई बिंदुओं को शुरू करने के लिए क्लिक करें, सिंगल लाइन के लिए खींचें", "linearElement": "कई बिंदुओं को शुरू करने के लिए क्लिक करें, सिंगल लाइन के लिए खींचें",
"freeDraw": "क्लिक करें और खींचें। समाप्त करने के लिए, छोड़ो", "freeDraw": "क्लिक करें और खींचें। समाप्त करने के लिए, छोड़ो",
"text": "आप चयन टूल से कहीं भी डबल-क्लिक करके टेक्स्ट जोड़ सकते हैं", "text": "आप चयन टूल से कहीं भी डबल-क्लिक करके टेक्स्ट जोड़ सकते हैं",
@@ -245,7 +246,8 @@
"publishLibrary": "", "publishLibrary": "",
"bindTextToElement": "", "bindTextToElement": "",
"deepBoxSelect": "", "deepBoxSelect": "",
"eraserRevert": "मिटाने के लिए चुने हुए चीजों को ना चुनने के लिए Alt साथ में दबाए" "eraserRevert": "मिटाने के लिए चुने हुए चीजों को ना चुनने के लिए Alt साथ में दबाए",
"firefox_clipboard_write": "\"dom.events.asyncClipboard.clipboardItem\" फ़्लैग को \"true\" पर सेट करके इस सुविधा को संभवतः सक्षम किया जा सकता है। Firefox में ब्राउज़र फ़्लैग बदलने के लिए, \"about:config\" पृष्ठ पर जाएँ।"
}, },
"canvasError": { "canvasError": {
"cannotShowPreview": "पूर्वावलोकन नहीं दिखा सकते हैं", "cannotShowPreview": "पूर्वावलोकन नहीं दिखा सकते हैं",
@@ -448,15 +450,15 @@
}, },
"welcomeScreen": { "welcomeScreen": {
"app": { "app": {
"center_heading": "", "center_heading": "आपका सर्व डेटा ब्राउज़र के भीतर स्थानिक जगह पे सुरक्षित किया गया.",
"center_heading_plus": "", "center_heading_plus": "बजाय आपको Excalidraw+ पर जाना है?",
"menuHint": "" "menuHint": "निर्यात, पसंद, भाषायें, ..."
}, },
"defaults": { "defaults": {
"menuHint": "", "menuHint": "निर्यात, पसंद, और भी...",
"center_heading": "", "center_heading": "चित्रांकन। बनाया गया। सरल।",
"toolbarHint": "", "toolbarHint": "एक औजार चुने और चित्रकारी प्रारंभ करे!",
"helpHint": "" "helpHint": "शॉर्ट्कट और सहाय्य"
} }
} }
} }
+5 -3
View File
@@ -219,7 +219,8 @@
"lock": "Rajzolás után az aktív eszközt tartsa kijelölve", "lock": "Rajzolás után az aktív eszközt tartsa kijelölve",
"penMode": "", "penMode": "",
"link": "Hivatkozás hozzáadása/frissítése a kiválasztott alakzathoz", "link": "Hivatkozás hozzáadása/frissítése a kiválasztott alakzathoz",
"eraser": "" "eraser": "",
"hand": ""
}, },
"headings": { "headings": {
"canvasActions": "Vászon műveletek", "canvasActions": "Vászon műveletek",
@@ -227,7 +228,7 @@
"shapes": "Alakzatok" "shapes": "Alakzatok"
}, },
"hints": { "hints": {
"canvasPanning": "A vászon mozgatásához tartsd lenyomva az egér görgőjét vagy a szóköz billentyűt húzás közben", "canvasPanning": "",
"linearElement": "Kattintással görbe, az eger húzásával pedig egyenes nyilat rajzolhatsz", "linearElement": "Kattintással görbe, az eger húzásával pedig egyenes nyilat rajzolhatsz",
"freeDraw": "Kattints és húzd, majd engedd el, amikor végeztél", "freeDraw": "Kattints és húzd, majd engedd el, amikor végeztél",
"text": "Tipp: A kijelölés eszközzel a dupla kattintás új szöveget hoz létre", "text": "Tipp: A kijelölés eszközzel a dupla kattintás új szöveget hoz létre",
@@ -245,7 +246,8 @@
"publishLibrary": "Tedd közzé saját könyvtáradat", "publishLibrary": "Tedd közzé saját könyvtáradat",
"bindTextToElement": "Nyomd meg az Entert szöveg hozzáadáshoz", "bindTextToElement": "Nyomd meg az Entert szöveg hozzáadáshoz",
"deepBoxSelect": "Tartsd lenyomva a Ctrl/Cmd billentyűt a mély kijelöléshez és a húzás megakadályozásához", "deepBoxSelect": "Tartsd lenyomva a Ctrl/Cmd billentyűt a mély kijelöléshez és a húzás megakadályozásához",
"eraserRevert": "" "eraserRevert": "",
"firefox_clipboard_write": ""
}, },
"canvasError": { "canvasError": {
"cannotShowPreview": "Előnézet nem jeleníthető meg", "cannotShowPreview": "Előnézet nem jeleníthető meg",
+5 -3
View File
@@ -219,7 +219,8 @@
"lock": "Biarkan alat yang dipilih aktif setelah menggambar", "lock": "Biarkan alat yang dipilih aktif setelah menggambar",
"penMode": "Mode pena - mencegah sentuhan", "penMode": "Mode pena - mencegah sentuhan",
"link": "Tambah/Perbarui tautan untuk bentuk yang dipilih", "link": "Tambah/Perbarui tautan untuk bentuk yang dipilih",
"eraser": "Penghapus" "eraser": "Penghapus",
"hand": ""
}, },
"headings": { "headings": {
"canvasActions": "Opsi Kanvas", "canvasActions": "Opsi Kanvas",
@@ -227,7 +228,7 @@
"shapes": "Bentuk" "shapes": "Bentuk"
}, },
"hints": { "hints": {
"canvasPanning": "Untuk memindahkan kanvas, tekan roda mouse atau spasi ketika menarik", "canvasPanning": "",
"linearElement": "Klik untuk memulai banyak poin, seret untuk satu baris", "linearElement": "Klik untuk memulai banyak poin, seret untuk satu baris",
"freeDraw": "Klik dan seret, lepaskan jika Anda selesai", "freeDraw": "Klik dan seret, lepaskan jika Anda selesai",
"text": "Tip: Anda juga dapat menambahkan teks dengan klik ganda di mana saja dengan alat pemilihan", "text": "Tip: Anda juga dapat menambahkan teks dengan klik ganda di mana saja dengan alat pemilihan",
@@ -245,7 +246,8 @@
"publishLibrary": "Terbitkan pustaka Anda", "publishLibrary": "Terbitkan pustaka Anda",
"bindTextToElement": "Tekan enter untuk tambahkan teks", "bindTextToElement": "Tekan enter untuk tambahkan teks",
"deepBoxSelect": "Tekan Ctrl atau Cmd untuk memilih yang di dalam, dan mencegah penggeseran", "deepBoxSelect": "Tekan Ctrl atau Cmd untuk memilih yang di dalam, dan mencegah penggeseran",
"eraserRevert": "Tahan Alt untuk mengembalikan elemen yang ditandai untuk dihapus" "eraserRevert": "Tahan Alt untuk mengembalikan elemen yang ditandai untuk dihapus",
"firefox_clipboard_write": "Fitur ini dapat diaktifkan melalui pengaturan flag \"dom.events.asyncClipboard.clipboardItem\" ke \"true\". Untuk mengganti flag di Firefox, pergi ke laman \"about:config\"."
}, },
"canvasError": { "canvasError": {
"cannotShowPreview": "Tidak dapat menampilkan pratinjau", "cannotShowPreview": "Tidak dapat menampilkan pratinjau",
+5 -3
View File
@@ -219,7 +219,8 @@
"lock": "Mantieni lo strumento selezionato attivo dopo aver disegnato", "lock": "Mantieni lo strumento selezionato attivo dopo aver disegnato",
"penMode": "Modalità penna - previene il tocco", "penMode": "Modalità penna - previene il tocco",
"link": "Aggiungi/ aggiorna il link per una forma selezionata", "link": "Aggiungi/ aggiorna il link per una forma selezionata",
"eraser": "Gomma" "eraser": "Gomma",
"hand": "Mano (strumento di panoramica)"
}, },
"headings": { "headings": {
"canvasActions": "Azioni sulla Tela", "canvasActions": "Azioni sulla Tela",
@@ -227,7 +228,7 @@
"shapes": "Forme" "shapes": "Forme"
}, },
"hints": { "hints": {
"canvasPanning": "Per spostare la tela, tieni premuta la rotella del mouse o la barra spaziatrice mentre la trascini", "canvasPanning": "Per spostare la tela, tieni premuta la rotellina del mouse o la barra spaziatrice mentre trascini oppure usa lo strumento mano",
"linearElement": "Clicca per iniziare una linea in più punti, trascina per singola linea", "linearElement": "Clicca per iniziare una linea in più punti, trascina per singola linea",
"freeDraw": "Clicca e trascina, rilascia quando avrai finito", "freeDraw": "Clicca e trascina, rilascia quando avrai finito",
"text": "Suggerimento: puoi anche aggiungere del testo facendo doppio clic ovunque con lo strumento di selezione", "text": "Suggerimento: puoi anche aggiungere del testo facendo doppio clic ovunque con lo strumento di selezione",
@@ -245,7 +246,8 @@
"publishLibrary": "Pubblica la tua libreria", "publishLibrary": "Pubblica la tua libreria",
"bindTextToElement": "Premi invio per aggiungere il testo", "bindTextToElement": "Premi invio per aggiungere il testo",
"deepBoxSelect": "Tieni premuto CtrlOCmd per selezionare in profondità e per impedire il trascinamento", "deepBoxSelect": "Tieni premuto CtrlOCmd per selezionare in profondità e per impedire il trascinamento",
"eraserRevert": "Tieni premuto Alt per ripristinare gli elementi contrassegnati per l'eliminazione" "eraserRevert": "Tieni premuto Alt per ripristinare gli elementi contrassegnati per l'eliminazione",
"firefox_clipboard_write": "Questa funzione può essere abilitata impostando il flag \"dom.events.asyncClipboard.clipboardItem\" su \"true\". Per modificare i flag del browser in Firefox, visitare la pagina \"about:config\"."
}, },
"canvasError": { "canvasError": {
"cannotShowPreview": "Impossibile visualizzare l'anteprima", "cannotShowPreview": "Impossibile visualizzare l'anteprima",
+5 -3
View File
@@ -219,7 +219,8 @@
"lock": "描画後も使用中のツールを選択したままにする", "lock": "描画後も使用中のツールを選択したままにする",
"penMode": "ペンモード - タッチ防止", "penMode": "ペンモード - タッチ防止",
"link": "選択した図形のリンクを追加/更新", "link": "選択した図形のリンクを追加/更新",
"eraser": "消しゴム" "eraser": "消しゴム",
"hand": ""
}, },
"headings": { "headings": {
"canvasActions": "キャンバス操作", "canvasActions": "キャンバス操作",
@@ -227,7 +228,7 @@
"shapes": "図形" "shapes": "図形"
}, },
"hints": { "hints": {
"canvasPanning": "キャンバスを移動するには、マウスホイールまたはスペースバーを押しながらドラッグします", "canvasPanning": "",
"linearElement": "クリックすると複数の頂点からなる曲線を開始、ドラッグすると直線", "linearElement": "クリックすると複数の頂点からなる曲線を開始、ドラッグすると直線",
"freeDraw": "クリックしてドラッグします。離すと終了します", "freeDraw": "クリックしてドラッグします。離すと終了します",
"text": "ヒント: 選択ツールを使用して任意の場所をダブルクリックしてテキストを追加することもできます", "text": "ヒント: 選択ツールを使用して任意の場所をダブルクリックしてテキストを追加することもできます",
@@ -245,7 +246,8 @@
"publishLibrary": "自分のライブラリを公開", "publishLibrary": "自分のライブラリを公開",
"bindTextToElement": "Enterを押してテキストを追加", "bindTextToElement": "Enterを押してテキストを追加",
"deepBoxSelect": "CtrlOrCmd を押し続けることでドラッグを抑止し、深い選択を行います", "deepBoxSelect": "CtrlOrCmd を押し続けることでドラッグを抑止し、深い選択を行います",
"eraserRevert": "Alt を押し続けることで削除マークされた要素を元に戻す" "eraserRevert": "Alt を押し続けることで削除マークされた要素を元に戻す",
"firefox_clipboard_write": "この機能は、\"dom.events.asyncClipboard.clipboardItem\" フラグを \"true\" に設定することで有効になる可能性があります。Firefox でブラウザーの設定を変更するには、\"about:config\" ページを参照してください。"
}, },
"canvasError": { "canvasError": {
"cannotShowPreview": "プレビューを表示できません", "cannotShowPreview": "プレビューを表示できません",
+8 -6
View File
@@ -202,8 +202,8 @@
"invalidSVGString": "SVG armeɣtu.", "invalidSVGString": "SVG armeɣtu.",
"cannotResolveCollabServer": "Ulamek tuqqna s aqeddac n umyalel. Ma ulac uɣilif ales asali n usebter sakin eɛreḍ tikkelt-nniḍen.", "cannotResolveCollabServer": "Ulamek tuqqna s aqeddac n umyalel. Ma ulac uɣilif ales asali n usebter sakin eɛreḍ tikkelt-nniḍen.",
"importLibraryError": "Ur d-ssalay ara tamkarḍit", "importLibraryError": "Ur d-ssalay ara tamkarḍit",
"collabSaveFailed": "", "collabSaveFailed": "Ulamek asekles deg uzadur n yisefka deg ugilal. Ma ikemmel wugur, isefk ad teskelseḍ afaylu s wudem adigan akken ad tetḥeqqeḍ ur tesruḥuyeḍ ara amahil-inek•inem.",
"collabSaveFailed_sizeExceeded": "" "collabSaveFailed_sizeExceeded": "Ulamek asekles deg uzadur n yisefka deg ugilal, taɣzut n usuneɣ tettban-d temqer aṭas. Isefk ad teskelseḍ afaylu s wudem adigan akken ad tetḥeqqeḍ ur tesruḥuyeḍ ara amahil-inek•inem."
}, },
"toolBar": { "toolBar": {
"selection": "Tafrayt", "selection": "Tafrayt",
@@ -217,9 +217,10 @@
"text": "Aḍris", "text": "Aḍris",
"library": "Tamkarḍit", "library": "Tamkarḍit",
"lock": "Eǧǧ afecku n tefrayt yermed mbaɛd asuneɣ", "lock": "Eǧǧ afecku n tefrayt yermed mbaɛd asuneɣ",
"penMode": "", "penMode": "Askar n yimru - gdel tanalit",
"link": "Rnu/leqqem aseɣwen i talɣa yettwafernen", "link": "Rnu/leqqem aseɣwen i talɣa yettwafernen",
"eraser": "Sfeḍ" "eraser": "Sfeḍ",
"hand": "Afus (afecku n usmutti n tmuɣli)"
}, },
"headings": { "headings": {
"canvasActions": "Tigawin n teɣzut n usuneɣ", "canvasActions": "Tigawin n teɣzut n usuneɣ",
@@ -227,7 +228,7 @@
"shapes": "Talɣiwin" "shapes": "Talɣiwin"
}, },
"hints": { "hints": {
"canvasPanning": "Akken ad tesmuttiḍ taɣzut n usuneɣ, ṭṭef ṛṛuda n umumed, neɣ afeggag n tallunt mi ara tzuɣreḍ", "canvasPanning": "",
"linearElement": "Ssit akken ad tebduḍ aṭas n tenqiḍin, zuɣer i yiwen n yizirig", "linearElement": "Ssit akken ad tebduḍ aṭas n tenqiḍin, zuɣer i yiwen n yizirig",
"freeDraw": "Ssit yerna zuɣer, serreḥ ticki tfukeḍ", "freeDraw": "Ssit yerna zuɣer, serreḥ ticki tfukeḍ",
"text": "Tixidest: tzemreḍ daɣen ad ternuḍ aḍris s usiti snat n tikkal anida tebɣiḍ s ufecku n tefrayt", "text": "Tixidest: tzemreḍ daɣen ad ternuḍ aḍris s usiti snat n tikkal anida tebɣiḍ s ufecku n tefrayt",
@@ -245,7 +246,8 @@
"publishLibrary": "Siẓreg tamkarḍit-inek•inem", "publishLibrary": "Siẓreg tamkarḍit-inek•inem",
"bindTextToElement": "Ssed ɣef kcem akken ad ternuḍ aḍris", "bindTextToElement": "Ssed ɣef kcem akken ad ternuḍ aḍris",
"deepBoxSelect": "Ṭṭef CtrlOrCmd akken ad tferneḍ s telqey, yerna ad trewleḍ i uzuɣer", "deepBoxSelect": "Ṭṭef CtrlOrCmd akken ad tferneḍ s telqey, yerna ad trewleḍ i uzuɣer",
"eraserRevert": "Ssed Alt akken ad tsefsxeḍ iferdisen yettwacerḍen i tukksa" "eraserRevert": "Ssed Alt akken ad tsefsxeḍ iferdisen yettwacerḍen i tukksa",
"firefox_clipboard_write": ""
}, },
"canvasError": { "canvasError": {
"cannotShowPreview": "Ulamek abeqqeḍ n teskant", "cannotShowPreview": "Ulamek abeqqeḍ n teskant",
+4 -2
View File
@@ -219,7 +219,8 @@
"lock": "", "lock": "",
"penMode": "", "penMode": "",
"link": "", "link": "",
"eraser": "" "eraser": "",
"hand": ""
}, },
"headings": { "headings": {
"canvasActions": "", "canvasActions": "",
@@ -245,7 +246,8 @@
"publishLibrary": "", "publishLibrary": "",
"bindTextToElement": "", "bindTextToElement": "",
"deepBoxSelect": "", "deepBoxSelect": "",
"eraserRevert": "" "eraserRevert": "",
"firefox_clipboard_write": ""
}, },
"canvasError": { "canvasError": {
"cannotShowPreview": "", "cannotShowPreview": "",
+5 -3
View File
@@ -219,7 +219,8 @@
"lock": "선택된 도구 유지하기", "lock": "선택된 도구 유지하기",
"penMode": "펜 모드 - 터치 방지", "penMode": "펜 모드 - 터치 방지",
"link": "선택한 도형에 대해서 링크를 추가/업데이트", "link": "선택한 도형에 대해서 링크를 추가/업데이트",
"eraser": "지우개" "eraser": "지우개",
"hand": ""
}, },
"headings": { "headings": {
"canvasActions": "캔버스 동작", "canvasActions": "캔버스 동작",
@@ -227,7 +228,7 @@
"shapes": "모양" "shapes": "모양"
}, },
"hints": { "hints": {
"canvasPanning": "캔버스를 옮기려면 마우스 휠이나 스페이스바를 누르고 드래그하기", "canvasPanning": "",
"linearElement": "여러 점을 연결하려면 클릭하고, 직선을 그리려면 바로 드래그하세요.", "linearElement": "여러 점을 연결하려면 클릭하고, 직선을 그리려면 바로 드래그하세요.",
"freeDraw": "클릭 후 드래그하세요. 완료되면 놓으세요.", "freeDraw": "클릭 후 드래그하세요. 완료되면 놓으세요.",
"text": "팁: 선택 툴로 아무 곳이나 더블 클릭해 텍스트를 추가할 수도 있습니다.", "text": "팁: 선택 툴로 아무 곳이나 더블 클릭해 텍스트를 추가할 수도 있습니다.",
@@ -245,7 +246,8 @@
"publishLibrary": "당신만의 라이브러리를 게시하기", "publishLibrary": "당신만의 라이브러리를 게시하기",
"bindTextToElement": "Enter 키를 눌러서 텍스트 추가하기", "bindTextToElement": "Enter 키를 눌러서 텍스트 추가하기",
"deepBoxSelect": "CtrlOrCmd 키를 눌러서 깊게 선택하고, 드래그하지 않도록 하기", "deepBoxSelect": "CtrlOrCmd 키를 눌러서 깊게 선택하고, 드래그하지 않도록 하기",
"eraserRevert": "Alt를 눌러서 삭제하도록 지정된 요소를 되돌리기" "eraserRevert": "Alt를 눌러서 삭제하도록 지정된 요소를 되돌리기",
"firefox_clipboard_write": ""
}, },
"canvasError": { "canvasError": {
"cannotShowPreview": "미리보기를 볼 수 없습니다", "cannotShowPreview": "미리보기를 볼 수 없습니다",
+5 -3
View File
@@ -219,7 +219,8 @@
"lock": "ئامێرە دیاریکراوەکان چالاک بهێڵەوە دوای وێنەکێشان", "lock": "ئامێرە دیاریکراوەکان چالاک بهێڵەوە دوای وێنەکێشان",
"penMode": "شێوازی قەڵەم - دەست لێدان ڕابگرە", "penMode": "شێوازی قەڵەم - دەست لێدان ڕابگرە",
"link": "زیادکردن/ نوێکردنەوەی لینک بۆ شێوەی دیاریکراو", "link": "زیادکردن/ نوێکردنەوەی لینک بۆ شێوەی دیاریکراو",
"eraser": "سڕەر" "eraser": "سڕەر",
"hand": ""
}, },
"headings": { "headings": {
"canvasActions": "کردارەکانی تابلۆ", "canvasActions": "کردارەکانی تابلۆ",
@@ -227,7 +228,7 @@
"shapes": "شێوەکان" "shapes": "شێوەکان"
}, },
"hints": { "hints": {
"canvasPanning": "بۆ جوڵاندنی تابلۆ، لە کاتی ڕاکێشاندا ویلی ماوس یان شریتی بۆشایی دابگرە", "canvasPanning": "",
"linearElement": "کرتە بکە بۆ دەستپێکردنی چەند خاڵێک، ڕایبکێشە بۆ یەک هێڵ", "linearElement": "کرتە بکە بۆ دەستپێکردنی چەند خاڵێک، ڕایبکێشە بۆ یەک هێڵ",
"freeDraw": "کرتە بکە و ڕایبکێشە، کاتێک تەواو بوویت دەست هەڵگرە", "freeDraw": "کرتە بکە و ڕایبکێشە، کاتێک تەواو بوویت دەست هەڵگرە",
"text": "زانیاری: هەروەها دەتوانیت دەق زیادبکەیت بە دوو کرتەکردن لە هەر شوێنێک لەگەڵ ئامڕازی دەستنیشانکردن", "text": "زانیاری: هەروەها دەتوانیت دەق زیادبکەیت بە دوو کرتەکردن لە هەر شوێنێک لەگەڵ ئامڕازی دەستنیشانکردن",
@@ -245,7 +246,8 @@
"publishLibrary": "کتێبخانەی تایبەت بە خۆت بڵاوبکەرەوە", "publishLibrary": "کتێبخانەی تایبەت بە خۆت بڵاوبکەرەوە",
"bindTextToElement": "بۆ زیادکردنی دەق enter بکە", "bindTextToElement": "بۆ زیادکردنی دەق enter بکە",
"deepBoxSelect": "CtrlOrCmd ڕابگرە بۆ هەڵبژاردنی قووڵ، و بۆ ڕێگریکردن لە ڕاکێشان", "deepBoxSelect": "CtrlOrCmd ڕابگرە بۆ هەڵبژاردنی قووڵ، و بۆ ڕێگریکردن لە ڕاکێشان",
"eraserRevert": "بۆ گەڕاندنەوەی ئەو توخمانەی کە بۆ سڕینەوە نیشانە کراون، Alt ڕابگرە" "eraserRevert": "بۆ گەڕاندنەوەی ئەو توخمانەی کە بۆ سڕینەوە نیشانە کراون، Alt ڕابگرە",
"firefox_clipboard_write": ""
}, },
"canvasError": { "canvasError": {
"cannotShowPreview": "ناتوانرێ پێشبینین پیشان بدرێت", "cannotShowPreview": "ناتوانرێ پێشبینین پیشان بدرێت",
+5 -3
View File
@@ -219,7 +219,8 @@
"lock": "Baigus piešti, išlaikyti pasirinktą įrankį", "lock": "Baigus piešti, išlaikyti pasirinktą įrankį",
"penMode": "Rašyklio režimas - neleisti prisilietimų", "penMode": "Rašyklio režimas - neleisti prisilietimų",
"link": "Pridėti / Atnaujinti pasirinktos figūros nuorodą", "link": "Pridėti / Atnaujinti pasirinktos figūros nuorodą",
"eraser": "Trintukas" "eraser": "Trintukas",
"hand": ""
}, },
"headings": { "headings": {
"canvasActions": "Veiksmai su drobe", "canvasActions": "Veiksmai su drobe",
@@ -227,7 +228,7 @@
"shapes": "Figūros" "shapes": "Figūros"
}, },
"hints": { "hints": {
"canvasPanning": "Norint judinti drobę, judink pelę kartu įspaudus pelės ratuką arba tarpo klavišą", "canvasPanning": "",
"linearElement": "Paspaudimai sukurs papildomus taškus, nepertraukiamas tempimas sukurs liniją", "linearElement": "Paspaudimai sukurs papildomus taškus, nepertraukiamas tempimas sukurs liniją",
"freeDraw": "Spausk ir tempk, paleisk kai norėsi pabaigti", "freeDraw": "Spausk ir tempk, paleisk kai norėsi pabaigti",
"text": "Užuomina: tekstą taip pat galima pridėti bet kur su dvigubu pelės paspaudimu, kol parinkas žymėjimo įrankis", "text": "Užuomina: tekstą taip pat galima pridėti bet kur su dvigubu pelės paspaudimu, kol parinkas žymėjimo įrankis",
@@ -245,7 +246,8 @@
"publishLibrary": "", "publishLibrary": "",
"bindTextToElement": "", "bindTextToElement": "",
"deepBoxSelect": "", "deepBoxSelect": "",
"eraserRevert": "" "eraserRevert": "",
"firefox_clipboard_write": ""
}, },
"canvasError": { "canvasError": {
"cannotShowPreview": "", "cannotShowPreview": "",
+5 -3
View File
@@ -219,7 +219,8 @@
"lock": "Paturēt izvēlēto rīku pēc darbības", "lock": "Paturēt izvēlēto rīku pēc darbības",
"penMode": "Pildspalvas režīms novērst pieskaršanos", "penMode": "Pildspalvas režīms novērst pieskaršanos",
"link": "Pievienot/rediģēt atlasītās figūras saiti", "link": "Pievienot/rediģēt atlasītās figūras saiti",
"eraser": "Dzēšgumija" "eraser": "Dzēšgumija",
"hand": ""
}, },
"headings": { "headings": {
"canvasActions": "Tāfeles darbības", "canvasActions": "Tāfeles darbības",
@@ -227,7 +228,7 @@
"shapes": "Formas" "shapes": "Formas"
}, },
"hints": { "hints": {
"canvasPanning": "Lai bīdītu tāfeli, turiet nospiestu ritināšanas vai atstarpes taustiņu, velkot ar peli", "canvasPanning": "",
"linearElement": "Klikšķiniet, lai sāktu zīmēt vairākus punktus; velciet, lai zīmētu līniju", "linearElement": "Klikšķiniet, lai sāktu zīmēt vairākus punktus; velciet, lai zīmētu līniju",
"freeDraw": "Spiediet un velciet; atlaidiet, kad pabeidzat", "freeDraw": "Spiediet un velciet; atlaidiet, kad pabeidzat",
"text": "Ieteikums: lai pievienotu tekstu, varat arī jebkur dubultklikšķināt ar atlases rīku", "text": "Ieteikums: lai pievienotu tekstu, varat arī jebkur dubultklikšķināt ar atlases rīku",
@@ -245,7 +246,8 @@
"publishLibrary": "Publicēt savu bibliotēku", "publishLibrary": "Publicēt savu bibliotēku",
"bindTextToElement": "Spiediet ievades taustiņu, lai pievienotu tekstu", "bindTextToElement": "Spiediet ievades taustiņu, lai pievienotu tekstu",
"deepBoxSelect": "Turient nospiestu Ctrl vai Cmd, lai atlasītu dziļumā un lai nepieļautu objektu pavilkšanu", "deepBoxSelect": "Turient nospiestu Ctrl vai Cmd, lai atlasītu dziļumā un lai nepieļautu objektu pavilkšanu",
"eraserRevert": "Turiet Alt, lai noņemtu elementus no dzēsšanas atlases" "eraserRevert": "Turiet Alt, lai noņemtu elementus no dzēsšanas atlases",
"firefox_clipboard_write": ""
}, },
"canvasError": { "canvasError": {
"cannotShowPreview": "Nevar rādīt priekšskatījumu", "cannotShowPreview": "Nevar rādīt priekšskatījumu",
+12 -10
View File
@@ -219,7 +219,8 @@
"lock": "निवडलेले यंत्र चित्रकरण झाल्या नंतर ही सक्रिय ठेवा", "lock": "निवडलेले यंत्र चित्रकरण झाल्या नंतर ही सक्रिय ठेवा",
"penMode": "पेन चा मोड - स्पर्श टाळा", "penMode": "पेन चा मोड - स्पर्श टाळा",
"link": "निवडलेल्या आकारासाठी दुवा जोडा/बदल करा", "link": "निवडलेल्या आकारासाठी दुवा जोडा/बदल करा",
"eraser": "खोड रबर" "eraser": "खोड रबर",
"hand": "हात ( सरकवण्या चे उपकरण)"
}, },
"headings": { "headings": {
"canvasActions": "पटल क्रिया", "canvasActions": "पटल क्रिया",
@@ -227,7 +228,7 @@
"shapes": "आकार" "shapes": "आकार"
}, },
"hints": { "hints": {
"canvasPanning": "पटल हलवण्यासाठी, ड्रग करताना माउस वील ला पकड़ा किव्हा स्पेसबार दाबून ठेवा", "canvasPanning": "कॅनव्हास सरकवण्या साठी, ड्रग करताना माउस व्हील धरा किवा स्पेसबार दाबून ठेवा अथवा हात वालं उपकरण वापरा",
"linearElement": "अनेक बिंदु साठी क्लिक करा, रेघे साठी ड्रैग करा", "linearElement": "अनेक बिंदु साठी क्लिक करा, रेघे साठी ड्रैग करा",
"freeDraw": "क्लिक आणि ड्रैग करा, झालं तेव्हा सोडा", "freeDraw": "क्लिक आणि ड्रैग करा, झालं तेव्हा सोडा",
"text": "टीप: तुम्हीं निवड यंत्रानी कोठेही दुहेरी क्लिक करून टेक्स्ट जोडू शकता", "text": "टीप: तुम्हीं निवड यंत्रानी कोठेही दुहेरी क्लिक करून टेक्स्ट जोडू शकता",
@@ -245,7 +246,8 @@
"publishLibrary": "आपला खाजगी संग्रह प्रकाशित करा", "publishLibrary": "आपला खाजगी संग्रह प्रकाशित करा",
"bindTextToElement": "मजकूर जोडण्यासाठी एंटर की दाबा", "bindTextToElement": "मजकूर जोडण्यासाठी एंटर की दाबा",
"deepBoxSelect": "खोल निवड ह्या साठी कंट्रोल किव्हा कमांड दाबून ठेवा, आणि बाहेर खेचणे वाचवण्या साठी पण", "deepBoxSelect": "खोल निवड ह्या साठी कंट्रोल किव्हा कमांड दाबून ठेवा, आणि बाहेर खेचणे वाचवण्या साठी पण",
"eraserRevert": "खोडण्या साठी घेतलेल्या वस्तु ना घेण्या साठी Alt दाबून ठेवावे" "eraserRevert": "खोडण्या साठी घेतलेल्या वस्तु ना घेण्या साठी Alt दाबून ठेवावे",
"firefox_clipboard_write": "हे वैशिष्ट्य \"dom.events.asyncClipboard.clipboardItem\" फ्लॅग \"सत्य\" वर सेट करून शक्यतो सक्षम केले जाऊ शकते. Firefox मध्ये ब्राउझर फ्लॅग बदलण्यासाठी, \"about:config\" पृष्ठावर जा."
}, },
"canvasError": { "canvasError": {
"cannotShowPreview": "पूर्वावलोकन दाखवू शकत नाही", "cannotShowPreview": "पूर्वावलोकन दाखवू शकत नाही",
@@ -448,15 +450,15 @@
}, },
"welcomeScreen": { "welcomeScreen": {
"app": { "app": {
"center_heading": "", "center_heading": "तुमचा सर्व डेटा तुमच्या ब्राउझरमध्ये स्थानिक पातळीवर जतन केला जातो.",
"center_heading_plus": "", "center_heading_plus": "त्याऐवजी तुम्हाला Excalidraw+ वर जायचे आहे का?",
"menuHint": "" "menuHint": "निर्यात, आवड़ी-निवडी, भाषा, ..."
}, },
"defaults": { "defaults": {
"menuHint": "", "menuHint": "निर्यात, आवड़ी निवडी आणि आणकिही...",
"center_heading": "", "center_heading": "आकृत्या. काढणे. सोपे.",
"toolbarHint": "", "toolbarHint": "एक साधन निवडा आणि चित्रीकरण सुरु करा!",
"helpHint": "" "helpHint": "शॉर्टकट आणि सहाय्य"
} }
} }
} }
+4 -2
View File
@@ -219,7 +219,8 @@
"lock": "ရွေးချယ်ထားသောကိရိယာကိုသာဆက်သုံး", "lock": "ရွေးချယ်ထားသောကိရိယာကိုသာဆက်သုံး",
"penMode": "", "penMode": "",
"link": "", "link": "",
"eraser": "" "eraser": "",
"hand": ""
}, },
"headings": { "headings": {
"canvasActions": "ကားချပ်လုပ်ဆောင်ချက်", "canvasActions": "ကားချပ်လုပ်ဆောင်ချက်",
@@ -245,7 +246,8 @@
"publishLibrary": "", "publishLibrary": "",
"bindTextToElement": "", "bindTextToElement": "",
"deepBoxSelect": "", "deepBoxSelect": "",
"eraserRevert": "" "eraserRevert": "",
"firefox_clipboard_write": ""
}, },
"canvasError": { "canvasError": {
"cannotShowPreview": "နမူနာမပြသနိုင်ပါ", "cannotShowPreview": "နမူနာမပြသနိုင်ပါ",
+5 -3
View File
@@ -219,7 +219,8 @@
"lock": "Behold merket verktøy som aktivt", "lock": "Behold merket verktøy som aktivt",
"penMode": "Pennemodus - forhindre berøring", "penMode": "Pennemodus - forhindre berøring",
"link": "Legg til / oppdater link for en valgt figur", "link": "Legg til / oppdater link for en valgt figur",
"eraser": "Viskelær" "eraser": "Viskelær",
"hand": "Hånd (panoreringsverktøy)"
}, },
"headings": { "headings": {
"canvasActions": "Handlinger: lerret", "canvasActions": "Handlinger: lerret",
@@ -227,7 +228,7 @@
"shapes": "Former" "shapes": "Former"
}, },
"hints": { "hints": {
"canvasPanning": "For å flytte lerretet, hold musehjulet eller mellomromstasten mens du drar", "canvasPanning": "For å flytte lerretet, hold musehjulet eller mellomromstasten mens du drar, eller bruk hånd-verktøyet",
"linearElement": "Klikk for å starte linje med flere punkter, eller dra for en enkel linje", "linearElement": "Klikk for å starte linje med flere punkter, eller dra for en enkel linje",
"freeDraw": "Klikk og dra, slipp når du er ferdig", "freeDraw": "Klikk og dra, slipp når du er ferdig",
"text": "Tips: du kan også legge til tekst ved å dobbeltklikke hvor som helst med utvalgsverktøyet", "text": "Tips: du kan også legge til tekst ved å dobbeltklikke hvor som helst med utvalgsverktøyet",
@@ -245,7 +246,8 @@
"publishLibrary": "Publiser ditt eget bibliotek", "publishLibrary": "Publiser ditt eget bibliotek",
"bindTextToElement": "Trykk Enter for å legge til tekst", "bindTextToElement": "Trykk Enter for å legge til tekst",
"deepBoxSelect": "Hold CTRL/CMD for å markere dypt og forhindre flytting", "deepBoxSelect": "Hold CTRL/CMD for å markere dypt og forhindre flytting",
"eraserRevert": "Hold Alt for å reversere elementene merket for sletting" "eraserRevert": "Hold Alt for å reversere elementene merket for sletting",
"firefox_clipboard_write": "Denne funksjonen kan sannsynligvis aktiveres ved å sette \"dom.events.asyncClipboard.clipboardItem\" flagget til \"true\". For å endre nettleserens flagg i Firefox, besøk \"about:config\"-siden."
}, },
"canvasError": { "canvasError": {
"cannotShowPreview": "Kan ikke vise forhåndsvisning", "cannotShowPreview": "Kan ikke vise forhåndsvisning",
+5 -3
View File
@@ -219,7 +219,8 @@
"lock": "Geselecteerde tool actief houden na tekenen", "lock": "Geselecteerde tool actief houden na tekenen",
"penMode": "Pen modus - Blokkeer aanraken", "penMode": "Pen modus - Blokkeer aanraken",
"link": "", "link": "",
"eraser": "Gum" "eraser": "Gum",
"hand": ""
}, },
"headings": { "headings": {
"canvasActions": "Canvasacties", "canvasActions": "Canvasacties",
@@ -227,7 +228,7 @@
"shapes": "Vormen" "shapes": "Vormen"
}, },
"hints": { "hints": {
"canvasPanning": "Om canvas te verplaatsen, houd muiswiel of spatiebalk ingedrukt tijdens slepen", "canvasPanning": "",
"linearElement": "Klik om meerdere punten te starten, sleep voor één lijn", "linearElement": "Klik om meerdere punten te starten, sleep voor één lijn",
"freeDraw": "Klik en sleep, laat los als je klaar bent", "freeDraw": "Klik en sleep, laat los als je klaar bent",
"text": "Tip: je kunt tekst toevoegen door ergens dubbel te klikken met de selectietool", "text": "Tip: je kunt tekst toevoegen door ergens dubbel te klikken met de selectietool",
@@ -245,7 +246,8 @@
"publishLibrary": "Publiceer je eigen bibliotheek", "publishLibrary": "Publiceer je eigen bibliotheek",
"bindTextToElement": "", "bindTextToElement": "",
"deepBoxSelect": "", "deepBoxSelect": "",
"eraserRevert": "" "eraserRevert": "",
"firefox_clipboard_write": ""
}, },
"canvasError": { "canvasError": {
"cannotShowPreview": "Kan voorbeeld niet tonen", "cannotShowPreview": "Kan voorbeeld niet tonen",
+5 -3
View File
@@ -219,7 +219,8 @@
"lock": "Hald fram med valt verktøy", "lock": "Hald fram med valt verktøy",
"penMode": "", "penMode": "",
"link": "Legg til/ oppdater lenke til valt figur", "link": "Legg til/ oppdater lenke til valt figur",
"eraser": "Viskelêr" "eraser": "Viskelêr",
"hand": ""
}, },
"headings": { "headings": {
"canvasActions": "Handlingar: lerret", "canvasActions": "Handlingar: lerret",
@@ -227,7 +228,7 @@
"shapes": "Formar" "shapes": "Formar"
}, },
"hints": { "hints": {
"canvasPanning": "For å flytte lerretet, hald inne musehjulet eller mellomromstasten medan du dreg", "canvasPanning": "",
"linearElement": "Klikk for å starte linje med fleire punkt, eller drag for ei enkel linje", "linearElement": "Klikk for å starte linje med fleire punkt, eller drag for ei enkel linje",
"freeDraw": "Klikk og drag, slepp når du er ferdig", "freeDraw": "Klikk og drag, slepp når du er ferdig",
"text": "Tips: du kan òg leggje til tekst ved å dobbeltklikke kor som helst med utvalgsverktyet", "text": "Tips: du kan òg leggje til tekst ved å dobbeltklikke kor som helst med utvalgsverktyet",
@@ -245,7 +246,8 @@
"publishLibrary": "Publiser ditt eige bibliotek", "publishLibrary": "Publiser ditt eige bibliotek",
"bindTextToElement": "Trykk på enter for å legge til tekst", "bindTextToElement": "Trykk på enter for å legge til tekst",
"deepBoxSelect": "Hald inne Ctrl / Cmd for å velje djupt, og forhindre flytting", "deepBoxSelect": "Hald inne Ctrl / Cmd for å velje djupt, og forhindre flytting",
"eraserRevert": "Hald inne Alt for å reversere markering av element for sletting" "eraserRevert": "Hald inne Alt for å reversere markering av element for sletting",
"firefox_clipboard_write": ""
}, },
"canvasError": { "canvasError": {
"cannotShowPreview": "Kan ikkje vise førehandsvising", "cannotShowPreview": "Kan ikkje vise førehandsvising",
+9 -7
View File
@@ -38,8 +38,8 @@
"arrowhead_bar": "Barra", "arrowhead_bar": "Barra",
"arrowhead_dot": "Ponch", "arrowhead_dot": "Ponch",
"arrowhead_triangle": "Triangle", "arrowhead_triangle": "Triangle",
"fontSize": "Talha poliça", "fontSize": "Talha polissa",
"fontFamily": "Familha de poliça", "fontFamily": "Familha de polissa",
"onlySelected": "Seleccion sonque", "onlySelected": "Seleccion sonque",
"withBackground": "Rèireplan", "withBackground": "Rèireplan",
"exportEmbedScene": "Scèna embarcada", "exportEmbedScene": "Scèna embarcada",
@@ -106,8 +106,8 @@
"toggleTheme": "Alternar tèma", "toggleTheme": "Alternar tèma",
"personalLib": "Bibliotèca personala", "personalLib": "Bibliotèca personala",
"excalidrawLib": "Bibliotèca Excalidraw", "excalidrawLib": "Bibliotèca Excalidraw",
"decreaseFontSize": "Reduire talha poliça", "decreaseFontSize": "Reduire talha polissa",
"increaseFontSize": "Aumentar talha poliça", "increaseFontSize": "Aumentar talha polissa",
"unbindText": "Dessociar lo tèxte", "unbindText": "Dessociar lo tèxte",
"bindText": "Ligar lo tèxt al contenidor", "bindText": "Ligar lo tèxt al contenidor",
"link": { "link": {
@@ -219,7 +219,8 @@
"lock": "Mantenir activa laisina aprèp dessenhar", "lock": "Mantenir activa laisina aprèp dessenhar",
"penMode": "Mòde estilo - empachar lo contact", "penMode": "Mòde estilo - empachar lo contact",
"link": "Apondre/Actualizar lo ligam per una fòrma seleccionada", "link": "Apondre/Actualizar lo ligam per una fòrma seleccionada",
"eraser": "Goma" "eraser": "Goma",
"hand": ""
}, },
"headings": { "headings": {
"canvasActions": "Accions del canabàs", "canvasActions": "Accions del canabàs",
@@ -227,7 +228,7 @@
"shapes": "Formas" "shapes": "Formas"
}, },
"hints": { "hints": {
"canvasPanning": "Per desplaçar los canabasses, tenètz la rodeta de la mirga o la barra despaci pendent lo desplaçament", "canvasPanning": "",
"linearElement": "Clicatz per començar mantun punt, lisatz per una sola linha", "linearElement": "Clicatz per començar mantun punt, lisatz per una sola linha",
"freeDraw": "Clicatz e lisatz, relargatz un còp acabat", "freeDraw": "Clicatz e lisatz, relargatz un còp acabat",
"text": "Astúcia: podètz tanben apondre de tèxt en doble clicant ont que siá amb laisina de seleccion", "text": "Astúcia: podètz tanben apondre de tèxt en doble clicant ont que siá amb laisina de seleccion",
@@ -245,7 +246,8 @@
"publishLibrary": "Publicar vòstra pròpria bibliotèca", "publishLibrary": "Publicar vòstra pròpria bibliotèca",
"bindTextToElement": "Quichatz Entrada per apondre de tèxte", "bindTextToElement": "Quichatz Entrada per apondre de tèxte",
"deepBoxSelect": "Gardar CtrlOCmd per una seleccion gropada e empachar lo desplaçament", "deepBoxSelect": "Gardar CtrlOCmd per una seleccion gropada e empachar lo desplaçament",
"eraserRevert": "Tenètz quichat Alt per anullar los elements marcats per supression" "eraserRevert": "Tenètz quichat Alt per anullar los elements marcats per supression",
"firefox_clipboard_write": ""
}, },
"canvasError": { "canvasError": {
"cannotShowPreview": "Afichatge impossible de lapercebut", "cannotShowPreview": "Afichatge impossible de lapercebut",
+4 -2
View File
@@ -219,7 +219,8 @@
"lock": "ਡਰਾਇੰਗ ਤੋਂ ਬਾਅਦ ਵੀ ਚੁਣੇ ਹੋਏ ਸੰਦ ਨੂੰ ਸਰਗਰਮ ਰੱਖੋ ", "lock": "ਡਰਾਇੰਗ ਤੋਂ ਬਾਅਦ ਵੀ ਚੁਣੇ ਹੋਏ ਸੰਦ ਨੂੰ ਸਰਗਰਮ ਰੱਖੋ ",
"penMode": "", "penMode": "",
"link": "", "link": "",
"eraser": "ਰਬੜ" "eraser": "ਰਬੜ",
"hand": ""
}, },
"headings": { "headings": {
"canvasActions": "ਕੈਨਵਸ ਦੀਆਂ ਕਾਰਵਾਈਆਂ", "canvasActions": "ਕੈਨਵਸ ਦੀਆਂ ਕਾਰਵਾਈਆਂ",
@@ -245,7 +246,8 @@
"publishLibrary": "ਆਪਣੀ ਲਾਇਬ੍ਰੇਰੀ ਪ੍ਰਕਾਸ਼ਿਤ ਕਰੋ", "publishLibrary": "ਆਪਣੀ ਲਾਇਬ੍ਰੇਰੀ ਪ੍ਰਕਾਸ਼ਿਤ ਕਰੋ",
"bindTextToElement": "ਪਾਠ ਜੋੜਨ ਲਈ ਐੰਟਰ ਦਬਾਓ", "bindTextToElement": "ਪਾਠ ਜੋੜਨ ਲਈ ਐੰਟਰ ਦਬਾਓ",
"deepBoxSelect": "", "deepBoxSelect": "",
"eraserRevert": "" "eraserRevert": "",
"firefox_clipboard_write": ""
}, },
"canvasError": { "canvasError": {
"cannotShowPreview": "ਝਲਕ ਨਹੀਂ ਦਿਖਾ ਸਕਦੇ", "cannotShowPreview": "ਝਲਕ ਨਹੀਂ ਦਿਖਾ ਸਕਦੇ",
+29 -29
View File
@@ -1,49 +1,49 @@
{ {
"ar-SA": 87, "ar-SA": 92,
"bg-BG": 55, "bg-BG": 54,
"bn-BD": 60, "bn-BD": 60,
"ca-ES": 94, "ca-ES": 93,
"cs-CZ": 75, "cs-CZ": 75,
"da-DK": 33, "da-DK": 33,
"de-DE": 100, "de-DE": 100,
"el-GR": 100, "el-GR": 99,
"en": 100, "en": 100,
"es-ES": 100, "es-ES": 100,
"eu-ES": 100, "eu-ES": 99,
"fa-IR": 96, "fa-IR": 95,
"fi-FI": 93, "fi-FI": 92,
"fr-FR": 100, "fr-FR": 100,
"gl-ES": 100, "gl-ES": 100,
"he-IL": 90, "he-IL": 89,
"hi-IN": 69, "hi-IN": 71,
"hu-HU": 89, "hu-HU": 89,
"id-ID": 100, "id-ID": 99,
"it-IT": 100, "it-IT": 100,
"ja-JP": 100, "ja-JP": 99,
"kab-KAB": 93, "kab-KAB": 94,
"kk-KZ": 21, "kk-KZ": 20,
"ko-KR": 99, "ko-KR": 98,
"ku-TR": 96, "ku-TR": 95,
"lt-LT": 64, "lt-LT": 63,
"lv-LV": 98, "lv-LV": 97,
"mr-IN": 98, "mr-IN": 100,
"my-MM": 41, "my-MM": 41,
"nb-NO": 100, "nb-NO": 100,
"nl-NL": 91, "nl-NL": 90,
"nn-NO": 90, "nn-NO": 89,
"oc-FR": 98, "oc-FR": 97,
"pa-IN": 83, "pa-IN": 83,
"pl-PL": 85, "pl-PL": 84,
"pt-BR": 98, "pt-BR": 97,
"pt-PT": 100, "pt-PT": 99,
"ro-RO": 100, "ro-RO": 99,
"ru-RU": 98, "ru-RU": 100,
"si-LK": 8, "si-LK": 8,
"sk-SK": 100, "sk-SK": 100,
"sl-SI": 100, "sl-SI": 100,
"sv-SE": 96, "sv-SE": 100,
"ta-IN": 93, "ta-IN": 92,
"tr-TR": 98, "tr-TR": 97,
"uk-UA": 96, "uk-UA": 96,
"vi-VN": 20, "vi-VN": 20,
"zh-CN": 100, "zh-CN": 100,
+5 -3
View File
@@ -219,7 +219,8 @@
"lock": "Zablokuj wybrane narzędzie", "lock": "Zablokuj wybrane narzędzie",
"penMode": "", "penMode": "",
"link": "", "link": "",
"eraser": "Gumka" "eraser": "Gumka",
"hand": ""
}, },
"headings": { "headings": {
"canvasActions": "Narzędzia", "canvasActions": "Narzędzia",
@@ -227,7 +228,7 @@
"shapes": "Kształty" "shapes": "Kształty"
}, },
"hints": { "hints": {
"canvasPanning": "Aby przesunąć płótno, przytrzymaj kółko myszy lub spację podczas przeciągania", "canvasPanning": "",
"linearElement": "Naciśnij, aby zrobić punkt, przeciągnij, aby narysować linię", "linearElement": "Naciśnij, aby zrobić punkt, przeciągnij, aby narysować linię",
"freeDraw": "Naciśnij i przeciągnij by rysować, puść kiedy skończysz", "freeDraw": "Naciśnij i przeciągnij by rysować, puść kiedy skończysz",
"text": "Wskazówka: możesz również dodać tekst klikając dwukrotnie gdziekolwiek za pomocą narzędzia zaznaczania", "text": "Wskazówka: możesz również dodać tekst klikając dwukrotnie gdziekolwiek za pomocą narzędzia zaznaczania",
@@ -245,7 +246,8 @@
"publishLibrary": "Opublikuj własną bibliotekę", "publishLibrary": "Opublikuj własną bibliotekę",
"bindTextToElement": "", "bindTextToElement": "",
"deepBoxSelect": "", "deepBoxSelect": "",
"eraserRevert": "" "eraserRevert": "",
"firefox_clipboard_write": ""
}, },
"canvasError": { "canvasError": {
"cannotShowPreview": "Nie można wyświetlić podglądu", "cannotShowPreview": "Nie można wyświetlić podglądu",
+5 -3
View File
@@ -219,7 +219,8 @@
"lock": "Manter ativa a ferramenta selecionada após desenhar", "lock": "Manter ativa a ferramenta selecionada após desenhar",
"penMode": "Modo caneta — impede o toque", "penMode": "Modo caneta — impede o toque",
"link": "Adicionar/Atualizar link para uma forma selecionada", "link": "Adicionar/Atualizar link para uma forma selecionada",
"eraser": "Borracha" "eraser": "Borracha",
"hand": ""
}, },
"headings": { "headings": {
"canvasActions": "Ações da tela", "canvasActions": "Ações da tela",
@@ -227,7 +228,7 @@
"shapes": "Formas" "shapes": "Formas"
}, },
"hints": { "hints": {
"canvasPanning": "Para mover a tela, segure a roda do mouse ou a barra de espaço enquanto arrasta", "canvasPanning": "",
"linearElement": "Clique para iniciar vários pontos, arraste para uma única linha", "linearElement": "Clique para iniciar vários pontos, arraste para uma única linha",
"freeDraw": "Toque e arraste, solte quando terminar", "freeDraw": "Toque e arraste, solte quando terminar",
"text": "Dica: você também pode adicionar texto clicando duas vezes em qualquer lugar com a ferramenta de seleção", "text": "Dica: você também pode adicionar texto clicando duas vezes em qualquer lugar com a ferramenta de seleção",
@@ -245,7 +246,8 @@
"publishLibrary": "Publicar sua própria biblioteca", "publishLibrary": "Publicar sua própria biblioteca",
"bindTextToElement": "Pressione Enter para adicionar o texto", "bindTextToElement": "Pressione Enter para adicionar o texto",
"deepBoxSelect": "Segure Ctrl/Cmd para seleção profunda e para evitar arrastar", "deepBoxSelect": "Segure Ctrl/Cmd para seleção profunda e para evitar arrastar",
"eraserRevert": "Segure a tecla Alt para inverter os elementos marcados para exclusão" "eraserRevert": "Segure a tecla Alt para inverter os elementos marcados para exclusão",
"firefox_clipboard_write": ""
}, },
"canvasError": { "canvasError": {
"cannotShowPreview": "Não é possível mostrar pré-visualização", "cannotShowPreview": "Não é possível mostrar pré-visualização",
+5 -3
View File
@@ -219,7 +219,8 @@
"lock": "Manter a ferramenta selecionada ativa após desenhar", "lock": "Manter a ferramenta selecionada ativa após desenhar",
"penMode": "Modo caneta - impedir toque", "penMode": "Modo caneta - impedir toque",
"link": "Acrescentar/ Adicionar ligação para uma forma seleccionada", "link": "Acrescentar/ Adicionar ligação para uma forma seleccionada",
"eraser": "Borracha" "eraser": "Borracha",
"hand": ""
}, },
"headings": { "headings": {
"canvasActions": "Ações da área de desenho", "canvasActions": "Ações da área de desenho",
@@ -227,7 +228,7 @@
"shapes": "Formas" "shapes": "Formas"
}, },
"hints": { "hints": {
"canvasPanning": "Para mover a tela, carregue na roda do rato ou na barra de espaço enquanto arrasta", "canvasPanning": "",
"linearElement": "Clique para iniciar vários pontos, arraste para uma única linha", "linearElement": "Clique para iniciar vários pontos, arraste para uma única linha",
"freeDraw": "Clique e arraste, large quando terminar", "freeDraw": "Clique e arraste, large quando terminar",
"text": "Dica: também pode adicionar texto clicando duas vezes em qualquer lugar com a ferramenta de seleção", "text": "Dica: também pode adicionar texto clicando duas vezes em qualquer lugar com a ferramenta de seleção",
@@ -245,7 +246,8 @@
"publishLibrary": "Publique a sua própria biblioteca", "publishLibrary": "Publique a sua própria biblioteca",
"bindTextToElement": "Carregue Enter para acrescentar texto", "bindTextToElement": "Carregue Enter para acrescentar texto",
"deepBoxSelect": "Mantenha a tecla CtrlOrCmd carregada para selecção profunda, impedindo o arrastamento", "deepBoxSelect": "Mantenha a tecla CtrlOrCmd carregada para selecção profunda, impedindo o arrastamento",
"eraserRevert": "Carregue também em Alt para reverter os elementos marcados para serem apagados" "eraserRevert": "Carregue também em Alt para reverter os elementos marcados para serem apagados",
"firefox_clipboard_write": ""
}, },
"canvasError": { "canvasError": {
"cannotShowPreview": "Não é possível mostrar uma pré-visualização", "cannotShowPreview": "Não é possível mostrar uma pré-visualização",
+5 -3
View File
@@ -219,7 +219,8 @@
"lock": "Menține activ instrumentul selectat după desenare", "lock": "Menține activ instrumentul selectat după desenare",
"penMode": "Mod stilou împiedică atingerea", "penMode": "Mod stilou împiedică atingerea",
"link": "Adăugare/actualizare URL pentru forma selectată", "link": "Adăugare/actualizare URL pentru forma selectată",
"eraser": "Radieră" "eraser": "Radieră",
"hand": ""
}, },
"headings": { "headings": {
"canvasActions": "Acțiuni pentru pânză", "canvasActions": "Acțiuni pentru pânză",
@@ -227,7 +228,7 @@
"shapes": "Forme" "shapes": "Forme"
}, },
"hints": { "hints": {
"canvasPanning": "Pentru a muta pânză, ține apăsată rotița mausului sau bara de spațiu în timpul glisării", "canvasPanning": "",
"linearElement": "Dă clic pentru a crea mai multe puncte, glisează pentru a forma o singură linie", "linearElement": "Dă clic pentru a crea mai multe puncte, glisează pentru a forma o singură linie",
"freeDraw": "Dă clic pe pânză și glisează cursorul, apoi eliberează-l când ai terminat", "freeDraw": "Dă clic pe pânză și glisează cursorul, apoi eliberează-l când ai terminat",
"text": "Sfat: poți adăuga text și dând dublu clic oriunde cu instrumentul de selecție", "text": "Sfat: poți adăuga text și dând dublu clic oriunde cu instrumentul de selecție",
@@ -245,7 +246,8 @@
"publishLibrary": "Publică propria bibliotecă", "publishLibrary": "Publică propria bibliotecă",
"bindTextToElement": "Apasă tasta Enter pentru a adăuga text", "bindTextToElement": "Apasă tasta Enter pentru a adăuga text",
"deepBoxSelect": "Ține apăsată tasta Ctrl sau Cmd pentru a efectua selectarea de adâncime și pentru a preveni glisarea", "deepBoxSelect": "Ține apăsată tasta Ctrl sau Cmd pentru a efectua selectarea de adâncime și pentru a preveni glisarea",
"eraserRevert": "Ține apăsată tasta Alt pentru a anula elementele marcate pentru ștergere" "eraserRevert": "Ține apăsată tasta Alt pentru a anula elementele marcate pentru ștergere",
"firefox_clipboard_write": ""
}, },
"canvasError": { "canvasError": {
"cannotShowPreview": "Nu se poate afișa previzualizarea", "cannotShowPreview": "Nu se poate afișa previzualizarea",
+12 -10
View File
@@ -219,7 +219,8 @@
"lock": "Сохранять выбранный инструмент активным после рисования", "lock": "Сохранять выбранный инструмент активным после рисования",
"penMode": "Режим пера - предотвращение касания", "penMode": "Режим пера - предотвращение касания",
"link": "Добавить/обновить ссылку для выбранной фигуры", "link": "Добавить/обновить ссылку для выбранной фигуры",
"eraser": "Ластик" "eraser": "Ластик",
"hand": "Рука (перемещение холста)"
}, },
"headings": { "headings": {
"canvasActions": "Операции холста", "canvasActions": "Операции холста",
@@ -227,7 +228,7 @@
"shapes": "Фигуры" "shapes": "Фигуры"
}, },
"hints": { "hints": {
"canvasPanning": "Чтобы перемещать холст, удерживайте колесо мыши или пробел во время перетаскивания", "canvasPanning": "Чтобы двигать холст, удерживайте колесо мыши или пробел во время перетаскивания, или используйте инструмент \"Рука\"",
"linearElement": "Нажмите, чтобы начать несколько точек, перетащите для одной линии", "linearElement": "Нажмите, чтобы начать несколько точек, перетащите для одной линии",
"freeDraw": "Нажмите и перетаскивайте, отпустите по завершении", "freeDraw": "Нажмите и перетаскивайте, отпустите по завершении",
"text": "Совет: при выбранном инструменте выделения дважды щёлкните в любом месте, чтобы добавить текст", "text": "Совет: при выбранном инструменте выделения дважды щёлкните в любом месте, чтобы добавить текст",
@@ -245,7 +246,8 @@
"publishLibrary": "Опубликовать свою собственную библиотеку", "publishLibrary": "Опубликовать свою собственную библиотеку",
"bindTextToElement": "Нажмите Enter для добавления текста", "bindTextToElement": "Нажмите Enter для добавления текста",
"deepBoxSelect": "Удерживайте Ctrl или Cmd для глубокого выделения, чтобы предотвратить перетаскивание", "deepBoxSelect": "Удерживайте Ctrl или Cmd для глубокого выделения, чтобы предотвратить перетаскивание",
"eraserRevert": "Удерживайте Alt, чтобы вернуть элементы, отмеченные для удаления" "eraserRevert": "Удерживайте Alt, чтобы вернуть элементы, отмеченные для удаления",
"firefox_clipboard_write": "Эта функция может быть включена при изменении значения флага \"dom.events.asyncClipboard.clipboardItem\" на \"true\". Чтобы изменить флаги браузера в Firefox, посетите страницу \"about:config\"."
}, },
"canvasError": { "canvasError": {
"cannotShowPreview": "Не удается отобразить предпросмотр", "cannotShowPreview": "Не удается отобразить предпросмотр",
@@ -448,15 +450,15 @@
}, },
"welcomeScreen": { "welcomeScreen": {
"app": { "app": {
"center_heading": "", "center_heading": "Все ваши данные сохраняются локально в вашем браузере.",
"center_heading_plus": "", "center_heading_plus": "Хотите перейти на Excalidraw+?",
"menuHint": "" "menuHint": "Экспорт, настройки, языки, ..."
}, },
"defaults": { "defaults": {
"menuHint": "", "menuHint": "Экспорт, настройки и другое...",
"center_heading": "", "center_heading": "Диаграммы. Просто.",
"toolbarHint": "", "toolbarHint": "Выберите инструмент и начните рисовать!",
"helpHint": "" "helpHint": "Сочетания клавиш и помощь"
} }
} }
} }
+4 -2
View File
@@ -219,7 +219,8 @@
"lock": "", "lock": "",
"penMode": "", "penMode": "",
"link": "", "link": "",
"eraser": "" "eraser": "",
"hand": ""
}, },
"headings": { "headings": {
"canvasActions": "", "canvasActions": "",
@@ -245,7 +246,8 @@
"publishLibrary": "", "publishLibrary": "",
"bindTextToElement": "", "bindTextToElement": "",
"deepBoxSelect": "", "deepBoxSelect": "",
"eraserRevert": "" "eraserRevert": "",
"firefox_clipboard_write": ""
}, },
"canvasError": { "canvasError": {
"cannotShowPreview": "", "cannotShowPreview": "",
+5 -3
View File
@@ -219,7 +219,8 @@
"lock": "Nechať zvolený nástroj aktívny po skončení kreslenia", "lock": "Nechať zvolený nástroj aktívny po skončení kreslenia",
"penMode": "Režim pera zabrániť dotyku", "penMode": "Režim pera zabrániť dotyku",
"link": "Pridať/ Upraviť odkaz pre vybraný tvar", "link": "Pridať/ Upraviť odkaz pre vybraný tvar",
"eraser": "Guma" "eraser": "Guma",
"hand": "Ruka (nástroj pre pohyb plátna)"
}, },
"headings": { "headings": {
"canvasActions": "Akcie plátna", "canvasActions": "Akcie plátna",
@@ -227,7 +228,7 @@
"shapes": "Tvary" "shapes": "Tvary"
}, },
"hints": { "hints": {
"canvasPanning": "Pre pohyb plátna podržte koliesko myši alebo medzerník počas ťahania", "canvasPanning": "Pre pohyb plátna podržte koliesko myši alebo medzerník počas ťahania, alebo použite nástroj ruka",
"linearElement": "Kliknite na vloženie viacerých bodov, potiahnite na vytvorenie jednej priamky", "linearElement": "Kliknite na vloženie viacerých bodov, potiahnite na vytvorenie jednej priamky",
"freeDraw": "Kliknite a ťahajte, pustite na ukončenie", "freeDraw": "Kliknite a ťahajte, pustite na ukončenie",
"text": "Tip: text môžete pridať aj dvojklikom kdekoľvek, ak je zvolený nástroj výber", "text": "Tip: text môžete pridať aj dvojklikom kdekoľvek, ak je zvolený nástroj výber",
@@ -245,7 +246,8 @@
"publishLibrary": "Uverejniť vašu knižnicu", "publishLibrary": "Uverejniť vašu knižnicu",
"bindTextToElement": "Stlačte enter na pridanie textu", "bindTextToElement": "Stlačte enter na pridanie textu",
"deepBoxSelect": "Podržte CtrlOrCmd na výber v skupine alebo zamedzeniu poťiahnutia", "deepBoxSelect": "Podržte CtrlOrCmd na výber v skupine alebo zamedzeniu poťiahnutia",
"eraserRevert": "Podržte Alt pre prehodenie položiek určených na vymazanie" "eraserRevert": "Podržte Alt pre prehodenie položiek určených na vymazanie",
"firefox_clipboard_write": "Táto sa funkcionalita sa dá zapnúť nastavením \"dom.events.asyncClipboard.clipboardItem\" na \"true\". Pre zmenu nastavení vo Firefox-e otvorte stránku \"about:config\"."
}, },
"canvasError": { "canvasError": {
"cannotShowPreview": "Nie je možné zobraziť náhľad plátna", "cannotShowPreview": "Nie je možné zobraziť náhľad plátna",
+5 -3
View File
@@ -219,7 +219,8 @@
"lock": "Ohrani izbrano orodje aktivno po risanju", "lock": "Ohrani izbrano orodje aktivno po risanju",
"penMode": "Način peresa - prepreči dotik", "penMode": "Način peresa - prepreči dotik",
"link": "Dodaj/posodobi povezavo za izbrano obliko", "link": "Dodaj/posodobi povezavo za izbrano obliko",
"eraser": "Radirka" "eraser": "Radirka",
"hand": "Roka (orodje za premikanje)"
}, },
"headings": { "headings": {
"canvasActions": "Dejanja za platno", "canvasActions": "Dejanja za platno",
@@ -227,7 +228,7 @@
"shapes": "Oblike" "shapes": "Oblike"
}, },
"hints": { "hints": {
"canvasPanning": "Za premik platna med vlečenjem držite kolesce miške ali preslednico", "canvasPanning": "Za premikanje platna med vlečenjem držite kolesce miške ali preslednico ali uporabite orodje roka",
"linearElement": "Kliknite za začetek več točk, povlecite za posamezno črto", "linearElement": "Kliknite za začetek več točk, povlecite za posamezno črto",
"freeDraw": "Kliknite in povlecite, spustite, ko končate", "freeDraw": "Kliknite in povlecite, spustite, ko končate",
"text": "Namig: besedilo lahko dodate tudi z dvoklikom kjer koli z orodjem za izbiro", "text": "Namig: besedilo lahko dodate tudi z dvoklikom kjer koli z orodjem za izbiro",
@@ -245,7 +246,8 @@
"publishLibrary": "Objavi svojo knjižnico", "publishLibrary": "Objavi svojo knjižnico",
"bindTextToElement": "Pritisnite tipko Enter za dodajanje besedila", "bindTextToElement": "Pritisnite tipko Enter za dodajanje besedila",
"deepBoxSelect": "Držite tipko CtrlOrCmd za globoko izbiro in preprečitev vlečenja", "deepBoxSelect": "Držite tipko CtrlOrCmd za globoko izbiro in preprečitev vlečenja",
"eraserRevert": "Pridržite tipko Alt, da razveljavite elemente, označene za brisanje" "eraserRevert": "Pridržite tipko Alt, da razveljavite elemente, označene za brisanje",
"firefox_clipboard_write": "To funkcijo lahko verjetno omogočite z nastavitvijo zastavice \"dom.events.asyncClipboard.clipboardItem\" na \"true\". Če želite spremeniti zastavice brskalnika v Firefoxu, obiščite stran \"about:config\"."
}, },
"canvasError": { "canvasError": {
"cannotShowPreview": "Predogleda ni bilo mogoče prikazati", "cannotShowPreview": "Predogleda ni bilo mogoče prikazati",
+19 -17
View File
@@ -1,7 +1,7 @@
{ {
"labels": { "labels": {
"paste": "Klistra in", "paste": "Klistra in",
"pasteAsPlaintext": "", "pasteAsPlaintext": "Klistra som oformaterad text",
"pasteCharts": "Klistra in diagram", "pasteCharts": "Klistra in diagram",
"selectAll": "Markera alla", "selectAll": "Markera alla",
"multiSelect": "Lägg till element till markering", "multiSelect": "Lägg till element till markering",
@@ -202,8 +202,8 @@
"invalidSVGString": "Ogiltig SVG.", "invalidSVGString": "Ogiltig SVG.",
"cannotResolveCollabServer": "Det gick inte att ansluta till samarbets-servern. Ladda om sidan och försök igen.", "cannotResolveCollabServer": "Det gick inte att ansluta till samarbets-servern. Ladda om sidan och försök igen.",
"importLibraryError": "Kunde inte ladda bibliotek", "importLibraryError": "Kunde inte ladda bibliotek",
"collabSaveFailed": "", "collabSaveFailed": "Det gick inte att spara i backend-databasen. Om problemen kvarstår bör du spara filen lokalt för att se till att du inte förlorar ditt arbete.",
"collabSaveFailed_sizeExceeded": "" "collabSaveFailed_sizeExceeded": "Det gick inte att spara till backend-databasen, whiteboarden verkar vara för stor. Du bör spara filen lokalt för att du inte ska förlora ditt arbete."
}, },
"toolBar": { "toolBar": {
"selection": "Markering", "selection": "Markering",
@@ -219,7 +219,8 @@
"lock": "Håll valt verktyg aktivt efter ritande", "lock": "Håll valt verktyg aktivt efter ritande",
"penMode": "Pennläge - förhindra touch", "penMode": "Pennläge - förhindra touch",
"link": "Lägg till / Uppdatera länk för en vald form", "link": "Lägg till / Uppdatera länk för en vald form",
"eraser": "Radergummi" "eraser": "Radergummi",
"hand": "Hand (panoreringsverktyg)"
}, },
"headings": { "headings": {
"canvasActions": "Canvas-åtgärder", "canvasActions": "Canvas-åtgärder",
@@ -227,7 +228,7 @@
"shapes": "Former" "shapes": "Former"
}, },
"hints": { "hints": {
"canvasPanning": "För att flytta canvas, håll mushjulet eller mellanslagstangenten medan du drar", "canvasPanning": "För att flytta whiteboarden, håll mushjulet eller mellanslagstangenten medan du drar eller använd handverktyget",
"linearElement": "Klicka för att starta flera punkter, dra för en linje", "linearElement": "Klicka för att starta flera punkter, dra för en linje",
"freeDraw": "Klicka och dra, släpp när du är klar", "freeDraw": "Klicka och dra, släpp när du är klar",
"text": "Tips: du kan också lägga till text genom att dubbelklicka var som helst med markeringsverktyget", "text": "Tips: du kan också lägga till text genom att dubbelklicka var som helst med markeringsverktyget",
@@ -238,14 +239,15 @@
"resize": "Du kan behålla proportioner genom att hålla SHIFT medan du ändrar storlek,\nhåller du ALT ändras storlek relativt mitten", "resize": "Du kan behålla proportioner genom att hålla SHIFT medan du ändrar storlek,\nhåller du ALT ändras storlek relativt mitten",
"resizeImage": "Du kan ändra storlek fritt genom att hålla SHIFT,\nhåll ALT för att ändra storlek från mitten", "resizeImage": "Du kan ändra storlek fritt genom att hålla SHIFT,\nhåll ALT för att ändra storlek från mitten",
"rotate": "Du kan begränsa vinklar genom att hålla SHIFT medan du roterar", "rotate": "Du kan begränsa vinklar genom att hålla SHIFT medan du roterar",
"lineEditor_info": "", "lineEditor_info": "Håll Ctrl/Cmd och dubbelklicka eller tryck på Ctrl/Cmd + Enter för att redigera punkter",
"lineEditor_pointSelected": "Tryck på Ta bort för att ta bort punkt(er), Ctrl + D eller Cmd + D för att duplicera, eller dra för att flytta", "lineEditor_pointSelected": "Tryck på Ta bort för att ta bort punkt(er), Ctrl + D eller Cmd + D för att duplicera, eller dra för att flytta",
"lineEditor_nothingSelected": "Välj en punkt att redigera (håll SHIFT för att välja flera),\neller håll ned Alt och klicka för att lägga till nya punkter", "lineEditor_nothingSelected": "Välj en punkt att redigera (håll SHIFT för att välja flera),\neller håll ned Alt och klicka för att lägga till nya punkter",
"placeImage": "Klicka för att placera bilden, eller klicka och dra för att ställa in dess storlek manuellt", "placeImage": "Klicka för att placera bilden, eller klicka och dra för att ställa in dess storlek manuellt",
"publishLibrary": "Publicera ditt eget bibliotek", "publishLibrary": "Publicera ditt eget bibliotek",
"bindTextToElement": "Tryck på Enter för att lägga till text", "bindTextToElement": "Tryck på Enter för att lägga till text",
"deepBoxSelect": "Håll Ctrl eller Cmd för att djupvälja, och för att förhindra att dra", "deepBoxSelect": "Håll Ctrl eller Cmd för att djupvälja, och för att förhindra att dra",
"eraserRevert": "Håll Alt för att återställa de element som är markerade för borttagning" "eraserRevert": "Håll Alt för att återställa de element som är markerade för borttagning",
"firefox_clipboard_write": "Denna funktion kan sannolikt aktiveras genom att ställa in \"dom.events.asyncClipboard.clipboardItem\" flaggan till \"true\". För att ändra webbläsarens flaggor i Firefox, besök \"about:config\" sidan."
}, },
"canvasError": { "canvasError": {
"cannotShowPreview": "Kan inte visa förhandsgranskning", "cannotShowPreview": "Kan inte visa förhandsgranskning",
@@ -314,8 +316,8 @@
"zoomToFit": "Zooma för att rymma alla element", "zoomToFit": "Zooma för att rymma alla element",
"zoomToSelection": "Zooma till markering", "zoomToSelection": "Zooma till markering",
"toggleElementLock": "Lås/Lås upp valda", "toggleElementLock": "Lås/Lås upp valda",
"movePageUpDown": "", "movePageUpDown": "Flytta sida upp/ner",
"movePageLeftRight": "" "movePageLeftRight": "Flytta sida vänster/höger"
}, },
"clearCanvasDialog": { "clearCanvasDialog": {
"title": "Rensa canvas" "title": "Rensa canvas"
@@ -397,7 +399,7 @@
"fileSavedToFilename": "Sparad till {filename}", "fileSavedToFilename": "Sparad till {filename}",
"canvas": "canvas", "canvas": "canvas",
"selection": "markering", "selection": "markering",
"pasteAsSingleElement": "" "pasteAsSingleElement": "Använd {{shortcut}} för att klistra in som ett enda element,\neller klistra in i en befintlig textredigerare"
}, },
"colors": { "colors": {
"ffffff": "Vit", "ffffff": "Vit",
@@ -448,15 +450,15 @@
}, },
"welcomeScreen": { "welcomeScreen": {
"app": { "app": {
"center_heading": "", "center_heading": "All data sparas lokalt i din webbläsare.",
"center_heading_plus": "", "center_heading_plus": "Ville du gå till Excalidraw+ istället?",
"menuHint": "" "menuHint": "Exportera, inställningar, språk, ..."
}, },
"defaults": { "defaults": {
"menuHint": "", "menuHint": "Exportera, inställningar och mer...",
"center_heading": "", "center_heading": "Förenklade. Diagram.",
"toolbarHint": "", "toolbarHint": "Välj ett verktyg & börja rita!",
"helpHint": "" "helpHint": "Genvägar & hjälp"
} }
} }
} }
+5 -3
View File
@@ -219,7 +219,8 @@
"lock": "தேர்ந்த கருவியை வரைந்த பின்பும் வைத்திரு", "lock": "தேர்ந்த கருவியை வரைந்த பின்பும் வைத்திரு",
"penMode": "", "penMode": "",
"link": "தேர்தெடுத்த வடிவத்திற்குத் தொடுப்பைச் சேர்/ புதுப்பி", "link": "தேர்தெடுத்த வடிவத்திற்குத் தொடுப்பைச் சேர்/ புதுப்பி",
"eraser": "அழிப்பி" "eraser": "அழிப்பி",
"hand": ""
}, },
"headings": { "headings": {
"canvasActions": "கித்தான் செயல்கள்", "canvasActions": "கித்தான் செயல்கள்",
@@ -227,7 +228,7 @@
"shapes": "வடிவங்கள்" "shapes": "வடிவங்கள்"
}, },
"hints": { "hints": {
"canvasPanning": "கித்தானை நகர்த்த, பிடித்திழுக்கையில் சுட்டிச்சக்கரத்தை அ இடைவெளிப்பட்டையை அழுத்திப்பிடி", "canvasPanning": "",
"linearElement": "பல புள்ளிகளைத் துவக்க சொடுக்கு, ஒற்றை வரிக்கு பிடித்திழு", "linearElement": "பல புள்ளிகளைத் துவக்க சொடுக்கு, ஒற்றை வரிக்கு பிடித்திழு",
"freeDraw": "சொடுக்கி பிடித்திழு, முடித்ததும் விடுவி", "freeDraw": "சொடுக்கி பிடித்திழு, முடித்ததும் விடுவி",
"text": "துணுக்குதவி: தெரிவு கருவி கொண்டு எங்காவது இரு-சொடுக்கி உரையைச் சேர்க்கலாம்", "text": "துணுக்குதவி: தெரிவு கருவி கொண்டு எங்காவது இரு-சொடுக்கி உரையைச் சேர்க்கலாம்",
@@ -245,7 +246,8 @@
"publishLibrary": "உம் சொந்த நூலகத்தைப் பிரசுரி", "publishLibrary": "உம் சொந்த நூலகத்தைப் பிரசுரி",
"bindTextToElement": "உரையைச் சேர்க்க enterஐ அழுத்து", "bindTextToElement": "உரையைச் சேர்க்க enterஐ அழுத்து",
"deepBoxSelect": "ஆழ்ந்துத் தேரவும் பிடித்திழுத்தலைத் தவிர்க்கவும் CtrlOrCmdஐ அழுத்திப்பிடி", "deepBoxSelect": "ஆழ்ந்துத் தேரவும் பிடித்திழுத்தலைத் தவிர்க்கவும் CtrlOrCmdஐ அழுத்திப்பிடி",
"eraserRevert": "" "eraserRevert": "",
"firefox_clipboard_write": ""
}, },
"canvasError": { "canvasError": {
"cannotShowPreview": "முன்னோட்டம் காட்ட இயலவில்லை", "cannotShowPreview": "முன்னோட்டம் காட்ட இயலவில்லை",
+5 -3
View File
@@ -219,7 +219,8 @@
"lock": "Seçilen aracı çizimden sonra aktif tut", "lock": "Seçilen aracı çizimden sonra aktif tut",
"penMode": "Kalem modu - dokunmayı engelle", "penMode": "Kalem modu - dokunmayı engelle",
"link": "Seçilen şekil için bağlantı Ekle/Güncelle", "link": "Seçilen şekil için bağlantı Ekle/Güncelle",
"eraser": "Silgi" "eraser": "Silgi",
"hand": ""
}, },
"headings": { "headings": {
"canvasActions": "Tuval eylemleri", "canvasActions": "Tuval eylemleri",
@@ -227,7 +228,7 @@
"shapes": "Şekiller" "shapes": "Şekiller"
}, },
"hints": { "hints": {
"canvasPanning": "Tuvali taşımak için, tuvali sürüklerken aynı zamanda fare tekerleğine veya boşluk tuşuna basılı tutun", "canvasPanning": "",
"linearElement": "Birden fazla nokta için tıklayın, tek çizgi için sürükleyin", "linearElement": "Birden fazla nokta için tıklayın, tek çizgi için sürükleyin",
"freeDraw": "Tıkla ve sürükle, bitirdiğinde serbest bırak", "freeDraw": "Tıkla ve sürükle, bitirdiğinde serbest bırak",
"text": "İpucu: seçme aracıyla herhangi bir yere çift tıklayarak da yazı ekleyebilirsin", "text": "İpucu: seçme aracıyla herhangi bir yere çift tıklayarak da yazı ekleyebilirsin",
@@ -245,7 +246,8 @@
"publishLibrary": "Kendi kitaplığınızı yayınlayın", "publishLibrary": "Kendi kitaplığınızı yayınlayın",
"bindTextToElement": "Enter tuşuna basarak metin ekleyin", "bindTextToElement": "Enter tuşuna basarak metin ekleyin",
"deepBoxSelect": "Ctrl/Cmd tuşuna basılı tutarak derin seçim yapın ya da sürüklemeyi engelleyin", "deepBoxSelect": "Ctrl/Cmd tuşuna basılı tutarak derin seçim yapın ya da sürüklemeyi engelleyin",
"eraserRevert": "Alt tuşuna basılı tutarak silinme için işaretlenmiş ögeleri tersine çevirin" "eraserRevert": "Alt tuşuna basılı tutarak silinme için işaretlenmiş ögeleri tersine çevirin",
"firefox_clipboard_write": ""
}, },
"canvasError": { "canvasError": {
"cannotShowPreview": "Önizleme gösterilemiyor", "cannotShowPreview": "Önizleme gösterilemiyor",
+5 -3
View File
@@ -219,7 +219,8 @@
"lock": "Залишити обраний інструмент після креслення", "lock": "Залишити обраний інструмент після креслення",
"penMode": "Режим пера - запобігання дотику", "penMode": "Режим пера - запобігання дотику",
"link": "Додати/Оновити посилання для вибраної форми", "link": "Додати/Оновити посилання для вибраної форми",
"eraser": "Очищувач" "eraser": "Очищувач",
"hand": ""
}, },
"headings": { "headings": {
"canvasActions": "Дії з полотном", "canvasActions": "Дії з полотном",
@@ -227,7 +228,7 @@
"shapes": "Фігури" "shapes": "Фігури"
}, },
"hints": { "hints": {
"canvasPanning": "Щоб перемістити полотно, утримуйте коліщатко миші або пробіл під час перетягування", "canvasPanning": "",
"linearElement": "Натисніть щоб додати кілька точок. Перетягніть щоб намалювати одну лінію", "linearElement": "Натисніть щоб додати кілька точок. Перетягніть щоб намалювати одну лінію",
"freeDraw": "Натисніть і потягніть, відпустіть коли завершите", "freeDraw": "Натисніть і потягніть, відпустіть коли завершите",
"text": "Порада: можна також додати текст, двічі клацнувши по будь-якому місці інструментом вибору", "text": "Порада: можна також додати текст, двічі клацнувши по будь-якому місці інструментом вибору",
@@ -245,7 +246,8 @@
"publishLibrary": "Опублікувати свою власну бібліотеку", "publishLibrary": "Опублікувати свою власну бібліотеку",
"bindTextToElement": "Натисніть Enter, щоб додати текст", "bindTextToElement": "Натисніть Enter, щоб додати текст",
"deepBoxSelect": "Втримуйте Ctrl/Cmd для глибокого виділення та щоб попередити перетягування", "deepBoxSelect": "Втримуйте Ctrl/Cmd для глибокого виділення та щоб попередити перетягування",
"eraserRevert": "Втримуйте клавішу Alt, щоб повернути елементи позначені для видалення" "eraserRevert": "Втримуйте клавішу Alt, щоб повернути елементи позначені для видалення",
"firefox_clipboard_write": ""
}, },
"canvasError": { "canvasError": {
"cannotShowPreview": "Не вдається показати попередній перегляд", "cannotShowPreview": "Не вдається показати попередній перегляд",
+7 -5
View File
@@ -66,9 +66,9 @@
"cartoonist": "Hoạt hình", "cartoonist": "Hoạt hình",
"fileTitle": "", "fileTitle": "",
"colorPicker": "Chọn màu", "colorPicker": "Chọn màu",
"canvasColors": "", "canvasColors": "Đã dùng trên canvas",
"canvasBackground": "", "canvasBackground": "Nền canvas",
"drawingCanvas": "", "drawingCanvas": "Canvas vẽ",
"layers": "Lớp", "layers": "Lớp",
"actions": "Chức năng", "actions": "Chức năng",
"language": "Ngôn ngữ", "language": "Ngôn ngữ",
@@ -219,7 +219,8 @@
"lock": "", "lock": "",
"penMode": "", "penMode": "",
"link": "", "link": "",
"eraser": "" "eraser": "",
"hand": ""
}, },
"headings": { "headings": {
"canvasActions": "", "canvasActions": "",
@@ -245,7 +246,8 @@
"publishLibrary": "", "publishLibrary": "",
"bindTextToElement": "", "bindTextToElement": "",
"deepBoxSelect": "", "deepBoxSelect": "",
"eraserRevert": "" "eraserRevert": "",
"firefox_clipboard_write": ""
}, },
"canvasError": { "canvasError": {
"cannotShowPreview": "", "cannotShowPreview": "",
+5 -3
View File
@@ -219,7 +219,8 @@
"lock": "绘制后保持所选的工具栏状态", "lock": "绘制后保持所选的工具栏状态",
"penMode": "笔模式 避免误触", "penMode": "笔模式 避免误触",
"link": "为选中的形状添加/更新链接", "link": "为选中的形状添加/更新链接",
"eraser": "橡皮" "eraser": "橡皮",
"hand": "抓手(平移工具)"
}, },
"headings": { "headings": {
"canvasActions": "画布动作", "canvasActions": "画布动作",
@@ -227,7 +228,7 @@
"shapes": "形状" "shapes": "形状"
}, },
"hints": { "hints": {
"canvasPanning": "要移动画布,请按住鼠标滚轮或空格键,再拖拽鼠标", "canvasPanning": "要移动画布,请按住鼠标滚轮或空格键同时拖拽鼠标,或使用抓手工具。",
"linearElement": "点击创建多个点 拖动创建直线", "linearElement": "点击创建多个点 拖动创建直线",
"freeDraw": "点击并拖动,完成时松开", "freeDraw": "点击并拖动,完成时松开",
"text": "提示:您也可以使用选择工具双击任意位置来添加文字", "text": "提示:您也可以使用选择工具双击任意位置来添加文字",
@@ -245,7 +246,8 @@
"publishLibrary": "发布您自己的素材库", "publishLibrary": "发布您自己的素材库",
"bindTextToElement": "按下 Enter 以添加文本", "bindTextToElement": "按下 Enter 以添加文本",
"deepBoxSelect": "按住 CtrlOrCmd 以深度选择,并避免拖拽", "deepBoxSelect": "按住 CtrlOrCmd 以深度选择,并避免拖拽",
"eraserRevert": "按住 Alt 以反选被标记删除的元素" "eraserRevert": "按住 Alt 以反选被标记删除的元素",
"firefox_clipboard_write": "将高级配置首选项“dom.events.asyncClipboard.lipboarditem”设置为“true”可以启用此功能。要更改 Firefox 的高级配置首选项,请前往“about:config”页面。"
}, },
"canvasError": { "canvasError": {
"cannotShowPreview": "无法显示预览", "cannotShowPreview": "无法显示预览",
+4 -2
View File
@@ -219,7 +219,8 @@
"lock": "", "lock": "",
"penMode": "", "penMode": "",
"link": "", "link": "",
"eraser": "" "eraser": "",
"hand": ""
}, },
"headings": { "headings": {
"canvasActions": "畫布動作", "canvasActions": "畫布動作",
@@ -245,7 +246,8 @@
"publishLibrary": "", "publishLibrary": "",
"bindTextToElement": "", "bindTextToElement": "",
"deepBoxSelect": "", "deepBoxSelect": "",
"eraserRevert": "" "eraserRevert": "",
"firefox_clipboard_write": ""
}, },
"canvasError": { "canvasError": {
"cannotShowPreview": "無法顯示預覽", "cannotShowPreview": "無法顯示預覽",
+5 -3
View File
@@ -219,7 +219,8 @@
"lock": "可連續使用選取的工具", "lock": "可連續使用選取的工具",
"penMode": "筆模式 - 避免觸摸", "penMode": "筆模式 - 避免觸摸",
"link": "為所選的形狀增加\b/更新連結", "link": "為所選的形狀增加\b/更新連結",
"eraser": "橡皮擦" "eraser": "橡皮擦",
"hand": "手形(平移工具)"
}, },
"headings": { "headings": {
"canvasActions": "canvas 動作", "canvasActions": "canvas 動作",
@@ -227,7 +228,7 @@
"shapes": "形狀" "shapes": "形狀"
}, },
"hints": { "hints": {
"canvasPanning": "若要移動畫布,請在拖曳時按住滑鼠滾輪或空白鍵", "canvasPanning": "若要移動畫布,請在拖曳時按住滑鼠滾輪或空白鍵,或使用手形工具",
"linearElement": "點擊以繪製多點曲線;或拖曳以繪製直線", "linearElement": "點擊以繪製多點曲線;或拖曳以繪製直線",
"freeDraw": "點擊並拖曳來繪圖,放開即結束", "freeDraw": "點擊並拖曳來繪圖,放開即結束",
"text": "提示:亦可使用選取工具在任何地方雙擊來加入文字", "text": "提示:亦可使用選取工具在任何地方雙擊來加入文字",
@@ -245,7 +246,8 @@
"publishLibrary": "發布個人資料庫", "publishLibrary": "發布個人資料庫",
"bindTextToElement": "按下 Enter 以加入文字。", "bindTextToElement": "按下 Enter 以加入文字。",
"deepBoxSelect": "按住 Ctrl 或 Cmd 以深度選取並避免拖曳", "deepBoxSelect": "按住 Ctrl 或 Cmd 以深度選取並避免拖曳",
"eraserRevert": "按住 Alt 以反選取已標記待刪除的元素" "eraserRevert": "按住 Alt 以反選取已標記待刪除的元素",
"firefox_clipboard_write": "此功能有機會透過將 \"dom.events.asyncClipboard.clipboardItem\" 設定為 \"true\" 來開啟。\n若要變更 Firefox 瀏覽器的此設定值,請至 \"about:config\" 頁面。"
}, },
"canvasError": { "canvasError": {
"cannotShowPreview": "無法顯示預覽", "cannotShowPreview": "無法顯示預覽",
+12
View File
@@ -459,3 +459,15 @@ export const mapIntervalToBezierT = (
export const arePointsEqual = (p1: Point, p2: Point) => { export const arePointsEqual = (p1: Point, p2: Point) => {
return p1[0] === p2[0] && p1[1] === p2[1]; return p1[0] === p2[0] && p1[1] === p2[1];
}; };
export const isRightAngle = (angle: number) => {
// if our angles were mathematically accurate, we could just check
//
// angle % (Math.PI / 2) === 0
//
// but since we're in floating point land, we need to round.
//
// Below, after dividing by Math.PI, a multiple of 0.5 indicates a right
// angle, which we can check with modulo after rounding.
return Math.round((angle / Math.PI) * 10000) % 5000 === 0;
};
+18
View File
@@ -11,6 +11,24 @@ The change should be grouped under one of the below section and must contain PR
Please add the latest change on the top under the correct section. Please add the latest change on the top under the correct section.
--> -->
## Unreleased
### Features
- [`restoreElements`](https://docs.excalidraw.com/docs/@excalidraw/excalidraw/api/utils/restore#restoreelements) API now takes an optional parameter `opts` which currently supports the below attributes
```js
{ refreshDimensions?: boolean, repairBindings?: boolean }
```
The same `opts` param has been added to [`restore`](https://docs.excalidraw.com/docs/@excalidraw/excalidraw/api/utils/restore#restore) API as well.
For more details refer to the [docs](https://docs.excalidraw.com)
#### BREAKING CHANGE
- The optional parameter `refreshDimensions` in [`restoreElements`](https://docs.excalidraw.com/docs/@excalidraw/excalidraw/api/utils/restore#restoreelements) has been removed and can be enabled via `opts`
## 0.14.2 (2023-02-01) ## 0.14.2 (2023-02-01)
### Features ### Features
+16 -8
View File
@@ -80,7 +80,13 @@ const COMMENT_ICON_DIMENSION = 32;
const COMMENT_INPUT_HEIGHT = 50; const COMMENT_INPUT_HEIGHT = 50;
const COMMENT_INPUT_WIDTH = 150; const COMMENT_INPUT_WIDTH = 150;
export default function App() { export interface AppProps {
appTitle: string;
useCustom: (api: ExcalidrawImperativeAPI | null, customArgs?: any[]) => void;
customArgs?: any[];
}
export default function App({ appTitle, useCustom, customArgs }: AppProps) {
const appRef = useRef<any>(null); const appRef = useRef<any>(null);
const [viewModeEnabled, setViewModeEnabled] = useState(false); const [viewModeEnabled, setViewModeEnabled] = useState(false);
const [zenModeEnabled, setZenModeEnabled] = useState(false); const [zenModeEnabled, setZenModeEnabled] = useState(false);
@@ -107,6 +113,8 @@ export default function App() {
const [excalidrawAPI, setExcalidrawAPI] = const [excalidrawAPI, setExcalidrawAPI] =
useState<ExcalidrawImperativeAPI | null>(null); useState<ExcalidrawImperativeAPI | null>(null);
useCustom(excalidrawAPI, customArgs);
useHandleLibrary({ excalidrawAPI }); useHandleLibrary({ excalidrawAPI });
useEffect(() => { useEffect(() => {
@@ -114,7 +122,7 @@ export default function App() {
return; return;
} }
const fetchData = async () => { const fetchData = async () => {
const res = await fetch("/rocket.jpeg"); const res = await fetch("/images/rocket.jpeg");
const imageData = await res.blob(); const imageData = await res.blob();
const reader = new FileReader(); const reader = new FileReader();
reader.readAsDataURL(imageData); reader.readAsDataURL(imageData);
@@ -150,7 +158,7 @@ export default function App() {
/> />
)} )}
<button <button
onClick={() => alert("This is dummy top right UI")} onClick={() => alert("This is an empty top right UI")}
style={{ height: "2.5rem" }} style={{ height: "2.5rem" }}
> >
{" "} {" "}
@@ -397,7 +405,7 @@ export default function App() {
}} }}
> >
<div className="comment-avatar"> <div className="comment-avatar">
<img src="doremon.png" alt="doremon" /> <img src="images/doremon.png" alt="doremon" />
</div> </div>
</div> </div>
); );
@@ -525,7 +533,7 @@ export default function App() {
}; };
return ( return (
<div className="App" ref={appRef}> <div className="App" ref={appRef}>
<h1> Excalidraw Example</h1> <h1>{appTitle}</h1>
<ExampleSidebar> <ExampleSidebar>
<div className="button-wrapper"> <div className="button-wrapper">
<button onClick={loadSceneOrLibrary}>Load Scene or Library</button> <button onClick={loadSceneOrLibrary}>Load Scene or Library</button>
@@ -611,15 +619,15 @@ export default function App() {
const collaborators = new Map(); const collaborators = new Map();
collaborators.set("id1", { collaborators.set("id1", {
username: "Doremon", username: "Doremon",
avatarUrl: "doremon.png", avatarUrl: "images/doremon.png",
}); });
collaborators.set("id2", { collaborators.set("id2", {
username: "Excalibot", username: "Excalibot",
avatarUrl: "excalibot.png", avatarUrl: "images/excalibot.png",
}); });
collaborators.set("id3", { collaborators.set("id3", {
username: "Pika", username: "Pika",
avatarUrl: "pika.jpeg", avatarUrl: "images/pika.jpeg",
}); });
collaborators.set("id4", { collaborators.set("id4", {
username: "fallback", username: "fallback",
+4 -1
View File
@@ -8,6 +8,9 @@ const root = createRoot(rootElement);
root.render( root.render(
<StrictMode> <StrictMode>
<App /> <App
appTitle={"Excalidraw Example"}
useCustom={(api: any, args?: any[]) => {}}
/>
</StrictMode>, </StrictMode>,
); );

Before

Width:  |  Height:  |  Size: 197 KiB

After

Width:  |  Height:  |  Size: 197 KiB

Before

Width:  |  Height:  |  Size: 30 KiB

After

Width:  |  Height:  |  Size: 30 KiB

Before

Width:  |  Height:  |  Size: 6.1 KiB

After

Width:  |  Height:  |  Size: 6.1 KiB

Before

Width:  |  Height:  |  Size: 39 KiB

After

Width:  |  Height:  |  Size: 39 KiB

@@ -10,8 +10,8 @@ export default function Sidebar({ children }: { children: React.ReactNode }) {
x x
</button> </button>
<div className="sidebar-links"> <div className="sidebar-links">
<button>Dummy Home</button> <button>Empty Home</button>
<button>Dummy About</button>{" "} <button>Empty About</button>{" "}
</div> </div>
</div> </div>
<div className={`${open ? "sidebar-open" : ""}`}> <div className={`${open ? "sidebar-open" : ""}`}>
+27 -1
View File
@@ -27,7 +27,7 @@ import { RoughGenerator } from "roughjs/bin/generator";
import { RenderConfig } from "../scene/types"; import { RenderConfig } from "../scene/types";
import { distance, getFontString, getFontFamilyString, isRTL } from "../utils"; import { distance, getFontString, getFontFamilyString, isRTL } from "../utils";
import { getCornerRadius, isPathALoop } from "../math"; import { getCornerRadius, isPathALoop, isRightAngle } from "../math";
import rough from "roughjs/bin/rough"; import rough from "roughjs/bin/rough";
import { AppState, BinaryFiles, Zoom } from "../types"; import { AppState, BinaryFiles, Zoom } from "../types";
import { getDefaultAppState } from "../appState"; import { getDefaultAppState } from "../appState";
@@ -989,7 +989,33 @@ export const renderElement = (
element, element,
renderConfig, renderConfig,
); );
const currentImageSmoothingStatus = context.imageSmoothingEnabled;
if (
// do not disable smoothing during zoom as blurry shapes look better
// on low resolution (while still zooming in) than sharp ones
!renderConfig?.shouldCacheIgnoreZoom &&
// angle is 0 -> always disable smoothing
(!element.angle ||
// or check if angle is a right angle in which case we can still
// disable smoothing without adversely affecting the result
isRightAngle(element.angle))
) {
// Disabling smoothing makes output much sharper, especially for
// text. Unless for non-right angles, where the aliasing is really
// terrible on Chromium.
//
// Note that `context.imageSmoothingQuality="high"` has almost
// zero effect.
//
context.imageSmoothingEnabled = false;
}
drawElementFromCanvas(elementWithCanvas, rc, context, renderConfig); drawElementFromCanvas(elementWithCanvas, rc, context, renderConfig);
// reset
context.imageSmoothingEnabled = currentImageSmoothingStatus;
} }
break; break;
} }
+45
View File
@@ -0,0 +1,45 @@
import ExcalidrawApp from "../excalidraw-app";
import {
mockBoundingClientRect,
render,
restoreOriginalGetBoundingClientRect,
} from "./test-utils";
import { UI } from "./helpers/ui";
describe("Test MobileMenu", () => {
const { h } = window;
const dimensions = { height: 400, width: 800 };
beforeEach(async () => {
await render(<ExcalidrawApp />);
//@ts-ignore
h.app.refreshDeviceState(h.app.excalidrawContainerRef.current!);
});
beforeAll(() => {
mockBoundingClientRect(dimensions);
});
afterAll(() => {
restoreOriginalGetBoundingClientRect();
});
it("should set device correctly", () => {
expect(h.app.device).toMatchInlineSnapshot(`
Object {
"canDeviceFitSidebar": false,
"isMobile": true,
"isSmScreen": false,
"isTouchScreen": false,
}
`);
});
it("should initialize with welcome screen and hide once user interacts", async () => {
expect(document.querySelector(".welcome-screen-center")).toMatchSnapshot();
UI.clickTool("rectangle");
expect(document.querySelector(".welcome-screen-center")).toBeNull();
});
});
@@ -0,0 +1,240 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Test MobileMenu should initialize with welcome screen and hide once user interacts 1`] = `
<div
class="welcome-screen-center"
>
<div
class="welcome-screen-center__logo virgil welcome-screen-decor"
>
<svg
aria-hidden="true"
class=""
fill="none"
focusable="false"
role="img"
viewBox="0 0 26 62"
>
<g
fill="currentColor"
>
<path
d="M24.296 12.214c0 .112-.134.224-.291.224-.135 0-.516.629-.808 1.392-.897 2.335-9.867 20.096-9.89 19.534 0-.292-.134-.494-.359-.494-.313 0-.358.18-.224 1.055.135 1.01.045 1.236-3.14 7.432-1.793 3.525-3.722 7.208-4.282 8.196-.584 1.032-1.032 2.155-1.077 2.626-.067.809.022.92 1.973 2.605 1.122.988 2.557 2.223 3.185 2.784 2.826 2.582 4.149 3.615 4.508 3.547.538-.09 8.858-8.823 8.88-9.317 0-.225-.403-3.638-.897-7.59-.852-6.735-1.66-14.616-1.57-15.38.068-.47-.269-2.85-.516-3.884-.201-.808-.112-1.145 1.503-4.827.942-2.178 2.176-4.85 2.714-5.928.515-1.077.964-2.02.964-2.088 0-.067-.157-.112-.336-.112-.18 0-.337.09-.337.225Zm-5.158 16.772c.247 1.572.74 5.344 1.099 8.375.695 5.568 1.503 11.742 1.727 13.314.135.786.045.943-1.413 2.56-2.534 2.851-5.225 5.658-6.145 6.376l-.852.674-4.373-4.086c-4.037-3.728-4.373-4.11-4.127-4.558a5154.2 5154.2 0 0 1 2.535-4.626 727.864 727.864 0 0 0 3.678-6.78c.784-1.46 1.502-2.717 1.637-2.785.156-.09.201 2.178.156 7.006-.09 7.207-.067 7.23.651 7.072.09 0 .157-3.637.157-8.06V35.43l2.355-4.715c1.3-2.605 2.377-4.693 2.422-4.67.045.022.27 1.347.493 2.94ZM9.562 1.818C7.903 3.143 5.346 5.388 3.328 7.32L1.735 8.823l.292 1.976c.157 1.078.449 3.188.628 4.67.202 1.482.404 2.874.47 3.077.09.269 0 .404-.246.404-.426 0-.449-.113.718 3.592.286.952.577 1.903.875 2.851.044.158.224.225.425.158.202-.09.314-.27.247-.427-.067-.18.045-.36.224-.427.247-.09.225-.269-.157-.92-.605-1.01-2.152-9.633-2.242-12.416-.067-1.976-.067-1.999.762-3.121.808-1.1 2.67-2.762 5.54-4.873.807-.605 1.614-1.28 1.839-1.504.336-.404.493-.292 3.319 2.717 1.637 1.729 3.453 3.502 4.037 3.952l1.076.808-.83 1.75c-.448.944-2.265 4.581-4.059 8.04-3.745 7.274-2.983 6.578-7.333 6.645l-2.826.023-.942 1.077c-.987 1.146-1.121 1.572-.65 2.29.18.248.313.652.313.898 0 .405.157.472 1.055.517.56.023 1.076.09 1.144.157.067.068.156 1.46.224 3.098l.09 2.965-1.503 3.232C1.735 45.422.749 47.891.749 48.7c0 .427.09.786.18.786.224 0 .224-.022 9.35-19.085a4398.495 4398.495 0 0 1 8.927-18.546c.672-1.369 1.278-2.626 1.323-2.806.045-.202-1.503-1.751-3.97-3.93-2.22-1.975-4.171-3.772-4.35-3.974-.516-.628-1.279-.426-2.647.674ZM8.441 31.231c-.18.472-.65 1.46-1.031 2.2-.629 1.258-.696 1.303-.853.786-.09-.314-.157-1.235-.18-2.066-.022-1.639-.067-1.616 1.817-1.728L8.8 30.4l-.358.831Zm1.884-3.592c-1.032 1.998-1.077 2.02-3.903 2.155-2.489.135-2.533.112-2.533-.36 0-.269-.09-.628-.203-.808-.134-.202-.044-.56.27-1.055l.493-.763H6.69c1.234-.023 2.647-.113 3.14-.202.494-.09.92-.135.965-.113.045.023-.18.54-.471 1.146Zm-.09-20.477c-.404.292-.516.584-.516 1.325 0 .875.067 1.01.673 1.257.605.247.763.224 1.458-.247.92-.629.941-.786.269-1.796-.583-.876-1.166-1.033-1.884-.54Z"
/>
<path
clip-rule="evenodd"
d="M23.703 11.793c.166-.291.501-.514.93-.514.38 0 .698.161.82.283.161.162.225.35.225.54a.822.822 0 0 1-.056.289c-.08.218-.5 1.106-.983 2.116-.535 1.071-1.76 3.727-2.699 5.895-.79 1.802-1.209 2.784-1.404 3.416-.142.461-.132.665-.058.961.264 1.103.6 3.647.53 4.132-.088.756.727 8.547 1.57 15.21.5 3.997.903 7.45.903 7.676l-.001.033c-.004.087-.041.288-.211.54-.24.354-.914 1.143-1.8 2.119-2.004 2.21-5.107 5.423-6.463 6.653-.322.292-.566.485-.696.56a.884.884 0 0 1-.289.111c-.194.037-.579-.007-1.11-.349-.707-.453-1.981-1.522-4-3.366-.627-.561-2.061-1.794-3.176-2.776-.81-.699-1.308-1.138-1.612-1.466-.32-.343-.47-.61-.549-.87-.078-.257-.085-.515-.055-.874.05-.52.521-1.769 1.166-2.91.559-.985 2.48-4.654 4.269-8.17 1.579-3.071 2.392-4.663 2.792-5.612.32-.759.329-1 .277-1.387-.085-.553-.092-.891-.052-1.092a.942.942 0 0 1 .274-.52c.164-.157.384-.261.704-.261.094 0 .184.011.27.033 1.924-3.44 8.554-16.632 9.316-18.616.276-.724.64-1.336.848-1.556a.965.965 0 0 1 .32-.228Zm-5.399 16.402c-.49.942-.971 1.888-1.446 2.837l-2.28 4.565v7.871c0 4.023-.06 7.404-.136 8.04-.067.552-.474.691-.654.722l.075-.008c-.317.07-.574.063-.778-.023-.234-.098-.5-.297-.63-.857-.156-.681-.158-2.462-.103-6.893.019-2.022.022-3.592.008-4.725-.156.276-.315.562-.467.843a737.624 737.624 0 0 1-3.682 6.79 3618.972 3618.972 0 0 0-2.462 4.493c.062.088.169.231.289.364.55.61 1.631 1.623 3.624 3.462l3.931 3.674.377-.298c.907-.709 3.554-3.479 6.055-6.293.425-.47.73-.814.946-1.084.175-.22.28-.36.319-.501.031-.117.002-.227-.024-.379l-.004-.02c-.224-1.572-1.032-7.753-1.728-13.33-.358-3.022-.85-6.782-1.096-8.349l-.002-.01c-.042-.301-.087-.603-.132-.891ZM9.118 1.264C9.91.628 10.537.27 11.028.144c.727-.186 1.27.003 1.713.53.186.209 2.107 1.972 4.287 3.912 2.02 1.783 3.434 3.16 3.897 3.743.326.41.322.756.296.873a1.046 1.046 0 0 1-.005.018c-.047.188-.669 1.512-1.374 2.947a4348.55 4348.55 0 0 0-8.923 18.54c-7.335 15.32-8.808 18.396-9.217 19.015-.235.355-.419.404-.525.437a.815.815 0 0 1-.249.036.745.745 0 0 1-.647-.363C.176 49.67.04 49.222.04 48.7c0-.286.09-.754.316-1.434.452-1.356 1.466-3.722 3.225-7.53l1.432-3.083-.084-2.787a72.902 72.902 0 0 0-.156-2.53 7.307 7.307 0 0 0-.539-.046c-.463-.024-.764-.062-.96-.124-.304-.096-.48-.252-.598-.438-.105-.165-.17-.374-.17-.663 0-.134-.081-.348-.178-.481l-.019-.028c-.293-.448-.406-.831-.373-1.234.04-.484.34-1.052 1.08-1.91l.759-.869c-.103-.325-.471-1.513-.854-2.787-.737-2.339-1.004-3.238-1.018-3.578-.016-.393.134-.59.27-.715a.721.721 0 0 1 .192-.125 89.87 89.87 0 0 1-.414-2.782 231.651 231.651 0 0 0-.625-4.652l-.292-1.976a.71.71 0 0 1 .215-.62l1.589-1.501C4.87 4.86 7.446 2.599 9.118 1.264Zm-1.833 33.75a.819.819 0 0 1-.406.208.73.73 0 0 1-.491-.063l.048 1.618v.009l.849-1.773Zm5.874.697c-.035.087-.07.175-.107.261a20.92 20.92 0 0 1-.36.798.688.688 0 0 1 .457.007l.01.004v-1.07Zm.72-1.892-.015.018a.745.745 0 0 1-.407.236c.02.195.027.378 0 .592l.422-.846ZM7.7 31.175c-.268.027-.489.055-.6.07-.006.056-.013.13-.016.194-.005.19 0 .42.004.694.003.111.006.225.011.338.232-.471.459-.956.6-1.296Zm2.12-1.456a2.04 2.04 0 0 1-.415.31c.064.104.099.222.104.341l.132-.277.18-.374Zm-.14-2.374c-.654.079-1.882.153-2.974.173h-1.87l-.281.435c-.09.141-.17.331-.203.414.102.21.189.508.226.788h.007c.364.006.928-.023 1.805-.07 1.243-.06 1.88-.052 2.315-.291.154-.086.266-.215.387-.393.176-.261.354-.605.587-1.056Zm2.136-1.784c-.157.16-.331.3-.52.422a.631.631 0 0 1 .182.281l.337-.703Zm7.205-1.478c-.222.442-.445.883-.667 1.32a.787.787 0 0 1 .61.007c.036.018.145.07.243.2-.032-.165-.067-.33-.105-.493-.088-.351-.137-.633-.08-1.034h-.001ZM11.415 2.546c-.358.319-1.039.879-1.725 1.394C6.903 5.989 5.087 7.59 4.301 8.662c-.28.38-.458.605-.556.852-.15.38-.103.798-.068 1.824.063 1.923.833 6.669 1.493 9.686.262 1.199.483 2.11.654 2.394.25.426.364.71.398.894a.923.923 0 0 1-.184.764l1.27-.01c.863-.014 1.523.003 2.056-.019.424-.017.75-.052 1.034-.187.336-.159.596-.458.921-.955.62-.948 1.373-2.515 2.705-5.103 1.789-3.448 3.6-7.076 4.047-8.015l.582-1.227-.62-.466c-.595-.458-2.45-2.263-4.12-4.027a59.654 59.654 0 0 0-2.498-2.52ZM5.81 24.876v-.001l-.013-.03.013.031Zm-.71-.835.027-.011a.55.55 0 0 0-.028.011Zm19.904-11.777v.01-.01Zm.002-.016v-.034.034ZM9.82 6.587c-.587.424-.81.823-.81 1.9 0 .787.12 1.157.344 1.42.158.186.388.339.77.494.352.144.603.207.838.209.347.002.688-.12 1.285-.525.707-.483.98-.864 1.036-1.238.052-.352-.09-.812-.574-1.54-.412-.619-.853-.95-1.29-1.072-.489-.139-1.016-.05-1.586.342l-.013.01Zm2.015 2.028a6.288 6.288 0 0 0-.306-.52c-.19-.284-.326-.488-.531-.5-.113-.007-.224.058-.352.146-.218.159-.218.34-.218.745 0 .198.02.419.028.504.047.025.133.068.204.097.133.054.222.102.312.103.04 0 .071-.027.12-.054a4.29 4.29 0 0 0 .358-.225c.147-.1.299-.223.385-.296ZM9.12 1.263l-.002.002.002-.002Z"
fill-rule="evenodd"
/>
</g>
</svg>
Excalidraw
</div>
<div
class="welcome-screen-center__heading welcome-screen-decor virgil"
>
All your data is saved locally in your browser.
</div>
<div
class="welcome-screen-menu"
>
<button
class="welcome-screen-menu-item "
type="button"
>
<div
class="welcome-screen-menu-item__icon"
>
<svg
aria-hidden="true"
class=""
fill="none"
focusable="false"
role="img"
stroke="currentColor"
stroke-linecap="round"
stroke-linejoin="round"
viewBox="0 0 20 20"
>
<path
d="m9.257 6.351.183.183H15.819c.34 0 .727.182 1.051.506.323.323.505.708.505 1.05v5.819c0 .316-.183.7-.52 1.035-.337.338-.723.522-1.037.522H4.182c-.352 0-.74-.181-1.058-.5-.318-.318-.499-.705-.499-1.057V5.182c0-.351.181-.736.5-1.054.32-.321.71-.503 1.057-.503H6.53l2.726 2.726Z"
stroke-width="1.25"
/>
</svg>
</div>
<div
class="welcome-screen-menu-item__text"
>
Open
</div>
<div
class="welcome-screen-menu-item__shortcut"
>
Ctrl+O
</div>
</button>
<button
class="welcome-screen-menu-item "
type="button"
>
<div
class="welcome-screen-menu-item__icon"
>
<svg
aria-hidden="true"
class=""
fill="none"
focusable="false"
role="img"
stroke="currentColor"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
viewBox="0 0 24 24"
>
<g
stroke-width="1.5"
>
<path
d="M0 0h24v24H0z"
fill="none"
stroke="none"
/>
<circle
cx="12"
cy="12"
r="9"
/>
<line
x1="12"
x2="12"
y1="17"
y2="17.01"
/>
<path
d="M12 13.5a1.5 1.5 0 0 1 1 -1.5a2.6 2.6 0 1 0 -3 -4"
/>
</g>
</svg>
</div>
<div
class="welcome-screen-menu-item__text"
>
Help
</div>
<div
class="welcome-screen-menu-item__shortcut"
>
?
</div>
</button>
<button
class="welcome-screen-menu-item "
type="button"
>
<div
class="welcome-screen-menu-item__icon"
>
<svg
aria-hidden="true"
class=""
fill="none"
focusable="false"
role="img"
stroke="currentColor"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
viewBox="0 0 24 24"
>
<g
stroke-width="1.5"
>
<path
d="M0 0h24v24H0z"
fill="none"
stroke="none"
/>
<circle
cx="9"
cy="7"
r="4"
/>
<path
d="M3 21v-2a4 4 0 0 1 4 -4h4a4 4 0 0 1 4 4v2"
/>
<path
d="M16 3.13a4 4 0 0 1 0 7.75"
/>
<path
d="M21 21v-2a4 4 0 0 0 -3 -3.85"
/>
</g>
</svg>
</div>
<div
class="welcome-screen-menu-item__text"
>
Live collaboration...
</div>
</button>
<a
class="welcome-screen-menu-item "
href="https://plus.excalidraw.com/plus?utm_source=excalidraw&utm_medium=app&utm_content=welcomeScreenGuest"
rel="noreferrer"
target="_blank"
>
<div
class="welcome-screen-menu-item__icon"
>
<svg
aria-hidden="true"
class=""
fill="none"
focusable="false"
role="img"
stroke="currentColor"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
viewBox="0 0 24 24"
>
<g
stroke-width="1.5"
>
<path
d="M0 0h24v24H0z"
fill="none"
stroke="none"
/>
<rect
height="4"
rx="1"
width="18"
x="3"
y="8"
/>
<line
x1="12"
x2="12"
y1="8"
y2="21"
/>
<path
d="M19 12v7a2 2 0 0 1 -2 2h-10a2 2 0 0 1 -2 -2v-7"
/>
<path
d="M7.5 8a2.5 2.5 0 0 1 0 -5a4.8 8 0 0 1 4.5 5a4.8 8 0 0 1 4.5 -5a2.5 2.5 0 0 1 0 5"
/>
</g>
</svg>
</div>
<div
class="welcome-screen-menu-item__text"
>
Try Excalidraw Plus!
</div>
</a>
</div>
</div>
`;
+5
View File
@@ -36,4 +36,9 @@ describe("getClientInitials", () => {
result = getClientInitials(null); result = getClientInitials(null);
expect(result).toBe("?"); expect(result).toBe("?");
}); });
it('returns "?" when value is blank', () => {
const result = getClientInitials(" ");
expect(result).toBe("?");
});
}); });
+78 -14
View File
@@ -534,7 +534,7 @@ describe("restore", () => {
}); });
describe("repairing bindings", () => { describe("repairing bindings", () => {
it("should repair container boundElements", () => { it("should repair container boundElements when repair is true", () => {
const container = API.createElement({ const container = API.createElement({
type: "rectangle", type: "rectangle",
boundElements: [], boundElements: [],
@@ -546,11 +546,28 @@ describe("repairing bindings", () => {
expect(container.boundElements).toEqual([]); expect(container.boundElements).toEqual([]);
const restoredElements = restore.restoreElements( let restoredElements = restore.restoreElements(
[container, boundElement], [container, boundElement],
null, null,
); );
expect(restoredElements).toEqual([
expect.objectContaining({
id: container.id,
boundElements: [],
}),
expect.objectContaining({
id: boundElement.id,
containerId: container.id,
}),
]);
restoredElements = restore.restoreElements(
[container, boundElement],
null,
{ repairBindings: true },
);
expect(restoredElements).toEqual([ expect(restoredElements).toEqual([
expect.objectContaining({ expect.objectContaining({
id: container.id, id: container.id,
@@ -563,7 +580,7 @@ describe("repairing bindings", () => {
]); ]);
}); });
it("should repair containerId of boundElements", () => { it("should repair containerId of boundElements when repair is true", () => {
const boundElement = API.createElement({ const boundElement = API.createElement({
type: "text", type: "text",
containerId: null, containerId: null,
@@ -573,11 +590,28 @@ describe("repairing bindings", () => {
boundElements: [{ type: boundElement.type, id: boundElement.id }], boundElements: [{ type: boundElement.type, id: boundElement.id }],
}); });
const restoredElements = restore.restoreElements( let restoredElements = restore.restoreElements(
[container, boundElement], [container, boundElement],
null, null,
); );
expect(restoredElements).toEqual([
expect.objectContaining({
id: container.id,
boundElements: [{ type: boundElement.type, id: boundElement.id }],
}),
expect.objectContaining({
id: boundElement.id,
containerId: null,
}),
]);
restoredElements = restore.restoreElements(
[container, boundElement],
null,
{ repairBindings: true },
);
expect(restoredElements).toEqual([ expect(restoredElements).toEqual([
expect.objectContaining({ expect.objectContaining({
id: container.id, id: container.id,
@@ -620,7 +654,7 @@ describe("repairing bindings", () => {
]); ]);
}); });
it("should remove bindings of deleted elements from boundElements", () => { it("should remove bindings of deleted elements from boundElements when repair is true", () => {
const container = API.createElement({ const container = API.createElement({
type: "rectangle", type: "rectangle",
boundElements: [], boundElements: [],
@@ -642,6 +676,8 @@ describe("repairing bindings", () => {
type: invisibleBoundElement.type, type: invisibleBoundElement.type,
id: invisibleBoundElement.id, id: invisibleBoundElement.id,
}; };
expect(container.boundElements).toEqual([]);
const nonExistentBinding = { type: "text", id: "non-existent" }; const nonExistentBinding = { type: "text", id: "non-existent" };
// @ts-ignore // @ts-ignore
container.boundElements = [ container.boundElements = [
@@ -650,17 +686,28 @@ describe("repairing bindings", () => {
nonExistentBinding, nonExistentBinding,
]; ];
expect(container.boundElements).toEqual([ let restoredElements = restore.restoreElements(
obsoleteBinding,
invisibleBinding,
nonExistentBinding,
]);
const restoredElements = restore.restoreElements(
[container, invisibleBoundElement, boundElement], [container, invisibleBoundElement, boundElement],
null, null,
); );
expect(restoredElements).toEqual([
expect.objectContaining({
id: container.id,
boundElements: [obsoleteBinding, invisibleBinding, nonExistentBinding],
}),
expect.objectContaining({
id: boundElement.id,
containerId: container.id,
}),
]);
restoredElements = restore.restoreElements(
[container, invisibleBoundElement, boundElement],
null,
{ repairBindings: true },
);
expect(restoredElements).toEqual([ expect(restoredElements).toEqual([
expect.objectContaining({ expect.objectContaining({
id: container.id, id: container.id,
@@ -673,7 +720,7 @@ describe("repairing bindings", () => {
]); ]);
}); });
it("should remove containerId if container not exists", () => { it("should remove containerId if container not exists when repair is true", () => {
const boundElement = API.createElement({ const boundElement = API.createElement({
type: "text", type: "text",
containerId: "non-existent", containerId: "non-existent",
@@ -684,11 +731,28 @@ describe("repairing bindings", () => {
isDeleted: true, isDeleted: true,
}); });
const restoredElements = restore.restoreElements( let restoredElements = restore.restoreElements(
[boundElement, boundElementDeleted], [boundElement, boundElementDeleted],
null, null,
); );
expect(restoredElements).toEqual([
expect.objectContaining({
id: boundElement.id,
containerId: "non-existent",
}),
expect.objectContaining({
id: boundElementDeleted.id,
containerId: "non-existent",
}),
]);
restoredElements = restore.restoreElements(
[boundElement, boundElementDeleted],
null,
{ repairBindings: true },
);
expect(restoredElements).toEqual([ expect(restoredElements).toEqual([
expect.objectContaining({ expect.objectContaining({
id: boundElement.id, id: boundElement.id,
+100 -59
View File
@@ -11,6 +11,7 @@ import {
} from "../actions"; } from "../actions";
import { AppState } from "../types"; import { AppState } from "../types";
import { API } from "./helpers/api"; import { API } from "./helpers/api";
import { selectGroupsForSelectedElements } from "../groups";
// Unmount ReactDOM from root // Unmount ReactDOM from root
ReactDOM.unmountComponentAtNode(document.getElementById("root")!); ReactDOM.unmountComponentAtNode(document.getElementById("root")!);
@@ -34,6 +35,7 @@ const populateElements = (
height?: number; height?: number;
containerId?: string; containerId?: string;
}[], }[],
appState?: Partial<AppState>,
) => { ) => {
const selectedElementIds: any = {}; const selectedElementIds: any = {};
@@ -84,6 +86,11 @@ const populateElements = (
}); });
h.setState({ h.setState({
...selectGroupsForSelectedElements(
{ ...h.state, ...appState, selectedElementIds },
h.elements,
),
...appState,
selectedElementIds, selectedElementIds,
}); });
@@ -111,11 +118,7 @@ const assertZindex = ({
appState?: Partial<AppState>; appState?: Partial<AppState>;
operations: [Actions, string[]][]; operations: [Actions, string[]][];
}) => { }) => {
const selectedElementIds = populateElements(elements); const selectedElementIds = populateElements(elements, appState);
h.setState({
editingGroupId: appState?.editingGroupId || null,
});
operations.forEach(([action, expected]) => { operations.forEach(([action, expected]) => {
h.app.actionManager.executeAction(action); h.app.actionManager.executeAction(action);
@@ -884,9 +887,6 @@ describe("z-index manipulation", () => {
{ id: "A", groupIds: ["g1"], isSelected: true }, { id: "A", groupIds: ["g1"], isSelected: true },
{ id: "B", groupIds: ["g1"], isSelected: true }, { id: "B", groupIds: ["g1"], isSelected: true },
]); ]);
h.setState({
selectedGroupIds: { g1: true },
});
h.app.actionManager.executeAction(actionDuplicateSelection); h.app.actionManager.executeAction(actionDuplicateSelection);
expect(h.elements).toMatchObject([ expect(h.elements).toMatchObject([
{ id: "A" }, { id: "A" },
@@ -908,9 +908,6 @@ describe("z-index manipulation", () => {
{ id: "B", groupIds: ["g1"], isSelected: true }, { id: "B", groupIds: ["g1"], isSelected: true },
{ id: "C" }, { id: "C" },
]); ]);
h.setState({
selectedGroupIds: { g1: true },
});
h.app.actionManager.executeAction(actionDuplicateSelection); h.app.actionManager.executeAction(actionDuplicateSelection);
expect(h.elements).toMatchObject([ expect(h.elements).toMatchObject([
{ id: "A" }, { id: "A" },
@@ -933,9 +930,6 @@ describe("z-index manipulation", () => {
{ id: "B", groupIds: ["g1"], isSelected: true }, { id: "B", groupIds: ["g1"], isSelected: true },
{ id: "C", isSelected: true }, { id: "C", isSelected: true },
]); ]);
h.setState({
selectedGroupIds: { g1: true },
});
h.app.actionManager.executeAction(actionDuplicateSelection); h.app.actionManager.executeAction(actionDuplicateSelection);
expect(h.elements.map((element) => element.id)).toEqual([ expect(h.elements.map((element) => element.id)).toEqual([
"A", "A",
@@ -952,9 +946,6 @@ describe("z-index manipulation", () => {
{ id: "C", groupIds: ["g2"], isSelected: true }, { id: "C", groupIds: ["g2"], isSelected: true },
{ id: "D", groupIds: ["g2"], isSelected: true }, { id: "D", groupIds: ["g2"], isSelected: true },
]); ]);
h.setState({
selectedGroupIds: { g1: true, g2: true },
});
h.app.actionManager.executeAction(actionDuplicateSelection); h.app.actionManager.executeAction(actionDuplicateSelection);
expect(h.elements.map((element) => element.id)).toEqual([ expect(h.elements.map((element) => element.id)).toEqual([
"A", "A",
@@ -967,14 +958,16 @@ describe("z-index manipulation", () => {
"D_copy", "D_copy",
]); ]);
populateElements([ populateElements(
{ id: "A", groupIds: ["g1", "g2"], isSelected: true }, [
{ id: "B", groupIds: ["g1", "g2"], isSelected: true }, { id: "A", groupIds: ["g1", "g2"], isSelected: true },
{ id: "C", groupIds: ["g2"], isSelected: true }, { id: "B", groupIds: ["g1", "g2"], isSelected: true },
]); { id: "C", groupIds: ["g2"], isSelected: true },
h.setState({ ],
selectedGroupIds: { g1: true }, {
}); selectedGroupIds: { g1: true },
},
);
h.app.actionManager.executeAction(actionDuplicateSelection); h.app.actionManager.executeAction(actionDuplicateSelection);
expect(h.elements.map((element) => element.id)).toEqual([ expect(h.elements.map((element) => element.id)).toEqual([
"A", "A",
@@ -985,14 +978,16 @@ describe("z-index manipulation", () => {
"C_copy", "C_copy",
]); ]);
populateElements([ populateElements(
{ id: "A", groupIds: ["g1", "g2"], isSelected: true }, [
{ id: "B", groupIds: ["g1", "g2"], isSelected: true }, { id: "A", groupIds: ["g1", "g2"], isSelected: true },
{ id: "C", groupIds: ["g2"], isSelected: true }, { id: "B", groupIds: ["g1", "g2"], isSelected: true },
]); { id: "C", groupIds: ["g2"], isSelected: true },
h.setState({ ],
selectedGroupIds: { g2: true }, {
}); selectedGroupIds: { g2: true },
},
);
h.app.actionManager.executeAction(actionDuplicateSelection); h.app.actionManager.executeAction(actionDuplicateSelection);
expect(h.elements.map((element) => element.id)).toEqual([ expect(h.elements.map((element) => element.id)).toEqual([
"A", "A",
@@ -1003,17 +998,19 @@ describe("z-index manipulation", () => {
"C_copy", "C_copy",
]); ]);
populateElements([ populateElements(
{ id: "A", groupIds: ["g1", "g2"], isSelected: true }, [
{ id: "B", groupIds: ["g1", "g2"], isSelected: true }, { id: "A", groupIds: ["g1", "g2"], isSelected: true },
{ id: "C", groupIds: ["g2"], isSelected: true }, { id: "B", groupIds: ["g1", "g2"], isSelected: true },
{ id: "D", groupIds: ["g3", "g4"], isSelected: true }, { id: "C", groupIds: ["g2"], isSelected: true },
{ id: "E", groupIds: ["g3", "g4"], isSelected: true }, { id: "D", groupIds: ["g3", "g4"], isSelected: true },
{ id: "F", groupIds: ["g4"], isSelected: true }, { id: "E", groupIds: ["g3", "g4"], isSelected: true },
]); { id: "F", groupIds: ["g4"], isSelected: true },
h.setState({ ],
selectedGroupIds: { g2: true, g4: true }, {
}); selectedGroupIds: { g2: true, g4: true },
},
);
h.app.actionManager.executeAction(actionDuplicateSelection); h.app.actionManager.executeAction(actionDuplicateSelection);
expect(h.elements.map((element) => element.id)).toEqual([ expect(h.elements.map((element) => element.id)).toEqual([
"A", "A",
@@ -1030,11 +1027,14 @@ describe("z-index manipulation", () => {
"F_copy", "F_copy",
]); ]);
populateElements([ populateElements(
{ id: "A", groupIds: ["g1", "g2"], isSelected: true }, [
{ id: "B", groupIds: ["g1", "g2"] }, { id: "A", groupIds: ["g1", "g2"], isSelected: true },
{ id: "C", groupIds: ["g2"] }, { id: "B", groupIds: ["g1", "g2"] },
]); { id: "C", groupIds: ["g2"] },
],
{ editingGroupId: "g1" },
);
h.app.actionManager.executeAction(actionDuplicateSelection); h.app.actionManager.executeAction(actionDuplicateSelection);
expect(h.elements.map((element) => element.id)).toEqual([ expect(h.elements.map((element) => element.id)).toEqual([
"A", "A",
@@ -1043,11 +1043,14 @@ describe("z-index manipulation", () => {
"C", "C",
]); ]);
populateElements([ populateElements(
{ id: "A", groupIds: ["g1", "g2"] }, [
{ id: "B", groupIds: ["g1", "g2"], isSelected: true }, { id: "A", groupIds: ["g1", "g2"] },
{ id: "C", groupIds: ["g2"] }, { id: "B", groupIds: ["g1", "g2"], isSelected: true },
]); { id: "C", groupIds: ["g2"] },
],
{ editingGroupId: "g1" },
);
h.app.actionManager.executeAction(actionDuplicateSelection); h.app.actionManager.executeAction(actionDuplicateSelection);
expect(h.elements.map((element) => element.id)).toEqual([ expect(h.elements.map((element) => element.id)).toEqual([
"A", "A",
@@ -1056,11 +1059,14 @@ describe("z-index manipulation", () => {
"C", "C",
]); ]);
populateElements([ populateElements(
{ id: "A", groupIds: ["g1", "g2"], isSelected: true }, [
{ id: "B", groupIds: ["g1", "g2"], isSelected: true }, { id: "A", groupIds: ["g1", "g2"], isSelected: true },
{ id: "C", groupIds: ["g2"], isSelected: true }, { id: "B", groupIds: ["g1", "g2"], isSelected: true },
]); { id: "C", groupIds: ["g2"] },
],
{ editingGroupId: "g1" },
);
h.app.actionManager.executeAction(actionDuplicateSelection); h.app.actionManager.executeAction(actionDuplicateSelection);
expect(h.elements.map((element) => element.id)).toEqual([ expect(h.elements.map((element) => element.id)).toEqual([
"A", "A",
@@ -1068,7 +1074,42 @@ describe("z-index manipulation", () => {
"B", "B",
"B_copy", "B_copy",
"C", "C",
]);
});
it("duplicating incorrectly interleaved elements (group elements should be together) should still produce reasonable result", () => {
populateElements([
{ id: "A", groupIds: ["g1"], isSelected: true },
{ id: "B" },
{ id: "C", groupIds: ["g1"], isSelected: true },
]);
h.app.actionManager.executeAction(actionDuplicateSelection);
expect(h.elements.map((element) => element.id)).toEqual([
"A",
"C",
"A_copy",
"C_copy", "C_copy",
"B",
]);
});
it("group-selected duplication should includes deleted elements that weren't selected on account of being deleted", () => {
populateElements([
{ id: "A", groupIds: ["g1"], isDeleted: true },
{ id: "B", groupIds: ["g1"], isSelected: true },
{ id: "C", groupIds: ["g1"], isSelected: true },
{ id: "D" },
]);
expect(h.state.selectedGroupIds).toEqual({ g1: true });
h.app.actionManager.executeAction(actionDuplicateSelection);
expect(h.elements.map((element) => element.id)).toEqual([
"A",
"B",
"C",
"A_copy",
"B_copy",
"C_copy",
"D",
]); ]);
}); });
+8
View File
@@ -607,6 +607,14 @@ export const arrayToMap = <T extends { id: string } | string>(
}, new Map()); }, new Map());
}; };
export const arrayToMapWithIndex = <T extends { id: string }>(
elements: readonly T[],
) =>
elements.reduce((acc, element: T, idx) => {
acc.set(element.id, [element, idx]);
return acc;
}, new Map<string, [element: T, index: number]>());
export const isTestEnv = () => export const isTestEnv = () =>
typeof process !== "undefined" && process.env?.NODE_ENV === "test"; typeof process !== "undefined" && process.env?.NODE_ENV === "test";