Compare commits

...

25 Commits

Author SHA1 Message Date
Daniel J. Geiger a07ac46f8d Revert "fix: React.memo resolvers not accounting for all props (#6042)"
This reverts commit 618442299f.
2023-01-19 20:03:09 -06:00
Daniel J. Geiger 157bb7d6d6 Do not merge. 2023-01-19 19:44:52 -06:00
David Luzar d0b33d35db build: temporarily disable pre-commit (#6132) 2023-01-19 13:50:42 +01:00
Aakansha Doshi d6a5ef1936 docs: release @excalidraw/excalidraw@0.14.1 🎉 (#6112) 2023-01-16 16:08:03 +05:30
Aakansha Doshi c7a11f5cd2 docs: release @excalidraw/excalidraw@0.14.0 🎉 (#6109) 2023-01-13 16:08:29 +05:30
Aakansha Doshi 893c487add fix: remove overflow hidden from button (#6110)
remove overflow hidden from button
2023-01-13 15:44:33 +05:30
Aakansha Doshi 99fdffdab7 fix: mobile tools positioning (#6107)
* fix: mobile tools positioning

* add var for padding

* use css var

* new line

* stupid mistake

* lint
2023-01-13 00:57:25 +05:30
Aakansha Doshi faad8a65f1 feat: new Live Collaboration Component API (#6104)
* feat: new Live Collaboration Component API

* namespace export icons into `icons` dictionary and lowercase

* update readme and changelog

* review fixes

* fix

* fix

* update docs

* remove

* allow button rest props

* update docs

* docs

* add `WelcomeScreen.Center.MenuItemLiveCollaborationTrigger`

* fix lint

* update changelog

Co-authored-by: dwelle <luzar.david@gmail.com>
2023-01-12 23:28:57 +05:30
Aakansha Doshi 9d04479f98 fix: renamed folder MainMenu->main-menu and support rest props (#6103)
* renamed folder MainMenu -> main-menu

* rename ariaLabel -> aria-label and dataTestId -> data-testid

* allow rest props

* fix

* lint

* add ts check

* ts for div

* fix

* fix

* fix
2023-01-12 20:40:09 +05:30
David Luzar 599a8f3c6f feat: support WelcomeScreen customization API (#6048) 2023-01-12 15:49:28 +01:00
David Luzar 0982da38fe feat: render unknown supplied children to UI (#6096) 2023-01-12 15:20:16 +01:00
Barnabás Molnár 699897f71b feat: generic button export (#6092)
Co-authored-by: dwelle <luzar.david@gmail.com>
2023-01-12 13:06:00 +01:00
Aakansha Doshi 328ff6c32d fix: use position absolute for mobile misc tools (#6099) 2023-01-11 19:47:40 +05:30
David Luzar 618442299f fix: React.memo resolvers not accounting for all props (#6042) 2023-01-09 10:24:17 +01:00
Antonio Della Fortuna 06b45e0cfc fix: image horizontal flip fix + improved tests (#5799)
Co-authored-by: Antonio Della Fortuna <a.dellafortuna00@gmail.com>
Co-authored-by: dwelle <luzar.david@gmail.com>
fixes https://github.com/excalidraw/excalidraw/issues/5784
2023-01-08 16:19:13 +00:00
David Luzar 809d5ba17f fix: png-exporting does not preserve angles correctly for flipped images (#6085)
* fix: png-exporting does not preserve angles correctly for flipped images

* refactor related code

* simplify further and comment
2023-01-08 16:22:04 +01:00
David Luzar 40d53d9231 fix: stale appState of MainMenu defaultItems rendered from Actions (#6074) 2023-01-06 14:32:55 +01:00
dependabot[bot] 9803a85381 build(deps): bump loader-utils from 2.0.3 to 2.0.4 in /src/packages/excalidraw (#5892)
build(deps): bump loader-utils in /src/packages/excalidraw

Bumps [loader-utils](https://github.com/webpack/loader-utils) from 2.0.3 to 2.0.4.
- [Release notes](https://github.com/webpack/loader-utils/releases)
- [Changelog](https://github.com/webpack/loader-utils/blob/v2.0.4/CHANGELOG.md)
- [Commits](https://github.com/webpack/loader-utils/compare/v2.0.3...v2.0.4)

---
updated-dependencies:
- dependency-name: loader-utils
  dependency-type: indirect
...

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-01-06 12:31:06 +05:30
dependabot[bot] 72784f9d29 build(deps): bump loader-utils from 2.0.3 to 2.0.4 (#5905)
Bumps [loader-utils](https://github.com/webpack/loader-utils) from 2.0.3 to 2.0.4.
- [Release notes](https://github.com/webpack/loader-utils/releases)
- [Changelog](https://github.com/webpack/loader-utils/blob/v2.0.4/CHANGELOG.md)
- [Commits](https://github.com/webpack/loader-utils/compare/v2.0.3...v2.0.4)

---
updated-dependencies:
- dependency-name: loader-utils
  dependency-type: indirect
...

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-01-06 12:30:43 +05:30
dependabot[bot] e3249f930c build(deps): bump json5 from 1.0.1 to 1.0.2 (#6076)
Bumps [json5](https://github.com/json5/json5) from 1.0.1 to 1.0.2.
- [Release notes](https://github.com/json5/json5/releases)
- [Changelog](https://github.com/json5/json5/blob/main/CHANGELOG.md)
- [Commits](https://github.com/json5/json5/compare/v1.0.1...v1.0.2)

---
updated-dependencies:
- dependency-name: json5
  dependency-type: indirect
...

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-01-06 06:59:44 +00:00
dependabot[bot] cbe0d34f1a build(deps): bump decode-uri-component from 0.2.0 to 0.2.2 (#5963)
Bumps [decode-uri-component](https://github.com/SamVerschueren/decode-uri-component) from 0.2.0 to 0.2.2.
- [Release notes](https://github.com/SamVerschueren/decode-uri-component/releases)
- [Commits](https://github.com/SamVerschueren/decode-uri-component/compare/v0.2.0...v0.2.2)

---
updated-dependencies:
- dependency-name: decode-uri-component
  dependency-type: indirect
...

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-01-06 12:26:23 +05:30
dependabot[bot] bed8093e47 build(deps): bump json5 from 2.2.1 to 2.2.3 in /dev-docs (#6060)
Bumps [json5](https://github.com/json5/json5) from 2.2.1 to 2.2.3.
- [Release notes](https://github.com/json5/json5/releases)
- [Changelog](https://github.com/json5/json5/blob/main/CHANGELOG.md)
- [Commits](https://github.com/json5/json5/compare/v2.2.1...v2.2.3)

---
updated-dependencies:
- dependency-name: json5
  dependency-type: indirect
...

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-01-06 12:25:48 +05:30
dependabot[bot] 1255ca2e84 build(deps): bump json5 from 2.2.1 to 2.2.3 in /src/packages/utils (#6061)
Bumps [json5](https://github.com/json5/json5) from 2.2.1 to 2.2.3.
- [Release notes](https://github.com/json5/json5/releases)
- [Changelog](https://github.com/json5/json5/blob/main/CHANGELOG.md)
- [Commits](https://github.com/json5/json5/compare/v2.2.1...v2.2.3)

---
updated-dependencies:
- dependency-name: json5
  dependency-type: indirect
...

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-01-06 12:25:35 +05:30
dependabot[bot] 14d02dcaea build(deps): bump json5 from 2.2.1 to 2.2.3 in /src/packages/excalidraw (#6062)
Bumps [json5](https://github.com/json5/json5) from 2.2.1 to 2.2.3.
- [Release notes](https://github.com/json5/json5/releases)
- [Changelog](https://github.com/json5/json5/blob/main/CHANGELOG.md)
- [Commits](https://github.com/json5/json5/compare/v2.2.1...v2.2.3)

---
updated-dependencies:
- dependency-name: json5
  dependency-type: indirect
...

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-01-06 12:25:20 +05:30
Excalidraw Bot 9747223705 chore: Update translations from Crowdin (#6052)
* New translations en.json (German)

* Auto commit: Calculate translation coverage

* New translations en.json (Hindi)

* New translations en.json (Marathi)

* New translations en.json (Hindi)

* Auto commit: Calculate translation coverage

* New translations en.json (Galician)

* Auto commit: Calculate translation coverage

* New translations en.json (Romanian)

* New translations en.json (French)

* New translations en.json (Spanish)

* New translations en.json (Arabic)

* New translations en.json (Bulgarian)

* New translations en.json (Catalan)

* New translations en.json (Czech)

* New translations en.json (Danish)

* New translations en.json (German)

* New translations en.json (Greek)

* New translations en.json (Basque)

* New translations en.json (Finnish)

* New translations en.json (Hebrew)

* New translations en.json (Hungarian)

* New translations en.json (Italian)

* New translations en.json (Japanese)

* New translations en.json (Korean)

* New translations en.json (Kurdish)

* New translations en.json (Lithuanian)

* New translations en.json (Dutch)

* New translations en.json (Punjabi)

* New translations en.json (Polish)

* New translations en.json (Portuguese)

* New translations en.json (Russian)

* New translations en.json (Slovak)

* New translations en.json (Slovenian)

* New translations en.json (Swedish)

* New translations en.json (Turkish)

* New translations en.json (Ukrainian)

* New translations en.json (Chinese Simplified)

* New translations en.json (Chinese Traditional)

* New translations en.json (Vietnamese)

* New translations en.json (Galician)

* New translations en.json (Portuguese, Brazilian)

* New translations en.json (Indonesian)

* New translations en.json (Persian)

* New translations en.json (Tamil)

* New translations en.json (Bengali)

* New translations en.json (Marathi)

* New translations en.json (Norwegian Nynorsk)

* New translations en.json (Kazakh)

* New translations en.json (Latvian)

* New translations en.json (Hindi)

* New translations en.json (Burmese)

* New translations en.json (Chinese Traditional, Hong Kong)

* New translations en.json (Sinhala)

* New translations en.json (Norwegian Bokmal)

* New translations en.json (Occitan)

* New translations en.json (Kabyle)

* Auto commit: Calculate translation coverage

* New translations en.json (Chinese Simplified)

* Auto commit: Calculate translation coverage

* New translations en.json (Chinese Traditional)

* New translations en.json (Chinese Traditional)

* New translations en.json (Norwegian Bokmal)

* Auto commit: Calculate translation coverage

* New translations en.json (Latvian)

* Auto commit: Calculate translation coverage

* New translations en.json (Romanian)

* Auto commit: Calculate translation coverage

* New translations en.json (Slovenian)

* Auto commit: Calculate translation coverage

* New translations en.json (Spanish)

* New translations en.json (Russian)

* Auto commit: Calculate translation coverage

* New translations en.json (German)

* Auto commit: Calculate translation coverage

* New translations en.json (Vietnamese)

* Auto commit: Calculate translation coverage

* New translations en.json (Hindi)

* Auto commit: Calculate translation coverage

* New translations en.json (Dutch)

* Auto commit: Calculate translation coverage

* New translations en.json (Marathi)

* Auto commit: Calculate translation coverage

* New translations en.json (Latvian)

* New translations en.json (French)

* Auto commit: Calculate translation coverage

* New translations en.json (French)

* Auto commit: Calculate translation coverage

* New translations en.json (Portuguese, Brazilian)

* Auto commit: Calculate translation coverage

* New translations en.json (Japanese)

* Auto commit: Calculate translation coverage
2023-01-06 12:23:14 +05:30
121 changed files with 2513 additions and 1679 deletions
+1 -1
View File
@@ -1,2 +1,2 @@
#!/bin/sh
yarn lint-staged
# yarn lint-staged
+3 -3
View File
@@ -4692,9 +4692,9 @@ json-schema-traverse@^1.0.0:
integrity sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==
json5@^2.1.2, json5@^2.2.1:
version "2.2.1"
resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.1.tgz#655d50ed1e6f95ad1a3caababd2b0efda10b395c"
integrity sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==
version "2.2.3"
resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.3.tgz#78cd6f1a19bdc12b73db5ad0c61efd66c1e29283"
integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==
jsonfile@^6.0.1:
version "6.1.0"
+2 -2
View File
@@ -26,7 +26,7 @@ export const actionUnbindText = register({
name: "unbindText",
contextItemLabel: "labels.unbindText",
trackEvent: { category: "element" },
contextItemPredicate: (elements, appState) => {
predicate: (elements, appState) => {
const selectedElements = getSelectedElements(elements, appState);
return selectedElements.some((element) => hasBoundTextElement(element));
},
@@ -76,7 +76,7 @@ export const actionBindText = register({
name: "bindText",
contextItemLabel: "labels.bindText",
trackEvent: { category: "element" },
contextItemPredicate: (elements, appState) => {
predicate: (elements, appState) => {
const selectedElements = getSelectedElements(elements, appState);
if (selectedElements.length === 2) {
+17 -31
View File
@@ -1,11 +1,5 @@
import { ColorPicker } from "../components/ColorPicker";
import {
eraser,
MoonIcon,
SunIcon,
ZoomInIcon,
ZoomOutIcon,
} from "../components/icons";
import { eraser, ZoomInIcon, ZoomOutIcon } from "../components/icons";
import { ToolButton } from "../components/ToolButton";
import { MIN_ZOOM, THEME, ZOOM_STEP } from "../constants";
import { getCommonBounds, getNonDeletedElements } from "../element";
@@ -21,14 +15,17 @@ import { register } from "./register";
import { Tooltip } from "../components/Tooltip";
import { newElementWith } from "../element/mutateElement";
import { getDefaultAppState, isEraserActive } from "../appState";
import ClearCanvas from "../components/ClearCanvas";
import clsx from "clsx";
import DropdownMenuItem from "../components/dropdownMenu/DropdownMenuItem";
import { getShortcutFromShortcutName } from "./shortcuts";
export const actionChangeViewBackgroundColor = register({
name: "changeViewBackgroundColor",
trackEvent: false,
predicate: (elements, appState, props, app) => {
return (
!!app.props.UIOptions.canvasActions.changeViewBackgroundColor &&
!appState.viewModeEnabled
);
},
perform: (_, appState, value) => {
return {
appState: { ...appState, ...value },
@@ -36,6 +33,7 @@ export const actionChangeViewBackgroundColor = register({
};
},
PanelComponent: ({ elements, appState, updateData }) => {
// FIXME move me to src/components/mainMenu/DefaultItems.tsx
return (
<div style={{ position: "relative" }}>
<ColorPicker
@@ -59,6 +57,12 @@ export const actionChangeViewBackgroundColor = register({
export const actionClearCanvas = register({
name: "clearCanvas",
trackEvent: { category: "canvas" },
predicate: (elements, appState, props, app) => {
return (
!!app.props.UIOptions.canvasActions.clearCanvas &&
!appState.viewModeEnabled
);
},
perform: (elements, appState, _, app) => {
app.imageCache.clear();
return {
@@ -84,8 +88,6 @@ export const actionClearCanvas = register({
commitToHistory: true,
};
},
PanelComponent: ({ updateData }) => <ClearCanvas onConfirm={updateData} />,
});
export const actionZoomIn = register({
@@ -298,26 +300,10 @@ export const actionToggleTheme = register({
commitToHistory: false,
};
},
PanelComponent: ({ appState, updateData }) => (
<DropdownMenuItem
onSelect={() => {
updateData(appState.theme === THEME.LIGHT ? THEME.DARK : THEME.LIGHT);
}}
icon={appState.theme === "dark" ? SunIcon : MoonIcon}
dataTestId="toggle-dark-mode"
shortcut={getShortcutFromShortcutName("toggleTheme")}
ariaLabel={
appState.theme === "dark"
? t("buttons.lightMode")
: t("buttons.darkMode")
}
>
{appState.theme === "dark"
? t("buttons.lightMode")
: t("buttons.darkMode")}
</DropdownMenuItem>
),
keyTest: (event) => event.altKey && event.shiftKey && event.code === CODES.D,
predicate: (elements, appState, props, app) => {
return !!app.props.UIOptions.canvasActions.toggleTheme;
},
});
export const actionErase = register({
+6 -6
View File
@@ -24,7 +24,7 @@ export const actionCopy = register({
commitToHistory: false,
};
},
contextItemPredicate: (elements, appState, appProps, app) => {
predicate: (elements, appState, appProps, app) => {
return app.device.isMobile && !!navigator.clipboard;
},
contextItemLabel: "labels.copy",
@@ -41,7 +41,7 @@ export const actionPaste = register({
commitToHistory: false,
};
},
contextItemPredicate: (elements, appState, appProps, app) => {
predicate: (elements, appState, appProps, app) => {
return app.device.isMobile && !!navigator.clipboard;
},
contextItemLabel: "labels.paste",
@@ -56,7 +56,7 @@ export const actionCut = register({
actionCopy.perform(elements, appState, data, app);
return actionDeleteSelected.perform(elements, appState);
},
contextItemPredicate: (elements, appState, appProps, app) => {
predicate: (elements, appState, appProps, app) => {
return app.device.isMobile && !!navigator.clipboard;
},
contextItemLabel: "labels.cut",
@@ -101,7 +101,7 @@ export const actionCopyAsSvg = register({
};
}
},
contextItemPredicate: (elements) => {
predicate: (elements) => {
return probablySupportsClipboardWriteText && elements.length > 0;
},
contextItemLabel: "labels.copyAsSvg",
@@ -158,7 +158,7 @@ export const actionCopyAsPng = register({
};
}
},
contextItemPredicate: (elements) => {
predicate: (elements) => {
return probablySupportsClipboardBlob && elements.length > 0;
},
contextItemLabel: "labels.copyAsPng",
@@ -188,7 +188,7 @@ export const copyText = register({
commitToHistory: false,
};
},
contextItemPredicate: (elements, appState) => {
predicate: (elements, appState) => {
return (
probablySupportsClipboardWriteText &&
getSelectedElements(elements, appState, true).some(isTextElement)
+15 -24
View File
@@ -1,7 +1,6 @@
import { LoadIcon, questionCircle, saveAs } from "../components/icons";
import { questionCircle, saveAs } from "../components/icons";
import { ProjectName } from "../components/ProjectName";
import { ToolButton } from "../components/ToolButton";
import "../components/ToolIcon.scss";
import { Tooltip } from "../components/Tooltip";
import { DarkModeToggle } from "../components/DarkModeToggle";
import { loadFromJSON, saveAsJSON } from "../data";
@@ -15,12 +14,11 @@ import { getExportSize } from "../scene/export";
import { DEFAULT_EXPORT_PADDING, EXPORT_SCALES, THEME } from "../constants";
import { getSelectedElements, isSomeElementSelected } from "../scene";
import { getNonDeletedElements } from "../element";
import { ActiveFile } from "../components/ActiveFile";
import { isImageFileHandle } from "../data/blob";
import { nativeFileSystemSupported } from "../data/filesystem";
import { Theme } from "../element/types";
import DropdownMenuItem from "../components/dropdownMenu/DropdownMenuItem";
import { getShortcutFromShortcutName } from "./shortcuts";
import "../components/ToolIcon.scss";
export const actionChangeProjectName = register({
name: "changeProjectName",
@@ -133,6 +131,13 @@ export const actionChangeExportEmbedScene = register({
export const actionSaveToActiveFile = register({
name: "saveToActiveFile",
trackEvent: { category: "export" },
predicate: (elements, appState, props, app) => {
return (
!!app.props.UIOptions.canvasActions.saveToActiveFile &&
!!appState.fileHandle &&
!appState.viewModeEnabled
);
},
perform: async (elements, appState, value, app) => {
const fileHandleExists = !!appState.fileHandle;
@@ -169,12 +174,6 @@ export const actionSaveToActiveFile = register({
},
keyTest: (event) =>
event.key === KEYS.S && event[KEYS.CTRL_OR_CMD] && !event.shiftKey,
PanelComponent: ({ updateData, appState }) => (
<ActiveFile
onSave={() => updateData(null)}
fileName={appState.fileHandle?.name}
/>
),
});
export const actionSaveFileToDisk = register({
@@ -220,6 +219,11 @@ export const actionSaveFileToDisk = register({
export const actionLoadScene = register({
name: "loadScene",
trackEvent: { category: "export" },
predicate: (elements, appState, props, app) => {
return (
!!app.props.UIOptions.canvasActions.loadScene && !appState.viewModeEnabled
);
},
perform: async (elements, appState, _, app) => {
try {
const {
@@ -247,19 +251,6 @@ export const actionLoadScene = register({
}
},
keyTest: (event) => event[KEYS.CTRL_OR_CMD] && event.key === KEYS.O,
PanelComponent: ({ updateData }) => {
return (
<DropdownMenuItem
icon={LoadIcon}
onSelect={updateData}
dataTestId="load-button"
shortcut={getShortcutFromShortcutName("loadScene")}
ariaLabel={t("buttons.load")}
>
{t("buttons.load")}
</DropdownMenuItem>
);
},
});
export const actionExportWithDarkMode = register({
+2 -2
View File
@@ -50,7 +50,7 @@ export const actionFlipHorizontal = register({
},
keyTest: (event) => event.shiftKey && event.code === "KeyH",
contextItemLabel: "labels.flipHorizontal",
contextItemPredicate: (elements, appState) =>
predicate: (elements, appState) =>
enableActionFlipHorizontal(elements, appState),
});
@@ -67,7 +67,7 @@ export const actionFlipVertical = register({
keyTest: (event) =>
event.shiftKey && event.code === "KeyV" && !event[KEYS.CTRL_OR_CMD],
contextItemLabel: "labels.flipVertical",
contextItemPredicate: (elements, appState) =>
predicate: (elements, appState) =>
enableActionFlipVertical(elements, appState),
});
+2 -4
View File
@@ -129,8 +129,7 @@ export const actionGroup = register({
};
},
contextItemLabel: "labels.group",
contextItemPredicate: (elements, appState) =>
enableActionGroup(elements, appState),
predicate: (elements, appState) => enableActionGroup(elements, appState),
keyTest: (event) =>
!event.shiftKey && event[KEYS.CTRL_OR_CMD] && event.key === KEYS.G,
PanelComponent: ({ elements, appState, updateData }) => (
@@ -193,8 +192,7 @@ export const actionUngroup = register({
event[KEYS.CTRL_OR_CMD] &&
event.key === KEYS.G.toUpperCase(),
contextItemLabel: "labels.ungroup",
contextItemPredicate: (elements, appState) =>
getSelectedGroupIds(appState).length > 0,
predicate: (elements, appState) => getSelectedGroupIds(appState).length > 0,
PanelComponent: ({ elements, appState, updateData }) => (
<ToolButton
+1 -1
View File
@@ -10,7 +10,7 @@ export const actionToggleLinearEditor = register({
trackEvent: {
category: "element",
},
contextItemPredicate: (elements, appState) => {
predicate: (elements, appState) => {
const selectedElements = getSelectedElements(elements, appState);
if (selectedElements.length === 1 && isLinearElement(selectedElements[0])) {
return true;
+1 -17
View File
@@ -1,12 +1,10 @@
import { HamburgerMenuIcon, HelpIcon, palette } from "../components/icons";
import { HamburgerMenuIcon, palette } from "../components/icons";
import { ToolButton } from "../components/ToolButton";
import { t } from "../i18n";
import { showSelectedShapeActions, getNonDeletedElements } from "../element";
import { register } from "./register";
import { allowFullScreen, exitFullScreen, isFullScreen } from "../utils";
import { KEYS } from "../keys";
import { HelpButton } from "../components/HelpButton";
import DropdownMenuItem from "../components/dropdownMenu/DropdownMenuItem";
export const actionToggleCanvasMenu = register({
name: "toggleCanvasMenu",
@@ -88,19 +86,5 @@ export const actionShortcuts = register({
commitToHistory: false,
};
},
PanelComponent: ({ updateData, isInHamburgerMenu }) =>
isInHamburgerMenu ? (
<DropdownMenuItem
dataTestId="help-menu-item"
icon={HelpIcon}
onSelect={updateData}
shortcut="?"
ariaLabel={t("helpDialog.title")}
>
{t("helpDialog.title")}
</DropdownMenuItem>
) : (
<HelpButton title={t("helpDialog.title")} onClick={updateData} />
),
keyTest: (event) => event.key === KEYS.QUESTION_MARK,
});
+1 -1
View File
@@ -20,7 +20,7 @@ export const actionToggleGridMode = register({
};
},
checked: (appState: AppState) => appState.gridSize !== null,
contextItemPredicate: (element, appState, props) => {
predicate: (element, appState, props) => {
return typeof props.gridModeEnabled === "undefined";
},
contextItemLabel: "labels.showGrid",
+1 -1
View File
@@ -18,7 +18,7 @@ export const actionToggleViewMode = register({
};
},
checked: (appState) => appState.viewModeEnabled,
contextItemPredicate: (elements, appState, appProps) => {
predicate: (elements, appState, appProps) => {
return typeof appProps.viewModeEnabled === "undefined";
},
contextItemLabel: "labels.viewMode",
+1 -1
View File
@@ -18,7 +18,7 @@ export const actionToggleZenMode = register({
};
},
checked: (appState) => appState.zenModeEnabled,
contextItemPredicate: (elements, appState, appProps) => {
predicate: (elements, appState, appProps) => {
return typeof appProps.zenModeEnabled === "undefined";
},
contextItemLabel: "buttons.zenMode",
+11 -6
View File
@@ -131,11 +131,7 @@ export class ActionManager {
/**
* @param data additional data sent to the PanelComponent
*/
renderAction = (
name: ActionName,
data?: PanelComponentProps["data"],
isInHamburgerMenu = false,
) => {
renderAction = (name: ActionName, data?: PanelComponentProps["data"]) => {
const canvasActions = this.app.props.UIOptions.canvasActions;
if (
@@ -170,11 +166,20 @@ export class ActionManager {
updateData={updateData}
appProps={this.app.props}
data={data}
isInHamburgerMenu={isInHamburgerMenu}
/>
);
}
return null;
};
isActionEnabled = (action: Action) => {
const elements = this.getElementsIncludingDeleted();
const appState = this.getAppState();
return (
!action.predicate ||
action.predicate(elements, appState, this.app.props, this.app)
);
};
}
+2 -4
View File
@@ -124,9 +124,7 @@ export type PanelComponentProps = {
export interface Action {
name: ActionName;
PanelComponent?: React.FC<
PanelComponentProps & { isInHamburgerMenu: boolean }
>;
PanelComponent?: React.FC<PanelComponentProps>;
perform: ActionFn;
keyPriority?: number;
keyTest?: (
@@ -140,7 +138,7 @@ export interface Action {
elements: readonly ExcalidrawElement[],
appState: Readonly<AppState>,
) => string);
contextItemPredicate?: (
predicate?: (
elements: readonly ExcalidrawElement[],
appState: AppState,
appProps: ExcalidrawProps,
-23
View File
@@ -1,23 +0,0 @@
// TODO barnabasmolnar/editor-redesign
// this icon is not great
import { getShortcutFromShortcutName } from "../actions/shortcuts";
import { save } from "../components/icons";
import { t } from "../i18n";
import "./ActiveFile.scss";
import DropdownMenuItem from "./dropdownMenu/DropdownMenuItem";
type ActiveFileProps = {
fileName?: string;
onSave: () => void;
};
export const ActiveFile = ({ fileName, onSave }: ActiveFileProps) => (
<DropdownMenuItem
shortcut={getShortcutFromShortcutName("saveScene")}
dataTestId="save-button"
onSelect={onSave}
icon={save}
ariaLabel={`${t("buttons.save")}`}
>{`${t("buttons.save")}`}</DropdownMenuItem>
);
+13 -10
View File
@@ -283,15 +283,12 @@ const deviceContextInitialValue = {
};
const DeviceContext = React.createContext<Device>(deviceContextInitialValue);
DeviceContext.displayName = "DeviceContext";
export const useDevice = () => useContext<Device>(DeviceContext);
export const ExcalidrawContainerContext = React.createContext<{
container: HTMLDivElement | null;
id: string | null;
}>({ container: null, id: null });
ExcalidrawContainerContext.displayName = "ExcalidrawContainerContext";
export const useExcalidrawContainer = () =>
useContext(ExcalidrawContainerContext);
const ExcalidrawElementsContext = React.createContext<
readonly NonDeletedExcalidrawElement[]
@@ -309,14 +306,19 @@ ExcalidrawAppStateContext.displayName = "ExcalidrawAppStateContext";
const ExcalidrawSetAppStateContext = React.createContext<
React.Component<any, AppState>["setState"]
>(() => {});
>(() => {
console.warn("unitialized ExcalidrawSetAppStateContext context!");
});
ExcalidrawSetAppStateContext.displayName = "ExcalidrawSetAppStateContext";
const ExcalidrawActionManagerContext = React.createContext<
ActionManager | { renderAction: ActionManager["renderAction"] }
>({ renderAction: () => null });
const ExcalidrawActionManagerContext = React.createContext<ActionManager>(
null!,
);
ExcalidrawActionManagerContext.displayName = "ExcalidrawActionManagerContext";
export const useDevice = () => useContext<Device>(DeviceContext);
export const useExcalidrawContainer = () =>
useContext(ExcalidrawContainerContext);
export const useExcalidrawElements = () =>
useContext(ExcalidrawElementsContext);
export const useExcalidrawAppState = () =>
@@ -537,8 +539,7 @@ class App extends React.Component<AppProps, AppState> {
this.scene.getNonDeletedElements(),
this.state,
);
const { onCollabButtonClick, renderTopRightUI, renderCustomStats } =
this.props;
const { renderTopRightUI, renderCustomStats } = this.props;
return (
<div
@@ -566,13 +567,13 @@ class App extends React.Component<AppProps, AppState> {
value={this.actionManager}
>
<LayerUI
test={<div />}
canvas={this.canvas}
appState={this.state}
files={this.files}
setAppState={this.setAppState}
actionManager={this.actionManager}
elements={this.scene.getNonDeletedElements()}
onCollabButtonClick={onCollabButtonClick}
onLockToggle={this.toggleLock}
onPenModeToggle={this.togglePenMode}
onInsertElements={(elements) =>
@@ -598,6 +599,8 @@ class App extends React.Component<AppProps, AppState> {
id={this.id}
onImageAction={this.onImageAction}
renderWelcomeScreen={
!this.state.isLoading &&
this.props.UIOptions.welcomeScreen &&
this.state.showWelcomeScreen &&
this.state.activeTool.type === "selection" &&
!this.scene.getElementsIncludingDeleted().length
+7
View File
@@ -0,0 +1,7 @@
@import "../css/theme";
.excalidraw {
.excalidraw-button {
@include outlineButtonStyles;
}
}
+35
View File
@@ -0,0 +1,35 @@
import "./Button.scss";
interface ButtonProps extends React.HTMLAttributes<HTMLButtonElement> {
type?: "button" | "submit" | "reset";
onSelect: () => any;
children: React.ReactNode;
className?: string;
}
/**
* A generic button component that follows Excalidraw's design system.
* Style can be customised using `className` or `style` prop.
* Accepts all props that a regular `button` element accepts.
*/
export const Button = ({
type = "button",
onSelect,
children,
className = "",
...rest
}: ButtonProps) => {
return (
<button
onClick={(event) => {
onSelect();
rest.onClick?.(event);
}}
type={type}
className={`excalidraw-button ${className}`}
{...rest}
>
{children}
</button>
);
};
-41
View File
@@ -1,41 +0,0 @@
import { useState } from "react";
import { t } from "../i18n";
import { TrashIcon } from "./icons";
import ConfirmDialog from "./ConfirmDialog";
import DropdownMenuItem from "./dropdownMenu/DropdownMenuItem";
const ClearCanvas = ({ onConfirm }: { onConfirm: () => void }) => {
const [showDialog, setShowDialog] = useState(false);
const toggleDialog = () => {
setShowDialog(!showDialog);
};
return (
<>
<DropdownMenuItem
icon={TrashIcon}
onSelect={toggleDialog}
dataTestId="clear-canvas-button"
ariaLabel={t("buttons.clearReset")}
>
{t("buttons.clearReset")}
</DropdownMenuItem>
{showDialog && (
<ConfirmDialog
onConfirm={() => {
onConfirm();
toggleDialog();
}}
onCancel={toggleDialog}
title={t("clearCanvasDialog.title")}
>
<p className="clear-canvas__content"> {t("alerts.clearReset")}</p>
</ConfirmDialog>
)}
</>
);
};
export default ClearCanvas;
-50
View File
@@ -1,50 +0,0 @@
import { t } from "../i18n";
import { UsersIcon } from "./icons";
import "./CollabButton.scss";
import DropdownMenuItem from "./dropdownMenu/DropdownMenuItem";
import clsx from "clsx";
const CollabButton = ({
isCollaborating,
collaboratorCount,
onClick,
isInHamburgerMenu = true,
}: {
isCollaborating: boolean;
collaboratorCount: number;
onClick: () => void;
isInHamburgerMenu?: boolean;
}) => {
return (
<>
{isInHamburgerMenu ? (
<DropdownMenuItem
dataTestId="collab-button"
icon={UsersIcon}
onSelect={onClick}
ariaLabel={t("labels.liveCollaboration")}
>
{t("labels.liveCollaboration")}
</DropdownMenuItem>
) : (
<button
className={clsx("collab-button", { active: isCollaborating })}
type="button"
onClick={onClick}
style={{ position: "relative" }}
title={t("labels.liveCollaboration")}
>
{UsersIcon}
{collaboratorCount > 0 && (
<div className="CollabButton-collaborators">
{collaboratorCount}
</div>
)}
</button>
)}
</>
);
};
export default CollabButton;
+2 -2
View File
@@ -39,8 +39,8 @@ export const ContextMenu = React.memo(
if (
item &&
(item === CONTEXT_MENU_SEPARATOR ||
!item.contextItemPredicate ||
item.contextItemPredicate(
!item.predicate ||
item.predicate(
elements,
appState,
actionManager.app.props,
+6 -4
View File
@@ -1,3 +1,5 @@
@import "../css/variables.module";
.excalidraw {
.FixedSideContainer {
position: absolute;
@@ -9,10 +11,10 @@
}
.FixedSideContainer_side_top {
left: 1rem;
top: 1rem;
right: 1rem;
bottom: 1rem;
left: var(--editor-container-padding);
top: var(--editor-container-padding);
right: var(--editor-container-padding);
bottom: var(--editor-container-padding);
z-index: 2;
}
+37 -52
View File
@@ -14,10 +14,10 @@ import {
ExcalidrawProps,
BinaryFiles,
UIChildrenComponents,
UIWelcomeScreenComponents,
} from "../types";
import { muteFSAbortError, ReactChildrenToObject } from "../utils";
import { muteFSAbortError, getReactChildren } from "../utils";
import { SelectedShapeActions, ShapesSwitcher } from "./Actions";
import CollabButton from "./CollabButton";
import { ErrorDialog } from "./ErrorDialog";
import { ExportCB, ImageExportDialog } from "./ImageExportDialog";
import { FixedSideContainer } from "./FixedSideContainer";
@@ -45,22 +45,20 @@ import { useDevice } from "../components/App";
import { Stats } from "./Stats";
import { actionToggleStats } from "../actions/actionToggleStats";
import Footer from "./footer/Footer";
import { WelcomeScreenMenuArrow, WelcomeScreenTopToolbarArrow } from "./icons";
import WelcomeScreen from "./WelcomeScreen";
import WelcomeScreen from "./welcome-screen/WelcomeScreen";
import { hostSidebarCountersAtom } from "./Sidebar/Sidebar";
import { jotaiScope } from "../jotai";
import { useAtom } from "jotai";
import WelcomeScreenDecor from "./WelcomeScreenDecor";
import MainMenu from "./mainMenu/MainMenu";
import MainMenu from "./main-menu/MainMenu";
interface LayerUIProps {
test: JSX.Element;
actionManager: ActionManager;
appState: AppState;
files: BinaryFiles;
canvas: HTMLCanvasElement | null;
setAppState: React.Component<any, AppState>["setState"];
elements: readonly NonDeletedExcalidrawElement[];
onCollabButtonClick?: () => void;
onLockToggle: () => void;
onPenModeToggle: () => void;
onInsertElements: (elements: readonly NonDeletedExcalidrawElement[]) => void;
@@ -87,7 +85,6 @@ const LayerUI = ({
setAppState,
elements,
canvas,
onCollabButtonClick,
onLockToggle,
onPenModeToggle,
onInsertElements,
@@ -107,8 +104,27 @@ const LayerUI = ({
}: LayerUIProps) => {
const device = useDevice();
const childrenComponents =
ReactChildrenToObject<UIChildrenComponents>(children);
const [childrenComponents, restChildren] =
getReactChildren<UIChildrenComponents>(children, {
Menu: true,
FooterCenter: true,
WelcomeScreen: true,
});
const [WelcomeScreenComponents] = getReactChildren<UIWelcomeScreenComponents>(
renderWelcomeScreen
? (
childrenComponents?.WelcomeScreen ?? (
<WelcomeScreen>
<WelcomeScreen.Center />
<WelcomeScreen.Hints.MenuHint />
<WelcomeScreen.Hints.ToolbarHint />
<WelcomeScreen.Hints.HelpHint />
</WelcomeScreen>
)
)?.props?.children
: null,
);
const renderJSONExportDialog = () => {
if (!UIOptions.canvasActions.export) {
@@ -183,16 +199,12 @@ const LayerUI = ({
<MainMenu>
<MainMenu.DefaultItems.LoadScene />
<MainMenu.DefaultItems.SaveToActiveFile />
{/* FIXME we should to test for this inside the item itself */}
{UIOptions.canvasActions.export && <MainMenu.DefaultItems.Export />}
{/* FIXME we should to test for this inside the item itself */}
{UIOptions.canvasActions.saveAsImage && (
<MainMenu.DefaultItems.SaveAsImage />
)}
{onCollabButtonClick && (
<MainMenu.DefaultItems.LiveCollaboration
onSelect={onCollabButtonClick}
isCollaborating={isCollaborating}
/>
)}
<MainMenu.DefaultItems.Help />
<MainMenu.DefaultItems.ClearCanvas />
<MainMenu.Separator />
@@ -208,15 +220,10 @@ const LayerUI = ({
};
const renderCanvasActions = () => (
<div style={{ position: "relative" }}>
<WelcomeScreenDecor
shouldRender={renderWelcomeScreen && !appState.isLoading}
>
<div className="virgil WelcomeScreen-decor WelcomeScreen-decor--menu-pointer">
{WelcomeScreenMenuArrow}
<div>{t("welcomeScreen.menuHints")}</div>
</div>
</WelcomeScreenDecor>
{renderMenu()}
{WelcomeScreenComponents.MenuHint}
{/* wrapping to Fragment stops React from occasionally complaining
about identical Keys */}
<>{renderMenu()}</>
</div>
);
@@ -253,9 +260,7 @@ const LayerUI = ({
return (
<FixedSideContainer side="top">
{renderWelcomeScreen && !appState.isLoading && (
<WelcomeScreen appState={appState} actionManager={actionManager} />
)}
{WelcomeScreenComponents.Center}
<div className="App-menu App-menu_top">
<Stack.Col
gap={6}
@@ -270,17 +275,7 @@ const LayerUI = ({
<Section heading="shapes" className="shapes-section">
{(heading: React.ReactNode) => (
<div style={{ position: "relative" }}>
<WelcomeScreenDecor
shouldRender={renderWelcomeScreen && !appState.isLoading}
>
<div className="virgil WelcomeScreen-decor WelcomeScreen-decor--top-toolbar-pointer">
<div className="WelcomeScreen-decor--top-toolbar-pointer__label">
{t("welcomeScreen.toolbarHints")}
</div>
{WelcomeScreenTopToolbarArrow}
</div>
</WelcomeScreenDecor>
{WelcomeScreenComponents.ToolbarHint}
<Stack.Col gap={4} align="start">
<Stack.Row
gap={1}
@@ -348,14 +343,6 @@ const LayerUI = ({
)}
>
<UserList collaborators={appState.collaborators} />
{onCollabButtonClick && (
<CollabButton
isInHamburgerMenu={false}
isCollaborating={isCollaborating}
collaboratorCount={appState.collaborators.size}
onClick={onCollabButtonClick}
/>
)}
{renderTopRightUI?.(device.isMobile, appState)}
{!appState.viewModeEnabled && (
<LibraryButton appState={appState} setAppState={setAppState} />
@@ -385,6 +372,7 @@ const LayerUI = ({
return (
<>
{restChildren}
{appState.isLoading && <LoadingMessage delay={250} />}
{appState.errorMessage && (
<ErrorDialog
@@ -415,24 +403,22 @@ const LayerUI = ({
)}
{device.isMobile && (
<MobileMenu
renderWelcomeScreen={renderWelcomeScreen}
appState={appState}
elements={elements}
actionManager={actionManager}
renderJSONExportDialog={renderJSONExportDialog}
renderImageExportDialog={renderImageExportDialog}
setAppState={setAppState}
onCollabButtonClick={onCollabButtonClick}
onLockToggle={() => onLockToggle()}
onPenModeToggle={onPenModeToggle}
canvas={canvas}
isCollaborating={isCollaborating}
onImageAction={onImageAction}
renderTopRightUI={renderTopRightUI}
renderCustomStats={renderCustomStats}
renderSidebars={renderSidebars}
device={device}
renderMenu={renderMenu}
welcomeScreenCenter={WelcomeScreenComponents.Center}
/>
)}
@@ -457,13 +443,12 @@ const LayerUI = ({
>
{renderFixedSideContainer()}
<Footer
renderWelcomeScreen={renderWelcomeScreen}
appState={appState}
actionManager={actionManager}
showExitZenModeBtn={showExitZenModeBtn}
footerCenter={childrenComponents.FooterCenter}
welcomeScreenHelp={WelcomeScreenComponents.HelpHint}
/>
{appState.showStats && (
<Stats
appState={appState}
+3 -3
View File
@@ -193,7 +193,7 @@ export const LibraryMenuHeader: React.FC<{
<DropdownMenu.Item
onSelect={onLibraryImport}
icon={LoadIcon}
dataTestId="lib-dropdown--load"
data-testid="lib-dropdown--load"
>
{t("buttons.load")}
</DropdownMenu.Item>
@@ -202,7 +202,7 @@ export const LibraryMenuHeader: React.FC<{
<DropdownMenu.Item
onSelect={onLibraryExport}
icon={ExportIcon}
dataTestId="lib-dropdown--export"
data-testid="lib-dropdown--export"
>
{t("buttons.export")}
</DropdownMenu.Item>
@@ -219,7 +219,7 @@ export const LibraryMenuHeader: React.FC<{
<DropdownMenu.Item
icon={publishIcon}
onSelect={() => setShowPublishLibraryDialog(true)}
dataTestId="lib-dropdown--remove"
data-testid="lib-dropdown--remove"
>
{t("buttons.publishLibrary")}
</DropdownMenu.Item>
+9 -25
View File
@@ -1,5 +1,10 @@
import React from "react";
import { AppState, Device, ExcalidrawProps } from "../types";
import {
AppState,
Device,
ExcalidrawProps,
UIWelcomeScreenComponents,
} from "../types";
import { ActionManager } from "../actions/manager";
import { t } from "../i18n";
import Stack from "./Stack";
@@ -17,7 +22,6 @@ import { LibraryButton } from "./LibraryButton";
import { PenModeButton } from "./PenModeButton";
import { Stats } from "./Stats";
import { actionToggleStats } from "../actions";
import WelcomeScreen from "./WelcomeScreen";
type MobileMenuProps = {
appState: AppState;
@@ -26,11 +30,9 @@ type MobileMenuProps = {
renderImageExportDialog: () => React.ReactNode;
setAppState: React.Component<any, AppState>["setState"];
elements: readonly NonDeletedExcalidrawElement[];
onCollabButtonClick?: () => void;
onLockToggle: () => void;
onPenModeToggle: () => void;
canvas: HTMLCanvasElement | null;
isCollaborating: boolean;
onImageAction: (data: { insertOnCanvasDirectly: boolean }) => void;
renderTopRightUI?: (
@@ -40,8 +42,8 @@ type MobileMenuProps = {
renderCustomStats?: ExcalidrawProps["renderCustomStats"];
renderSidebars: () => JSX.Element | null;
device: Device;
renderWelcomeScreen?: boolean;
renderMenu: () => React.ReactNode;
welcomeScreenCenter: UIWelcomeScreenComponents["Center"];
};
export const MobileMenu = ({
@@ -52,21 +54,18 @@ export const MobileMenu = ({
onLockToggle,
onPenModeToggle,
canvas,
isCollaborating,
onImageAction,
renderTopRightUI,
renderCustomStats,
renderSidebars,
device,
renderWelcomeScreen,
renderMenu,
welcomeScreenCenter,
}: MobileMenuProps) => {
const renderToolbar = () => {
return (
<FixedSideContainer side="top" className="App-top-bar">
{renderWelcomeScreen && !appState.isLoading && (
<WelcomeScreen appState={appState} actionManager={actionManager} />
)}
{welcomeScreenCenter}
<Section heading="shapes">
{(heading: React.ReactNode) => (
<Stack.Col gap={4} align="center">
@@ -74,20 +73,6 @@ export const MobileMenu = ({
<Island padding={1} className="App-toolbar App-toolbar--mobile">
{heading}
<Stack.Row gap={1}>
{/* <PenModeButton
checked={appState.penMode}
onChange={onPenModeToggle}
title={t("toolBar.penMode")}
isMobile
penDetected={appState.penDetected}
/>
<LockButton
checked={appState.activeTool.locked}
onChange={onLockToggle}
title={t("toolBar.lock")}
isMobile
/>
<div className="App-toolbar__divider"></div> */}
<ShapesSwitcher
appState={appState}
canvas={canvas}
@@ -109,7 +94,6 @@ export const MobileMenu = ({
title={t("toolBar.penMode")}
isMobile
penDetected={appState.penDetected}
// penDetected={true}
/>
<LockButton
checked={appState.activeTool.locked}
-121
View File
@@ -1,121 +0,0 @@
import { actionLoadScene, actionShortcuts } from "../actions";
import { ActionManager } from "../actions/manager";
import { getShortcutFromShortcutName } from "../actions/shortcuts";
import { isExcalidrawPlusSignedUser } from "../constants";
import { t } from "../i18n";
import { AppState } from "../types";
import { ExcalLogo, HelpIcon, LoadIcon, PlusPromoIcon } from "./icons";
import "./WelcomeScreen.scss";
const WelcomeScreenItem = ({
label,
shortcut,
onClick,
icon,
link,
}: {
label: string;
shortcut: string | null;
onClick?: () => void;
icon: JSX.Element;
link?: string;
}) => {
if (link) {
return (
<a
className="WelcomeScreen-item"
href={link}
target="_blank"
rel="noreferrer"
>
<div className="WelcomeScreen-item__label">
{icon}
{label}
</div>
</a>
);
}
return (
<button className="WelcomeScreen-item" type="button" onClick={onClick}>
<div className="WelcomeScreen-item__label">
{icon}
{label}
</div>
{shortcut && (
<div className="WelcomeScreen-item__shortcut">{shortcut}</div>
)}
</button>
);
};
const WelcomeScreen = ({
appState,
actionManager,
}: {
appState: AppState;
actionManager: ActionManager;
}) => {
let subheadingJSX;
if (isExcalidrawPlusSignedUser) {
subheadingJSX = t("welcomeScreen.switchToPlusApp")
.split(/(Excalidraw\+)/)
.map((bit, idx) => {
if (bit === "Excalidraw+") {
return (
<a
style={{ pointerEvents: "all" }}
href={`${process.env.REACT_APP_PLUS_APP}?utm_source=excalidraw&utm_medium=app&utm_content=welcomeScreenSignedInUser`}
key={idx}
>
Excalidraw+
</a>
);
}
return bit;
});
} else {
subheadingJSX = t("welcomeScreen.data");
}
return (
<div className="WelcomeScreen-container">
<div className="WelcomeScreen-logo virgil WelcomeScreen-decor">
{ExcalLogo} Excalidraw
</div>
<div className="virgil WelcomeScreen-decor WelcomeScreen-decor--subheading">
{subheadingJSX}
</div>
<div className="WelcomeScreen-items">
{!appState.viewModeEnabled && (
<WelcomeScreenItem
// TODO barnabasmolnar/editor-redesign
// do we want the internationalized labels here that are currently
// in use elsewhere or new ones?
label={t("buttons.load")}
onClick={() => actionManager.executeAction(actionLoadScene)}
shortcut={getShortcutFromShortcutName("loadScene")}
icon={LoadIcon}
/>
)}
<WelcomeScreenItem
onClick={() => actionManager.executeAction(actionShortcuts)}
label={t("helpDialog.title")}
shortcut="?"
icon={HelpIcon}
/>
{!isExcalidrawPlusSignedUser && (
<WelcomeScreenItem
link="https://plus.excalidraw.com/plus?utm_source=excalidraw&utm_medium=app&utm_content=welcomeScreenGuest"
label="Try Excalidraw Plus!"
shortcut={null}
icon={PlusPromoIcon}
/>
)}
</div>
</div>
);
};
export default WelcomeScreen;
-11
View File
@@ -1,11 +0,0 @@
import { ReactNode } from "react";
const WelcomeScreenDecor = ({
children,
shouldRender,
}: {
children: ReactNode;
shouldRender: boolean;
}) => (shouldRender ? <>{children}</> : null);
export default WelcomeScreenDecor;
@@ -73,7 +73,7 @@
}
&:hover {
background-color: var(--button-hover);
background-color: var(--button-hover-bg);
text-decoration: none;
}
@@ -9,30 +9,23 @@ const DropdownMenuItem = ({
icon,
onSelect,
children,
dataTestId,
shortcut,
className,
style,
ariaLabel,
...rest
}: {
icon?: JSX.Element;
onSelect: () => void;
children: React.ReactNode;
dataTestId?: string;
shortcut?: string;
className?: string;
style?: React.CSSProperties;
ariaLabel?: string;
}) => {
} & React.ButtonHTMLAttributes<HTMLButtonElement>) => {
return (
<button
aria-label={ariaLabel}
{...rest}
onClick={onSelect}
data-testid={dataTestId}
title={ariaLabel}
type="button"
className={getDrodownMenuItemClassName(className)}
style={style}
title={rest.title ?? rest["aria-label"]}
>
<MenuItemContent icon={icon} shortcut={shortcut}>
{children}
@@ -1,19 +1,17 @@
import React from "react";
const DropdownMenuItemCustom = ({
children,
className = "",
style,
dataTestId,
...rest
}: {
children: React.ReactNode;
className?: string;
style?: React.CSSProperties;
dataTestId?: string;
}) => {
} & React.HTMLAttributes<HTMLDivElement>) => {
return (
<div
{...rest}
className={`dropdown-menu-item-base dropdown-menu-item-custom ${className}`.trim()}
style={style}
data-testid={dataTestId}
>
{children}
</div>
@@ -3,33 +3,26 @@ import React from "react";
import { getDrodownMenuItemClassName } from "./DropdownMenuItem";
const DropdownMenuItemLink = ({
icon,
dataTestId,
shortcut,
href,
children,
className = "",
style,
ariaLabel,
...rest
}: {
icon?: JSX.Element;
children: React.ReactNode;
dataTestId?: string;
shortcut?: string;
className?: string;
href: string;
style?: React.CSSProperties;
ariaLabel?: string;
}) => {
} & React.AnchorHTMLAttributes<HTMLAnchorElement>) => {
return (
<a
{...rest}
href={href}
target="_blank"
rel="noreferrer"
className={getDrodownMenuItemClassName(className)}
style={style}
data-testid={dataTestId}
title={ariaLabel}
aria-label={ariaLabel}
title={rest.title ?? rest["aria-label"]}
>
<MenuItemContent icon={icon} shortcut={shortcut}>
{children}
+13 -16
View File
@@ -1,7 +1,11 @@
import clsx from "clsx";
import { actionShortcuts } from "../../actions";
import { ActionManager } from "../../actions/manager";
import { t } from "../../i18n";
import { AppState, UIChildrenComponents } from "../../types";
import {
AppState,
UIChildrenComponents,
UIWelcomeScreenComponents,
} from "../../types";
import {
ExitZenModeAction,
FinalizeAction,
@@ -9,23 +13,22 @@ import {
ZoomActions,
} from "../Actions";
import { useDevice } from "../App";
import { WelcomeScreenHelpArrow } from "../icons";
import { HelpButton } from "../HelpButton";
import { Section } from "../Section";
import Stack from "../Stack";
import WelcomeScreenDecor from "../WelcomeScreenDecor";
const Footer = ({
appState,
actionManager,
showExitZenModeBtn,
renderWelcomeScreen,
footerCenter,
welcomeScreenHelp,
}: {
appState: AppState;
actionManager: ActionManager;
showExitZenModeBtn: boolean;
renderWelcomeScreen: boolean;
footerCenter: UIChildrenComponents["FooterCenter"];
welcomeScreenHelp: UIWelcomeScreenComponents["HelpHint"];
}) => {
const device = useDevice();
const showFinalize =
@@ -77,16 +80,10 @@ const Footer = ({
})}
>
<div style={{ position: "relative" }}>
<WelcomeScreenDecor
shouldRender={renderWelcomeScreen && !appState.isLoading}
>
<div className="virgil WelcomeScreen-decor WelcomeScreen-decor--help-pointer">
<div>{t("welcomeScreen.helpHints")}</div>
{WelcomeScreenHelpArrow}
</div>
</WelcomeScreenDecor>
{actionManager.renderAction("toggleShortcuts")}
{welcomeScreenHelp}
<HelpButton
onClick={() => actionManager.executeAction(actionShortcuts)}
/>
</div>
</div>
<ExitZenModeAction
+1 -1
View File
@@ -883,7 +883,7 @@ export const CenterHorizontallyIcon = createIcon(
modifiedTablerIconProps,
);
export const UsersIcon = createIcon(
export const usersIcon = createIcon(
<g strokeWidth="1.5">
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
<circle cx="9" cy="7" r="4"></circle>
@@ -1,30 +1,23 @@
@import "../css/variables.module";
@import "../../css/variables.module";
.excalidraw {
.collab-button {
@include outlineButtonStyles;
width: var(--lg-button-size);
height: var(--lg-button-size);
--button-bg: var(--color-primary);
--button-color: white;
--button-border: var(--color-primary);
--button-width: var(--lg-button-size);
--button-height: var(--lg-button-size);
--button-hover-bg: var(--color-primary-darker);
--button-hover-border: var(--color-primary-darker);
--button-active-bg: var(--color-primary-darker);
svg {
width: var(--lg-icon-size);
height: var(--lg-icon-size);
}
background-color: var(--color-primary);
border-color: var(--color-primary);
color: white;
flex-shrink: 0;
&:hover {
background-color: var(--color-primary-darker);
border-color: var(--color-primary-darker);
}
&:active {
background-color: var(--color-primary-darker);
}
&.active {
// double .active to force specificity
&.active.active {
background-color: #0fb884;
border-color: #0fb884;
@@ -0,0 +1,40 @@
import { t } from "../../i18n";
import { usersIcon } from "../icons";
import { Button } from "../Button";
import clsx from "clsx";
import { useExcalidrawAppState } from "../App";
import "./LiveCollaborationTrigger.scss";
const LiveCollaborationTrigger = ({
isCollaborating,
onSelect,
...rest
}: {
isCollaborating: boolean;
onSelect: () => void;
} & React.ButtonHTMLAttributes<HTMLButtonElement>) => {
const appState = useExcalidrawAppState();
return (
<Button
{...rest}
className={clsx("collab-button", { active: isCollaborating })}
type="button"
onSelect={onSelect}
style={{ position: "relative" }}
title={t("labels.liveCollaboration")}
>
{usersIcon}
{appState.collaborators.size > 0 && (
<div className="CollabButton-collaborators">
{appState.collaborators.size}
</div>
)}
</Button>
);
};
export default LiveCollaborationTrigger;
LiveCollaborationTrigger.displayName = "LiveCollaborationTrigger";
@@ -1,4 +1,3 @@
import clsx from "clsx";
import { getShortcutFromShortcutName } from "../../actions/shortcuts";
import { t } from "../../i18n";
import {
@@ -6,43 +5,91 @@ import {
useExcalidrawSetAppState,
useExcalidrawActionManager,
} from "../App";
import { ExportIcon, ExportImageIcon, UsersIcon } from "../icons";
import {
ExportIcon,
ExportImageIcon,
HelpIcon,
LoadIcon,
MoonIcon,
save,
SunIcon,
TrashIcon,
usersIcon,
} from "../icons";
import { GithubIcon, DiscordIcon, TwitterIcon } from "../icons";
import DropdownMenuItem from "../dropdownMenu/DropdownMenuItem";
import DropdownMenuItemLink from "../dropdownMenu/DropdownMenuItemLink";
import {
actionClearCanvas,
actionLoadScene,
actionSaveToActiveFile,
actionShortcuts,
actionToggleTheme,
} from "../../actions";
import "./DefaultItems.scss";
import { useState } from "react";
import ConfirmDialog from "../ConfirmDialog";
import clsx from "clsx";
export const LoadScene = () => {
// FIXME Hack until we tie "t" to lang state
// eslint-disable-next-line
const appState = useExcalidrawAppState();
const actionManager = useExcalidrawActionManager();
if (appState.viewModeEnabled) {
if (!actionManager.isActionEnabled(actionLoadScene)) {
return null;
}
return actionManager.renderAction("loadScene");
return (
<DropdownMenuItem
icon={LoadIcon}
onSelect={() => actionManager.executeAction(actionLoadScene)}
data-testid="load-button"
shortcut={getShortcutFromShortcutName("loadScene")}
aria-label={t("buttons.load")}
>
{t("buttons.load")}
</DropdownMenuItem>
);
};
LoadScene.displayName = "LoadScene";
export const SaveToActiveFile = () => {
// FIXME Hack until we tie "t" to lang state
// eslint-disable-next-line
const appState = useExcalidrawAppState();
const actionManager = useExcalidrawActionManager();
if (!appState.fileHandle) {
if (!actionManager.isActionEnabled(actionSaveToActiveFile)) {
return null;
}
return actionManager.renderAction("saveToActiveFile");
return (
<DropdownMenuItem
shortcut={getShortcutFromShortcutName("saveScene")}
data-testid="save-button"
onSelect={() => actionManager.executeAction(actionSaveToActiveFile)}
icon={save}
aria-label={`${t("buttons.save")}`}
>{`${t("buttons.save")}`}</DropdownMenuItem>
);
};
SaveToActiveFile.displayName = "SaveToActiveFile";
export const SaveAsImage = () => {
const setAppState = useExcalidrawSetAppState();
// Hack until we tie "t" to lang state
// FIXME Hack until we tie "t" to lang state
// eslint-disable-next-line
const appState = useExcalidrawAppState();
return (
<DropdownMenuItem
icon={ExportImageIcon}
dataTestId="image-export-button"
data-testid="image-export-button"
onSelect={() => setAppState({ openDialog: "imageExport" })}
shortcut={getShortcutFromShortcutName("imageExport")}
ariaLabel={t("buttons.exportImage")}
aria-label={t("buttons.exportImage")}
>
{t("buttons.exportImage")}
</DropdownMenuItem>
@@ -51,32 +98,96 @@ export const SaveAsImage = () => {
SaveAsImage.displayName = "SaveAsImage";
export const Help = () => {
// Hack until we tie "t" to lang state
// FIXME Hack until we tie "t" to lang state
// eslint-disable-next-line
const appState = useExcalidrawAppState();
const actionManager = useExcalidrawActionManager();
return actionManager.renderAction("toggleShortcuts", undefined, true);
return (
<DropdownMenuItem
data-testid="help-menu-item"
icon={HelpIcon}
onSelect={() => actionManager.executeAction(actionShortcuts)}
shortcut="?"
aria-label={t("helpDialog.title")}
>
{t("helpDialog.title")}
</DropdownMenuItem>
);
};
Help.displayName = "Help";
export const ClearCanvas = () => {
// FIXME Hack until we tie "t" to lang state
// eslint-disable-next-line
const appState = useExcalidrawAppState();
const actionManager = useExcalidrawActionManager();
if (appState.viewModeEnabled) {
const [showDialog, setShowDialog] = useState(false);
const toggleDialog = () => setShowDialog(!showDialog);
if (!actionManager.isActionEnabled(actionClearCanvas)) {
return null;
}
return actionManager.renderAction("clearCanvas");
return (
<>
<DropdownMenuItem
icon={TrashIcon}
onSelect={toggleDialog}
data-testid="clear-canvas-button"
aria-label={t("buttons.clearReset")}
>
{t("buttons.clearReset")}
</DropdownMenuItem>
{/* FIXME this should live outside MainMenu so it stays open
if menu is closed */}
{showDialog && (
<ConfirmDialog
onConfirm={() => {
actionManager.executeAction(actionClearCanvas);
toggleDialog();
}}
onCancel={toggleDialog}
title={t("clearCanvasDialog.title")}
>
<p className="clear-canvas__content"> {t("alerts.clearReset")}</p>
</ConfirmDialog>
)}
</>
);
};
ClearCanvas.displayName = "ClearCanvas";
export const ToggleTheme = () => {
// Hack until we tie "t" to lang state
// eslint-disable-next-line
const appState = useExcalidrawAppState();
const actionManager = useExcalidrawActionManager();
return actionManager.renderAction("toggleTheme");
if (!actionManager.isActionEnabled(actionToggleTheme)) {
return null;
}
return (
<DropdownMenuItem
onSelect={() => {
return actionManager.executeAction(actionToggleTheme);
}}
icon={appState.theme === "dark" ? SunIcon : MoonIcon}
data-testid="toggle-dark-mode"
shortcut={getShortcutFromShortcutName("toggleTheme")}
aria-label={
appState.theme === "dark"
? t("buttons.lightMode")
: t("buttons.darkMode")
}
>
{appState.theme === "dark"
? t("buttons.lightMode")
: t("buttons.darkMode")}
</DropdownMenuItem>
);
};
ToggleTheme.displayName = "ToggleTheme";
@@ -101,7 +212,7 @@ export const ChangeCanvasBackground = () => {
ChangeCanvasBackground.displayName = "ChangeCanvasBackground";
export const Export = () => {
// Hack until we tie "t" to lang state
// FIXME Hack until we tie "t" to lang state
// eslint-disable-next-line
const appState = useExcalidrawAppState();
const setAppState = useExcalidrawSetAppState();
@@ -111,8 +222,8 @@ export const Export = () => {
onSelect={() => {
setAppState({ openDialog: "jsonExport" });
}}
dataTestId="json-export-button"
ariaLabel={t("buttons.export")}
data-testid="json-export-button"
aria-label={t("buttons.export")}
>
{t("buttons.export")}
</DropdownMenuItem>
@@ -125,21 +236,21 @@ export const Socials = () => (
<DropdownMenuItemLink
icon={GithubIcon}
href="https://github.com/excalidraw/excalidraw"
ariaLabel="GitHub"
aria-label="GitHub"
>
GitHub
</DropdownMenuItemLink>
<DropdownMenuItemLink
icon={DiscordIcon}
href="https://discord.gg/UexuTaE"
ariaLabel="Discord"
aria-label="Discord"
>
Discord
</DropdownMenuItemLink>
<DropdownMenuItemLink
icon={TwitterIcon}
href="https://twitter.com/excalidraw"
ariaLabel="Twitter"
aria-label="Twitter"
>
Twitter
</DropdownMenuItemLink>
@@ -147,20 +258,20 @@ export const Socials = () => (
);
Socials.displayName = "Socials";
export const LiveCollaboration = ({
export const LiveCollaborationTrigger = ({
onSelect,
isCollaborating,
}: {
onSelect: () => void;
isCollaborating: boolean;
}) => {
// Hack until we tie "t" to lang state
// FIXME Hack until we tie "t" to lang state
// eslint-disable-next-line
const appState = useExcalidrawAppState();
return (
<DropdownMenuItem
dataTestId="collab-button"
icon={UsersIcon}
data-testid="collab-button"
icon={usersIcon}
className={clsx({
"active-collab": isCollaborating,
})}
@@ -171,4 +282,4 @@ export const LiveCollaboration = ({
);
};
LiveCollaboration.displayName = "LiveCollaboration";
LiveCollaborationTrigger.displayName = "LiveCollaborationTrigger";
@@ -0,0 +1,195 @@
import { actionLoadScene, actionShortcuts } from "../../actions";
import { getShortcutFromShortcutName } from "../../actions/shortcuts";
import { t } from "../../i18n";
import {
useDevice,
useExcalidrawActionManager,
useExcalidrawAppState,
} from "../App";
import { ExcalLogo, HelpIcon, LoadIcon, usersIcon } from "../icons";
const WelcomeScreenMenuItemContent = ({
icon,
shortcut,
children,
}: {
icon?: JSX.Element;
shortcut?: string | null;
children: React.ReactNode;
}) => {
const device = useDevice();
return (
<>
<div className="welcome-screen-menu-item__icon">{icon}</div>
<div className="welcome-screen-menu-item__text">{children}</div>
{shortcut && !device.isMobile && (
<div className="welcome-screen-menu-item__shortcut">{shortcut}</div>
)}
</>
);
};
WelcomeScreenMenuItemContent.displayName = "WelcomeScreenMenuItemContent";
const WelcomeScreenMenuItem = ({
onSelect,
children,
icon,
shortcut,
className = "",
...props
}: {
onSelect: () => void;
children: React.ReactNode;
icon?: JSX.Element;
shortcut?: string | null;
} & React.ButtonHTMLAttributes<HTMLButtonElement>) => {
return (
<button
{...props}
type="button"
className={`welcome-screen-menu-item ${className}`}
onClick={onSelect}
>
<WelcomeScreenMenuItemContent icon={icon} shortcut={shortcut}>
{children}
</WelcomeScreenMenuItemContent>
</button>
);
};
WelcomeScreenMenuItem.displayName = "WelcomeScreenMenuItem";
const WelcomeScreenMenuItemLink = ({
children,
href,
icon,
shortcut,
className = "",
...props
}: {
children: React.ReactNode;
href: string;
icon?: JSX.Element;
shortcut?: string | null;
} & React.AnchorHTMLAttributes<HTMLAnchorElement>) => {
return (
<a
{...props}
className={`welcome-screen-menu-item ${className}`}
href={href}
target="_blank"
rel="noreferrer"
>
<WelcomeScreenMenuItemContent icon={icon} shortcut={shortcut}>
{children}
</WelcomeScreenMenuItemContent>
</a>
);
};
WelcomeScreenMenuItemLink.displayName = "WelcomeScreenMenuItemLink";
const Center = ({ children }: { children?: React.ReactNode }) => {
return (
<div className="welcome-screen-center">
{children || (
<>
<Logo />
<Heading>{t("welcomeScreen.defaults.center_heading")}</Heading>
<Menu>
<MenuItemLoadScene />
<MenuItemHelp />
</Menu>
</>
)}
</div>
);
};
Center.displayName = "Center";
const Logo = ({ children }: { children?: React.ReactNode }) => {
return (
<div className="welcome-screen-center__logo virgil welcome-screen-decor">
{children || <>{ExcalLogo} Excalidraw</>}
</div>
);
};
Logo.displayName = "Logo";
const Heading = ({ children }: { children: React.ReactNode }) => {
return (
<div className="welcome-screen-center__heading welcome-screen-decor virgil">
{children}
</div>
);
};
Heading.displayName = "Heading";
const Menu = ({ children }: { children?: React.ReactNode }) => {
return <div className="welcome-screen-menu">{children}</div>;
};
Menu.displayName = "Menu";
const MenuItemHelp = () => {
const actionManager = useExcalidrawActionManager();
return (
<WelcomeScreenMenuItem
onSelect={() => actionManager.executeAction(actionShortcuts)}
shortcut="?"
icon={HelpIcon}
>
{t("helpDialog.title")}
</WelcomeScreenMenuItem>
);
};
MenuItemHelp.displayName = "MenuItemHelp";
const MenuItemLoadScene = () => {
const appState = useExcalidrawAppState();
const actionManager = useExcalidrawActionManager();
if (appState.viewModeEnabled) {
return null;
}
return (
<WelcomeScreenMenuItem
onSelect={() => actionManager.executeAction(actionLoadScene)}
shortcut={getShortcutFromShortcutName("loadScene")}
icon={LoadIcon}
>
{t("buttons.load")}
</WelcomeScreenMenuItem>
);
};
MenuItemLoadScene.displayName = "MenuItemLoadScene";
const MenuItemLiveCollaborationTrigger = ({
onSelect,
}: {
onSelect: () => any;
}) => {
// FIXME when we tie t() to lang state
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const appState = useExcalidrawAppState();
return (
<WelcomeScreenMenuItem shortcut={null} onSelect={onSelect} icon={usersIcon}>
{t("labels.liveCollaboration")}
</WelcomeScreenMenuItem>
);
};
MenuItemLiveCollaborationTrigger.displayName =
"MenuItemLiveCollaborationTrigger";
// -----------------------------------------------------------------------------
Center.Logo = Logo;
Center.Heading = Heading;
Center.Menu = Menu;
Center.MenuItem = WelcomeScreenMenuItem;
Center.MenuItemLink = WelcomeScreenMenuItemLink;
Center.MenuItemHelp = MenuItemHelp;
Center.MenuItemLoadScene = MenuItemLoadScene;
Center.MenuItemLiveCollaborationTrigger = MenuItemLiveCollaborationTrigger;
export { Center };
@@ -0,0 +1,42 @@
import { t } from "../../i18n";
import {
WelcomeScreenHelpArrow,
WelcomeScreenMenuArrow,
WelcomeScreenTopToolbarArrow,
} from "../icons";
const MenuHint = ({ children }: { children?: React.ReactNode }) => {
return (
<div className="virgil welcome-screen-decor welcome-screen-decor-hint welcome-screen-decor-hint--menu">
{WelcomeScreenMenuArrow}
<div className="welcome-screen-decor-hint__label">
{children || t("welcomeScreen.defaults.menuHint")}
</div>
</div>
);
};
MenuHint.displayName = "MenuHint";
const ToolbarHint = ({ children }: { children?: React.ReactNode }) => {
return (
<div className="virgil welcome-screen-decor welcome-screen-decor-hint welcome-screen-decor-hint--toolbar">
<div className="welcome-screen-decor-hint__label">
{children || t("welcomeScreen.defaults.toolbarHint")}
</div>
{WelcomeScreenTopToolbarArrow}
</div>
);
};
ToolbarHint.displayName = "ToolbarHint";
const HelpHint = ({ children }: { children?: React.ReactNode }) => {
return (
<div className="virgil welcome-screen-decor welcome-screen-decor-hint welcome-screen-decor-hint--help">
<div>{children || t("welcomeScreen.defaults.helpHint")}</div>
{WelcomeScreenHelpArrow}
</div>
);
};
HelpHint.displayName = "HelpHint";
export { HelpHint, MenuHint, ToolbarHint };
@@ -3,29 +3,39 @@
font-family: "Virgil";
}
.WelcomeScreen-logo {
display: flex;
align-items: center;
column-gap: 0.75rem;
font-size: 2.25rem;
// WelcomeSreen common
// ---------------------------------------------------------------------------
svg {
width: 1.625rem;
height: auto;
}
}
.WelcomeScreen-decor {
.welcome-screen-decor {
pointer-events: none;
color: var(--color-gray-40);
}
&--subheading {
font-size: 1.125rem;
text-align: center;
&.theme--dark {
.welcome-screen-decor {
color: var(--color-gray-60);
}
}
// WelcomeScreen.Hints
// ---------------------------------------------------------------------------
.welcome-screen-decor-hint {
@media (max-height: 599px) {
display: none !important;
}
&--help-pointer {
@media (max-width: 1024px), (max-width: 800px) {
.welcome-screen-decor {
&--help,
&--menu {
display: none;
}
}
}
&--help {
display: flex;
position: absolute;
right: 0;
@@ -49,7 +59,7 @@
}
}
&--top-toolbar-pointer {
&--toolbar {
position: absolute;
top: 100%;
left: 50%;
@@ -58,7 +68,7 @@
display: flex;
align-items: baseline;
&__label {
.welcome-screen-decor-hint__label {
width: 120px;
position: relative;
top: -0.5rem;
@@ -74,7 +84,7 @@
}
}
&--menu-pointer {
&--menu {
position: absolute;
width: 320px;
font-size: 1rem;
@@ -95,10 +105,19 @@
transform: scaleX(-1);
}
}
@media (max-width: 860px) {
.welcome-screen-decor-hint__label {
max-width: 160px;
}
}
}
}
.WelcomeScreen-container {
// WelcomeSreen.Center
// ---------------------------------------------------------------------------
.welcome-screen-center {
display: flex;
flex-direction: column;
gap: 2rem;
@@ -112,7 +131,24 @@
bottom: 1rem;
}
.WelcomeScreen-items {
.welcome-screen-center__logo {
display: flex;
align-items: center;
column-gap: 0.75rem;
font-size: 2.25rem;
svg {
width: 1.625rem;
height: auto;
}
}
.welcome-screen-center__heading {
font-size: 1.125rem;
text-align: center;
}
.welcome-screen-menu {
display: flex;
flex-direction: column;
gap: 2px;
@@ -120,7 +156,7 @@
align-items: center;
}
.WelcomeScreen-item {
.welcome-screen-menu-item {
box-sizing: border-box;
pointer-events: all;
@@ -128,8 +164,10 @@
color: var(--color-gray-50);
font-size: 0.875rem;
width: 100%;
min-width: 300px;
display: flex;
max-width: 400px;
display: grid;
align-items: center;
justify-content: space-between;
@@ -140,44 +178,49 @@
border-radius: var(--border-radius-md);
&__label {
grid-template-columns: calc(var(--default-icon-size) + 0.5rem) 1fr 3rem;
&__text {
display: flex;
align-items: center;
margin-right: auto;
text-align: left;
column-gap: 0.5rem;
}
svg {
width: var(--default-icon-size);
height: var(--default-icon-size);
}
&__icon {
width: var(--default-icon-size);
height: var(--default-icon-size);
}
&__shortcut {
margin-left: auto;
color: var(--color-gray-40);
font-size: 0.75rem;
}
}
&:not(:active) .WelcomeScreen-item:hover {
&:not(:active) .welcome-screen-menu-item:hover {
text-decoration: none;
background: var(--color-gray-10);
.WelcomeScreen-item__shortcut {
.welcome-screen-menu-item__shortcut {
color: var(--color-gray-50);
}
.WelcomeScreen-item__label {
.welcome-screen-menu-item__text {
color: var(--color-gray-100);
}
}
.WelcomeScreen-item:active {
.welcome-screen-menu-item:active {
background: var(--color-gray-20);
.WelcomeScreen-item__shortcut {
.welcome-screen-menu-item__shortcut {
color: var(--color-gray-50);
}
.WelcomeScreen-item__label {
.welcome-screen-menu-item__text {
color: var(--color-gray-100);
}
@@ -185,7 +228,7 @@
color: var(--color-promo) !important;
&:hover {
.WelcomeScreen-item__label {
.welcome-screen-menu-item__text {
color: var(--color-promo) !important;
}
}
@@ -193,11 +236,7 @@
}
&.theme--dark {
.WelcomeScreen-decor {
color: var(--color-gray-60);
}
.WelcomeScreen-item {
.welcome-screen-menu-item {
color: var(--color-gray-60);
&__shortcut {
@@ -205,69 +244,41 @@
}
}
&:not(:active) .WelcomeScreen-item:hover {
&:not(:active) .welcome-screen-menu-item:hover {
background: var(--color-gray-85);
.WelcomeScreen-item__shortcut {
.welcome-screen-menu-item__shortcut {
color: var(--color-gray-50);
}
.WelcomeScreen-item__label {
.welcome-screen-menu-item__text {
color: var(--color-gray-10);
}
}
.WelcomeScreen-item:active {
.welcome-screen-menu-item:active {
background-color: var(--color-gray-90);
.WelcomeScreen-item__label {
.welcome-screen-menu-item__text {
color: var(--color-gray-10);
}
}
}
// Can tweak these values but for an initial effort, it looks OK to me
@media (max-width: 1024px) {
.WelcomeScreen-decor {
&--help-pointer,
&--menu-pointer {
display: none;
}
}
}
// @media (max-height: 400px) {
// .WelcomeScreen-container {
// margin-top: 0;
// }
// }
@media (max-height: 599px) {
.WelcomeScreen-container {
.welcome-screen-center {
margin-top: 4rem;
}
}
@media (min-height: 600px) and (max-height: 900px) {
.WelcomeScreen-container {
.welcome-screen-center {
margin-top: 8rem;
}
}
@media (max-height: 630px) {
.WelcomeScreen-decor--top-toolbar-pointer {
display: none;
}
}
@media (max-height: 500px) {
.WelcomeScreen-container {
@media (max-height: 500px), (max-width: 320px) {
.welcome-screen-center {
display: none;
}
}
// @media (max-height: 740px) {
// .WelcomeScreen-decor {
// &--help-pointer,
// &--top-toolbar-pointer,
// &--menu-pointer {
// display: none;
// }
// }
// }
// ---------------------------------------------------------------------------
}
@@ -0,0 +1,17 @@
import { Center } from "./WelcomeScreen.Center";
import { MenuHint, ToolbarHint, HelpHint } from "./WelcomeScreen.Hints";
import "./WelcomeScreen.scss";
const WelcomeScreen = (props: { children: React.ReactNode }) => {
// NOTE this component is used as a dummy wrapper to retrieve child props
// from, and will never be rendered to DOM directly. As such, we can't
// do anything here (use hooks and such)
return null;
};
WelcomeScreen.displayName = "WelcomeScreen";
WelcomeScreen.Center = Center;
WelcomeScreen.Hints = { MenuHint, ToolbarHint, HelpHint };
export default WelcomeScreen;
+1 -8
View File
@@ -150,6 +150,7 @@ export const DEFAULT_UI_OPTIONS: AppProps["UIOptions"] = {
toggleTheme: null,
saveAsImage: true,
},
welcomeScreen: true,
};
// breakpoints
@@ -236,14 +237,6 @@ export const ROUNDNESS = {
ADAPTIVE_RADIUS: 3,
} as const;
export const COOKIES = {
AUTH_STATE_COOKIE: "excplus-auth",
} as const;
/** key containt id of precedeing elemnt id we use in reconciliation during
* collaboration */
export const PRECEDING_ELEMENT_KEY = "__precedingElement__";
export const isExcalidrawPlusSignedUser = document.cookie.includes(
COOKIES.AUTH_STATE_COOKIE,
);
+4 -4
View File
@@ -408,7 +408,7 @@
pointer-events: all;
&:hover {
background-color: var(--button-hover);
background-color: var(--button-hover-bg);
}
&:active {
@@ -540,9 +540,9 @@
}
.mobile-misc-tools-container {
position: fixed;
top: 5rem;
right: 0;
position: absolute;
top: calc(5rem - var(--editor-container-padding));
right: calc(var(--editor-container-padding) * -1);
display: flex;
flex-direction: column;
border: 1px solid var(--sidebar-border-color);
+3 -2
View File
@@ -35,13 +35,14 @@
--shadow-island: 0px 7px 14px rgba(0, 0, 0, 0.05),
0px 0px 3.12708px rgba(0, 0, 0, 0.0798),
0px 0px 0.931014px rgba(0, 0, 0, 0.1702);
--button-hover: var(--color-gray-10);
--button-hover-bg: var(--color-gray-10);
--default-border-color: var(--color-gray-30);
--default-button-size: 2rem;
--default-icon-size: 1rem;
--lg-button-size: 2.25rem;
--lg-icon-size: 1rem;
--editor-container-padding: 1rem;
@media screen and (min-device-width: 1921px) {
--lg-button-size: 2.5rem;
@@ -135,7 +136,7 @@
--popup-text-inverted-color: #2c2c2c;
--select-highlight-color: #{$oc-blue-4};
--text-primary-color: var(--color-gray-40);
--button-hover: var(--color-gray-80);
--button-hover-bg: var(--color-gray-80);
--default-border-color: var(--color-gray-80);
--shadow-island: 0px 13px 33px rgba(0, 0, 0, 0.07),
0px 4.13px 9.94853px rgba(0, 0, 0, 0.0456112),
+15 -11
View File
@@ -39,11 +39,11 @@
.ToolIcon__icon {
&:hover {
background: var(--button-hover);
background: var(--button-hover-bg);
}
&:active {
background: var(--button-hover);
background: var(--button-hover-bg);
border: 1px solid var(--color-primary-darkest);
}
}
@@ -54,24 +54,25 @@
justify-content: center;
align-items: center;
padding: 0.625rem;
width: var(--default-button-size);
height: var(--default-button-size);
width: var(--button-width, var(--default-button-size));
height: var(--button-height, var(--default-button-size));
box-sizing: border-box;
border-width: 1px;
border-style: solid;
border-color: var(--default-border-color);
border-color: var(--button-border, var(--default-border-color));
border-radius: var(--border-radius-lg);
cursor: pointer;
background-color: transparent;
color: var(--text-primary-color);
background-color: var(--button-bg, var(--island-bg-color));
color: var(--button-color, var(--text-primary-color));
&:hover {
background-color: var(--button-hover);
background-color: var(--button-hover-bg);
border-color: var(--button-hover-border, var(--default-border-color));
}
&:active {
background-color: var(--button-hover);
border-color: var(--color-primary-darkest);
background-color: var(--button-active-bg);
border-color: var(--button-active-border, var(--color-primary-darkest));
}
&.active {
@@ -83,7 +84,10 @@
}
svg {
color: var(--color-primary-darker);
color: var(--button-color, var(--color-primary-darker));
width: var(--button-width, var(--lg-icon-size));
height: var(--button-height, var(--lg-icon-size));
}
}
}
+1 -1
View File
@@ -267,7 +267,7 @@ export const actionLink = register({
keyTest: (event) => event[KEYS.CTRL_OR_CMD] && event.key === KEYS.K,
contextItemLabel: (elements, appState) =>
getContextMenuLabel(elements, appState),
contextItemPredicate: (elements, appState) => {
predicate: (elements, appState) => {
const selectedElements = getSelectedElements(elements, appState);
return selectedElements.length === 1;
},
+4 -4
View File
@@ -557,10 +557,10 @@ export const resizeSingleElement = (
mutateElement(element, {
scale: [
// defaulting because scaleX/Y can be 0/-0
(Math.sign(scaleX) || stateAtResizeStart.scale[0]) *
stateAtResizeStart.scale[0],
(Math.sign(scaleY) || stateAtResizeStart.scale[1]) *
stateAtResizeStart.scale[1],
(Math.sign(newBoundsX2 - stateAtResizeStart.x) ||
stateAtResizeStart.scale[0]) * stateAtResizeStart.scale[0],
(Math.sign(newBoundsY2 - stateAtResizeStart.y) ||
stateAtResizeStart.scale[1]) * stateAtResizeStart.scale[1],
],
});
}
+8
View File
@@ -38,3 +38,11 @@ export const STORAGE_KEYS = {
VERSION_DATA_STATE: "version-dataState",
VERSION_FILES: "version-files",
} as const;
export const COOKIES = {
AUTH_STATE_COOKIE: "excplus-auth",
} as const;
export const isExcalidrawPlusSignedUser = document.cookie.includes(
COOKIES.AUTH_STATE_COOKIE,
);
@@ -1,4 +1,4 @@
import { isExcalidrawPlusSignedUser } from "../../constants";
import { isExcalidrawPlusSignedUser } from "../app_constants";
export const ExcalidrawPlusAppLink = () => {
if (!isExcalidrawPlusSignedUser) {
+75 -3
View File
@@ -1,6 +1,6 @@
import polyfill from "../polyfill";
import LanguageDetector from "i18next-browser-languagedetector";
import { useEffect, useRef, useState } from "react";
import { useEffect, useMemo, useRef, useState } from "react";
import { trackEvent } from "../analytics";
import { getDefaultAppState } from "../appState";
import { ErrorDialog } from "../components/ErrorDialog";
@@ -26,6 +26,8 @@ import {
defaultLang,
Footer,
MainMenu,
LiveCollaborationTrigger,
WelcomeScreen,
} from "../packages/excalidraw/index";
import {
AppState,
@@ -45,6 +47,7 @@ import {
} from "../utils";
import {
FIREBASE_STORAGE_PREFIXES,
isExcalidrawPlusSignedUser,
STORAGE_KEYS,
SYNC_BROWSER_TABS_TIMEOUT,
} from "./app_constants";
@@ -608,7 +611,7 @@ const ExcalidrawWrapper = () => {
<MainMenu.DefaultItems.SaveToActiveFile />
<MainMenu.DefaultItems.Export />
<MainMenu.DefaultItems.SaveAsImage />
<MainMenu.DefaultItems.LiveCollaboration
<MainMenu.DefaultItems.LiveCollaborationTrigger
isCollaborating={isCollaborating}
onSelect={() => setCollabDialogShown(true)}
/>
@@ -634,6 +637,63 @@ const ExcalidrawWrapper = () => {
);
};
const welcomeScreenJSX = useMemo(() => {
let headingContent;
if (isExcalidrawPlusSignedUser) {
headingContent = t("welcomeScreen.app.center_heading_plus")
.split(/(Excalidraw\+)/)
.map((bit, idx) => {
if (bit === "Excalidraw+") {
return (
<a
style={{ pointerEvents: "all" }}
href={`${process.env.REACT_APP_PLUS_APP}?utm_source=excalidraw&utm_medium=app&utm_content=welcomeScreenSignedInUser`}
key={idx}
>
Excalidraw+
</a>
);
}
return bit;
});
} else {
headingContent = t("welcomeScreen.app.center_heading");
}
return (
<WelcomeScreen>
<WelcomeScreen.Hints.MenuHint>
{t("welcomeScreen.app.menuHint")}
</WelcomeScreen.Hints.MenuHint>
<WelcomeScreen.Hints.ToolbarHint />
<WelcomeScreen.Hints.HelpHint />
<WelcomeScreen.Center>
<WelcomeScreen.Center.Logo />
<WelcomeScreen.Center.Heading>
{headingContent}
</WelcomeScreen.Center.Heading>
<WelcomeScreen.Center.Menu>
<WelcomeScreen.Center.MenuItemLoadScene />
<WelcomeScreen.Center.MenuItemHelp />
<WelcomeScreen.Center.MenuItemLiveCollaborationTrigger
onSelect={() => setCollabDialogShown(true)}
/>
{!isExcalidrawPlusSignedUser && (
<WelcomeScreen.Center.MenuItemLink
href="https://plus.excalidraw.com/plus?utm_source=excalidraw&utm_medium=app&utm_content=welcomeScreenGuest"
shortcut={null}
icon={PlusPromoIcon}
>
Try Excalidraw Plus!
</WelcomeScreen.Center.MenuItemLink>
)}
</WelcomeScreen.Center.Menu>
</WelcomeScreen.Center>
</WelcomeScreen>
);
}, [setCollabDialogShown]);
return (
<div
style={{ height: "100%" }}
@@ -645,7 +705,6 @@ const ExcalidrawWrapper = () => {
ref={excalidrawRefCallback}
onChange={onChange}
initialData={initialStatePromiseRef.current.promise}
onCollabButtonClick={() => setCollabDialogShown(true)}
isCollaborating={isCollaborating}
onPointerUpdate={collabAPI?.onPointerUpdate}
UIOptions={{
@@ -679,14 +738,27 @@ const ExcalidrawWrapper = () => {
onLibraryChange={onLibraryChange}
autoFocus={true}
theme={theme}
renderTopRightUI={(isMobile) => {
if (isMobile) {
return null;
}
return (
<LiveCollaborationTrigger
isCollaborating={isCollaborating}
onSelect={() => setCollabDialogShown(true)}
/>
);
}}
>
{renderMenu()}
<Footer>
<div style={{ display: "flex", gap: ".5rem", alignItems: "center" }}>
<ExcalidrawPlusAppLink />
<EncryptedIcon />
</div>
</Footer>
{welcomeScreenJSX}
</Excalidraw>
{excalidrawAPI && <Collab excalidrawAPI={excalidrawAPI} />}
{errorMessage && (
+3 -1
View File
@@ -201,7 +201,9 @@
"svgImageInsertError": "تعذر إدراج صورة SVG. يبدو أن ترميز SVG غير صحيح.",
"invalidSVGString": "SVG غير صالح.",
"cannotResolveCollabServer": "",
"importLibraryError": ""
"importLibraryError": "",
"collabSaveFailed": "",
"collabSaveFailed_sizeExceeded": ""
},
"toolBar": {
"selection": "تحديد",
+3 -1
View File
@@ -201,7 +201,9 @@
"svgImageInsertError": "",
"invalidSVGString": "",
"cannotResolveCollabServer": "",
"importLibraryError": ""
"importLibraryError": "",
"collabSaveFailed": "",
"collabSaveFailed_sizeExceeded": ""
},
"toolBar": {
"selection": "Селекция",
+3 -1
View File
@@ -201,7 +201,9 @@
"svgImageInsertError": "এসভীজী ছবি সন্নিবেশ করা যায়নি। এসভীজী মার্কআপটি অবৈধ মনে হচ্ছে৷",
"invalidSVGString": "এসভীজী মার্কআপটি অবৈধ মনে হচ্ছে৷",
"cannotResolveCollabServer": "কোল্যাব সার্ভারের সাথে সংযোগ করা যায়নি। পৃষ্ঠাটি পুনরায় লোড করে আবার চেষ্টা করুন।",
"importLibraryError": "সংগ্রহ লোড করা যায়নি"
"importLibraryError": "সংগ্রহ লোড করা যায়নি",
"collabSaveFailed": "",
"collabSaveFailed_sizeExceeded": ""
},
"toolBar": {
"selection": "বাছাই",
+3 -1
View File
@@ -201,7 +201,9 @@
"svgImageInsertError": "No ha estat possible inserir la imatge SVG. Les marques SVG semblen invàlides.",
"invalidSVGString": "SVG no vàlid.",
"cannotResolveCollabServer": "No ha estat possible connectar amb el servidor collab. Si us plau recarregueu la pàgina i torneu a provar.",
"importLibraryError": "No s'ha pogut carregar la biblioteca"
"importLibraryError": "No s'ha pogut carregar la biblioteca",
"collabSaveFailed": "",
"collabSaveFailed_sizeExceeded": ""
},
"toolBar": {
"selection": "Selecció",
+3 -1
View File
@@ -201,7 +201,9 @@
"svgImageInsertError": "",
"invalidSVGString": "",
"cannotResolveCollabServer": "",
"importLibraryError": ""
"importLibraryError": "",
"collabSaveFailed": "",
"collabSaveFailed_sizeExceeded": ""
},
"toolBar": {
"selection": "Výběr",
+3 -1
View File
@@ -201,7 +201,9 @@
"svgImageInsertError": "",
"invalidSVGString": "",
"cannotResolveCollabServer": "",
"importLibraryError": ""
"importLibraryError": "",
"collabSaveFailed": "",
"collabSaveFailed_sizeExceeded": ""
},
"toolBar": {
"selection": "",
+5 -3
View File
@@ -201,7 +201,9 @@
"svgImageInsertError": "SVG-Bild konnte nicht eingefügt werden. Das SVG-Markup sieht ungültig aus.",
"invalidSVGString": "Ungültige SVG.",
"cannotResolveCollabServer": "Konnte keine Verbindung zum Collab-Server herstellen. Bitte lade die Seite neu und versuche es erneut.",
"importLibraryError": "Bibliothek konnte nicht geladen werden"
"importLibraryError": "Bibliothek konnte nicht geladen werden",
"collabSaveFailed": "Keine Speicherung in der Backend-Datenbank möglich. Wenn die Probleme weiterhin bestehen, solltest Du Deine Datei lokal speichern, um sicherzustellen, dass Du Deine Arbeit nicht verlierst.",
"collabSaveFailed_sizeExceeded": "Keine Speicherung in der Backend-Datenbank möglich, die Zeichenfläche scheint zu groß zu sein. Du solltest Deine Datei lokal speichern, um sicherzustellen, dass Du Deine Arbeit nicht verlierst."
},
"toolBar": {
"selection": "Auswahl",
@@ -312,8 +314,8 @@
"zoomToFit": "Zoomen um alle Elemente einzupassen",
"zoomToSelection": "Auf Auswahl zoomen",
"toggleElementLock": "Auswahl sperren/entsperren",
"movePageUpDown": "",
"movePageLeftRight": ""
"movePageUpDown": "Seite nach oben/unten verschieben",
"movePageLeftRight": "Seite nach links/rechts verschieben"
},
"clearCanvasDialog": {
"title": "Zeichenfläche löschen"
+3 -1
View File
@@ -201,7 +201,9 @@
"svgImageInsertError": "Αδυναμία εισαγωγής εικόνας SVG. Η σήμανση της SVG δεν φαίνεται έγκυρη.",
"invalidSVGString": "Μη έγκυρο SVG.",
"cannotResolveCollabServer": "Αδυναμία σύνδεσης με τον διακομιστή συνεργασίας. Παρακαλώ ανανεώστε τη σελίδα και προσπαθήστε ξανά.",
"importLibraryError": "Αδυναμία φόρτωσης βιβλιοθήκης"
"importLibraryError": "Αδυναμία φόρτωσης βιβλιοθήκης",
"collabSaveFailed": "",
"collabSaveFailed_sizeExceeded": ""
},
"toolBar": {
"selection": "Επιλογή",
+11 -5
View File
@@ -448,10 +448,16 @@
"d9480f": "Orange 9"
},
"welcomeScreen": {
"data": "All your data is saved locally in your browser.",
"switchToPlusApp": "Did you want to go to the Excalidraw+ instead?",
"menuHints": "Export, preferences, languages, ...",
"toolbarHints": "Pick a tool & Start drawing!",
"helpHints": "Shortcuts & help"
"app": {
"center_heading": "All your data is saved locally in your browser.",
"center_heading_plus": "Did you want to go to the Excalidraw+ instead?",
"menuHint": "Export, preferences, languages, ..."
},
"defaults": {
"menuHint": "Export, preferences, and more...",
"center_heading": "Diagrams. Made. Simple.",
"toolbarHint": "Pick a tool & Start drawing!",
"helpHint": "Shortcuts & help"
}
}
}
+3 -1
View File
@@ -201,7 +201,9 @@
"svgImageInsertError": "No se pudo insertar la imagen SVG. El código SVG parece inválido.",
"invalidSVGString": "SVG no válido.",
"cannotResolveCollabServer": "No se pudo conectar al servidor colaborador. Por favor, vuelva a cargar la página y vuelva a intentarlo.",
"importLibraryError": "No se pudo cargar la librería"
"importLibraryError": "No se pudo cargar la librería",
"collabSaveFailed": "No se pudo guardar en la base de datos del backend. Si los problemas persisten, debería guardar su archivo localmente para asegurarse de que no pierde su trabajo.",
"collabSaveFailed_sizeExceeded": "No se pudo guardar en la base de datos del backend, el lienzo parece ser demasiado grande. Debería guardar el archivo localmente para asegurarse de que no pierde su trabajo."
},
"toolBar": {
"selection": "Selección",
+3 -1
View File
@@ -201,7 +201,9 @@
"svgImageInsertError": "Ezin izan da SVG irudia txertatu. SVG markak baliogabea dirudi.",
"invalidSVGString": "SVG baliogabea.",
"cannotResolveCollabServer": "Ezin izan da elkarlaneko zerbitzarira konektatu. Mesedez, berriro kargatu orria eta saiatu berriro.",
"importLibraryError": "Ezin izan da liburutegia kargatu"
"importLibraryError": "Ezin izan da liburutegia kargatu",
"collabSaveFailed": "",
"collabSaveFailed_sizeExceeded": ""
},
"toolBar": {
"selection": "Hautapena",
+3 -1
View File
@@ -201,7 +201,9 @@
"svgImageInsertError": "تصویر SVG وارد نشد. نشانه گذاری SVG نامعتبر به نظر می رسد.",
"invalidSVGString": "SVG نادرست.",
"cannotResolveCollabServer": "به سرور collab متصل نشد. لطفا صفحه را مجددا بارگذاری کنید و دوباره تلاش کنید.",
"importLibraryError": "داده‌ها بارگذاری نشدند"
"importLibraryError": "داده‌ها بارگذاری نشدند",
"collabSaveFailed": "",
"collabSaveFailed_sizeExceeded": ""
},
"toolBar": {
"selection": "گزینش",
+3 -1
View File
@@ -201,7 +201,9 @@
"svgImageInsertError": "SVG- kuvaa ei voitu lisätä. Tiedoston SVG-sisältö näyttää virheelliseltä.",
"invalidSVGString": "Virheellinen SVG.",
"cannotResolveCollabServer": "Yhteyden muodostaminen collab-palvelimeen epäonnistui. Virkistä sivu ja yritä uudelleen.",
"importLibraryError": ""
"importLibraryError": "",
"collabSaveFailed": "",
"collabSaveFailed_sizeExceeded": ""
},
"toolBar": {
"selection": "Valinta",
+8 -6
View File
@@ -1,7 +1,7 @@
{
"labels": {
"paste": "Coller",
"pasteAsPlaintext": "",
"pasteAsPlaintext": "Coller comme texte brut",
"pasteCharts": "Coller les graphiques",
"selectAll": "Tout sélectionner",
"multiSelect": "Ajouter l'élément à la sélection",
@@ -201,7 +201,9 @@
"svgImageInsertError": "Impossible d'insérer l'image SVG. Le balisage SVG semble invalide.",
"invalidSVGString": "SVG invalide.",
"cannotResolveCollabServer": "Impossible de se connecter au serveur collaboratif. Veuillez recharger la page et réessayer.",
"importLibraryError": "Impossible de charger la bibliothèque"
"importLibraryError": "Impossible de charger la bibliothèque",
"collabSaveFailed": "Impossible d'enregistrer dans la base de données en arrière-plan. Si des problèmes persistent, vous devriez enregistrer votre fichier localement pour vous assurer de ne pas perdre votre travail.",
"collabSaveFailed_sizeExceeded": "Impossible d'enregistrer dans la base de données en arrière-plan, le tableau semble trop grand. Vous devriez enregistrer le fichier localement pour vous assurer de ne pas perdre votre travail."
},
"toolBar": {
"selection": "Sélection",
@@ -236,7 +238,7 @@
"resize": "Vous pouvez conserver les proportions en maintenant la touche MAJ pendant le redimensionnement, maintenez la touche ALT pour redimensionner par rapport au centre",
"resizeImage": "Vous pouvez redimensionner librement en maintenant SHIFT,\nmaintenez ALT pour redimensionner depuis le centre",
"rotate": "Vous pouvez restreindre les angles en maintenant MAJ pendant la rotation",
"lineEditor_info": "",
"lineEditor_info": "Maintenez CtrlOrCmd et Double-cliquez ou appuyez sur CtrlOrCmd + Entrée pour modifier les points",
"lineEditor_pointSelected": "Appuyer sur Suppr. pour supprimer des points, Ctrl ou Cmd+D pour dupliquer, ou faire glisser pour déplacer",
"lineEditor_nothingSelected": "Sélectionner un point pour éditer (maintenir la touche MAJ pour en sélectionner plusieurs),\nou maintenir la touche Alt enfoncée et cliquer pour ajouter de nouveaux points",
"placeImage": "Cliquez pour placer l'image, ou cliquez et faites glisser pour définir sa taille manuellement",
@@ -312,8 +314,8 @@
"zoomToFit": "Zoomer pour voir tous les éléments",
"zoomToSelection": "Zoomer sur la sélection",
"toggleElementLock": "Verrouiller/déverrouiller la sélection",
"movePageUpDown": "",
"movePageLeftRight": ""
"movePageUpDown": "Déplacer la page vers le haut/bas",
"movePageLeftRight": "Déplacer la page vers la gauche/droite"
},
"clearCanvasDialog": {
"title": "Effacer la zone de dessin"
@@ -395,7 +397,7 @@
"fileSavedToFilename": "Enregistré sous {filename}",
"canvas": "canevas",
"selection": "sélection",
"pasteAsSingleElement": ""
"pasteAsSingleElement": "Utiliser {{shortcut}} pour coller comme un seul élément,\nou coller dans un éditeur de texte existant"
},
"colors": {
"ffffff": "Blanc",
+8 -6
View File
@@ -1,7 +1,7 @@
{
"labels": {
"paste": "Pegar",
"pasteAsPlaintext": "",
"pasteAsPlaintext": "Pegar coma texto sen formato",
"pasteCharts": "Pegar gráficos",
"selectAll": "Seleccionar todo",
"multiSelect": "Engadir elemento á selección",
@@ -201,7 +201,9 @@
"svgImageInsertError": "Non se puido inserir como imaxe SVG. O marcado SVG semella inválido.",
"invalidSVGString": "SVG inválido.",
"cannotResolveCollabServer": "Non se puido conectar ao servidor de colaboración. Por favor recargue a páxina e probe de novo.",
"importLibraryError": "Non se puido cargar a biblioteca"
"importLibraryError": "Non se puido cargar a biblioteca",
"collabSaveFailed": "",
"collabSaveFailed_sizeExceeded": ""
},
"toolBar": {
"selection": "Selección",
@@ -236,7 +238,7 @@
"resize": "Pode reducir as proporcións mantendo SHIFT mentres axusta o tamaño,\nmanteña ALT para axustalo dende o centro",
"resizeImage": "Pode axustar o tamaño libremente mantendo SHIFT,\nmanteña ALT para axustalo dende o centro",
"rotate": "Podes reducir os ángulos mantendo SHIFT mentres os rotas",
"lineEditor_info": "",
"lineEditor_info": "Manteña pulsado CtrlOrCmd e faga dobre clic ou prema CtrlOrCmd + Enter para editar puntos",
"lineEditor_pointSelected": "Prema Suprimir para eliminar o(s) punto(s)\nCtrlOrCmd+D para duplicalos, ou arrastre para movelos",
"lineEditor_nothingSelected": "Seleccione un punto para editar (manteña pulsado SHIFT para selección múltiple),\nou manteña pulsado Alt e faga clic para engadir novos puntos",
"placeImage": "Faga clic para colocar a imaxe, ou faga clic e arrastre para establecer o seu tamaño manualmente",
@@ -312,8 +314,8 @@
"zoomToFit": "Zoom que se axuste a todos os elementos",
"zoomToSelection": "Zoom á selección",
"toggleElementLock": "Bloquear/desbloquear selección",
"movePageUpDown": "",
"movePageLeftRight": ""
"movePageUpDown": "Mover páxina cara enriba/abaixo",
"movePageLeftRight": "Mover páxina cara a esquerda/dereita"
},
"clearCanvasDialog": {
"title": "Limpar lenzo"
@@ -395,7 +397,7 @@
"fileSavedToFilename": "Gardado en {filename}",
"canvas": "lenzo",
"selection": "selección",
"pasteAsSingleElement": ""
"pasteAsSingleElement": "Usa {{shortcut}} para pegar como un único elemento\nou pega nun editor de texto existente"
},
"colors": {
"ffffff": "Branco",
+3 -1
View File
@@ -201,7 +201,9 @@
"svgImageInsertError": "לא ניתן היה להטמיע את תמונת ה-SVG. קידוד ה-SVG אינו תקני.",
"invalidSVGString": "SVG בלתי תקני.",
"cannotResolveCollabServer": "",
"importLibraryError": "לא ניתן היה לטעון את הספריה"
"importLibraryError": "לא ניתן היה לטעון את הספריה",
"collabSaveFailed": "",
"collabSaveFailed_sizeExceeded": ""
},
"toolBar": {
"selection": "בחירה",
+6 -4
View File
@@ -1,7 +1,7 @@
{
"labels": {
"paste": "चिपकाएँ",
"pasteAsPlaintext": "",
"pasteAsPlaintext": "सादे पाठ के रूप में चिपकाएं",
"pasteCharts": "चार्ट चिपकाएँ",
"selectAll": "सभी चुनें",
"multiSelect": "आकार को चयन में जोड़ें",
@@ -201,7 +201,9 @@
"svgImageInsertError": "एसवीजी छवि सम्मिलित नहीं कर सके, एसवीजी रचना अनुचित हैं",
"invalidSVGString": "अनुचित SVG",
"cannotResolveCollabServer": "कॉलेब सर्वर से कनेक्शन नहीं हो पा रहा. कृपया पृष्ठ को पुनः लाने का प्रयास करे.",
"importLibraryError": "संग्रह प्रतिष्ठापित नहीं किया जा सका"
"importLibraryError": "संग्रह प्रतिष्ठापित नहीं किया जा सका",
"collabSaveFailed": "किसी कारण वश अंदरूनी डेटाबेस में सहेजा नहीं जा सका। यदि समस्या बनी रहती है, तो किये काम को खोने न देने के लिये अपनी फ़ाइल को स्थानीय रूप से सहेजे।",
"collabSaveFailed_sizeExceeded": "लगता है कि पृष्ठ तल काफ़ी बड़ा है, इस्कारण अंदरूनी डेटाबेस में सहेजा नहीं जा सका। किये काम को खोने न देने के लिये अपनी फ़ाइल को स्थानीय रूप से सहेजे।"
},
"toolBar": {
"selection": "चयन",
@@ -236,7 +238,7 @@
"resize": "आकार बदलते समय आप SHIFT को पकड़ कर अनुपात में कमी कर सकते हैं,\nकेंद्र से आकार बदलने के लिए ALT दबाए रखें",
"resizeImage": "",
"rotate": "आप घूर्णन करते समय SHIFT पकड़कर कोणों को विवश कर सकते हैं",
"lineEditor_info": "",
"lineEditor_info": "बिंदुओं को सम्पादित करने के लिए CtrlOrCmd को दबायें रखते हुये डबल क्लिक करे, अथवा CtrlOrCmd + Enter साथ दबाये",
"lineEditor_pointSelected": "",
"lineEditor_nothingSelected": "",
"placeImage": "",
@@ -395,7 +397,7 @@
"fileSavedToFilename": "",
"canvas": "",
"selection": "",
"pasteAsSingleElement": ""
"pasteAsSingleElement": "एक अवयव के रूप में चिपकाने के लिए {{shortcut}} का उपयोग करें,\nया किसी मौजूदा पाठ संपादक में चिपकायें"
},
"colors": {
"ffffff": "सफेद",
+3 -1
View File
@@ -201,7 +201,9 @@
"svgImageInsertError": "Nem sikerült beszúrni az SVG-képet. Az SVG szintaktika érvénytelennek tűnik.",
"invalidSVGString": "Érvénytelen SVG.",
"cannotResolveCollabServer": "",
"importLibraryError": ""
"importLibraryError": "",
"collabSaveFailed": "",
"collabSaveFailed_sizeExceeded": ""
},
"toolBar": {
"selection": "Kijelölés",
+3 -1
View File
@@ -201,7 +201,9 @@
"svgImageInsertError": "Tidak dapat menyisipkan gambar SVG. Markup SVG sepertinya tidak valid.",
"invalidSVGString": "SVG tidak valid.",
"cannotResolveCollabServer": "Tidak dapat terhubung ke server kolab. Muat ulang laman dan coba lagi.",
"importLibraryError": "Tidak dapat memuat pustaka"
"importLibraryError": "Tidak dapat memuat pustaka",
"collabSaveFailed": "",
"collabSaveFailed_sizeExceeded": ""
},
"toolBar": {
"selection": "Pilihan",
+3 -1
View File
@@ -201,7 +201,9 @@
"svgImageInsertError": "Impossibile inserire l'immagine SVG. Il markup SVG non sembra corretto.",
"invalidSVGString": "SVG non valido.",
"cannotResolveCollabServer": "Impossibile connettersi al server di collab. Ricarica la pagina e riprova.",
"importLibraryError": "Impossibile caricare la libreria"
"importLibraryError": "Impossibile caricare la libreria",
"collabSaveFailed": "",
"collabSaveFailed_sizeExceeded": ""
},
"toolBar": {
"selection": "Selezione",
+8 -6
View File
@@ -1,7 +1,7 @@
{
"labels": {
"paste": "貼り付け",
"pasteAsPlaintext": "",
"pasteAsPlaintext": "書式なしテキストとして貼り付け",
"pasteCharts": "チャートの貼り付け",
"selectAll": "すべて選択",
"multiSelect": "複数選択",
@@ -201,7 +201,9 @@
"svgImageInsertError": "SVGイメージを挿入できませんでした。SVGマークアップは無効に見えます。",
"invalidSVGString": "無効なSVGです。",
"cannotResolveCollabServer": "コラボレーションサーバに接続できませんでした。ページを再読み込みして、もう一度お試しください。",
"importLibraryError": "ライブラリを読み込めませんでした。"
"importLibraryError": "ライブラリを読み込めませんでした。",
"collabSaveFailed": "バックエンドデータベースに保存できませんでした。問題が解決しない場合は、作業を失わないようにローカルにファイルを保存してください。",
"collabSaveFailed_sizeExceeded": "キャンバスが大きすぎるため、バックエンドデータベースに保存できませんでした。問題が解決しない場合は、作業を失わないようにローカルにファイルを保存してください。"
},
"toolBar": {
"selection": "選択",
@@ -236,7 +238,7 @@
"resize": "サイズを変更中にSHIFTを押すと縦横比を固定できます。Altを押すと中央からサイズを変更できます",
"resizeImage": "SHIFTを長押しすると自由にサイズを変更できます。\n中央からサイズを変更するにはALTを長押しします",
"rotate": "回転中にSHIFT キーを押すと角度を制限することができます",
"lineEditor_info": "",
"lineEditor_info": "CtrlOrCmd を押したままダブルクリックするか、CtrlOrCmd + Enter を押して点を編集します",
"lineEditor_pointSelected": "Deleteキーを押すと点を削除、CtrlOrCmd+Dで複製、マウスドラッグで移動",
"lineEditor_nothingSelected": "編集する点を選択(SHIFTを押したままで複数選択)、\nAltキーを押しながらクリックすると新しい点を追加",
"placeImage": "クリックして画像を配置するか、クリックしてドラッグしてサイズを手動で設定します",
@@ -312,8 +314,8 @@
"zoomToFit": "すべての要素が収まるようにズーム",
"zoomToSelection": "選択要素にズーム",
"toggleElementLock": "選択したアイテムをロック/ロック解除",
"movePageUpDown": "",
"movePageLeftRight": ""
"movePageUpDown": "ページを上下に移動",
"movePageLeftRight": "ページを左右に移動"
},
"clearCanvasDialog": {
"title": "キャンバスを消去"
@@ -395,7 +397,7 @@
"fileSavedToFilename": "{filename} に保存しました",
"canvas": "キャンバス",
"selection": "選択",
"pasteAsSingleElement": ""
"pasteAsSingleElement": "{{shortcut}} を使用して単一の要素として貼り付けるか、\n既存のテキストエディタに貼り付け"
},
"colors": {
"ffffff": "ホワイト",
+3 -1
View File
@@ -201,7 +201,9 @@
"svgImageInsertError": "D awezɣi tugra n tugna SVG. Acraḍ SVG yettban-d d 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.",
"importLibraryError": "Ur d-ssalay ara tamkarḍit"
"importLibraryError": "Ur d-ssalay ara tamkarḍit",
"collabSaveFailed": "",
"collabSaveFailed_sizeExceeded": ""
},
"toolBar": {
"selection": "Tafrayt",
+3 -1
View File
@@ -201,7 +201,9 @@
"svgImageInsertError": "",
"invalidSVGString": "",
"cannotResolveCollabServer": "",
"importLibraryError": ""
"importLibraryError": "",
"collabSaveFailed": "",
"collabSaveFailed_sizeExceeded": ""
},
"toolBar": {
"selection": "",
+3 -1
View File
@@ -201,7 +201,9 @@
"svgImageInsertError": "SVG 이미지를 삽입하지 못했습니다. SVG 문법이 유효하지 않은 것 같습니다.",
"invalidSVGString": "유효하지 않은 SVG입니다.",
"cannotResolveCollabServer": "협업 서버에 접속하는데 실패했습니다. 페이지를 새로고침하고 다시 시도해보세요.",
"importLibraryError": "라이브러리를 불러오지 못했습니다."
"importLibraryError": "라이브러리를 불러오지 못했습니다.",
"collabSaveFailed": "",
"collabSaveFailed_sizeExceeded": ""
},
"toolBar": {
"selection": "선택",
+3 -1
View File
@@ -201,7 +201,9 @@
"svgImageInsertError": "نەیتوانی وێنەی SVG داخڵ بکات. نیشانەی ئێس ڤی جی نادروست دیارە.",
"invalidSVGString": "ئێس ڤی جی نادروستە.",
"cannotResolveCollabServer": "ناتوانێت پەیوەندی بکات بە سێرڤەری کۆلاب. تکایە لاپەڕەکە دووبارە باربکەوە و دووبارە هەوڵ بدەوە.",
"importLibraryError": "نەیتوانی کتێبخانە بار بکات"
"importLibraryError": "نەیتوانی کتێبخانە بار بکات",
"collabSaveFailed": "",
"collabSaveFailed_sizeExceeded": ""
},
"toolBar": {
"selection": "دەستنیشانکردن",
+3 -1
View File
@@ -201,7 +201,9 @@
"svgImageInsertError": "Nepavyko įtraukti SVG paveiksliuko. Panašu, jog SVG yra nevalidus.",
"invalidSVGString": "Nevalidus SVG.",
"cannotResolveCollabServer": "Nepavyko prisijungti prie serverio bendradarbiavimui. Perkrauk puslapį ir pabandyk prisijungti dar kartą.",
"importLibraryError": "Nepavyko įkelti bibliotekos"
"importLibraryError": "Nepavyko įkelti bibliotekos",
"collabSaveFailed": "",
"collabSaveFailed_sizeExceeded": ""
},
"toolBar": {
"selection": "Žymėjimas",
+8 -6
View File
@@ -1,7 +1,7 @@
{
"labels": {
"paste": "Ielīmēt",
"pasteAsPlaintext": "",
"pasteAsPlaintext": "Ielīmēt kā vienkāršu tekstu",
"pasteCharts": "Ielīmēt grafikus",
"selectAll": "Atlasīt visu",
"multiSelect": "Pievienot elementu atlasei",
@@ -201,7 +201,9 @@
"svgImageInsertError": "Nevarēja ievietot SVG attēlu. Šķiet, ka SVG marķējums nav derīgs.",
"invalidSVGString": "Nederīgs SVG.",
"cannotResolveCollabServer": "Nevarēja savienoties ar sadarbošanās serveri. Lūdzu, pārlādējiet lapu un mēģiniet vēlreiz.",
"importLibraryError": "Nevarēja ielādēt bibliotēku"
"importLibraryError": "Nevarēja ielādēt bibliotēku",
"collabSaveFailed": "Darbs nav saglabāts datubāzē. Ja problēma turpinās, saglabājiet datni lokālajā krātuvē, lai nodrošinātos pret darba pazaudēšanu.",
"collabSaveFailed_sizeExceeded": "Darbs nav saglabāts datubāzē, šķiet, ka tāfele ir pārāk liela. Saglabājiet datni lokālajā krātuvē, lai nodrošinātos pret darba pazaudēšanu."
},
"toolBar": {
"selection": "Atlase",
@@ -236,7 +238,7 @@
"resize": "Kad maināt izmēru, varat ierobežot proporcijas, turot nospiestu SHIFT,\nvai arī ALT, lai mainītu izmēru ap centru",
"resizeImage": "Varat brīvi mainīt izmēru, turot nospiestu SHIFT;\nturiet nospiestu ALT, lai mainītu izmēru ap centru",
"rotate": "Rotējot varat ierobežot leņķi, turot nospiestu SHIFT",
"lineEditor_info": "",
"lineEditor_info": "Turiet CtrlOrCmd un dubultklikšķiniet, vai spiediet CtrlOrCmd + Enter, lai rediģētu punktus",
"lineEditor_pointSelected": "Spiediet dzēšanas taustiņu, lai noņemtu punktus, CtrlOrCmd+D, lai to kopētu, vai velciet, lai pārvietotu",
"lineEditor_nothingSelected": "Atlasiet punktu, lai labotu (turiet nospiestu SHIFT, lai atlasītu vairākus),\nvai turiet Alt un clikšķiniet, lai pievienotu jaunus punktus",
"placeImage": "Klikšķiniet, lai novietotu attēlu, vai spiediet un velciet, lai iestatītu tā izmēru",
@@ -312,8 +314,8 @@
"zoomToFit": "Iestatīt mērogu, kas iekļauj visus elementus",
"zoomToSelection": "Iestatīt mērogu, lai rādītu atlasi",
"toggleElementLock": "Fiksēt/atbrīvot atlasīto",
"movePageUpDown": "",
"movePageLeftRight": ""
"movePageUpDown": "Pārvietot lapu augšup/lejup",
"movePageLeftRight": "Pārvietot lapu pa labi/kreisi"
},
"clearCanvasDialog": {
"title": "Notīrīt tāfeli"
@@ -395,7 +397,7 @@
"fileSavedToFilename": "Saglabāts kā {filename}",
"canvas": "tāfeli",
"selection": "atlasi",
"pasteAsSingleElement": ""
"pasteAsSingleElement": "Izmantojiet {{shortcut}}, lai ielīmētu kā jaunu elementu, vai ielīmētu esošā teksta lauciņā"
},
"colors": {
"ffffff": "Balts",
+6 -4
View File
@@ -1,7 +1,7 @@
{
"labels": {
"paste": "चिटकवा",
"pasteAsPlaintext": "",
"pasteAsPlaintext": "साधा मजकूर च्या रुपात पेस्ट करा",
"pasteCharts": "चार्ट चिकटवा",
"selectAll": "समस्त निवडा",
"multiSelect": "निवडित तत्व जोडा",
@@ -201,7 +201,9 @@
"svgImageInsertError": "एस-वी-जी प्रतिमा आत घालवू शकलो नाही. एस-वी-जी-मार्क-अप यंत्र अयोग्य आहे.",
"invalidSVGString": "अयोग्य एस-वी-जी.",
"cannotResolveCollabServer": "कॉलेब-सर्वर हे पोहोचत नाही आहे. पान परत लोड करायचा प्रयत्न करावे.",
"importLibraryError": "संग्रह प्रतिस्थापित नाही करता आला"
"importLibraryError": "संग्रह प्रतिस्थापित नाही करता आला",
"collabSaveFailed": "काही कारणा निमित्त आतल्या डेटाबेसमध्ये जतन करू शकत नाही। समस्या तशिस राहिल्यास, तुम्ही तुमचे काम गमावणार नाही याची खात्री करण्यासाठी तुम्ही तुमची फाइल स्थानिक जतन करावी.",
"collabSaveFailed_sizeExceeded": "लगता है कि पृष्ठ तल काफ़ी बड़ा है, इस्कारण अंदरूनी डेटाबेस में सहेजा नहीं जा सका। किये काम को खोने न देने के लिये अपनी फ़ाइल को स्थानीय रूप से सहेजे।\n\nबॅकएंड डेटाबेसमध्ये जतन करू शकत नाही, कॅनव्हास खूप मोठा असल्याचे दिसते. तुम्ही तुमचे काम गमावणार नाही याची खात्री करण्यासाठी तुम्ही फाइल स्थानिक पातळीवर जतन करावी."
},
"toolBar": {
"selection": "निवड",
@@ -236,7 +238,7 @@
"resize": "आकार छोटा मोठा करताना SHIFT धरून तुम्ही प्रमाण मर्यादित करू शकता, \nकेंद्रापासून आकार छोटा मोठा करण्यासाठी ALT धरून ठेवा",
"resizeImage": "SHIFT धरून तुम्ही मुक्तपणे आकार मोठा छोटा करु शकता,\nकेंद्रापासून आकार मोठा छोटा करण्यासाठी ALT धरून ठेवा",
"rotate": "फिरवत असताना शिफ्ट धरून तुम्ही कोन मर्यादित करू शकता",
"lineEditor_info": "",
"lineEditor_info": "पॉइंट संपादित करण्यासाठी CtrlOrCmd दाबून ठेवुन डबल-क्लिक करा किंवा CtrlOrCmd + Enter बरोबर दाबा",
"lineEditor_pointSelected": "बिंदु (एक किव्हा अनेक) काढ़ण्या साठी डिलीट की दाबा,\nCtrlOrCmd बरोबार D प्रति साठी,\nकिव्हा ड्रेग हलवण्या साठी",
"lineEditor_nothingSelected": "संपादित करण्यासाठी एक बिंदू निवडा (अनेक निवडण्यासाठी SHIFT धरून ठेवा),\nकिंवा Alt धरून ठेवा आणि नवीन बिंदू जोडण्यासाठी क्लिक करा",
"placeImage": "प्रतिमा ठेवण्यासाठी क्लिक करा, किंवा त्याचा आकार बदलण्या साठी क्लिक करा आणि ड्रॅग करा",
@@ -395,7 +397,7 @@
"fileSavedToFilename": "{filename} मधे जतन झाली",
"canvas": "पटल",
"selection": "निवड",
"pasteAsSingleElement": ""
"pasteAsSingleElement": "एक घटक म्हणून चिपकावण्या साठी {{shortcut}} वापरा,\nकिंवा विद्यमान मजकूर संपादकात चिपकवा"
},
"colors": {
"ffffff": "पांढरा",
+3 -1
View File
@@ -201,7 +201,9 @@
"svgImageInsertError": "",
"invalidSVGString": "",
"cannotResolveCollabServer": "",
"importLibraryError": ""
"importLibraryError": "",
"collabSaveFailed": "",
"collabSaveFailed_sizeExceeded": ""
},
"toolBar": {
"selection": "ရွေးချယ်",
+3 -1
View File
@@ -201,7 +201,9 @@
"svgImageInsertError": "Kunne ikke sette inn SVG-bilde. SVG-koden ser ugyldig ut.",
"invalidSVGString": "Ugyldig SVG.",
"cannotResolveCollabServer": "Kunne ikke koble til samarbeidsserveren. Vennligst oppdater siden og prøv på nytt.",
"importLibraryError": "Kunne ikke laste bibliotek"
"importLibraryError": "Kunne ikke laste bibliotek",
"collabSaveFailed": "Kan ikke lagre i backend-databasen. Hvis problemer vedvarer, bør du lagre filen lokalt for å sikre at du ikke mister arbeidet.",
"collabSaveFailed_sizeExceeded": "Kunne ikke lagre til backend-databasen, lerretet ser ut til å være for stort. Du bør lagre filen lokalt for å sikre at du ikke mister arbeidet ditt."
},
"toolBar": {
"selection": "Velg",
+5 -3
View File
@@ -201,7 +201,9 @@
"svgImageInsertError": "Kon geen SVG-afbeelding invoegen. De SVG-opmaak ziet er niet geldig uit.",
"invalidSVGString": "Ongeldige SVG.",
"cannotResolveCollabServer": "Kan geen verbinding maken met de collab server. Herlaad de pagina en probeer het opnieuw.",
"importLibraryError": "Kon bibliotheek niet laden"
"importLibraryError": "Kon bibliotheek niet laden",
"collabSaveFailed": "Kan niet opslaan in de backend database. Als de problemen blijven bestaan, moet u het bestand lokaal opslaan om ervoor te zorgen dat u uw werk niet verliest.",
"collabSaveFailed_sizeExceeded": "Kan de backend database niet opslaan, het canvas lijkt te groot te zijn. U moet het bestand lokaal opslaan om ervoor te zorgen dat u uw werk niet verliest."
},
"toolBar": {
"selection": "Selectie",
@@ -236,7 +238,7 @@
"resize": "Houd tijdens het vergroten SHIFT ingedrukt om verhoudingen te behouden,\ngebruik ALT om vanuit het midden te vergroten/verkleinen",
"resizeImage": "",
"rotate": "Je kan hoeken beperken door SHIFT ingedrukt te houden wanneer je draait",
"lineEditor_info": "",
"lineEditor_info": "Houd CtrlOrCmd en Dubbelklik of druk op CtrlOrCmd + Enter om punten te bewerken",
"lineEditor_pointSelected": "",
"lineEditor_nothingSelected": "",
"placeImage": "",
@@ -395,7 +397,7 @@
"fileSavedToFilename": "Opgeslagen als {filename}",
"canvas": "canvas",
"selection": "selectie",
"pasteAsSingleElement": ""
"pasteAsSingleElement": "Gebruik {{shortcut}} om te plakken als een enkel element,\nof plak in een bestaande teksteditor"
},
"colors": {
"ffffff": "Wit",
+3 -1
View File
@@ -201,7 +201,9 @@
"svgImageInsertError": "Kunne ikkje sette inn SVG-biletet. SVG-koden ser ugyldig ut.",
"invalidSVGString": "Ugyldig SVG.",
"cannotResolveCollabServer": "Kunne ikkje kople til samarbeidsserveren. Ver vennleg å oppdatere inn sida og prøv på nytt.",
"importLibraryError": ""
"importLibraryError": "",
"collabSaveFailed": "",
"collabSaveFailed_sizeExceeded": ""
},
"toolBar": {
"selection": "Vel",
+3 -1
View File
@@ -201,7 +201,9 @@
"svgImageInsertError": "Insercion dimatge SVG impossibla. Las balisas SVG semblan invalidas.",
"invalidSVGString": "SVG invalid.",
"cannotResolveCollabServer": "Connexion impossibla al servidor collab. Mercés de recargar la pagina e tornar ensajar.",
"importLibraryError": "Impossible de cargar la bibliotèca"
"importLibraryError": "Impossible de cargar la bibliotèca",
"collabSaveFailed": "",
"collabSaveFailed_sizeExceeded": ""
},
"toolBar": {
"selection": "Seleccion",
+3 -1
View File
@@ -201,7 +201,9 @@
"svgImageInsertError": "",
"invalidSVGString": "SVG ਨਜਾਇਜ਼ ਹੈ।",
"cannotResolveCollabServer": "",
"importLibraryError": ""
"importLibraryError": "",
"collabSaveFailed": "",
"collabSaveFailed_sizeExceeded": ""
},
"toolBar": {
"selection": "ਚੋਣਕਾਰ",
+23 -23
View File
@@ -1,52 +1,52 @@
{
"ar-SA": 87,
"ar-SA": 86,
"bg-BG": 55,
"bn-BD": 61,
"ca-ES": 95,
"cs-CZ": 76,
"bn-BD": 60,
"ca-ES": 94,
"cs-CZ": 75,
"da-DK": 33,
"de-DE": 99,
"de-DE": 100,
"el-GR": 95,
"en": 100,
"es-ES": 100,
"eu-ES": 98,
"fa-IR": 98,
"fi-FI": 94,
"fr-FR": 98,
"gl-ES": 98,
"fi-FI": 93,
"fr-FR": 100,
"gl-ES": 99,
"he-IL": 91,
"hi-IN": 70,
"hi-IN": 71,
"hu-HU": 90,
"id-ID": 100,
"it-IT": 100,
"ja-JP": 98,
"kab-KAB": 95,
"id-ID": 99,
"it-IT": 99,
"ja-JP": 100,
"kab-KAB": 94,
"kk-KZ": 21,
"ko-KR": 96,
"ko-KR": 95,
"ku-TR": 98,
"lt-LT": 66,
"lv-LV": 98,
"mr-IN": 99,
"lv-LV": 99,
"mr-IN": 100,
"my-MM": 42,
"nb-NO": 100,
"nl-NL": 91,
"nn-NO": 91,
"oc-FR": 98,
"pa-IN": 84,
"pa-IN": 83,
"pl-PL": 85,
"pt-BR": 97,
"pt-BR": 100,
"pt-PT": 98,
"ro-RO": 100,
"ru-RU": 100,
"si-LK": 8,
"sk-SK": 100,
"sk-SK": 99,
"sl-SI": 100,
"sv-SE": 98,
"ta-IN": 94,
"tr-TR": 99,
"uk-UA": 98,
"ta-IN": 93,
"tr-TR": 98,
"uk-UA": 97,
"vi-VN": 20,
"zh-CN": 98,
"zh-CN": 100,
"zh-HK": 26,
"zh-TW": 100
}
+3 -1
View File
@@ -201,7 +201,9 @@
"svgImageInsertError": "Nie udało się wstawić obrazu SVG. Znacznik SVG wygląda na nieprawidłowy.",
"invalidSVGString": "Nieprawidłowy SVG.",
"cannotResolveCollabServer": "",
"importLibraryError": ""
"importLibraryError": "",
"collabSaveFailed": "",
"collabSaveFailed_sizeExceeded": ""
},
"toolBar": {
"selection": "Zaznaczenie",
+13 -11
View File
@@ -1,7 +1,7 @@
{
"labels": {
"paste": "Colar",
"pasteAsPlaintext": "",
"pasteAsPlaintext": "Colar como texto sem formatação",
"pasteCharts": "Colar gráficos",
"selectAll": "Selecionar tudo",
"multiSelect": "Adicionar elemento à seleção",
@@ -201,7 +201,9 @@
"svgImageInsertError": "Não foi possível inserir a imagem SVG. A marcação SVG parece inválida.",
"invalidSVGString": "SVG Inválido.",
"cannotResolveCollabServer": "Não foi possível conectar-se ao servidor colaborativo. Por favor, recarregue a página e tente novamente.",
"importLibraryError": "Não foi possível carregar a biblioteca"
"importLibraryError": "Não foi possível carregar a biblioteca",
"collabSaveFailed": "Não foi possível salvar no banco de dados do servidor. Se os problemas persistirem, salve o arquivo localmente para garantir que não perca o seu trabalho.",
"collabSaveFailed_sizeExceeded": "Não foi possível salvar no banco de dados do servidor, a tela parece ser muito grande. Se os problemas persistirem, salve o arquivo localmente para garantir que não perca o seu trabalho."
},
"toolBar": {
"selection": "Seleção",
@@ -215,7 +217,7 @@
"text": "Texto",
"library": "Biblioteca",
"lock": "Manter ativa a ferramenta selecionada após desenhar",
"penMode": "",
"penMode": "Modo caneta — impede o toque",
"link": "Adicionar/Atualizar link para uma forma selecionada",
"eraser": "Borracha"
},
@@ -236,7 +238,7 @@
"resize": "Você pode restringir proporções segurando SHIFT enquanto redimensiona,\nsegure ALT para redimensionar do centro",
"resizeImage": "Você pode redimensionar livremente segurando SHIFT,\nsegure ALT para redimensionar a partir do centro",
"rotate": "Você pode restringir os ângulos segurando SHIFT enquanto gira",
"lineEditor_info": "",
"lineEditor_info": "Pressione CtrlOuCmd e duplo-clique ou pressione CtrlOuCmd + Enter para editar pontos",
"lineEditor_pointSelected": "Pressione Delete para remover o(s) ponto(s),\nCtrl/Cmd+D para duplicar ou arraste para mover",
"lineEditor_nothingSelected": "Selecione um ponto para editar (segure SHIFT para selecionar vários) ou segure Alt e clique para adicionar novos pontos",
"placeImage": "Clique para colocar a imagem, ou clique e arraste para definir manualmente o seu tamanho",
@@ -312,8 +314,8 @@
"zoomToFit": "Ampliar para encaixar todos os elementos",
"zoomToSelection": "Ampliar a seleção",
"toggleElementLock": "Bloquear/desbloquear seleção",
"movePageUpDown": "",
"movePageLeftRight": ""
"movePageUpDown": "Mover a página para cima/baixo",
"movePageLeftRight": "Mover a página para esquerda/direita"
},
"clearCanvasDialog": {
"title": "Limpar a tela"
@@ -395,7 +397,7 @@
"fileSavedToFilename": "Salvo em {filename}",
"canvas": "tela",
"selection": "seleção",
"pasteAsSingleElement": ""
"pasteAsSingleElement": "Use {{shortcut}} para colar como um único elemento,\nou cole em um editor de texto já existente"
},
"colors": {
"ffffff": "Braco",
@@ -446,9 +448,9 @@
},
"welcomeScreen": {
"data": "Todos os dados são salvos localmente no seu navegador.",
"switchToPlusApp": "",
"menuHints": "",
"toolbarHints": "",
"helpHints": ""
"switchToPlusApp": "Você queria ir para o Excalidraw+ em vez disso?",
"menuHints": "Exportar, preferências, idiomas, ...",
"toolbarHints": "Escolha uma ferramenta & Comece a desenhar!",
"helpHints": "Atalhos & ajuda"
}
}
+3 -1
View File
@@ -201,7 +201,9 @@
"svgImageInsertError": "Não foi possível inserir a imagem SVG. A marcação SVG parece inválida.",
"invalidSVGString": "SVG inválido.",
"cannotResolveCollabServer": "Não foi possível fazer a ligação ao servidor colaborativo. Por favor, volte a carregar a página e tente novamente.",
"importLibraryError": "Não foi possível carregar a biblioteca"
"importLibraryError": "Não foi possível carregar a biblioteca",
"collabSaveFailed": "",
"collabSaveFailed_sizeExceeded": ""
},
"toolBar": {
"selection": "Seleção",
+3 -1
View File
@@ -201,7 +201,9 @@
"svgImageInsertError": "Imaginea SVG nu a putut fi introdus. Marcajul SVG pare invalid.",
"invalidSVGString": "SVG invalid.",
"cannotResolveCollabServer": "Nu a putut fi realizată conexiunea la serverul de colaborare. Reîncarcă pagina și încearcă din nou.",
"importLibraryError": "Biblioteca nu a putut fi încărcată"
"importLibraryError": "Biblioteca nu a putut fi încărcată",
"collabSaveFailed": "Nu s-a putut salva în baza de date la nivel de server. Dacă problemele persistă, ar trebui să salvezi fișierul la nivel local pentru a te asigura că nu îți pierzi munca.",
"collabSaveFailed_sizeExceeded": "Nu s-a putut salva în baza de date la nivel de server, întrucât se pare că pânza este prea mare. Ar trebui să salvezi fișierul la nivel local pentru a te asigura că nu îți pierzi munca."
},
"toolBar": {
"selection": "Selecție",
+3 -1
View File
@@ -201,7 +201,9 @@
"svgImageInsertError": "Не удалось вставить изображение SVG. Разметка SVG выглядит недействительной.",
"invalidSVGString": "Некорректный SVG.",
"cannotResolveCollabServer": "Не удалось подключиться к серверу совместного редактирования. Перезагрузите страницу и повторите попытку.",
"importLibraryError": "Не удалось загрузить библиотеку"
"importLibraryError": "Не удалось загрузить библиотеку",
"collabSaveFailed": "Не удалось сохранить в базу данных. Если проблема повторится, нужно будет сохранить файл локально, чтобы быть уверенным, что вы не потеряете вашу работу.",
"collabSaveFailed_sizeExceeded": "Не удалось сохранить в базу данных. Похоже, что холст слишком большой. Нужно сохранить файл локально, чтобы быть уверенным, что вы не потеряете вашу работу."
},
"toolBar": {
"selection": "Выделение области",
+3 -1
View File
@@ -201,7 +201,9 @@
"svgImageInsertError": "",
"invalidSVGString": "",
"cannotResolveCollabServer": "",
"importLibraryError": ""
"importLibraryError": "",
"collabSaveFailed": "",
"collabSaveFailed_sizeExceeded": ""
},
"toolBar": {
"selection": "",
+3 -1
View File
@@ -201,7 +201,9 @@
"svgImageInsertError": "Nepodarilo sa vložiť SVG obrázok. SVG formát je pravdepodobne nevalidný.",
"invalidSVGString": "Nevalidné SVG.",
"cannotResolveCollabServer": "Nepodarilo sa pripojiť ku kolaboračnému serveru. Prosím obnovte stránku a skúste to znovu.",
"importLibraryError": "Nepodarilo sa načítať knižnicu"
"importLibraryError": "Nepodarilo sa načítať knižnicu",
"collabSaveFailed": "",
"collabSaveFailed_sizeExceeded": ""
},
"toolBar": {
"selection": "Výber",
+3 -1
View File
@@ -201,7 +201,9 @@
"svgImageInsertError": "Vstavljanje slike SVG ni uspelo. Oznake SVG so videti neveljavne.",
"invalidSVGString": "Neveljaven SVG.",
"cannotResolveCollabServer": "Povezave s strežnikom za sodelovanje ni bilo mogoče vzpostaviti. Ponovno naložite stran in poskusite znova.",
"importLibraryError": "Nalaganje knjižnice ni uspelo"
"importLibraryError": "Nalaganje knjižnice ni uspelo",
"collabSaveFailed": "Ni bilo mogoče shraniti v zaledno bazo podatkov. Če se težave nadaljujejo, shranite datoteko lokalno, da ne boste izgubili svojega dela.",
"collabSaveFailed_sizeExceeded": "Ni bilo mogoče shraniti v zaledno bazo podatkov, zdi se, da je platno preveliko. Datoteko shranite lokalno, da ne izgubite svojega dela."
},
"toolBar": {
"selection": "Izbor",
+3 -1
View File
@@ -201,7 +201,9 @@
"svgImageInsertError": "Kunde inte infoga SVG-bild. SVG-koden ser ogiltig ut.",
"invalidSVGString": "Ogiltig SVG.",
"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_sizeExceeded": ""
},
"toolBar": {
"selection": "Markering",
+3 -1
View File
@@ -201,7 +201,9 @@
"svgImageInsertError": "எஸ்விஜி படத்தைப் புகுத்தவியலா. எஸ்விஜியின் மார்க்அப் செல்லாததாக தெரிகிறது.",
"invalidSVGString": "செல்லாத SVG.",
"cannotResolveCollabServer": "",
"importLibraryError": "நூலகத்தை ஏற்ற முடியவில்லை"
"importLibraryError": "நூலகத்தை ஏற்ற முடியவில்லை",
"collabSaveFailed": "",
"collabSaveFailed_sizeExceeded": ""
},
"toolBar": {
"selection": "தெரிவு",
+3 -1
View File
@@ -201,7 +201,9 @@
"svgImageInsertError": "SVG resmi eklenemedi. SVG işaretlemesi geçersiz görünüyor.",
"invalidSVGString": "Geçersiz SVG.",
"cannotResolveCollabServer": "İş birliği sunucusuna bağlanılamıyor. Lütfen sayfayı yenileyip tekrar deneyin.",
"importLibraryError": "Kütüphane yüklenemedi"
"importLibraryError": "Kütüphane yüklenemedi",
"collabSaveFailed": "",
"collabSaveFailed_sizeExceeded": ""
},
"toolBar": {
"selection": "Seçme",
+3 -1
View File
@@ -201,7 +201,9 @@
"svgImageInsertError": "Не вдалося вставити SVG-зображення. Помилка розмітки SVG.",
"invalidSVGString": "Недійсний SVG.",
"cannotResolveCollabServer": "Не вдалося приєднатися до сервера. Перезавантажте сторінку та повторіть спробу.",
"importLibraryError": "Не вдалося завантажити бібліотеку"
"importLibraryError": "Не вдалося завантажити бібліотеку",
"collabSaveFailed": "",
"collabSaveFailed_sizeExceeded": ""
},
"toolBar": {
"selection": "Виділення",
+4 -2
View File
@@ -1,7 +1,7 @@
{
"labels": {
"paste": "Dán",
"pasteAsPlaintext": "",
"pasteAsPlaintext": "Dán kiểu văn bản thuần",
"pasteCharts": "Dán biểu đồ",
"selectAll": "Chọn tất cả",
"multiSelect": "Thêm mới vào Select",
@@ -201,7 +201,9 @@
"svgImageInsertError": "",
"invalidSVGString": "",
"cannotResolveCollabServer": "",
"importLibraryError": ""
"importLibraryError": "",
"collabSaveFailed": "",
"collabSaveFailed_sizeExceeded": ""
},
"toolBar": {
"selection": "",

Some files were not shown because too many files have changed in this diff Show More