Compare commits

..

14 Commits

Author SHA1 Message Date
Arun Kumar eb206cc932 Fix name input style in dark mode 2021-03-20 16:02:15 +05:30
Aakansha Doshi 16c287c848 Give preference to name prop when initialData.appState.name is present and update specs 2021-03-20 14:25:02 +05:30
Aakansha Doshi 78024873e5 no border and on hover no background change 2021-03-20 13:52:15 +05:30
Aakansha Doshi 4e41bd9dbb Merge remote-tracking branch 'origin/master' into updatescene-name 2021-03-20 13:43:01 +05:30
Aakansha Doshi edc23b854f minor fixes 2021-03-20 13:41:12 +05:30
Arun Kumar 4843c49556 remove eempty lines 2021-03-20 01:15:01 +05:30
Arun Kumar d565413082 Add tests, update changelog and minor fixes 2021-03-20 01:07:49 +05:30
Aakansha Doshi dcda7184d0 Merge remote-tracking branch 'origin/master' into updatescene-name 2021-03-20 00:24:24 +05:30
Arun Kumar 8d413670c8 Remove redundant if statement 2021-03-19 01:32:14 +05:30
Arun Kumar f774452124 Remove customName from state 2021-03-19 01:30:27 +05:30
Arun Kumar db4ed1ecb1 Make requested changes 2021-03-18 22:49:37 +05:30
Arun Kumar 489f45b910 Make requested changes 2021-03-18 16:36:12 +05:30
Arun Kumar a17be085b0 Revert "Allow updating name on updateScene"
This reverts commit 4e07a608d3.
2021-03-18 16:06:28 +05:30
Arun Kumar 4e07a608d3 Allow updating name on updateScene 2021-03-18 01:04:02 +05:30
68 changed files with 1417 additions and 1610 deletions
+1 -1
View File
@@ -1,5 +1,5 @@
REACT_APP_BACKEND_V1_GET_URL=https://json.excalidraw.com/api/v1/
REACT_APP_BACKEND_V2_GET_URL=https://json.excalidraw.com/api/v2/
REACT_APP_BACKEND_V2_POST_URL=https://json.excalidraw.com/api/v2/post/
REACT_APP_SOCKET_SERVER_URL=https://excalidraw-portal.uc.r.appspot.com
REACT_APP_SOCKET_SERVER_URL=https://portal.excalidraw.com
REACT_APP_FIREBASE_CONFIG='{"apiKey":"AIzaSyAd15pYlMci_xIp9ko6wkEsDzAAA0Dn0RU","authDomain":"excalidraw-room-persistence.firebaseapp.com","databaseURL":"https://excalidraw-room-persistence.firebaseio.com","projectId":"excalidraw-room-persistence","storageBucket":"excalidraw-room-persistence.appspot.com","messagingSenderId":"654800341332","appId":"1:654800341332:web:4a692de832b55bd57ce0c1"}'
+4 -4
View File
@@ -23,11 +23,11 @@
"@sentry/integrations": "6.2.1",
"@testing-library/jest-dom": "5.11.9",
"@testing-library/react": "11.2.5",
"@types/jest": "26.0.21",
"@types/react": "17.0.3",
"@types/react-dom": "17.0.2",
"@types/jest": "26.0.20",
"@types/react": "17.0.2",
"@types/react-dom": "17.0.1",
"@types/socket.io-client": "1.4.36",
"browser-fs-access": "0.15.3",
"browser-fs-access": "0.14.2",
"clsx": "1.1.1",
"firebase": "8.2.10",
"i18next-browser-languagedetector": "6.0.1",
-2
View File
@@ -88,8 +88,6 @@
<link rel="stylesheet" href="fonts.css" type="text/css" />
<script>
window.EXCALIDRAW_ASSET_PATH = "/";
// setting this so that libraries installation reuses this window tab.
window.name = "_excalidraw";
</script>
<% if (process.env.REACT_APP_GOOGLE_ANALYTICS_ID) { %>
<script
+4 -5
View File
@@ -11,7 +11,6 @@ import { t } from "../i18n";
import useIsMobile from "../is-mobile";
import { KEYS } from "../keys";
import { register } from "./register";
import { supported } from "browser-fs-access";
export const actionChangeProjectName = register({
name: "changeProjectName",
@@ -24,9 +23,7 @@ export const actionChangeProjectName = register({
label={t("labels.fileTitle")}
value={appState.name || "Unnamed"}
onChange={(name: string) => updateData(name)}
isNameEditable={
typeof appProps.name === "undefined" && !appState.viewModeEnabled
}
isNameEditable={typeof appProps.name === "undefined"}
/>
),
});
@@ -165,7 +162,9 @@ export const actionSaveAsScene = register({
title={t("buttons.saveAs")}
aria-label={t("buttons.saveAs")}
showAriaLabel={useIsMobile()}
hidden={!supported}
hidden={
!("chooseFileSystemEntries" in window || "showOpenFilePicker" in window)
}
onClick={() => updateData(null)}
/>
),
+15 -64
View File
@@ -1,6 +1,7 @@
import React from "react";
import { AppState } from "../../src/types";
import { ButtonIconSelect } from "../components/ButtonIconSelect";
import { ButtonSelect } from "../components/ButtonSelect";
import { ColorPicker } from "../components/ColorPicker";
import { IconPicker } from "../components/IconPicker";
import {
@@ -20,16 +21,6 @@ import {
StrokeStyleDottedIcon,
StrokeStyleSolidIcon,
StrokeWidthIcon,
FontSizeSmallIcon,
FontSizeMediumIcon,
FontSizeLargeIcon,
FontSizeExtraLargeIcon,
FontFamilyHandDrawnIcon,
FontFamilyNormalIcon,
FontFamilyCodeIcon,
TextAlignLeftIcon,
TextAlignCenterIcon,
TextAlignRightIcon,
} from "../components/icons";
import { DEFAULT_FONT_FAMILY, DEFAULT_FONT_SIZE } from "../constants";
import {
@@ -422,29 +413,13 @@ export const actionChangeFontSize = register({
PanelComponent: ({ elements, appState, updateData }) => (
<fieldset>
<legend>{t("labels.fontSize")}</legend>
<ButtonIconSelect
<ButtonSelect
group="font-size"
options={[
{
value: 16,
text: t("labels.small"),
icon: <FontSizeSmallIcon theme={appState.theme} />,
},
{
value: 20,
text: t("labels.medium"),
icon: <FontSizeMediumIcon theme={appState.theme} />,
},
{
value: 28,
text: t("labels.large"),
icon: <FontSizeLargeIcon theme={appState.theme} />,
},
{
value: 36,
text: t("labels.veryLarge"),
icon: <FontSizeExtraLargeIcon theme={appState.theme} />,
},
{ value: 16, text: t("labels.small") },
{ value: 20, text: t("labels.medium") },
{ value: 28, text: t("labels.large") },
{ value: 36, text: t("labels.veryLarge") },
]}
value={getFormValue(
elements,
@@ -481,28 +456,16 @@ export const actionChangeFontFamily = register({
};
},
PanelComponent: ({ elements, appState, updateData }) => {
const options: { value: FontFamily; text: string; icon: JSX.Element }[] = [
{
value: 1,
text: t("labels.handDrawn"),
icon: <FontFamilyHandDrawnIcon theme={appState.theme} />,
},
{
value: 2,
text: t("labels.normal"),
icon: <FontFamilyNormalIcon theme={appState.theme} />,
},
{
value: 3,
text: t("labels.code"),
icon: <FontFamilyCodeIcon theme={appState.theme} />,
},
const options: { value: FontFamily; text: string }[] = [
{ value: 1, text: t("labels.handDrawn") },
{ value: 2, text: t("labels.normal") },
{ value: 3, text: t("labels.code") },
];
return (
<fieldset>
<legend>{t("labels.fontFamily")}</legend>
<ButtonIconSelect<FontFamily | false>
<ButtonSelect<FontFamily | false>
group="font-family"
options={options}
value={getFormValue(
@@ -543,24 +506,12 @@ export const actionChangeTextAlign = register({
PanelComponent: ({ elements, appState, updateData }) => (
<fieldset>
<legend>{t("labels.textAlign")}</legend>
<ButtonIconSelect<TextAlign | false>
<ButtonSelect<TextAlign | false>
group="text-align"
options={[
{
value: "left",
text: t("labels.left"),
icon: <TextAlignLeftIcon theme={appState.theme} />,
},
{
value: "center",
text: t("labels.center"),
icon: <TextAlignCenterIcon theme={appState.theme} />,
},
{
value: "right",
text: t("labels.right"),
icon: <TextAlignRightIcon theme={appState.theme} />,
},
{ value: "left", text: t("labels.left") },
{ value: "center", text: t("labels.center") },
{ value: "right", text: t("labels.right") },
]}
value={getFormValue(
elements,
+15 -14
View File
@@ -7,10 +7,12 @@ import { AppState } from "./types";
import { SVG_EXPORT_TAG } from "./scene/export";
import { tryParseSpreadsheet, Spreadsheet, VALID_SPREADSHEET } from "./charts";
import { canvasToBlob } from "./data/blob";
import { EXPORT_DATA_TYPES } from "./constants";
const TYPE_ELEMENTS = "excalidraw/elements";
type ElementsClipboard = {
type: typeof EXPORT_DATA_TYPES.excalidrawClipboard;
type: typeof TYPE_ELEMENTS;
created: number;
elements: ExcalidrawElement[];
};
@@ -29,16 +31,8 @@ export const probablySupportsClipboardBlob =
"ClipboardItem" in window &&
"toBlob" in HTMLCanvasElement.prototype;
const clipboardContainsElements = (
contents: any,
): contents is { elements: ExcalidrawElement[] } => {
if (
[
EXPORT_DATA_TYPES.excalidraw,
EXPORT_DATA_TYPES.excalidrawClipboard,
].includes(contents?.type) &&
Array.isArray(contents.elements)
) {
const isElementsClipboard = (contents: any): contents is ElementsClipboard => {
if (contents?.type === TYPE_ELEMENTS) {
return true;
}
return false;
@@ -49,7 +43,8 @@ export const copyToClipboard = async (
appState: AppState,
) => {
const contents: ElementsClipboard = {
type: EXPORT_DATA_TYPES.excalidrawClipboard,
type: TYPE_ELEMENTS,
created: Date.now(),
elements: getSelectedElements(elements, appState),
};
const json = JSON.stringify(contents);
@@ -136,9 +131,15 @@ export const parseClipboard = async (
try {
const systemClipboardData = JSON.parse(systemClipboard);
if (clipboardContainsElements(systemClipboardData)) {
// system clipboard elements are newer than in-app clipboard
if (
isElementsClipboard(systemClipboardData) &&
(!appClipboardData?.created ||
appClipboardData.created < systemClipboardData.created)
) {
return { elements: systemClipboardData.elements };
}
// in-app clipboard is newer than system clipboard
return appClipboardData;
} catch {
// system clipboard doesn't contain excalidraw elements → return plaintext
+7 -4
View File
@@ -3,7 +3,6 @@ import React from "react";
import { RoughCanvas } from "roughjs/bin/canvas";
import rough from "roughjs/bin/rough";
import clsx from "clsx";
import { supported } from "browser-fs-access";
import {
actionAddToLibrary,
@@ -526,7 +525,8 @@ class App extends React.Component<ExcalidrawProps, AppState> {
let zenModeEnabled = actionResult?.appState?.zenModeEnabled || false;
let gridSize = actionResult?.appState?.gridSize || null;
let theme = actionResult?.appState?.theme || "light";
let name = actionResult?.appState?.name ?? this.state.name;
let name = actionResult?.appState?.name || this.state.name;
if (typeof this.props.viewModeEnabled !== "undefined") {
viewModeEnabled = this.props.viewModeEnabled;
}
@@ -1405,7 +1405,7 @@ class App extends React.Component<ExcalidrawProps, AppState> {
return;
}
if (event[KEYS.CTRL_OR_CMD] && this.state.isBindingEnabled) {
if (event[KEYS.CTRL_OR_CMD]) {
this.setState({ isBindingEnabled: false });
}
@@ -3610,7 +3610,10 @@ class App extends React.Component<ExcalidrawProps, AppState> {
file?.name.endsWith(".excalidraw")
) {
this.setState({ isLoading: true });
if (supported) {
if (
"chooseFileSystemEntries" in window ||
"showOpenFilePicker" in window
) {
try {
// This will only work as of Chrome 86,
// but can be safely ignored on older releases.
-10
View File
@@ -31,16 +31,9 @@
.ExportDialog__name {
grid-column: project-name;
margin: auto;
display: flex;
align-items: center;
.TextInput {
height: calc(1rem - 3px);
width: 200px;
overflow: hidden;
text-align: center;
margin-left: 8px;
text-overflow: ellipsis;
&--readonly {
background: none;
@@ -48,9 +41,6 @@
&:hover {
background: none;
}
width: auto;
max-width: 200px;
padding-left: 2px;
}
}
}
+3 -9
View File
@@ -9,13 +9,7 @@ type HelpIconProps = {
};
export const HelpIcon = (props: HelpIconProps) => (
<button
className="help-icon"
onClick={props.onClick}
type="button"
title={`${props.title} — ?`}
aria-label={props.title}
>
{questionCircle}
</button>
<label title={`${props.title} — ?`} className="help-icon">
<div onClick={props.onClick}>{questionCircle}</div>
</label>
);
+29 -34
View File
@@ -144,41 +144,36 @@ const LibraryMenuItems = ({
});
}}
/>
{!!library.length && (
<>
<ToolButton
key="export"
type="button"
title={t("buttons.export")}
aria-label={t("buttons.export")}
icon={exportFile}
onClick={() => {
saveLibraryAsJSON()
.catch(muteFSAbortError)
.catch((error) => {
setAppState({ errorMessage: error.message });
});
}}
/>
<ToolButton
key="reset"
type="button"
title={t("buttons.resetLibrary")}
aria-label={t("buttons.resetLibrary")}
icon={trash}
onClick={() => {
if (window.confirm(t("alerts.resetLibrary"))) {
Library.resetLibrary();
setLibraryItems([]);
}
}}
/>
</>
)}
<ToolButton
key="export"
type="button"
title={t("buttons.export")}
aria-label={t("buttons.export")}
icon={exportFile}
onClick={() => {
saveLibraryAsJSON()
.catch(muteFSAbortError)
.catch((error) => {
setAppState({ errorMessage: error.message });
});
}}
/>
<ToolButton
key="reset"
type="button"
title={t("buttons.resetLibrary")}
aria-label={t("buttons.resetLibrary")}
icon={trash}
onClick={() => {
if (window.confirm(t("alerts.resetLibrary"))) {
Library.resetLibrary();
setLibraryItems([]);
}
}}
/>
<a
href={`https://libraries.excalidraw.com?target=${
window.name || "_blank"
}&referrer=${referrer}`}
href={`https://libraries.excalidraw.com?referrer=${referrer}`}
target="_excalidraw_libraries"
>
{t("labels.libraries")}
+39 -30
View File
@@ -1,6 +1,7 @@
import "./TextInput.scss";
import React, { Component } from "react";
import { selectNode, removeSelection } from "../utils";
type Props = {
value: string;
@@ -9,18 +10,17 @@ type Props = {
isNameEditable: boolean;
};
type State = {
fileName: string;
};
export class ProjectName extends Component<Props, State> {
state = {
fileName: this.props.value,
export class ProjectName extends Component<Props> {
private handleFocus = (event: React.FocusEvent<HTMLElement>) => {
selectNode(event.currentTarget);
};
private handleBlur = (event: any) => {
const value = event.target.value;
private handleBlur = (event: React.FocusEvent<HTMLElement>) => {
const value = event.currentTarget.innerText.trim();
if (value !== this.props.value) {
this.props.onChange(value);
}
removeSelection();
};
private handleKeyDown = (event: React.KeyboardEvent<HTMLElement>) => {
@@ -32,30 +32,39 @@ export class ProjectName extends Component<Props, State> {
event.currentTarget.blur();
}
};
private makeEditable = (editable: HTMLSpanElement | null) => {
if (!editable) {
return;
}
try {
editable.contentEditable = "plaintext-only";
} catch {
editable.contentEditable = "true";
}
};
public render() {
return (
<>
<label htmlFor="file-name">
{`${this.props.label}${this.props.isNameEditable ? "" : ":"}`}
</label>
{this.props.isNameEditable ? (
<input
className="TextInput"
onBlur={this.handleBlur}
onKeyDown={this.handleKeyDown}
id="file-name"
value={this.state.fileName}
onChange={(event) =>
this.setState({ fileName: event.target.value })
}
/>
) : (
<span className="TextInput TextInput--readonly" id="file-name">
{this.props.value}
</span>
)}
</>
return this.props.isNameEditable ? (
<span
suppressContentEditableWarning
ref={this.makeEditable}
data-type="wysiwyg"
className="TextInput"
role="textbox"
aria-label={this.props.label}
onBlur={this.handleBlur}
onKeyDown={this.handleKeyDown}
onFocus={this.handleFocus}
>
{this.props.value}
</span>
) : (
<span
className="TextInput TextInput--readonly"
aria-label={this.props.label}
>
{this.props.value}
</span>
);
}
}
-134
View File
@@ -123,22 +123,6 @@ export const shareIOS = createIcon(
{ width: 24, height: 24 },
);
export const shareWindows = createIcon(
<>
<path
stroke="currentColor"
fill="currentColor"
d="M40 5.6v6.1l-4.1.7c-8.9 1.4-16.5 6.9-20.6 15C13 32 10.9 43 12.4 43c.4 0 2.4-1.3 4.4-3 5-3.9 12.1-7 18.2-7.7l5-.6v12.8l11.2-11.3L62.5 22 51.2 10.8 40-.5v6.1zm10.2 22.6L44 34.5v-6.8l-6.9.6c-3.9.3-9.8 1.7-13.2 3.1-3.5 1.4-6.5 2.4-6.7 2.2-.9-1 3-7.5 6.4-10.8C28 18.6 34.4 16 40.1 16c3.7 0 3.9-.1 3.9-3.2V9.5l6.2 6.3 6.3 6.2-6.3 6.2z"
/>
<path
stroke="currentColor"
fill="currentColor"
d="M0 36v20h48v-6.2c0-6 0-6.1-2-4.3-1.1 1-2 2.9-2 4.2V52H4V34c0-17.3-.1-18-2-18s-2 .7-2 20z"
/>
</>,
{ width: 64, height: 64 },
);
// Icon imported form Storybook
// Storybook is licensed under MIT https://github.com/storybookjs/storybook/blob/next/LICENSE
export const resetZoom = createIcon(
@@ -810,121 +794,3 @@ export const ArrowheadBarIcon = React.memo(
{ width: 40, height: 20 },
),
);
export const FontSizeSmallIcon = React.memo(
({ theme }: { theme: "light" | "dark" }) =>
createIcon(
<path
fill={iconFillColor(theme)}
d="M 0 69.092 L 0 55.03 A 124.24 124.24 0 0 0 4.706 57.02 Q 6.826 57.863 8.708 58.5 A 53.466 53.466 0 0 0 12.231 59.571 Q 17.236 60.889 21.387 60.889 A 20.909 20.909 0 0 0 24.265 60.704 Q 25.719 60.502 26.903 60.077 A 8.649 8.649 0 0 0 29.028 58.985 Q 31.689 57.08 31.689 53.321 Q 31.689 51.221 30.518 49.585 A 10.126 10.126 0 0 0 29.282 48.177 Q 28.352 47.287 27.075 46.436 A 23.719 23.719 0 0 0 25.752 45.627 Q 23.774 44.492 20.176 42.735 A 254.44 254.44 0 0 0 17.822 41.602 Q 11.503 38.631 8.236 35.888 A 19.742 19.742 0 0 1 8.008 35.694 A 22.18 22.18 0 0 1 2.783 29.102 Q 0.83 25.342 0.83 20.313 A 22.471 22.471 0 0 1 1.733 13.778 A 17.283 17.283 0 0 1 7.251 5.42 A 21.486 21.486 0 0 1 15.177 1.272 Q 18.361 0.338 22.166 0.09 A 43.573 43.573 0 0 1 25 0 A 42.399 42.399 0 0 1 34.349 1.01 A 39.075 39.075 0 0 1 35.62 1.319 A 67.407 67.407 0 0 1 42.108 3.382 A 83.357 83.357 0 0 1 46.191 5.03 L 41.309 16.797 Q 35.596 14.453 31.86 13.526 A 30.762 30.762 0 0 0 25.417 12.612 A 28.337 28.337 0 0 0 24.512 12.598 A 14.846 14.846 0 0 0 22.022 12.793 Q 19.498 13.224 17.92 14.6 Q 15.625 16.602 15.625 19.824 Q 15.625 21.826 16.553 23.316 Q 17.48 24.805 19.507 26.197 A 18.343 18.343 0 0 0 20.659 26.912 Q 22.596 28.035 26.516 29.953 A 299.99 299.99 0 0 0 29.102 31.201 Q 37.91 35.412 41.841 39.642 A 16.553 16.553 0 0 1 42.822 40.796 A 17.675 17.675 0 0 1 46.301 49.233 A 23.517 23.517 0 0 1 46.533 52.588 A 21.581 21.581 0 0 1 45.471 59.515 A 17.733 17.733 0 0 1 39.575 67.823 Q 33.745 72.486 24.094 73.243 A 49.683 49.683 0 0 1 20.215 73.389 A 51.712 51.712 0 0 1 9.448 72.315 A 40.672 40.672 0 0 1 0 69.092 Z"
/>,
{ width: 47, height: 77 },
),
);
export const FontSizeMediumIcon = React.memo(
({ theme }: { theme: "light" | "dark" }) =>
createIcon(
<path
fill={iconFillColor(theme)}
d="M 44.092 71.387 L 30.225 71.387 L 13.037 15.381 L 12.598 15.381 A 1505.093 1505.093 0 0 1 12.959 22.313 Q 13.426 31.715 13.508 36.4 A 102.991 102.991 0 0 1 13.525 38.184 L 13.525 71.387 L 0 71.387 L 0 0 L 20.605 0 L 37.5 54.59 L 37.793 54.59 L 55.713 0 L 76.318 0 L 76.318 71.387 L 62.207 71.387 L 62.207 37.598 Q 62.207 35.205 62.28 32.08 A 160.703 160.703 0 0 1 62.326 30.544 Q 62.452 26.754 62.866 17.168 A 5390.536 5390.536 0 0 1 62.939 15.479 L 62.5 15.479 L 44.092 71.387 Z"
/>,
{ width: 77, height: 75 },
),
);
export const FontSizeLargeIcon = React.memo(
({ theme }: { theme: "light" | "dark" }) =>
createIcon(
<path
fill={iconFillColor(theme)}
d="M 44.092 71.387 L 0 71.387 L 0 0 L 15.137 0 L 15.137 58.887 L 44.092 58.887 L 44.092 71.387 Z"
/>,
{ width: 45, height: 75 },
),
);
export const FontSizeExtraLargeIcon = React.memo(
({ theme }: { theme: "light" | "dark" }) =>
createIcon(
<path
fill={iconFillColor(theme)}
d="M 42.578 35.4 L 66.699 71.387 L 49.414 71.387 L 32.813 44.385 L 16.211 71.387 L 0 71.387 L 23.682 34.57 L 1.514 0 L 18.213 0 L 33.594 25.684 L 48.682 0 L 64.99 0 L 42.578 35.4 Z M 119.775 71.387 L 75.684 71.387 L 75.684 0 L 90.82 0 L 90.82 58.887 L 119.775 58.887 L 119.775 71.387 Z"
/>,
{ width: 120, height: 75 },
),
);
export const FontFamilyHandDrawnIcon = React.memo(
({ theme }: { theme: "light" | "dark" }) =>
createIcon(
<path
fill={iconFillColor(theme)}
d="M290.74 93.24l128.02 128.02-277.99 277.99-114.14 12.6C11.35 513.54-1.56 500.62.14 485.34l12.7-114.22 277.9-277.88zm207.2-19.06l-60.11-60.11c-18.75-18.75-49.16-18.75-67.91 0l-56.55 56.55 128.02 128.02 56.55-56.55c18.75-18.76 18.75-49.16 0-67.91z"
/>,
{ width: 448, height: 512 },
),
);
export const FontFamilyNormalIcon = React.memo(
({ theme }: { theme: "light" | "dark" }) =>
createIcon(
<>
<path
fill={iconFillColor(theme)}
d="M 63.818 71.68 L 54.492 71.68 L 45.898 49.561 L 17.578 49.561 L 9.082 71.68 L 0 71.68 L 27.881 0 L 35.986 0 L 63.818 71.68 Z M 20.605 41.602 L 43.213 41.602 L 35.205 19.971 L 31.787 9.277 Q 30.322 15.137 28.711 19.971 L 20.605 41.602 Z"
/>
<path
fill={iconFillColor(theme)}
d="M 68.994 71.68 L 52.686 71.68 L 47.51 54.688 L 21.484 54.688 L 16.309 71.68 L 0 71.68 L 25.195 0 L 43.701 0 L 68.994 71.68 Z M 25.293 41.992 L 43.896 41.992 A 27590.463 27590.463 0 0 1 42.2 36.532 Q 36.965 19.676 35.937 16.273 A 120.932 120.932 0 0 1 35.815 15.869 A 131.65 131.65 0 0 1 35.396 14.435 Q 34.951 12.879 34.675 11.741 A 34.866 34.866 0 0 1 34.521 11.084 A 141.762 141.762 0 0 1 33.706 14.075 Q 31.482 21.957 25.293 41.992 Z"
/>
</>,
{ width: 70, height: 78 },
),
);
export const FontFamilyCodeIcon = React.memo(
({ theme }: { theme: "light" | "dark" }) =>
createIcon(
<>
<path
fill={iconFillColor(theme)}
d="M278.9 511.5l-61-17.7c-6.4-1.8-10-8.5-8.2-14.9L346.2 8.7c1.8-6.4 8.5-10 14.9-8.2l61 17.7c6.4 1.8 10 8.5 8.2 14.9L293.8 503.3c-1.9 6.4-8.5 10.1-14.9 8.2zm-114-112.2l43.5-46.4c4.6-4.9 4.3-12.7-.8-17.2L117 256l90.6-79.7c5.1-4.5 5.5-12.3.8-17.2l-43.5-46.4c-4.5-4.8-12.1-5.1-17-.5L3.8 247.2c-5.1 4.7-5.1 12.8 0 17.5l144.1 135.1c4.9 4.6 12.5 4.4 17-.5zm327.2.6l144.1-135.1c5.1-4.7 5.1-12.8 0-17.5L492.1 112.1c-4.8-4.5-12.4-4.3-17 .5L431.6 159c-4.6 4.9-4.3 12.7.8 17.2L523 256l-90.6 79.7c-5.1 4.5-5.5 12.3-.8 17.2l43.5 46.4c4.5 4.9 12.1 5.1 17 .6z"
/>
</>,
{ width: 640, height: 512 },
),
);
export const TextAlignLeftIcon = React.memo(
({ theme }: { theme: "light" | "dark" }) =>
createIcon(
<path
d="M12.83 352h262.34A12.82 12.82 0 00288 339.17v-38.34A12.82 12.82 0 00275.17 288H12.83A12.82 12.82 0 000 300.83v38.34A12.82 12.82 0 0012.83 352zm0-256h262.34A12.82 12.82 0 00288 83.17V44.83A12.82 12.82 0 00275.17 32H12.83A12.82 12.82 0 000 44.83v38.34A12.82 12.82 0 0012.83 96zM432 160H16a16 16 0 00-16 16v32a16 16 0 0016 16h416a16 16 0 0016-16v-32a16 16 0 00-16-16zm0 256H16a16 16 0 00-16 16v32a16 16 0 0016 16h416a16 16 0 0016-16v-32a16 16 0 00-16-16z"
fill={iconFillColor(theme)}
/>,
{ width: 448, height: 512 },
),
);
export const TextAlignCenterIcon = React.memo(
({ theme }: { theme: "light" | "dark" }) =>
createIcon(
<path
d="M432 160H16a16 16 0 00-16 16v32a16 16 0 0016 16h416a16 16 0 0016-16v-32a16 16 0 00-16-16zm0 256H16a16 16 0 00-16 16v32a16 16 0 0016 16h416a16 16 0 0016-16v-32a16 16 0 00-16-16zM108.1 96h231.81A12.09 12.09 0 00352 83.9V44.09A12.09 12.09 0 00339.91 32H108.1A12.09 12.09 0 0096 44.09V83.9A12.1 12.1 0 00108.1 96zm231.81 256A12.09 12.09 0 00352 339.9v-39.81A12.09 12.09 0 00339.91 288H108.1A12.09 12.09 0 0096 300.09v39.81a12.1 12.1 0 0012.1 12.1z"
fill={iconFillColor(theme)}
/>,
{ width: 448, height: 512 },
),
);
export const TextAlignRightIcon = React.memo(
({ theme }: { theme: "light" | "dark" }) =>
createIcon(
<path
d="M16 224h416a16 16 0 0016-16v-32a16 16 0 00-16-16H16a16 16 0 00-16 16v32a16 16 0 0016 16zm416 192H16a16 16 0 00-16 16v32a16 16 0 0016 16h416a16 16 0 0016-16v-32a16 16 0 00-16-16zm3.17-384H172.83A12.82 12.82 0 00160 44.83v38.34A12.82 12.82 0 00172.83 96h262.34A12.82 12.82 0 00448 83.17V44.83A12.82 12.82 0 00435.17 32zm0 256H172.83A12.82 12.82 0 00160 300.83v38.34A12.82 12.82 0 00172.83 352h262.34A12.82 12.82 0 00448 339.17v-38.34A12.82 12.82 0 00435.17 288z"
fill={iconFillColor(theme)}
/>,
{ width: 448, height: 512 },
),
);
+1 -7
View File
@@ -84,15 +84,9 @@ export const MIME_TYPES = {
excalidrawlib: "application/vnd.excalidrawlib+json",
};
export const EXPORT_DATA_TYPES = {
excalidraw: "excalidraw",
excalidrawClipboard: "excalidraw/clipboard",
excalidrawLibrary: "excalidrawlib",
} as const;
export const STORAGE_KEYS = {
LOCAL_STORAGE_LIBRARY: "excalidraw-library",
} as const;
};
// time in milliseconds
export const TAP_TWICE_TIMEOUT = 300;
+1 -10
View File
@@ -222,8 +222,7 @@
align-items: center;
svg {
width: 36px;
height: 14px;
padding: 2px;
height: 18px;
opacity: 0.6;
}
&.active svg {
@@ -454,14 +453,6 @@
fill: $oc-gray-6;
bottom: 14px;
width: 1.5rem;
padding: 0;
margin: 0;
background: none;
color: var(--icon-fill-color);
&:hover {
background: none;
}
:root[dir="ltr"] & {
right: 14px;
+2 -2
View File
@@ -1,5 +1,5 @@
import { cleanAppStateForExport } from "../appState";
import { EXPORT_DATA_TYPES, MIME_TYPES } from "../constants";
import { MIME_TYPES } from "../constants";
import { clearElementsForExport } from "../element";
import { CanvasError } from "../errors";
import { t } from "../i18n";
@@ -121,7 +121,7 @@ export const loadFromBlob = async (
export const loadLibraryFromBlob = async (blob: Blob) => {
const contents = await parseFileContents(blob);
const data: LibraryData = JSON.parse(contents);
if (data.type !== EXPORT_DATA_TYPES.excalidrawLibrary) {
if (data.type !== "excalidrawlib") {
throw new Error(t("alerts.couldNotLoadInvalidFile"));
}
return data;
+3 -9
View File
@@ -2,7 +2,7 @@ import decodePng from "png-chunks-extract";
import tEXt from "png-chunk-text";
import encodePng from "png-chunks-encode";
import { stringToBase64, encode, decode, base64ToString } from "./encode";
import { EXPORT_DATA_TYPES, MIME_TYPES } from "../constants";
import { MIME_TYPES } from "../constants";
// -----------------------------------------------------------------------------
// PNG
@@ -67,10 +67,7 @@ export const decodePngMetadata = async (blob: Blob) => {
const encodedData = JSON.parse(metadata.text);
if (!("encoded" in encodedData)) {
// legacy, un-encoded scene JSON
if (
"type" in encodedData &&
encodedData.type === EXPORT_DATA_TYPES.excalidraw
) {
if ("type" in encodedData && encodedData.type === "excalidraw") {
return metadata.text;
}
throw new Error("FAILED");
@@ -118,10 +115,7 @@ export const decodeSvgMetadata = async ({ svg }: { svg: string }) => {
const encodedData = JSON.parse(json);
if (!("encoded" in encodedData)) {
// legacy, un-encoded scene JSON
if (
"type" in encodedData &&
encodedData.type === EXPORT_DATA_TYPES.excalidraw
) {
if ("type" in encodedData && encodedData.type === "excalidraw") {
return json;
}
throw new Error("FAILED");
+5 -5
View File
@@ -1,6 +1,6 @@
import { fileOpen, fileSave } from "browser-fs-access";
import { cleanAppStateForExport } from "../appState";
import { EXPORT_DATA_TYPES, MIME_TYPES } from "../constants";
import { MIME_TYPES } from "../constants";
import { clearElementsForExport } from "../element";
import { ExcalidrawElement } from "../element/types";
import { AppState } from "../types";
@@ -14,7 +14,7 @@ export const serializeAsJSON = (
): string =>
JSON.stringify(
{
type: EXPORT_DATA_TYPES.excalidraw,
type: "excalidraw",
version: 2,
source: window.location.origin,
elements: clearElementsForExport(elements),
@@ -69,7 +69,7 @@ export const isValidExcalidrawData = (data?: {
appState?: any;
}): data is ImportedDataState => {
return (
data?.type === EXPORT_DATA_TYPES.excalidraw &&
data?.type === "excalidraw" &&
(!data.elements ||
(Array.isArray(data.elements) &&
(!data.appState || typeof data.appState === "object")))
@@ -80,7 +80,7 @@ export const isValidLibrary = (json: any) => {
return (
typeof json === "object" &&
json &&
json.type === EXPORT_DATA_TYPES.excalidrawLibrary &&
json.type === "excalidrawlib" &&
json.version === 1
);
};
@@ -89,7 +89,7 @@ export const saveLibraryAsJSON = async () => {
const library = await Library.loadLibrary();
const serialized = JSON.stringify(
{
type: EXPORT_DATA_TYPES.excalidrawLibrary,
type: "excalidrawlib",
version: 1,
library,
},
+6 -27
View File
@@ -87,30 +87,9 @@ export const mutateElement = <TElement extends Mutable<ExcalidrawElement>>(
export const newElementWith = <TElement extends ExcalidrawElement>(
element: TElement,
updates: ElementUpdate<TElement>,
): TElement => {
let didChange = false;
for (const key in updates) {
const value = (updates as any)[key];
if (typeof value !== "undefined") {
if (
(element as any)[key] === value &&
// if object, always update in case its deep prop was mutated
(typeof value !== "object" || value === null || key === "groupIds")
) {
continue;
}
didChange = true;
}
}
if (!didChange) {
return element;
}
return {
...element,
...updates,
version: element.version + 1,
versionNonce: randomInteger(),
};
};
): TElement => ({
...element,
...updates,
version: element.version + 1,
versionNonce: randomInteger(),
});
+1 -2
View File
@@ -207,8 +207,7 @@ export const textWysiwyg = ({
// prevent blur when changing properties from the menu
const onPointerDown = (event: MouseEvent) => {
if (
(event.target instanceof HTMLElement ||
event.target instanceof SVGElement) &&
event.target instanceof HTMLElement &&
event.target.closest(`.${CLASSES.SHAPE_ACTIONS_MENU}`) &&
!isWritableElement(event.target)
) {
+8 -1
View File
@@ -448,8 +448,15 @@ class CollabWrapper extends PureComponent<Props, CollabState> {
private handleRemoteSceneUpdate = (
elements: ReconciledElements,
{ init = false }: { init?: boolean } = {},
{
init = false,
initFromSnapshot = false,
}: { init?: boolean; initFromSnapshot?: boolean } = {},
) => {
if (init || initFromSnapshot) {
this.excalidrawAPI.setScrollToContent(elements);
}
this.excalidrawAPI.updateScene({
elements,
commitToHistory: !!init,
+3 -16
View File
@@ -7,27 +7,12 @@ import {
stop,
share,
shareIOS,
shareWindows,
} from "../../components/icons";
import { ToolButton } from "../../components/ToolButton";
import { t } from "../../i18n";
import "./RoomDialog.scss";
import Stack from "../../components/Stack";
const getShareIcon = () => {
const navigator = window.navigator as any;
const isAppleBrowser = /Apple/.test(navigator.vendor);
const isWindowsBrowser = navigator.appVersion.indexOf("Win") !== -1;
if (isAppleBrowser) {
return shareIOS;
} else if (isWindowsBrowser) {
return shareWindows;
}
return share;
};
const RoomDialog = ({
handleClose,
activeRoomLink,
@@ -46,6 +31,8 @@ const RoomDialog = ({
setErrorMessage: (message: string) => void;
}) => {
const roomLinkInput = useRef<HTMLInputElement>(null);
const navigator = window.navigator as any;
const isAppleBrowser = /Apple/.test(navigator.vendor);
const copyRoomLink = async () => {
try {
@@ -106,7 +93,7 @@ const RoomDialog = ({
{"share" in navigator ? (
<ToolButton
type="button"
icon={getShareIcon()}
icon={isAppleBrowser ? shareIOS : share}
title={t("labels.share")}
aria-label={t("labels.share")}
onClick={shareRoomLink}
+15 -41
View File
@@ -80,10 +80,8 @@ export type SocketUpdateData = SocketUpdateDataSource[keyof SocketUpdateDataSour
_brand: "socketUpdateData";
};
const IV_LENGTH_BYTES = 12; // 96 bits
export const createIV = () => {
const arr = new Uint8Array(IV_LENGTH_BYTES);
const arr = new Uint8Array(12);
return window.crypto.getRandomValues(arr);
};
@@ -177,22 +175,6 @@ export const getImportedKey = (key: string, usage: KeyUsage) =>
[usage],
);
const decryptImported = async (
iv: ArrayBuffer,
encrypted: ArrayBuffer,
privateKey: string,
): Promise<ArrayBuffer> => {
const key = await getImportedKey(privateKey, "decrypt");
return window.crypto.subtle.decrypt(
{
name: "AES-GCM",
iv,
},
key,
encrypted,
);
};
const importFromBackend = async (
id: string | null,
privateKey?: string | null,
@@ -201,7 +183,6 @@ const importFromBackend = async (
const response = await fetch(
privateKey ? `${BACKEND_V2_GET}${id}` : `${BACKEND_GET}${id}.json`,
);
if (!response.ok) {
window.alert(t("alerts.importBackendFailed"));
return {};
@@ -209,19 +190,16 @@ const importFromBackend = async (
let data: ImportedDataState;
if (privateKey) {
const buffer = await response.arrayBuffer();
let decrypted: ArrayBuffer;
try {
// Buffer should contain both the IV (fixed length) and encrypted data
const iv = buffer.slice(0, IV_LENGTH_BYTES);
const encrypted = buffer.slice(IV_LENGTH_BYTES, buffer.byteLength);
decrypted = await decryptImported(iv, encrypted, privateKey);
} catch (error) {
// Fixed IV (old format, backward compatibility)
const fixedIv = new Uint8Array(IV_LENGTH_BYTES);
decrypted = await decryptImported(fixedIv, buffer, privateKey);
}
const key = await getImportedKey(privateKey, "decrypt");
const iv = new Uint8Array(12);
const decrypted = await window.crypto.subtle.decrypt(
{
name: "AES-GCM",
iv,
},
key,
buffer,
);
// We need to convert the decrypted array buffer to a string
const string = new window.TextDecoder("utf-8").decode(
new Uint8Array(decrypted) as any,
@@ -285,8 +263,9 @@ export const exportToBackend = async (
true, // extractable
["encrypt", "decrypt"],
);
const iv = createIV();
// The iv is set to 0. We are never going to reuse the same key so we don't
// need to have an iv. (I hope that's correct...)
const iv = new Uint8Array(12);
// We use symmetric encryption. AES-GCM is the recommended algorithm and
// includes checks that the ciphertext has not been modified by an attacker.
const encrypted = await window.crypto.subtle.encrypt(
@@ -297,11 +276,6 @@ export const exportToBackend = async (
key,
encoded,
);
// Concatenate IV with encrypted data (IV does not have to be secret).
const payloadBlob = new Blob([iv.buffer, encrypted]);
const payload = await new Response(payloadBlob).arrayBuffer();
// We use jwk encoding to be able to extract just the base64 encoded key.
// We will hardcode the rest of the attributes when importing back the key.
const exportedKey = await window.crypto.subtle.exportKey("jwk", key);
@@ -309,7 +283,7 @@ export const exportToBackend = async (
try {
const response = await fetch(BACKEND_V2_POST, {
method: "POST",
body: payload,
body: encrypted,
});
const json = await response.json();
if (json.id) {
+9 -9
View File
@@ -61,7 +61,7 @@
"architect": "معماري",
"artist": "رسام",
"cartoonist": "كرتوني",
"fileTitle": "",
"fileTitle": "عنوان الملف",
"colorPicker": "اختيار الألوان",
"canvasBackground": "خلفية اللوحة",
"drawingCanvas": "لوحة الرسم",
@@ -77,7 +77,7 @@
"group": "تحديد مجموعة",
"ungroup": "إلغاء تحديد مجموعة",
"collaborators": "المتعاونون",
"showGrid": "إظهار الشبكة",
"showGrid": "",
"addToLibrary": "أضف إلى المكتبة",
"removeFromLibrary": "حذف من المكتبة",
"libraryLoadingMessage": "جارٍ تحميل المكتبة…",
@@ -92,9 +92,9 @@
"centerHorizontally": "توسيط أفقي",
"distributeHorizontally": "التوزيع الأفقي",
"distributeVertically": "التوزيع عمودياً",
"viewMode": "نمط العرض",
"viewMode": "",
"toggleExportColorScheme": "",
"share": "مشاركة"
"share": ""
},
"buttons": {
"clearReset": "إعادة تعيين اللوحة",
@@ -119,7 +119,7 @@
"edit": "تعديل",
"undo": "تراجع",
"redo": "إعادة تنفيذ",
"resetLibrary": "إعادة ضبط المكتبة",
"resetLibrary": "",
"createNewRoom": "إنشاء غرفة جديدة",
"fullScreen": "شاشة كاملة",
"darkMode": "الوضع المظلم",
@@ -138,7 +138,7 @@
"decryptFailed": "تعذر فك تشفير البيانات.",
"uploadedSecurly": "تم تأمين التحميل بتشفير النهاية إلى النهاية، مما يعني أن خادوم Excalidraw والأطراف الثالثة لا يمكنها قراءة المحتوى.",
"loadSceneOverridePrompt": "تحميل الرسم الخارجي سيحل محل المحتوى الموجود لديك. هل ترغب في المتابعة؟",
"collabStopOverridePrompt": "إيقاف الجلسة سيؤدي إلى الكتابة فوق رسومك السابقة المخزنة داخليا. هل أنت متأكد؟\n\n(إذا كنت ترغب في الاحتفاظ برسمك المخزن داخليا، ببساطة أغلق علامة تبويب المتصفح بدلاً من ذلك.)",
"collabStopOverridePrompt": "",
"errorLoadingLibrary": "حصل خطأ أثناء تحميل مكتبة الطرف الثالث.",
"confirmAddLibrary": "هذا سيضيف {{numShapes}} شكل إلى مكتبتك. هل أنت متأكد؟",
"imageDoesNotContainScene": "استيراد الصور غير مدعوم في الوقت الراهن.\n\nهل تريد استيراد مشهد؟ لا يبدو أن هذه الصورة تحتوي على أي بيانات مشهد. هل قمت بسماح هذا أثناء التصدير؟",
@@ -212,9 +212,9 @@
"curvedArrow": "",
"curvedLine": "",
"documentation": "",
"drag": "اسحب",
"editor": "المحرر",
"github": "عثرت على مشكلة؟ إرسال",
"drag": "",
"editor": "",
"github": "",
"howto": "",
"or": "",
"preventBinding": "",
+1 -1
View File
@@ -61,7 +61,7 @@
"architect": "Архитект",
"artist": "Художник",
"cartoonist": "Карикатурист",
"fileTitle": "",
"fileTitle": "Заглавие на файл",
"colorPicker": "Избор на цвят",
"canvasBackground": "Фон на платно",
"drawingCanvas": "Платно за рисуване",
+1 -1
View File
@@ -61,7 +61,7 @@
"architect": "Arquitecte",
"artist": "Artista",
"cartoonist": "Dibuixant",
"fileTitle": "",
"fileTitle": "Títol del fitxer",
"colorPicker": "Selector de colors",
"canvasBackground": "Fons del llenç",
"drawingCanvas": "Llenç de dibuix",
+2 -2
View File
@@ -61,7 +61,7 @@
"architect": "Αρχιτέκτονας",
"artist": "Καλλιτέχνης",
"cartoonist": "Σκιτσογράφος",
"fileTitle": "",
"fileTitle": "Τίτλος αρχείου",
"colorPicker": "Επιλογή Χρώματος",
"canvasBackground": "Φόντο καμβά",
"drawingCanvas": "Σχεδίαση καμβά",
@@ -94,7 +94,7 @@
"distributeVertically": "Κατακόρυφη κατανομή",
"viewMode": "Λειτουργία προβολής",
"toggleExportColorScheme": "Εναλλαγή εξαγωγής θέματος χρωμάτων",
"share": "Κοινοποίηση"
"share": ""
},
"buttons": {
"clearReset": "Επαναφορά του καμβά",
+1 -1
View File
@@ -61,7 +61,7 @@
"architect": "Architect",
"artist": "Artist",
"cartoonist": "Cartoonist",
"fileTitle": "File name",
"fileTitle": "File title",
"colorPicker": "Color picker",
"canvasBackground": "Canvas background",
"drawingCanvas": "Drawing canvas",
+1 -1
View File
@@ -61,7 +61,7 @@
"architect": "Arquitecto",
"artist": "Artista",
"cartoonist": "Caricatura",
"fileTitle": "Nombre del archivo",
"fileTitle": "Título del archivo",
"colorPicker": "Selector de color",
"canvasBackground": "Fondo del lienzo",
"drawingCanvas": "Lienzo de dibujo",
+1 -1
View File
@@ -61,7 +61,7 @@
"architect": "معمار",
"artist": "هنرمند",
"cartoonist": "کارتونیست",
"fileTitle": "",
"fileTitle": "عنوان فایل",
"colorPicker": "انتخابگر رنگ",
"canvasBackground": "بوم",
"drawingCanvas": "بوم نقاشی",
+1 -1
View File
@@ -61,7 +61,7 @@
"architect": "Arkkitehti",
"artist": "Taiteilija",
"cartoonist": "Sarjakuva",
"fileTitle": "Tiedostonimi",
"fileTitle": "Tiedoston otsikko",
"colorPicker": "Värin valinta",
"canvasBackground": "Piirtoalueen tausta",
"drawingCanvas": "Piirtoalue",
+1 -1
View File
@@ -61,7 +61,7 @@
"architect": "Architecte",
"artist": "Artiste",
"cartoonist": "Caricaturiste",
"fileTitle": "Nom du fichier",
"fileTitle": "Titre du fichier",
"colorPicker": "Sélecteur de couleur",
"canvasBackground": "Arrière-plan du canevas",
"drawingCanvas": "Zone de dessin",
+1 -1
View File
@@ -61,7 +61,7 @@
"architect": "ארכיטקט",
"artist": "אמן",
"cartoonist": "קריקטוריסט",
"fileTitle": "",
"fileTitle": "כותרת הקובץ",
"colorPicker": "בחירת צבע",
"canvasBackground": "רקע הלוח",
"drawingCanvas": "לוח ציור",
+1 -1
View File
@@ -61,7 +61,7 @@
"architect": "वास्तुकार",
"artist": "कलाकार",
"cartoonist": "व्यंग्य चित्रकार",
"fileTitle": "",
"fileTitle": "फ़ाइल का शीर्षक",
"colorPicker": "रंग चयन",
"canvasBackground": "कैनवास बैकग्राउंड",
"drawingCanvas": "कैनवास बना रहे हैं",
+1 -1
View File
@@ -61,7 +61,7 @@
"architect": "Tervezői",
"artist": "Művészi",
"cartoonist": "Karikatúrás",
"fileTitle": "",
"fileTitle": "Fájl címe",
"colorPicker": "Színválasztó",
"canvasBackground": "Vászon háttérszíne",
"drawingCanvas": "Rajzvászon",
+2 -2
View File
@@ -61,7 +61,7 @@
"architect": "Arsitek",
"artist": "Artis",
"cartoonist": "Kartunis",
"fileTitle": "Nama file",
"fileTitle": "Judul file",
"colorPicker": "Pilihan Warna",
"canvasBackground": "Latar Kanvas",
"drawingCanvas": "Kanvas",
@@ -143,7 +143,7 @@
"confirmAddLibrary": "Ini akan menambahkan {{numShapes}} bentuk ke pustaka Anda. Anda yakin?",
"imageDoesNotContainScene": "Mengimpor gambar tidak didukung saat ini.\n\nApakah Anda ingin impor pemandangan? Gambar ini tidak berisi data pemandangan. Sudah ka Anda aktifkan ini ketika ekspor?",
"cannotRestoreFromImage": "Pemandangan tidak dapat dipulihkan dari file gambar ini",
"invalidSceneUrl": "Tidak dapat impor pemandangan dari URL. Kemungkinan URL itu rusak atau tidak berisi data JSON Excalidraw yang valid.",
"invalidSceneUrl": "",
"resetLibrary": "Ini akan menghapus pustaka Anda. Anda yakin?"
},
"toolBar": {
+1 -1
View File
@@ -61,7 +61,7 @@
"architect": "Architetto",
"artist": "Artista",
"cartoonist": "Fumettista",
"fileTitle": "Nome del file",
"fileTitle": "Titolo del file",
"colorPicker": "Selettore colore",
"canvasBackground": "Sfondo tela",
"drawingCanvas": "Area di disegno",
+1 -1
View File
@@ -61,7 +61,7 @@
"architect": "正確",
"artist": "アート",
"cartoonist": "漫画風",
"fileTitle": "",
"fileTitle": "ファイル名",
"colorPicker": "色選択",
"canvasBackground": "キャンバスの背景",
"drawingCanvas": "キャンバスの描画",
+1 -1
View File
@@ -61,7 +61,7 @@
"architect": "Amasdag",
"artist": "Anaẓur",
"cartoonist": "",
"fileTitle": "",
"fileTitle": "Azwel n ufaylu",
"colorPicker": "Amafran n yini",
"canvasBackground": "Agilal n teɣzut n usuneɣ",
"drawingCanvas": "Taɣzut n usuneɣ",
+1 -1
View File
@@ -61,7 +61,7 @@
"architect": "건축가",
"artist": "예술가",
"cartoonist": "만화가",
"fileTitle": "",
"fileTitle": "파일명",
"colorPicker": "색상 선택기",
"canvasBackground": "캔버스 배경",
"drawingCanvas": "캔버스 그리기",
+1 -1
View File
@@ -61,7 +61,7 @@
"architect": "ဗိသုကာ",
"artist": "ပန်းချီ",
"cartoonist": "ကာတွန်း",
"fileTitle": "",
"fileTitle": "ခေါင်းစဉ်",
"colorPicker": "အရောင်ရွေး",
"canvasBackground": "ကားချပ်နောက်ခံ",
"drawingCanvas": "ပုံဆွဲကားချပ်",
+1 -1
View File
@@ -138,7 +138,7 @@
"decryptFailed": "Kan gegevens niet decoderen.",
"uploadedSecurly": "De upload is beveiligd met end-to-end encryptie, wat betekent dat de Excalidraw server en derden de inhoud niet kunnen lezen.",
"loadSceneOverridePrompt": "Het laden van externe tekening zal uw bestaande inhoud vervangen. Wil je doorgaan?",
"collabStopOverridePrompt": "Wanneer de sessie wordt gestopt, overschrijft u de eerdere, lokaal opgeslagen tekening. Weet je het zeker?\n\n(Als je de lokale tekening wilt behouden, sluit je in plaats daarvan het browsertabblad)",
"collabStopOverridePrompt": "",
"errorLoadingLibrary": "Bij het laden van de externe bibliotheek is een fout opgetreden.",
"confirmAddLibrary": "Hiermee worden {{numShapes}} vorm(n) aan uw bibliotheek toegevoegd. Ben je het zeker?",
"imageDoesNotContainScene": "Afbeeldingen importeren wordt op dit moment niet ondersteund.\n\nWil je een scène importeren? Deze afbeelding lijkt geen scène gegevens te bevatten. Heb je dit geactiveerd tijdens het exporteren?",
+1 -1
View File
@@ -61,7 +61,7 @@
"architect": "Arkitekt",
"artist": "Kunstnar",
"cartoonist": "Teiknar",
"fileTitle": "",
"fileTitle": "Filnamn",
"colorPicker": "Fargeveljar",
"canvasBackground": "Lerretsbakgrunn",
"drawingCanvas": "Lerret",
+1 -1
View File
@@ -61,7 +61,7 @@
"architect": "Arquitècte",
"artist": "Artista",
"cartoonist": "Dessenhaire",
"fileTitle": "Nom del fichièr",
"fileTitle": "Títol del fichièr",
"colorPicker": "Selector de color",
"canvasBackground": "Rèireplan del canabàs",
"drawingCanvas": "Zòna de dessenh",
+1 -1
View File
@@ -61,7 +61,7 @@
"architect": "ਭਵਨ ਨਿਰਮਾਣਕਾਰੀ",
"artist": "ਕਲਾਕਾਰ",
"cartoonist": "ਕਾਰਟੂਨਿਸਟ",
"fileTitle": "",
"fileTitle": "ਫਾਈਲ ਦਾ ਸਿਰਨਾਵਾਂ",
"colorPicker": "ਰੰਗ ਚੋਣਕਾਰ",
"canvasBackground": "ਕੈਨਵਸ ਦਾ ਬੈਕਗਰਾਉਂਡ",
"drawingCanvas": "ਡਰਾਇੰਗ ਕੈਨਵਸ",
+14 -14
View File
@@ -1,37 +1,37 @@
{
"ar-SA": 86,
"ar-SA": 83,
"bg-BG": 94,
"ca-ES": 99,
"ca-ES": 100,
"de-DE": 100,
"el-GR": 98,
"en": 100,
"es-ES": 100,
"fa-IR": 89,
"fa-IR": 90,
"fi-FI": 100,
"fr-FR": 100,
"he-IL": 90,
"hi-IN": 92,
"hu-HU": 82,
"id-ID": 100,
"he-IL": 91,
"hi-IN": 93,
"hu-HU": 83,
"id-ID": 99,
"it-IT": 100,
"ja-JP": 96,
"kab-KAB": 98,
"kab-KAB": 99,
"ko-KR": 94,
"my-MM": 77,
"nb-NO": 100,
"nl-NL": 100,
"nn-NO": 84,
"nl-NL": 99,
"nn-NO": 85,
"oc-FR": 100,
"pa-IN": 95,
"pl-PL": 96,
"pt-BR": 100,
"pt-PT": 96,
"pt-PT": 97,
"ro-RO": 100,
"ru-RU": 100,
"sk-SK": 100,
"sv-SE": 100,
"tr-TR": 97,
"uk-UA": 100,
"zh-CN": 99,
"tr-TR": 83,
"uk-UA": 95,
"zh-CN": 100,
"zh-TW": 100
}
+1 -1
View File
@@ -61,7 +61,7 @@
"architect": "Dokładny",
"artist": "Artystyczny",
"cartoonist": "Rysunkowy",
"fileTitle": "",
"fileTitle": "Tytuł pliku",
"colorPicker": "Paleta kolorów",
"canvasBackground": "Kolor dokumentu",
"drawingCanvas": "Obszar roboczy",
+1 -1
View File
@@ -61,7 +61,7 @@
"architect": "Arquiteto",
"artist": "Artista",
"cartoonist": "Cartunista",
"fileTitle": "Nome do arquivo",
"fileTitle": "Título do arquivo",
"colorPicker": "Seletor de cores",
"canvasBackground": "Fundo da tela",
"drawingCanvas": "Tela de desenho",
+1 -1
View File
@@ -61,7 +61,7 @@
"architect": "Arquitecto",
"artist": "Artista",
"cartoonist": "Caricaturista",
"fileTitle": "",
"fileTitle": "Título do ficheiro",
"colorPicker": "Seletor de cores",
"canvasBackground": "Fundo da tela",
"drawingCanvas": "Tela de desenho",
+1 -1
View File
@@ -61,7 +61,7 @@
"architect": "Arhitect",
"artist": "Artist",
"cartoonist": "Caricaturist",
"fileTitle": "Nume de fișier",
"fileTitle": "Denumirea fișierului",
"colorPicker": "Selector de culoare",
"canvasBackground": "Fundalul pânzei",
"drawingCanvas": "Pânză pentru desenat",
+1 -1
View File
@@ -61,7 +61,7 @@
"architect": "Архитектор",
"artist": "Художник",
"cartoonist": "Карикатурист",
"fileTitle": "Имя файла",
"fileTitle": "Название файла",
"colorPicker": "Выбор цвета",
"canvasBackground": "Фон холста",
"drawingCanvas": "Полотно",
+1 -1
View File
@@ -61,7 +61,7 @@
"architect": "Arkitekt",
"artist": "Artist",
"cartoonist": "Serietecknare",
"fileTitle": "Filnamn",
"fileTitle": "Filtitel",
"colorPicker": "Färgväljare",
"canvasBackground": "Canvas-bakgrund",
"drawingCanvas": "Ritar canvas",
+35 -35
View File
@@ -1,7 +1,7 @@
{
"labels": {
"paste": "Yapıştır",
"pasteCharts": "Grafikleri yapıştır",
"pasteCharts": "Dairesel grafik",
"selectAll": "Tümünü seç",
"multiSelect": "Seçime öge ekle",
"moveCanvas": "Tuvali taşı",
@@ -68,7 +68,7 @@
"layers": "Katmanlar",
"actions": "Eylemler",
"language": "Dil",
"liveCollaboration": "Canlı ortak çalışma alanı",
"liveCollaboration": "",
"duplicateSelection": "Çoğalt",
"untitled": "Adsız",
"name": "İsim",
@@ -77,7 +77,7 @@
"group": "Seçimi grup yap",
"ungroup": "Seçilen grubu dağıt",
"collaborators": "Ortaklar",
"showGrid": "Izgarayı göster",
"showGrid": "",
"addToLibrary": "Kütüphaneye ekle",
"removeFromLibrary": "Kütüphaneden kaldır",
"libraryLoadingMessage": "Kütüphane yükleniyor…",
@@ -94,7 +94,7 @@
"distributeVertically": "Dikey dağıt",
"viewMode": "",
"toggleExportColorScheme": "",
"share": "Paylaş"
"share": ""
},
"buttons": {
"clearReset": "Tuvali sıfırla",
@@ -119,7 +119,7 @@
"edit": "Düzenle",
"undo": "Geri Al",
"redo": "Yeniden yap",
"resetLibrary": "Kütüphaneyi sıfırla",
"resetLibrary": "",
"createNewRoom": "Yeni oda oluştur",
"fullScreen": "Tam ekran",
"darkMode": "Koyu tema",
@@ -138,13 +138,13 @@
"decryptFailed": "Şifrelenmiş veri çözümlenemedi.",
"uploadedSecurly": "Yükleme uçtan uca şifreleme ile korunmaktadır. Excalidraw sunucusu ve üçüncül şahıslar içeriği okuyamayacaktır.",
"loadSceneOverridePrompt": "Harici çizimler yüklemek mevcut olan içeriği değiştirecektir. Devam etmek istiyor musunuz?",
"collabStopOverridePrompt": "Oturumu sonlandırmak daha önceki, yerel olarak kaydedilmiş çizimin üzerine kaydedilmesine sebep olacak. Emin misiniz?\n\n(Yerel çiziminizi kaybetmemek için tarayıcı sekmesini kapatabilirsiniz.)",
"collabStopOverridePrompt": "",
"errorLoadingLibrary": "Üçüncü taraf kitaplığı yüklerken bir hata oluştu.",
"confirmAddLibrary": "Bu, kitaplığınıza {{numShapes}} tane şekil ekleyecek. Emin misiniz?",
"imageDoesNotContainScene": "Resim ekleme şuan için desteklenmiyor.\nBir sahneyi içeri aktarmak mı istediniz? Bu dosya herhangi bir sahne içeriyor gibi durmuyor. Çıktı alırken sahneyi dahil ettiniz mi?",
"cannotRestoreFromImage": "Sahne bu dosyadan oluşturulamıyor",
"invalidSceneUrl": "Verilen URL'den çalışma alanı yüklenemedi. Dosya bozuk olabilir veya geçerli bir Excalidraw JSON verisi bulundurmuyor olabilir.",
"resetLibrary": "Bu işlem kütüphanenizi sıfırlayacak. Emin misiniz?"
"invalidSceneUrl": "",
"resetLibrary": ""
},
"toolBar": {
"selection": "Seçme",
@@ -201,31 +201,31 @@
"desc_inProgressIntro": "Ortak çalışma ortamı oluşturuldu.",
"desc_shareLink": "Bu bağlantıyı birlikte çalışacağınız kişilerle paylaşabilirsiniz:",
"desc_exitSession": "Çalışma ortamını kapattığınızda ortak çalışmadan ayrılmış olursunuz ancak kendi versiyonunuzda çalışmaya devam edebilirsiniz. Bu durumda ortak çalıştığınız diğer kişiler etkilenmeyecek, çalışma ortamındaki versiyon üzerinden çalışmaya devam edebilecekler.",
"shareTitle": "Excalidraw'da canlı ortak calışma oturumuna katıl"
"shareTitle": ""
},
"errorDialog": {
"title": "Hata"
},
"helpDialog": {
"blog": "Blog'umuzu okuyun",
"click": "tıkla",
"curvedArrow": "Eğri ok",
"curvedLine": "Eğri çizgi",
"documentation": "Dokümantasyon",
"drag": "sürükle",
"blog": "",
"click": "",
"curvedArrow": "",
"curvedLine": "",
"documentation": "",
"drag": "",
"editor": "",
"github": "Bir hata mı buldun? Bildir",
"howto": "Rehberlerimizi takip edin",
"or": "veya",
"github": "",
"howto": "",
"or": "",
"preventBinding": "",
"shapes": "Şekiller",
"shortcuts": "Klavye kısayolları",
"textFinish": "(Metin) düzenlemeyi bitir",
"textNewLine": "Yeni satır ekle (metin)",
"title": "Yardım",
"shapes": "",
"shortcuts": "",
"textFinish": "",
"textNewLine": "",
"title": "",
"view": "",
"zoomToFit": "Tüm öğeleri sığdırmak için yakınlaştır",
"zoomToSelection": "Seçime yakınlaş"
"zoomToFit": "",
"zoomToSelection": ""
},
"encrypted": {
"tooltip": "Çizimleriniz uçtan-uca şifrelenmiştir, Excalidraw'ın sunucuları bile onları göremez."
@@ -240,18 +240,18 @@
"storage": "Depolama",
"title": "İnekler için istatistikler",
"total": "Toplam",
"version": "Sürüm",
"versionCopy": "Kopyalamak için tıkla",
"versionNotAvailable": "Sürüm mevcut değil",
"version": "",
"versionCopy": "",
"versionNotAvailable": "",
"width": "Genişlik"
},
"toast": {
"copyStyles": "Stiller kopyalandı.",
"copyToClipboard": "Panoya kopyalandı.",
"copyToClipboardAsPng": "{{exportSelection}} panoya PNG olarak\n({{exportColorScheme}}) kopyalandı",
"fileSaved": "Dosya kaydedildi.",
"fileSavedToFilename": "{filename} kaydedildi",
"canvas": "tuval",
"selection": "seçim"
"copyStyles": "",
"copyToClipboard": "",
"copyToClipboardAsPng": "",
"fileSaved": "",
"fileSavedToFilename": "",
"canvas": "",
"selection": ""
}
}
+10 -10
View File
@@ -68,7 +68,7 @@
"layers": "Шари",
"actions": "Дії",
"language": "Мова",
"liveCollaboration": "Спільна співпраця",
"liveCollaboration": "",
"duplicateSelection": "Дублювати",
"untitled": "Без назви",
"name": "Ім’я",
@@ -93,8 +93,8 @@
"distributeHorizontally": "Розподілити по горизонталі",
"distributeVertically": "Розподілити вертикально",
"viewMode": "Режим перегляду",
"toggleExportColorScheme": "Переключити колірну схему експорту",
"share": "Поділитися"
"toggleExportColorScheme": "",
"share": ""
},
"buttons": {
"clearReset": "Очистити полотно",
@@ -119,7 +119,7 @@
"edit": "Редагувати",
"undo": "Відмінити",
"redo": "Повторити",
"resetLibrary": "Очистити бібліотеку",
"resetLibrary": "",
"createNewRoom": "Створити нову кімнату",
"fullScreen": "Повноекранний режим",
"darkMode": "Темний режим",
@@ -143,8 +143,8 @@
"confirmAddLibrary": "Це призведе до додавання {{numShapes}} фігур до вашої бібліотеки. Ви впевнені?",
"imageDoesNotContainScene": "Імпортування зображень на даний момент не підтримується.\n\nЧи хочете ви імпортувати сцену? Це зображення не містить ніяких даних сцен. Ви увімкнули це під час експорту?",
"cannotRestoreFromImage": "Сцена не може бути відновлена з цього файлу зображення",
"invalidSceneUrl": "Не вдалося імпортувати сцену з наданого URL. Він або недоформований, або не містить дійсних даних Excalidraw JSON.",
"resetLibrary": "Це призведе до очищення бібліотеки. Ви впевнені?"
"invalidSceneUrl": "",
"resetLibrary": ""
},
"toolBar": {
"selection": "Виділення",
@@ -201,7 +201,7 @@
"desc_inProgressIntro": "Сесія спільної роботи над кресленням триває.",
"desc_shareLink": "Поділіться цим посиланням з будь-ким для спільної роботи:",
"desc_exitSession": "Зупинка сесії відключить вас від кімнати, але ви зможете продовжити роботу з полотном локально. Зверніть увагу, що це не вплине на інших людей, і вони все одно зможуть працювати над їх версією.",
"shareTitle": "Приєднатися до сеансу спільної роботи на Excalidraw"
"shareTitle": ""
},
"errorDialog": {
"title": "Помилка"
@@ -248,10 +248,10 @@
"toast": {
"copyStyles": "Скопійовані стилі.",
"copyToClipboard": "Скопіювати до буферу обміну.",
"copyToClipboardAsPng": "Скопійовано {{exportSelection}} до буфера обміну як PNG\n({{exportColorScheme}})",
"copyToClipboardAsPng": "",
"fileSaved": "Файл збережено.",
"fileSavedToFilename": "Збережено в {filename}",
"canvas": "полотно",
"selection": "виділення"
"canvas": "",
"selection": ""
}
}
+1 -1
View File
@@ -61,7 +61,7 @@
"architect": "朴素",
"artist": "艺术",
"cartoonist": "漫画家",
"fileTitle": "",
"fileTitle": "文件标题",
"colorPicker": "调色盘",
"canvasBackground": "画布背景",
"drawingCanvas": "绘制 Canvas",
+1 -1
View File
@@ -61,7 +61,7 @@
"architect": "精確",
"artist": "藝術",
"cartoonist": "卡通",
"fileTitle": "檔案名稱",
"fileTitle": "檔案標題",
"colorPicker": "色彩選擇工具",
"canvasBackground": "Canvas 背景",
"drawingCanvas": "繪圖 canvas",
+2 -21
View File
@@ -12,14 +12,13 @@ The change should be grouped under one of the below section and must contain PR
Please add the latest change on the top under the correct section.
-->
## 0.5.0 (2021-03-21)
## Unreleased
## Excalidraw API
### Features
- Set the target to `window.name` if present during excalidraw libraries installation so it opens in same tab for the host. If `window.name` is not set it will open in a new tab [#3299](https://github.com/excalidraw/excalidraw/pull/3299).
- Add `name` prop to indicate the name of the drawing which will be used when exporting the drawing. When supplied, the value takes precedence over `intialData.appState.name`, the `name` will be fully controlled by host app and the users won't be able to edit from within Excalidraw [#3273](https://github.com/excalidraw/excalidraw/pull/3273).
- Add `name` prop which indicates the name of the drawing which will be used when exporting the drawing. When supplied, the value takes precedence over `intialData.appState.name`, the `name` will be fully controlled by host app and the users won't be able to edit from within Excalidraw [#3273](https://github.com/excalidraw/excalidraw/pull/3273).
- Export API `setCanvasOffsets` via `ref` to set the offsets for Excalidraw[#3265](https://github.com/excalidraw/excalidraw/pull/3265).
#### BREAKING CHANGE
- `offsetLeft` and `offsetTop` props have been removed now so you have to use the `setCanvasOffsets` via `ref` to achieve the same.
@@ -37,24 +36,6 @@ Please add the latest change on the top under the correct section.
- The class `Appearance_dark` is renamed to `theme--dark`.
- The class `Appearance_dark-background-none` is renamed to `theme--dark-background-none`.
## Excalidraw Library
### Features
- Support pasting file contents & always prefer system clip [#3257](https://github.com/excalidraw/excalidraw/pull/3257)
- Add label for name field and use input when editable in export dialog [#3286](https://github.com/excalidraw/excalidraw/pull/3286)
- Implement the Web Share Target API [#3230](https://github.com/excalidraw/excalidraw/pull/3230).
### Fixes
- Don't show export and delete when library is empty [#3288](https://github.com/excalidraw/excalidraw/pull/3288)
- Overflow in textinput in export dialog [#3284](https://github.com/excalidraw/excalidraw/pull/3284).
- Bail on noop updates for newElementWith [#3279](https://github.com/excalidraw/excalidraw/pull/3279).
- Prevent State continuously updated when holding ctrl/cmd #3283
- Debounce flush not invoked if lastArgs not defined [#3281](https://github.com/excalidraw/excalidraw/pull/3281).
- Stop preventing canvas pointerdown/tapend events [#3207](https://github.com/excalidraw/excalidraw/pull/3207).
- Double scrollbar on modals [#3226](https://github.com/excalidraw/excalidraw/pull/3226).
---
## 0.4.3 (2021-03-12)
+8 -23
View File
@@ -28,8 +28,7 @@ If you want to load assets from a different path you can set a variable `window.
[Try here](https://codesandbox.io/s/excalidraw-ehlz3).
<details id="usage">
<summary><strong>Usage</strong></summary>
### Usage
1. If you are using a Web bundler (for instance, Webpack), you can import it as an ES6 module as shown below
@@ -164,8 +163,6 @@ export default function App() {
}
```
To view the full example visit :point_down:
[![Edit excalidraw](https://codesandbox.io/static/img/play-codesandbox.svg)](https://codesandbox.io/s/excalidraw-ehlz3?fontsize=14&hidenavigation=1&theme=dark)
2. To use it in a browser directly:
@@ -344,8 +341,6 @@ const excalidrawWrapper = document.getElementById("app");
ReactDOM.render(React.createElement(App), excalidrawWrapper);
```
To view the full example visit :point_down:
[![Edit excalidraw-in-browser](https://codesandbox.io/static/img/play-codesandbox.svg)](https://codesandbox.io/s/excalidraw-in-browser-tlqom?fontsize=14&hidenavigation=1&theme=dark)
Since Excalidraw doesn't support server side rendering yet so you will have to make sure the component is rendered once host is mounted.
@@ -361,10 +356,7 @@ export default function IndexPage() {
}
```
</details>
<details id="props">
<summary><strong>Props</strong></summary>
### Props
| Name | Type | Default | Description |
| --- | --- | --- | --- |
@@ -535,22 +527,19 @@ This prop indicates whether the app is in `zen mode`. When supplied, the value t
This prop indicates whether the shows the grid. When supplied, the value takes precedence over `intialData.appState.gridModeEnabled`, the grid will be fully controlled by the host app, and users won't be able to toggle it from within the app.
#### `libraryReturnUrl`
### `libraryReturnUrl`
If supplied, this URL will be used when user tries to install a library from [libraries.excalidraw.com](https://libraries.excalidraw.com). Default to `window.location.origin`. To install the libraries in the same tab from which it was opened, you need to set `window.name` (to any alphanumeric string) — if it's not set it will open in a new tab.
If supplied, this URL will be used when user tries to install a library from [libraries.excalidraw.com](https://libraries.excalidraw.com). Default to `window.location.origin`.
#### `theme`
### `theme`
This prop controls Excalidraw's theme. When supplied, the value takes precedence over `intialData.appState.theme`, the theme will be fully controlled by the host app, and users won't be able to toggle it from within the app.
#### `name`
### `name`
This prop sets the name of the drawing which will be used when exporting the drawing. When supplied, the value takes precedence over `intialData.appState.name`, the `name` will be fully controlled by host app and the users won't be able to edit from within Excalidraw.
</details>
<details id="extra-apis">
<summary><strong>Extra API's</strong></summary>
### Extra API's
#### `getSceneVersion`
@@ -595,9 +584,6 @@ import { getElementsMap } from "@excalidraw/excalidraw";
This function returns an object where each element is mapped to its id.
<details id="restore-utils">
<summary><strong>Restore utilities</strong></summary>
#### `restoreAppState`
**_Signature_**
@@ -646,7 +632,7 @@ import { restore } from "@excalidraw/excalidraw";
This function makes sure elements and state is set to appropriate values and set to default value if not present. It is combination of [restoreElements](#restoreElements) and [restoreAppState](#restoreAppState)
</details>
**_The below APIs will be available in [next version](https://github.com/excalidraw/excalidraw/blob/master/src/packages/excalidraw/CHANGELOG.md#unreleased)_**
<details id="export-utils">
<summary><strong>Export utilities</strong></summary>
@@ -735,4 +721,3 @@ This function returns a svg with the exported elements.
| exportWithDarkMode | boolean | false | Indicates whether to export with dark mode |
</details>
</details>
+3 -3
View File
@@ -1,6 +1,6 @@
{
"name": "@excalidraw/excalidraw",
"version": "0.5.0",
"version": "0.4.3",
"main": "dist/excalidraw.min.js",
"files": [
"dist/*"
@@ -52,13 +52,13 @@
"babel-loader": "8.2.2",
"babel-plugin-transform-class-properties": "6.24.1",
"cross-env": "7.0.3",
"css-loader": "5.1.3",
"css-loader": "5.1.2",
"file-loader": "6.2.0",
"mini-css-extract-plugin": "1.3.9",
"sass-loader": "11.0.1",
"terser-webpack-plugin": "5.1.1",
"ts-loader": "8.0.18",
"webpack": "5.27.1",
"webpack": "5.24.3",
"webpack-bundle-analyzer": "4.4.0",
"webpack-cli": "4.5.0"
},
+8 -8
View File
@@ -1497,10 +1497,10 @@ cross-spawn@^7.0.1, cross-spawn@^7.0.3:
shebang-command "^2.0.0"
which "^2.0.1"
css-loader@5.1.3:
version "5.1.3"
resolved "https://registry.yarnpkg.com/css-loader/-/css-loader-5.1.3.tgz#87f6fc96816b20debe3cf682f85c7e56a963d0d1"
integrity sha512-CoPZvyh8sLiGARK3gqczpfdedbM74klGWurF2CsNZ2lhNaXdLIUks+3Mfax3WBeRuHoglU+m7KG/+7gY6G4aag==
css-loader@5.1.2:
version "5.1.2"
resolved "https://registry.yarnpkg.com/css-loader/-/css-loader-5.1.2.tgz#b93dba498ec948b543b49d4fab5017205d4f5c3e"
integrity sha512-T7vTXHSx0KrVEg/xjcl7G01RcVXpcw4OELwDPvkr7izQNny85A84dK3dqrczuEfBcu7Yg7mdTjJLSTibRUoRZg==
dependencies:
camelcase "^6.2.0"
cssesc "^3.0.0"
@@ -2663,10 +2663,10 @@ webpack-sources@^2.1.1:
source-list-map "^2.0.1"
source-map "^0.6.1"
webpack@5.27.1:
version "5.27.1"
resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.27.1.tgz#6808fb6e45e35290cdb8ae43c7a10884839a3079"
integrity sha512-rxIDsPZ3Apl3JcqiemiLmWH+hAq04YeOXqvCxNZOnTp8ZgM9NEPtbu4CaMfMEf9KShnx/Ym8uLGmM6P4XnwCoA==
webpack@5.24.3:
version "5.24.3"
resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.24.3.tgz#6ec0f5059f8d7c7961075fa553cfce7b7928acb3"
integrity sha512-x7lrWZ7wlWAdyKdML6YPvfVZkhD1ICuIZGODE5SzKJjqI9A4SpqGTjGJTc6CwaHqn19gGaoOR3ONJ46nYsn9rw==
dependencies:
"@types/eslint-scope" "^3.7.0"
"@types/estree" "^0.0.46"
+2 -2
View File
@@ -44,11 +44,11 @@
"babel-loader": "8.2.2",
"babel-plugin-transform-class-properties": "6.24.1",
"cross-env": "7.0.3",
"css-loader": "5.1.3",
"css-loader": "5.1.2",
"file-loader": "6.2.0",
"sass-loader": "11.0.1",
"ts-loader": "8.0.18",
"webpack": "5.27.1",
"webpack": "5.24.3",
"webpack-bundle-analyzer": "4.4.0",
"webpack-cli": "4.5.0"
},
+8 -8
View File
@@ -1446,10 +1446,10 @@ cross-spawn@^7.0.1, cross-spawn@^7.0.3:
shebang-command "^2.0.0"
which "^2.0.1"
css-loader@5.1.3:
version "5.1.3"
resolved "https://registry.yarnpkg.com/css-loader/-/css-loader-5.1.3.tgz#87f6fc96816b20debe3cf682f85c7e56a963d0d1"
integrity sha512-CoPZvyh8sLiGARK3gqczpfdedbM74klGWurF2CsNZ2lhNaXdLIUks+3Mfax3WBeRuHoglU+m7KG/+7gY6G4aag==
css-loader@5.1.2:
version "5.1.2"
resolved "https://registry.yarnpkg.com/css-loader/-/css-loader-5.1.2.tgz#b93dba498ec948b543b49d4fab5017205d4f5c3e"
integrity sha512-T7vTXHSx0KrVEg/xjcl7G01RcVXpcw4OELwDPvkr7izQNny85A84dK3dqrczuEfBcu7Yg7mdTjJLSTibRUoRZg==
dependencies:
camelcase "^6.2.0"
cssesc "^3.0.0"
@@ -2595,10 +2595,10 @@ webpack-sources@^2.1.1:
source-list-map "^2.0.1"
source-map "^0.6.1"
webpack@5.27.1:
version "5.27.1"
resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.27.1.tgz#6808fb6e45e35290cdb8ae43c7a10884839a3079"
integrity sha512-rxIDsPZ3Apl3JcqiemiLmWH+hAq04YeOXqvCxNZOnTp8ZgM9NEPtbu4CaMfMEf9KShnx/Ym8uLGmM6P4XnwCoA==
webpack@5.24.3:
version "5.24.3"
resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.24.3.tgz#6ec0f5059f8d7c7961075fa553cfce7b7928acb3"
integrity sha512-x7lrWZ7wlWAdyKdML6YPvfVZkhD1ICuIZGODE5SzKJjqI9A4SpqGTjGJTc6CwaHqn19gGaoOR3ONJ46nYsn9rw==
dependencies:
"@types/eslint-scope" "^3.7.0"
"@types/estree" "^0.0.46"
@@ -3357,8 +3357,8 @@ Object {
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
"version": 4,
"versionNonce": 453191,
"version": 8,
"versionNonce": 1116226695,
"width": 10,
"x": 10,
"y": 10,
@@ -3429,7 +3429,7 @@ Object {
"elements": Array [
Object {
"angle": 0,
"backgroundColor": "#fa5252",
"backgroundColor": "transparent",
"boundElementIds": null,
"fillStyle": "hachure",
"groupIds": Array [],
@@ -3452,6 +3452,78 @@ Object {
},
],
},
Object {
"appState": Object {
"editingGroupId": null,
"editingLinearElement": null,
"name": "Untitled-201933152653",
"selectedElementIds": Object {
"id0": true,
},
"viewBackgroundColor": "#ffffff",
},
"elements": Array [
Object {
"angle": 0,
"backgroundColor": "#fa5252",
"boundElementIds": null,
"fillStyle": "hachure",
"groupIds": Array [],
"height": 10,
"id": "id0",
"isDeleted": false,
"opacity": 100,
"roughness": 1,
"seed": 337897,
"strokeColor": "#000000",
"strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
"version": 5,
"versionNonce": 401146281,
"width": 10,
"x": 10,
"y": 10,
},
],
},
Object {
"appState": Object {
"editingGroupId": null,
"editingLinearElement": null,
"name": "Untitled-201933152653",
"selectedElementIds": Object {
"id0": true,
},
"viewBackgroundColor": "#ffffff",
},
"elements": Array [
Object {
"angle": 0,
"backgroundColor": "#fa5252",
"boundElementIds": null,
"fillStyle": "hachure",
"groupIds": Array [],
"height": 10,
"id": "id0",
"isDeleted": false,
"opacity": 100,
"roughness": 1,
"seed": 337897,
"strokeColor": "#000000",
"strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
"version": 6,
"versionNonce": 2019559783,
"width": 10,
"x": 10,
"y": 10,
},
],
},
Object {
"appState": Object {
"editingGroupId": null,
@@ -3480,8 +3552,8 @@ Object {
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
"version": 4,
"versionNonce": 453191,
"version": 8,
"versionNonce": 1116226695,
"width": 10,
"x": 10,
"y": 10,
@@ -14673,7 +14745,7 @@ Object {
"strokeWidth": 2,
"type": "rectangle",
"version": 3,
"versionNonce": 1505387817,
"versionNonce": 81784553,
"width": 20,
"x": 10,
"y": 10,
@@ -14692,14 +14764,14 @@ Object {
"isDeleted": false,
"opacity": 60,
"roughness": 2,
"seed": 238820263,
"seed": 23633383,
"strokeColor": "#c92a2a",
"strokeSharpness": "sharp",
"strokeStyle": "dotted",
"strokeWidth": 2,
"type": "rectangle",
"version": 9,
"versionNonce": 1604849351,
"version": 13,
"versionNonce": 915032327,
"width": 20,
"x": 40,
"y": 40,
@@ -14862,7 +14934,7 @@ Object {
"opacity": 100,
"roughness": 1,
"seed": 449462985,
"strokeColor": "#c92a2a",
"strokeColor": "#000000",
"strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
@@ -14909,42 +14981,6 @@ Object {
"x": 10,
"y": 10,
},
Object {
"angle": 0,
"backgroundColor": "#e64980",
"boundElementIds": null,
"fillStyle": "hachure",
"groupIds": Array [],
"height": 20,
"id": "id1",
"isDeleted": false,
"opacity": 100,
"roughness": 1,
"seed": 449462985,
"strokeColor": "#c92a2a",
"strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
"version": 4,
"versionNonce": 2019559783,
"width": 20,
"x": 40,
"y": 40,
},
],
},
Object {
"appState": Object {
"editingGroupId": null,
"editingLinearElement": null,
"name": "Untitled-201933152653",
"selectedElementIds": Object {
"id1": true,
},
"viewBackgroundColor": "#ffffff",
},
"elements": Array [
Object {
"angle": 0,
"backgroundColor": "transparent",
@@ -14952,29 +14988,6 @@ Object {
"fillStyle": "hachure",
"groupIds": Array [],
"height": 20,
"id": "id0",
"isDeleted": false,
"opacity": 100,
"roughness": 1,
"seed": 337897,
"strokeColor": "#000000",
"strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
"version": 2,
"versionNonce": 1278240551,
"width": 20,
"x": 10,
"y": 10,
},
Object {
"angle": 0,
"backgroundColor": "#e64980",
"boundElementIds": null,
"fillStyle": "cross-hatch",
"groupIds": Array [],
"height": 20,
"id": "id1",
"isDeleted": false,
"opacity": 100,
@@ -15029,9 +15042,9 @@ Object {
},
Object {
"angle": 0,
"backgroundColor": "#e64980",
"backgroundColor": "transparent",
"boundElementIds": null,
"fillStyle": "cross-hatch",
"fillStyle": "hachure",
"groupIds": Array [],
"height": 20,
"id": "id1",
@@ -15042,7 +15055,7 @@ Object {
"strokeColor": "#c92a2a",
"strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 2,
"strokeWidth": 1,
"type": "rectangle",
"version": 6,
"versionNonce": 1116226695,
@@ -15090,7 +15103,7 @@ Object {
"angle": 0,
"backgroundColor": "#e64980",
"boundElementIds": null,
"fillStyle": "cross-hatch",
"fillStyle": "hachure",
"groupIds": Array [],
"height": 20,
"id": "id1",
@@ -15100,11 +15113,11 @@ Object {
"seed": 449462985,
"strokeColor": "#c92a2a",
"strokeSharpness": "sharp",
"strokeStyle": "dotted",
"strokeWidth": 2,
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
"version": 7,
"versionNonce": 1014066025,
"version": 8,
"versionNonce": 238820263,
"width": 20,
"x": 40,
"y": 40,
@@ -15155,14 +15168,14 @@ Object {
"id": "id1",
"isDeleted": false,
"opacity": 100,
"roughness": 2,
"seed": 238820263,
"roughness": 1,
"seed": 449462985,
"strokeColor": "#c92a2a",
"strokeSharpness": "sharp",
"strokeStyle": "dotted",
"strokeWidth": 2,
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
"version": 8,
"version": 9,
"versionNonce": 400692809,
"width": 20,
"x": 40,
@@ -15213,16 +15226,193 @@ Object {
"height": 20,
"id": "id1",
"isDeleted": false,
"opacity": 60,
"roughness": 2,
"seed": 238820263,
"opacity": 100,
"roughness": 1,
"seed": 449462985,
"strokeColor": "#c92a2a",
"strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 2,
"type": "rectangle",
"version": 10,
"versionNonce": 1604849351,
"width": 20,
"x": 40,
"y": 40,
},
],
},
Object {
"appState": Object {
"editingGroupId": null,
"editingLinearElement": null,
"name": "Untitled-201933152653",
"selectedElementIds": Object {
"id1": true,
},
"viewBackgroundColor": "#ffffff",
},
"elements": Array [
Object {
"angle": 0,
"backgroundColor": "transparent",
"boundElementIds": null,
"fillStyle": "hachure",
"groupIds": Array [],
"height": 20,
"id": "id0",
"isDeleted": false,
"opacity": 100,
"roughness": 1,
"seed": 337897,
"strokeColor": "#000000",
"strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
"version": 2,
"versionNonce": 1278240551,
"width": 20,
"x": 10,
"y": 10,
},
Object {
"angle": 0,
"backgroundColor": "#e64980",
"boundElementIds": null,
"fillStyle": "cross-hatch",
"groupIds": Array [],
"height": 20,
"id": "id1",
"isDeleted": false,
"opacity": 100,
"roughness": 1,
"seed": 449462985,
"strokeColor": "#c92a2a",
"strokeSharpness": "sharp",
"strokeStyle": "dotted",
"strokeWidth": 2,
"type": "rectangle",
"version": 9,
"versionNonce": 1604849351,
"version": 11,
"versionNonce": 1505387817,
"width": 20,
"x": 40,
"y": 40,
},
],
},
Object {
"appState": Object {
"editingGroupId": null,
"editingLinearElement": null,
"name": "Untitled-201933152653",
"selectedElementIds": Object {
"id1": true,
},
"viewBackgroundColor": "#ffffff",
},
"elements": Array [
Object {
"angle": 0,
"backgroundColor": "transparent",
"boundElementIds": null,
"fillStyle": "hachure",
"groupIds": Array [],
"height": 20,
"id": "id0",
"isDeleted": false,
"opacity": 100,
"roughness": 1,
"seed": 337897,
"strokeColor": "#000000",
"strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
"version": 2,
"versionNonce": 1278240551,
"width": 20,
"x": 10,
"y": 10,
},
Object {
"angle": 0,
"backgroundColor": "#e64980",
"boundElementIds": null,
"fillStyle": "cross-hatch",
"groupIds": Array [],
"height": 20,
"id": "id1",
"isDeleted": false,
"opacity": 100,
"roughness": 2,
"seed": 23633383,
"strokeColor": "#c92a2a",
"strokeSharpness": "sharp",
"strokeStyle": "dotted",
"strokeWidth": 2,
"type": "rectangle",
"version": 12,
"versionNonce": 493213705,
"width": 20,
"x": 40,
"y": 40,
},
],
},
Object {
"appState": Object {
"editingGroupId": null,
"editingLinearElement": null,
"name": "Untitled-201933152653",
"selectedElementIds": Object {
"id1": true,
},
"viewBackgroundColor": "#ffffff",
},
"elements": Array [
Object {
"angle": 0,
"backgroundColor": "transparent",
"boundElementIds": null,
"fillStyle": "hachure",
"groupIds": Array [],
"height": 20,
"id": "id0",
"isDeleted": false,
"opacity": 100,
"roughness": 1,
"seed": 337897,
"strokeColor": "#000000",
"strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
"version": 2,
"versionNonce": 1278240551,
"width": 20,
"x": 10,
"y": 10,
},
Object {
"angle": 0,
"backgroundColor": "#e64980",
"boundElementIds": null,
"fillStyle": "cross-hatch",
"groupIds": Array [],
"height": 20,
"id": "id1",
"isDeleted": false,
"opacity": 60,
"roughness": 2,
"seed": 23633383,
"strokeColor": "#c92a2a",
"strokeSharpness": "sharp",
"strokeStyle": "dotted",
"strokeWidth": 2,
"type": "rectangle",
"version": 13,
"versionNonce": 915032327,
"width": 20,
"x": 40,
"y": 40,
@@ -15258,7 +15448,7 @@ Object {
"strokeWidth": 2,
"type": "rectangle",
"version": 3,
"versionNonce": 1505387817,
"versionNonce": 81784553,
"width": 20,
"x": 10,
"y": 10,
@@ -15274,14 +15464,14 @@ Object {
"isDeleted": false,
"opacity": 60,
"roughness": 2,
"seed": 238820263,
"seed": 23633383,
"strokeColor": "#c92a2a",
"strokeSharpness": "sharp",
"strokeStyle": "dotted",
"strokeWidth": 2,
"type": "rectangle",
"version": 9,
"versionNonce": 1604849351,
"version": 13,
"versionNonce": 915032327,
"width": 20,
"x": 40,
"y": 40,
@@ -16879,8 +17069,8 @@ Object {
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
"version": 3,
"versionNonce": 449462985,
"version": 5,
"versionNonce": 401146281,
"width": 10,
"x": 0,
"y": 0,
@@ -16951,7 +17141,7 @@ Object {
"elements": Array [
Object {
"angle": 0,
"backgroundColor": "#fa5252",
"backgroundColor": "transparent",
"boundElementIds": null,
"fillStyle": "hachure",
"groupIds": Array [],
@@ -16974,6 +17164,42 @@ Object {
},
],
},
Object {
"appState": Object {
"editingGroupId": null,
"editingLinearElement": null,
"name": "Untitled-201933152653",
"selectedElementIds": Object {
"id0": true,
},
"viewBackgroundColor": "#ffffff",
},
"elements": Array [
Object {
"angle": 0,
"backgroundColor": "#fa5252",
"boundElementIds": null,
"fillStyle": "hachure",
"groupIds": Array [],
"height": 10,
"id": "id0",
"isDeleted": false,
"opacity": 100,
"roughness": 1,
"seed": 337897,
"strokeColor": "#000000",
"strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
"version": 5,
"versionNonce": 401146281,
"width": 10,
"x": 0,
"y": 0,
},
],
},
],
}
`;
@@ -20381,7 +20607,7 @@ Object {
"strokeWidth": 1,
"type": "rectangle",
"version": 6,
"versionNonce": 760410951,
"versionNonce": 1006504105,
"width": 20,
"x": 10,
"y": -10,
@@ -20407,7 +20633,7 @@ Object {
"strokeWidth": 1,
"type": "rectangle",
"version": 6,
"versionNonce": 1006504105,
"versionNonce": 289600103,
"width": 30,
"x": 40,
"y": 0,
@@ -20450,8 +20676,8 @@ Object {
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "arrow",
"version": 9,
"versionNonce": 81784553,
"version": 11,
"versionNonce": 1315507081,
"width": 60,
"x": 130,
"y": 10,
+1 -2
View File
@@ -3,7 +3,6 @@ import { render, waitFor } from "./test-utils";
import ExcalidrawApp from "../excalidraw-app";
import { API } from "./helpers/api";
import { getDefaultAppState } from "../appState";
import { EXPORT_DATA_TYPES } from "../constants";
const { h } = window;
@@ -30,7 +29,7 @@ describe("appState", () => {
new Blob(
[
JSON.stringify({
type: EXPORT_DATA_TYPES.excalidraw,
type: "excalidraw",
appState: {
viewBackgroundColor: "#000",
},
+6 -5
View File
@@ -111,11 +111,11 @@ describe("<Excalidraw/>", () => {
const { container } = await render(<Excalidraw />);
fireEvent.click(queryByTestId(container, "export-button")!);
const textInput: HTMLInputElement | null = document.querySelector(
const textInput = document.querySelector(
".ExportDialog__name .TextInput",
);
expect(textInput?.value).toContain(`${t("labels.untitled")}`);
expect(textInput?.nodeName).toBe("INPUT");
expect(textInput?.textContent).toContain(`${t("labels.untitled")}`);
expect(textInput?.hasAttribute("data-type")).toBe(true);
});
it('should set the name and not allow editing when the name prop is present"', async () => {
@@ -124,10 +124,11 @@ describe("<Excalidraw/>", () => {
await fireEvent.click(queryByTestId(container, "export-button")!);
const textInput = document.querySelector(
".ExportDialog__name .TextInput--readonly",
".ExportDialog__name .TextInput",
);
expect(textInput?.textContent).toEqual(name);
expect(textInput?.nodeName).toBe("SPAN");
expect(textInput?.hasAttribute("data-type")).toBe(false);
});
});
});
+1 -2
View File
@@ -6,7 +6,6 @@ import { API } from "./helpers/api";
import { getDefaultAppState } from "../appState";
import { waitFor } from "@testing-library/react";
import { createUndoAction, createRedoAction } from "../actions/actionHistory";
import { EXPORT_DATA_TYPES } from "../constants";
const { h } = window;
@@ -77,7 +76,7 @@ describe("history", () => {
new Blob(
[
JSON.stringify({
type: EXPORT_DATA_TYPES.excalidraw,
type: "excalidraw",
appState: {
...getDefaultAppState(),
viewBackgroundColor: "#000",
+1 -1
View File
@@ -607,7 +607,7 @@ describe("regression tests", () => {
it("updates fontSize & fontFamily appState", () => {
UI.clickTool("text");
expect(h.state.currentItemFontFamily).toEqual(1); // Virgil
fireEvent.click(screen.getByTitle(/code/i));
fireEvent.click(screen.getByText(/code/i));
expect(h.state.currentItemFontFamily).toEqual(3); // Cascadia
});
+3 -1
View File
@@ -131,7 +131,9 @@ export const debounce = <T extends any[]>(
};
ret.flush = () => {
clearTimeout(handle);
fn(...(lastArgs || []));
if (lastArgs) {
fn(...lastArgs);
}
};
ret.cancel = () => {
clearTimeout(handle);
+789 -906
View File
File diff suppressed because it is too large Load Diff