mirror of
https://github.com/excalidraw/excalidraw
synced 2025-07-25 13:58:22 +08:00
Compare commits
26 Commits
updatescen
...
gcp-portal
Author | SHA1 | Date | |
---|---|---|---|
7988cdfb60 | |||
40656c70d1 | |||
c5b4b04d6b | |||
1ad212677b | |||
32427c355c | |||
402a812159 | |||
0480753581 | |||
f7e17a28fa | |||
78f3a92dd1 | |||
c8743a8c02 | |||
127c1be6ad | |||
86bf2d697d | |||
7ee8de0a46 | |||
981f327b48 | |||
eeea8406c9 | |||
0f249e3b26 | |||
2c7c80bd75 | |||
94ad8eaa19 | |||
13d9374cde | |||
efb6d0825b | |||
80a61db72f | |||
9a13dd8836 | |||
cf6a5ff16b | |||
fa8c7abf50 | |||
c3ecbcb3ab | |||
1f7c75de63 |
2
.env
2
.env
@ -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://portal.excalidraw.com
|
||||
REACT_APP_SOCKET_SERVER_URL=https://excalidraw-portal.uc.r.appspot.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"}'
|
||||
|
@ -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.20",
|
||||
"@types/react": "17.0.2",
|
||||
"@types/react-dom": "17.0.1",
|
||||
"@types/jest": "26.0.21",
|
||||
"@types/react": "17.0.3",
|
||||
"@types/react-dom": "17.0.2",
|
||||
"@types/socket.io-client": "1.4.36",
|
||||
"browser-fs-access": "0.14.2",
|
||||
"browser-fs-access": "0.15.3",
|
||||
"clsx": "1.1.1",
|
||||
"firebase": "8.2.10",
|
||||
"i18next-browser-languagedetector": "6.0.1",
|
||||
|
@ -88,6 +88,8 @@
|
||||
<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
|
||||
|
@ -11,6 +11,7 @@ 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",
|
||||
@ -18,11 +19,14 @@ export const actionChangeProjectName = register({
|
||||
trackEvent("change", "title");
|
||||
return { appState: { ...appState, name: value }, commitToHistory: false };
|
||||
},
|
||||
PanelComponent: ({ appState, updateData }) => (
|
||||
PanelComponent: ({ appState, updateData, appProps }) => (
|
||||
<ProjectName
|
||||
label={t("labels.fileTitle")}
|
||||
value={appState.name || "Unnamed"}
|
||||
onChange={(name: string) => updateData(name)}
|
||||
isNameEditable={
|
||||
typeof appProps.name === "undefined" && !appState.viewModeEnabled
|
||||
}
|
||||
/>
|
||||
),
|
||||
});
|
||||
@ -161,9 +165,7 @@ export const actionSaveAsScene = register({
|
||||
title={t("buttons.saveAs")}
|
||||
aria-label={t("buttons.saveAs")}
|
||||
showAriaLabel={useIsMobile()}
|
||||
hidden={
|
||||
!("chooseFileSystemEntries" in window || "showOpenFilePicker" in window)
|
||||
}
|
||||
hidden={!supported}
|
||||
onClick={() => updateData(null)}
|
||||
/>
|
||||
),
|
||||
|
@ -1,7 +1,6 @@
|
||||
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 {
|
||||
@ -21,6 +20,16 @@ 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 {
|
||||
@ -413,13 +422,29 @@ export const actionChangeFontSize = register({
|
||||
PanelComponent: ({ elements, appState, updateData }) => (
|
||||
<fieldset>
|
||||
<legend>{t("labels.fontSize")}</legend>
|
||||
<ButtonSelect
|
||||
<ButtonIconSelect
|
||||
group="font-size"
|
||||
options={[
|
||||
{ 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: 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={getFormValue(
|
||||
elements,
|
||||
@ -456,16 +481,28 @@ export const actionChangeFontFamily = register({
|
||||
};
|
||||
},
|
||||
PanelComponent: ({ elements, appState, updateData }) => {
|
||||
const options: { value: FontFamily; text: string }[] = [
|
||||
{ value: 1, text: t("labels.handDrawn") },
|
||||
{ value: 2, text: t("labels.normal") },
|
||||
{ value: 3, text: t("labels.code") },
|
||||
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} />,
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<fieldset>
|
||||
<legend>{t("labels.fontFamily")}</legend>
|
||||
<ButtonSelect<FontFamily | false>
|
||||
<ButtonIconSelect<FontFamily | false>
|
||||
group="font-family"
|
||||
options={options}
|
||||
value={getFormValue(
|
||||
@ -506,12 +543,24 @@ export const actionChangeTextAlign = register({
|
||||
PanelComponent: ({ elements, appState, updateData }) => (
|
||||
<fieldset>
|
||||
<legend>{t("labels.textAlign")}</legend>
|
||||
<ButtonSelect<TextAlign | false>
|
||||
<ButtonIconSelect<TextAlign | false>
|
||||
group="text-align"
|
||||
options={[
|
||||
{ value: "left", text: t("labels.left") },
|
||||
{ value: "center", text: t("labels.center") },
|
||||
{ value: "right", text: t("labels.right") },
|
||||
{
|
||||
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={getFormValue(
|
||||
elements,
|
||||
|
@ -122,6 +122,7 @@ export class ActionManager implements ActionsManagerInterface {
|
||||
appState={this.getAppState()}
|
||||
updateData={updateData}
|
||||
id={id}
|
||||
appProps={this.app.props}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
import React from "react";
|
||||
import { ExcalidrawElement } from "../element/types";
|
||||
import { AppState } from "../types";
|
||||
import { AppState, ExcalidrawProps } from "../types";
|
||||
|
||||
/** if false, the action should be prevented */
|
||||
export type ActionResult =
|
||||
@ -94,6 +94,7 @@ export interface Action {
|
||||
elements: readonly ExcalidrawElement[];
|
||||
appState: AppState;
|
||||
updateData: (formData?: any) => void;
|
||||
appProps: ExcalidrawProps;
|
||||
id?: string;
|
||||
}>;
|
||||
perform: ActionFn;
|
||||
|
@ -7,12 +7,10 @@ import { AppState } from "./types";
|
||||
import { SVG_EXPORT_TAG } from "./scene/export";
|
||||
import { tryParseSpreadsheet, Spreadsheet, VALID_SPREADSHEET } from "./charts";
|
||||
import { canvasToBlob } from "./data/blob";
|
||||
|
||||
const TYPE_ELEMENTS = "excalidraw/elements";
|
||||
import { EXPORT_DATA_TYPES } from "./constants";
|
||||
|
||||
type ElementsClipboard = {
|
||||
type: typeof TYPE_ELEMENTS;
|
||||
created: number;
|
||||
type: typeof EXPORT_DATA_TYPES.excalidrawClipboard;
|
||||
elements: ExcalidrawElement[];
|
||||
};
|
||||
|
||||
@ -31,8 +29,16 @@ export const probablySupportsClipboardBlob =
|
||||
"ClipboardItem" in window &&
|
||||
"toBlob" in HTMLCanvasElement.prototype;
|
||||
|
||||
const isElementsClipboard = (contents: any): contents is ElementsClipboard => {
|
||||
if (contents?.type === TYPE_ELEMENTS) {
|
||||
const clipboardContainsElements = (
|
||||
contents: any,
|
||||
): contents is { elements: ExcalidrawElement[] } => {
|
||||
if (
|
||||
[
|
||||
EXPORT_DATA_TYPES.excalidraw,
|
||||
EXPORT_DATA_TYPES.excalidrawClipboard,
|
||||
].includes(contents?.type) &&
|
||||
Array.isArray(contents.elements)
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
@ -43,8 +49,7 @@ export const copyToClipboard = async (
|
||||
appState: AppState,
|
||||
) => {
|
||||
const contents: ElementsClipboard = {
|
||||
type: TYPE_ELEMENTS,
|
||||
created: Date.now(),
|
||||
type: EXPORT_DATA_TYPES.excalidrawClipboard,
|
||||
elements: getSelectedElements(elements, appState),
|
||||
};
|
||||
const json = JSON.stringify(contents);
|
||||
@ -131,15 +136,9 @@ export const parseClipboard = async (
|
||||
|
||||
try {
|
||||
const systemClipboardData = JSON.parse(systemClipboard);
|
||||
// system clipboard elements are newer than in-app clipboard
|
||||
if (
|
||||
isElementsClipboard(systemClipboardData) &&
|
||||
(!appClipboardData?.created ||
|
||||
appClipboardData.created < systemClipboardData.created)
|
||||
) {
|
||||
if (clipboardContainsElements(systemClipboardData)) {
|
||||
return { elements: systemClipboardData.elements };
|
||||
}
|
||||
// in-app clipboard is newer than system clipboard
|
||||
return appClipboardData;
|
||||
} catch {
|
||||
// system clipboard doesn't contain excalidraw elements → return plaintext
|
||||
|
@ -3,6 +3,7 @@ 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,
|
||||
@ -303,6 +304,7 @@ class App extends React.Component<ExcalidrawProps, AppState> {
|
||||
zenModeEnabled = false,
|
||||
gridModeEnabled = false,
|
||||
theme = defaultAppState.theme,
|
||||
name = defaultAppState.name,
|
||||
} = props;
|
||||
this.state = {
|
||||
...defaultAppState,
|
||||
@ -314,6 +316,7 @@ class App extends React.Component<ExcalidrawProps, AppState> {
|
||||
viewModeEnabled,
|
||||
zenModeEnabled,
|
||||
gridSize: gridModeEnabled ? GRID_SIZE : null,
|
||||
name,
|
||||
};
|
||||
if (excalidrawRef) {
|
||||
const readyPromise =
|
||||
@ -523,7 +526,7 @@ 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;
|
||||
if (typeof this.props.viewModeEnabled !== "undefined") {
|
||||
viewModeEnabled = this.props.viewModeEnabled;
|
||||
}
|
||||
@ -540,6 +543,10 @@ class App extends React.Component<ExcalidrawProps, AppState> {
|
||||
theme = this.props.theme;
|
||||
}
|
||||
|
||||
if (typeof this.props.name !== "undefined") {
|
||||
name = this.props.name;
|
||||
}
|
||||
|
||||
this.setState(
|
||||
(state) => {
|
||||
// using Object.assign instead of spread to fool TS 4.2.2+ into
|
||||
@ -556,6 +563,7 @@ class App extends React.Component<ExcalidrawProps, AppState> {
|
||||
zenModeEnabled,
|
||||
gridSize,
|
||||
theme,
|
||||
name,
|
||||
});
|
||||
},
|
||||
() => {
|
||||
@ -890,6 +898,13 @@ class App extends React.Component<ExcalidrawProps, AppState> {
|
||||
gridSize: this.props.gridModeEnabled ? GRID_SIZE : null,
|
||||
});
|
||||
}
|
||||
|
||||
if (this.props.name && prevProps.name !== this.props.name) {
|
||||
this.setState({
|
||||
name: this.props.name,
|
||||
});
|
||||
}
|
||||
|
||||
document
|
||||
.querySelector(".excalidraw")
|
||||
?.classList.toggle("theme--dark", this.state.theme === "dark");
|
||||
@ -1390,7 +1405,7 @@ class App extends React.Component<ExcalidrawProps, AppState> {
|
||||
return;
|
||||
}
|
||||
|
||||
if (event[KEYS.CTRL_OR_CMD]) {
|
||||
if (event[KEYS.CTRL_OR_CMD] && this.state.isBindingEnabled) {
|
||||
this.setState({ isBindingEnabled: false });
|
||||
}
|
||||
|
||||
@ -3595,10 +3610,7 @@ class App extends React.Component<ExcalidrawProps, AppState> {
|
||||
file?.name.endsWith(".excalidraw")
|
||||
) {
|
||||
this.setState({ isLoading: true });
|
||||
if (
|
||||
"chooseFileSystemEntries" in window ||
|
||||
"showOpenFilePicker" in window
|
||||
) {
|
||||
if (supported) {
|
||||
try {
|
||||
// This will only work as of Chrome 86,
|
||||
// but can be safely ignored on older releases.
|
||||
|
@ -31,9 +31,27 @@
|
||||
.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;
|
||||
border: none;
|
||||
&:hover {
|
||||
background: none;
|
||||
}
|
||||
width: auto;
|
||||
max-width: 200px;
|
||||
padding-left: 2px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -257,6 +257,7 @@ export const ExportDialog = ({
|
||||
onClick={() => {
|
||||
setModalIsShown(true);
|
||||
}}
|
||||
data-testid="export-button"
|
||||
icon={exportFile}
|
||||
type="button"
|
||||
aria-label={t("buttons.export")}
|
||||
|
@ -9,7 +9,13 @@ type HelpIconProps = {
|
||||
};
|
||||
|
||||
export const HelpIcon = (props: HelpIconProps) => (
|
||||
<label title={`${props.title} — ?`} className="help-icon">
|
||||
<div onClick={props.onClick}>{questionCircle}</div>
|
||||
</label>
|
||||
<button
|
||||
className="help-icon"
|
||||
onClick={props.onClick}
|
||||
type="button"
|
||||
title={`${props.title} — ?`}
|
||||
aria-label={props.title}
|
||||
>
|
||||
{questionCircle}
|
||||
</button>
|
||||
);
|
||||
|
@ -144,36 +144,41 @@ const LibraryMenuItems = ({
|
||||
});
|
||||
}}
|
||||
/>
|
||||
<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([]);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
|
||||
{!!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([]);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
<a
|
||||
href={`https://libraries.excalidraw.com?referrer=${referrer}`}
|
||||
href={`https://libraries.excalidraw.com?target=${
|
||||
window.name || "_blank"
|
||||
}&referrer=${referrer}`}
|
||||
target="_excalidraw_libraries"
|
||||
>
|
||||
{t("labels.libraries")}
|
||||
|
@ -1,25 +1,26 @@
|
||||
import "./TextInput.scss";
|
||||
|
||||
import React, { Component } from "react";
|
||||
import { selectNode, removeSelection } from "../utils";
|
||||
|
||||
type Props = {
|
||||
value: string;
|
||||
onChange: (value: string) => void;
|
||||
label: string;
|
||||
isNameEditable: boolean;
|
||||
};
|
||||
|
||||
export class ProjectName extends Component<Props> {
|
||||
private handleFocus = (event: React.FocusEvent<HTMLElement>) => {
|
||||
selectNode(event.currentTarget);
|
||||
type State = {
|
||||
fileName: string;
|
||||
};
|
||||
export class ProjectName extends Component<Props, State> {
|
||||
state = {
|
||||
fileName: this.props.value,
|
||||
};
|
||||
|
||||
private handleBlur = (event: React.FocusEvent<HTMLElement>) => {
|
||||
const value = event.currentTarget.innerText.trim();
|
||||
private handleBlur = (event: any) => {
|
||||
const value = event.target.value;
|
||||
if (value !== this.props.value) {
|
||||
this.props.onChange(value);
|
||||
}
|
||||
removeSelection();
|
||||
};
|
||||
|
||||
private handleKeyDown = (event: React.KeyboardEvent<HTMLElement>) => {
|
||||
@ -31,32 +32,30 @@ export class ProjectName extends Component<Props> {
|
||||
event.currentTarget.blur();
|
||||
}
|
||||
};
|
||||
private makeEditable = (editable: HTMLSpanElement | null) => {
|
||||
if (!editable) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
editable.contentEditable = "plaintext-only";
|
||||
} catch {
|
||||
editable.contentEditable = "true";
|
||||
}
|
||||
};
|
||||
|
||||
public render() {
|
||||
return (
|
||||
<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>
|
||||
<>
|
||||
<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>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -58,6 +58,7 @@ export const ToolButton = React.forwardRef((props: ToolButtonProps, ref) => {
|
||||
"ToolIcon--selected": props.selected,
|
||||
},
|
||||
)}
|
||||
data-testid={props["data-testid"]}
|
||||
hidden={props.hidden}
|
||||
title={props.title}
|
||||
aria-label={props["aria-label"]}
|
||||
|
@ -123,6 +123,22 @@ 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(
|
||||
@ -794,3 +810,121 @@ 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 },
|
||||
),
|
||||
);
|
||||
|
@ -84,9 +84,15 @@ 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;
|
||||
|
@ -222,7 +222,8 @@
|
||||
align-items: center;
|
||||
svg {
|
||||
width: 36px;
|
||||
height: 18px;
|
||||
height: 14px;
|
||||
padding: 2px;
|
||||
opacity: 0.6;
|
||||
}
|
||||
&.active svg {
|
||||
@ -453,6 +454,14 @@
|
||||
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;
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { cleanAppStateForExport } from "../appState";
|
||||
import { MIME_TYPES } from "../constants";
|
||||
import { EXPORT_DATA_TYPES, 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 !== "excalidrawlib") {
|
||||
if (data.type !== EXPORT_DATA_TYPES.excalidrawLibrary) {
|
||||
throw new Error(t("alerts.couldNotLoadInvalidFile"));
|
||||
}
|
||||
return data;
|
||||
|
@ -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 { MIME_TYPES } from "../constants";
|
||||
import { EXPORT_DATA_TYPES, MIME_TYPES } from "../constants";
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// PNG
|
||||
@ -67,7 +67,10 @@ 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 === "excalidraw") {
|
||||
if (
|
||||
"type" in encodedData &&
|
||||
encodedData.type === EXPORT_DATA_TYPES.excalidraw
|
||||
) {
|
||||
return metadata.text;
|
||||
}
|
||||
throw new Error("FAILED");
|
||||
@ -115,7 +118,10 @@ 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 === "excalidraw") {
|
||||
if (
|
||||
"type" in encodedData &&
|
||||
encodedData.type === EXPORT_DATA_TYPES.excalidraw
|
||||
) {
|
||||
return json;
|
||||
}
|
||||
throw new Error("FAILED");
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { fileOpen, fileSave } from "browser-fs-access";
|
||||
import { cleanAppStateForExport } from "../appState";
|
||||
import { MIME_TYPES } from "../constants";
|
||||
import { EXPORT_DATA_TYPES, 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: "excalidraw",
|
||||
type: EXPORT_DATA_TYPES.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 === "excalidraw" &&
|
||||
data?.type === EXPORT_DATA_TYPES.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 === "excalidrawlib" &&
|
||||
json.type === EXPORT_DATA_TYPES.excalidrawLibrary &&
|
||||
json.version === 1
|
||||
);
|
||||
};
|
||||
@ -89,7 +89,7 @@ export const saveLibraryAsJSON = async () => {
|
||||
const library = await Library.loadLibrary();
|
||||
const serialized = JSON.stringify(
|
||||
{
|
||||
type: "excalidrawlib",
|
||||
type: EXPORT_DATA_TYPES.excalidrawLibrary,
|
||||
version: 1,
|
||||
library,
|
||||
},
|
||||
|
@ -87,9 +87,30 @@ export const mutateElement = <TElement extends Mutable<ExcalidrawElement>>(
|
||||
export const newElementWith = <TElement extends ExcalidrawElement>(
|
||||
element: TElement,
|
||||
updates: ElementUpdate<TElement>,
|
||||
): TElement => ({
|
||||
...element,
|
||||
...updates,
|
||||
version: element.version + 1,
|
||||
versionNonce: randomInteger(),
|
||||
});
|
||||
): 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(),
|
||||
};
|
||||
};
|
||||
|
@ -207,7 +207,8 @@ export const textWysiwyg = ({
|
||||
// prevent blur when changing properties from the menu
|
||||
const onPointerDown = (event: MouseEvent) => {
|
||||
if (
|
||||
event.target instanceof HTMLElement &&
|
||||
(event.target instanceof HTMLElement ||
|
||||
event.target instanceof SVGElement) &&
|
||||
event.target.closest(`.${CLASSES.SHAPE_ACTIONS_MENU}`) &&
|
||||
!isWritableElement(event.target)
|
||||
) {
|
||||
|
@ -448,15 +448,8 @@ class CollabWrapper extends PureComponent<Props, CollabState> {
|
||||
|
||||
private handleRemoteSceneUpdate = (
|
||||
elements: ReconciledElements,
|
||||
{
|
||||
init = false,
|
||||
initFromSnapshot = false,
|
||||
}: { init?: boolean; initFromSnapshot?: boolean } = {},
|
||||
{ init = false }: { init?: boolean } = {},
|
||||
) => {
|
||||
if (init || initFromSnapshot) {
|
||||
this.excalidrawAPI.setScrollToContent(elements);
|
||||
}
|
||||
|
||||
this.excalidrawAPI.updateScene({
|
||||
elements,
|
||||
commitToHistory: !!init,
|
||||
|
@ -7,12 +7,27 @@ 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,
|
||||
@ -31,8 +46,6 @@ 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 {
|
||||
@ -93,7 +106,7 @@ const RoomDialog = ({
|
||||
{"share" in navigator ? (
|
||||
<ToolButton
|
||||
type="button"
|
||||
icon={isAppleBrowser ? shareIOS : share}
|
||||
icon={getShareIcon()}
|
||||
title={t("labels.share")}
|
||||
aria-label={t("labels.share")}
|
||||
onClick={shareRoomLink}
|
||||
|
@ -80,8 +80,10 @@ export type SocketUpdateData = SocketUpdateDataSource[keyof SocketUpdateDataSour
|
||||
_brand: "socketUpdateData";
|
||||
};
|
||||
|
||||
const IV_LENGTH_BYTES = 12; // 96 bits
|
||||
|
||||
export const createIV = () => {
|
||||
const arr = new Uint8Array(12);
|
||||
const arr = new Uint8Array(IV_LENGTH_BYTES);
|
||||
return window.crypto.getRandomValues(arr);
|
||||
};
|
||||
|
||||
@ -175,6 +177,22 @@ 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,
|
||||
@ -183,6 +201,7 @@ 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 {};
|
||||
@ -190,16 +209,19 @@ const importFromBackend = async (
|
||||
let data: ImportedDataState;
|
||||
if (privateKey) {
|
||||
const buffer = await response.arrayBuffer();
|
||||
const key = await getImportedKey(privateKey, "decrypt");
|
||||
const iv = new Uint8Array(12);
|
||||
const decrypted = await window.crypto.subtle.decrypt(
|
||||
{
|
||||
name: "AES-GCM",
|
||||
iv,
|
||||
},
|
||||
key,
|
||||
buffer,
|
||||
);
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
// We need to convert the decrypted array buffer to a string
|
||||
const string = new window.TextDecoder("utf-8").decode(
|
||||
new Uint8Array(decrypted) as any,
|
||||
@ -263,9 +285,8 @@ export const exportToBackend = async (
|
||||
true, // extractable
|
||||
["encrypt", "decrypt"],
|
||||
);
|
||||
// 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);
|
||||
|
||||
const iv = createIV();
|
||||
// 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(
|
||||
@ -276,6 +297,11 @@ 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);
|
||||
@ -283,7 +309,7 @@ export const exportToBackend = async (
|
||||
try {
|
||||
const response = await fetch(BACKEND_V2_POST, {
|
||||
method: "POST",
|
||||
body: encrypted,
|
||||
body: payload,
|
||||
});
|
||||
const json = await response.json();
|
||||
if (json.id) {
|
||||
|
@ -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": "",
|
||||
"collabStopOverridePrompt": "إيقاف الجلسة سيؤدي إلى الكتابة فوق رسومك السابقة المخزنة داخليا. هل أنت متأكد؟\n\n(إذا كنت ترغب في الاحتفاظ برسمك المخزن داخليا، ببساطة أغلق علامة تبويب المتصفح بدلاً من ذلك.)",
|
||||
"errorLoadingLibrary": "حصل خطأ أثناء تحميل مكتبة الطرف الثالث.",
|
||||
"confirmAddLibrary": "هذا سيضيف {{numShapes}} شكل إلى مكتبتك. هل أنت متأكد؟",
|
||||
"imageDoesNotContainScene": "استيراد الصور غير مدعوم في الوقت الراهن.\n\nهل تريد استيراد مشهد؟ لا يبدو أن هذه الصورة تحتوي على أي بيانات مشهد. هل قمت بسماح هذا أثناء التصدير؟",
|
||||
@ -212,9 +212,9 @@
|
||||
"curvedArrow": "",
|
||||
"curvedLine": "",
|
||||
"documentation": "",
|
||||
"drag": "",
|
||||
"editor": "",
|
||||
"github": "",
|
||||
"drag": "اسحب",
|
||||
"editor": "المحرر",
|
||||
"github": "عثرت على مشكلة؟ إرسال",
|
||||
"howto": "",
|
||||
"or": "",
|
||||
"preventBinding": "",
|
||||
|
@ -61,7 +61,7 @@
|
||||
"architect": "Архитект",
|
||||
"artist": "Художник",
|
||||
"cartoonist": "Карикатурист",
|
||||
"fileTitle": "Заглавие на файл",
|
||||
"fileTitle": "",
|
||||
"colorPicker": "Избор на цвят",
|
||||
"canvasBackground": "Фон на платно",
|
||||
"drawingCanvas": "Платно за рисуване",
|
||||
|
@ -61,7 +61,7 @@
|
||||
"architect": "Arquitecte",
|
||||
"artist": "Artista",
|
||||
"cartoonist": "Dibuixant",
|
||||
"fileTitle": "Títol del fitxer",
|
||||
"fileTitle": "",
|
||||
"colorPicker": "Selector de colors",
|
||||
"canvasBackground": "Fons del llenç",
|
||||
"drawingCanvas": "Llenç de dibuix",
|
||||
|
@ -61,7 +61,7 @@
|
||||
"architect": "Αρχιτέκτονας",
|
||||
"artist": "Καλλιτέχνης",
|
||||
"cartoonist": "Σκιτσογράφος",
|
||||
"fileTitle": "Τίτλος αρχείου",
|
||||
"fileTitle": "",
|
||||
"colorPicker": "Επιλογή Χρώματος",
|
||||
"canvasBackground": "Φόντο καμβά",
|
||||
"drawingCanvas": "Σχεδίαση καμβά",
|
||||
@ -94,7 +94,7 @@
|
||||
"distributeVertically": "Κατακόρυφη κατανομή",
|
||||
"viewMode": "Λειτουργία προβολής",
|
||||
"toggleExportColorScheme": "Εναλλαγή εξαγωγής θέματος χρωμάτων",
|
||||
"share": ""
|
||||
"share": "Κοινοποίηση"
|
||||
},
|
||||
"buttons": {
|
||||
"clearReset": "Επαναφορά του καμβά",
|
||||
|
@ -61,7 +61,7 @@
|
||||
"architect": "Architect",
|
||||
"artist": "Artist",
|
||||
"cartoonist": "Cartoonist",
|
||||
"fileTitle": "File title",
|
||||
"fileTitle": "File name",
|
||||
"colorPicker": "Color picker",
|
||||
"canvasBackground": "Canvas background",
|
||||
"drawingCanvas": "Drawing canvas",
|
||||
|
@ -61,7 +61,7 @@
|
||||
"architect": "Arquitecto",
|
||||
"artist": "Artista",
|
||||
"cartoonist": "Caricatura",
|
||||
"fileTitle": "Título del archivo",
|
||||
"fileTitle": "Nombre del archivo",
|
||||
"colorPicker": "Selector de color",
|
||||
"canvasBackground": "Fondo del lienzo",
|
||||
"drawingCanvas": "Lienzo de dibujo",
|
||||
|
@ -61,7 +61,7 @@
|
||||
"architect": "معمار",
|
||||
"artist": "هنرمند",
|
||||
"cartoonist": "کارتونیست",
|
||||
"fileTitle": "عنوان فایل",
|
||||
"fileTitle": "",
|
||||
"colorPicker": "انتخابگر رنگ",
|
||||
"canvasBackground": "بوم",
|
||||
"drawingCanvas": "بوم نقاشی",
|
||||
|
@ -61,7 +61,7 @@
|
||||
"architect": "Arkkitehti",
|
||||
"artist": "Taiteilija",
|
||||
"cartoonist": "Sarjakuva",
|
||||
"fileTitle": "Tiedoston otsikko",
|
||||
"fileTitle": "Tiedostonimi",
|
||||
"colorPicker": "Värin valinta",
|
||||
"canvasBackground": "Piirtoalueen tausta",
|
||||
"drawingCanvas": "Piirtoalue",
|
||||
|
@ -61,7 +61,7 @@
|
||||
"architect": "Architecte",
|
||||
"artist": "Artiste",
|
||||
"cartoonist": "Caricaturiste",
|
||||
"fileTitle": "Titre du fichier",
|
||||
"fileTitle": "Nom du fichier",
|
||||
"colorPicker": "Sélecteur de couleur",
|
||||
"canvasBackground": "Arrière-plan du canevas",
|
||||
"drawingCanvas": "Zone de dessin",
|
||||
|
@ -61,7 +61,7 @@
|
||||
"architect": "ארכיטקט",
|
||||
"artist": "אמן",
|
||||
"cartoonist": "קריקטוריסט",
|
||||
"fileTitle": "כותרת הקובץ",
|
||||
"fileTitle": "",
|
||||
"colorPicker": "בחירת צבע",
|
||||
"canvasBackground": "רקע הלוח",
|
||||
"drawingCanvas": "לוח ציור",
|
||||
|
@ -61,7 +61,7 @@
|
||||
"architect": "वास्तुकार",
|
||||
"artist": "कलाकार",
|
||||
"cartoonist": "व्यंग्य चित्रकार",
|
||||
"fileTitle": "फ़ाइल का शीर्षक",
|
||||
"fileTitle": "",
|
||||
"colorPicker": "रंग चयन",
|
||||
"canvasBackground": "कैनवास बैकग्राउंड",
|
||||
"drawingCanvas": "कैनवास बना रहे हैं",
|
||||
|
@ -61,7 +61,7 @@
|
||||
"architect": "Tervezői",
|
||||
"artist": "Művészi",
|
||||
"cartoonist": "Karikatúrás",
|
||||
"fileTitle": "Fájl címe",
|
||||
"fileTitle": "",
|
||||
"colorPicker": "Színválasztó",
|
||||
"canvasBackground": "Vászon háttérszíne",
|
||||
"drawingCanvas": "Rajzvászon",
|
||||
|
@ -61,7 +61,7 @@
|
||||
"architect": "Arsitek",
|
||||
"artist": "Artis",
|
||||
"cartoonist": "Kartunis",
|
||||
"fileTitle": "Judul file",
|
||||
"fileTitle": "Nama 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": "",
|
||||
"invalidSceneUrl": "Tidak dapat impor pemandangan dari URL. Kemungkinan URL itu rusak atau tidak berisi data JSON Excalidraw yang valid.",
|
||||
"resetLibrary": "Ini akan menghapus pustaka Anda. Anda yakin?"
|
||||
},
|
||||
"toolBar": {
|
||||
|
@ -61,7 +61,7 @@
|
||||
"architect": "Architetto",
|
||||
"artist": "Artista",
|
||||
"cartoonist": "Fumettista",
|
||||
"fileTitle": "Titolo del file",
|
||||
"fileTitle": "Nome del file",
|
||||
"colorPicker": "Selettore colore",
|
||||
"canvasBackground": "Sfondo tela",
|
||||
"drawingCanvas": "Area di disegno",
|
||||
|
@ -61,7 +61,7 @@
|
||||
"architect": "正確",
|
||||
"artist": "アート",
|
||||
"cartoonist": "漫画風",
|
||||
"fileTitle": "ファイル名",
|
||||
"fileTitle": "",
|
||||
"colorPicker": "色選択",
|
||||
"canvasBackground": "キャンバスの背景",
|
||||
"drawingCanvas": "キャンバスの描画",
|
||||
|
@ -61,7 +61,7 @@
|
||||
"architect": "Amasdag",
|
||||
"artist": "Anaẓur",
|
||||
"cartoonist": "",
|
||||
"fileTitle": "Azwel n ufaylu",
|
||||
"fileTitle": "",
|
||||
"colorPicker": "Amafran n yini",
|
||||
"canvasBackground": "Agilal n teɣzut n usuneɣ",
|
||||
"drawingCanvas": "Taɣzut n usuneɣ",
|
||||
|
@ -61,7 +61,7 @@
|
||||
"architect": "건축가",
|
||||
"artist": "예술가",
|
||||
"cartoonist": "만화가",
|
||||
"fileTitle": "파일명",
|
||||
"fileTitle": "",
|
||||
"colorPicker": "색상 선택기",
|
||||
"canvasBackground": "캔버스 배경",
|
||||
"drawingCanvas": "캔버스 그리기",
|
||||
|
@ -61,7 +61,7 @@
|
||||
"architect": "ဗိသုကာ",
|
||||
"artist": "ပန်းချီ",
|
||||
"cartoonist": "ကာတွန်း",
|
||||
"fileTitle": "ခေါင်းစဉ်",
|
||||
"fileTitle": "",
|
||||
"colorPicker": "အရောင်ရွေး",
|
||||
"canvasBackground": "ကားချပ်နောက်ခံ",
|
||||
"drawingCanvas": "ပုံဆွဲကားချပ်",
|
||||
|
@ -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": "",
|
||||
"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)",
|
||||
"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?",
|
||||
|
@ -61,7 +61,7 @@
|
||||
"architect": "Arkitekt",
|
||||
"artist": "Kunstnar",
|
||||
"cartoonist": "Teiknar",
|
||||
"fileTitle": "Filnamn",
|
||||
"fileTitle": "",
|
||||
"colorPicker": "Fargeveljar",
|
||||
"canvasBackground": "Lerretsbakgrunn",
|
||||
"drawingCanvas": "Lerret",
|
||||
|
@ -61,7 +61,7 @@
|
||||
"architect": "Arquitècte",
|
||||
"artist": "Artista",
|
||||
"cartoonist": "Dessenhaire",
|
||||
"fileTitle": "Títol del fichièr",
|
||||
"fileTitle": "Nom del fichièr",
|
||||
"colorPicker": "Selector de color",
|
||||
"canvasBackground": "Rèireplan del canabàs",
|
||||
"drawingCanvas": "Zòna de dessenh",
|
||||
|
@ -61,7 +61,7 @@
|
||||
"architect": "ਭਵਨ ਨਿਰਮਾਣਕਾਰੀ",
|
||||
"artist": "ਕਲਾਕਾਰ",
|
||||
"cartoonist": "ਕਾਰਟੂਨਿਸਟ",
|
||||
"fileTitle": "ਫਾਈਲ ਦਾ ਸਿਰਨਾਵਾਂ",
|
||||
"fileTitle": "",
|
||||
"colorPicker": "ਰੰਗ ਚੋਣਕਾਰ",
|
||||
"canvasBackground": "ਕੈਨਵਸ ਦਾ ਬੈਕਗਰਾਉਂਡ",
|
||||
"drawingCanvas": "ਡਰਾਇੰਗ ਕੈਨਵਸ",
|
||||
|
@ -1,37 +1,37 @@
|
||||
{
|
||||
"ar-SA": 83,
|
||||
"ar-SA": 86,
|
||||
"bg-BG": 94,
|
||||
"ca-ES": 100,
|
||||
"ca-ES": 99,
|
||||
"de-DE": 100,
|
||||
"el-GR": 98,
|
||||
"en": 100,
|
||||
"es-ES": 100,
|
||||
"fa-IR": 90,
|
||||
"fa-IR": 89,
|
||||
"fi-FI": 100,
|
||||
"fr-FR": 100,
|
||||
"he-IL": 91,
|
||||
"hi-IN": 93,
|
||||
"hu-HU": 83,
|
||||
"id-ID": 99,
|
||||
"he-IL": 90,
|
||||
"hi-IN": 92,
|
||||
"hu-HU": 82,
|
||||
"id-ID": 100,
|
||||
"it-IT": 100,
|
||||
"ja-JP": 96,
|
||||
"kab-KAB": 99,
|
||||
"kab-KAB": 98,
|
||||
"ko-KR": 94,
|
||||
"my-MM": 77,
|
||||
"nb-NO": 100,
|
||||
"nl-NL": 99,
|
||||
"nn-NO": 85,
|
||||
"nl-NL": 100,
|
||||
"nn-NO": 84,
|
||||
"oc-FR": 100,
|
||||
"pa-IN": 95,
|
||||
"pl-PL": 96,
|
||||
"pt-BR": 100,
|
||||
"pt-PT": 97,
|
||||
"pt-PT": 96,
|
||||
"ro-RO": 100,
|
||||
"ru-RU": 100,
|
||||
"sk-SK": 100,
|
||||
"sv-SE": 100,
|
||||
"tr-TR": 83,
|
||||
"uk-UA": 95,
|
||||
"zh-CN": 100,
|
||||
"tr-TR": 97,
|
||||
"uk-UA": 100,
|
||||
"zh-CN": 99,
|
||||
"zh-TW": 100
|
||||
}
|
||||
|
@ -61,7 +61,7 @@
|
||||
"architect": "Dokładny",
|
||||
"artist": "Artystyczny",
|
||||
"cartoonist": "Rysunkowy",
|
||||
"fileTitle": "Tytuł pliku",
|
||||
"fileTitle": "",
|
||||
"colorPicker": "Paleta kolorów",
|
||||
"canvasBackground": "Kolor dokumentu",
|
||||
"drawingCanvas": "Obszar roboczy",
|
||||
|
@ -61,7 +61,7 @@
|
||||
"architect": "Arquiteto",
|
||||
"artist": "Artista",
|
||||
"cartoonist": "Cartunista",
|
||||
"fileTitle": "Título do arquivo",
|
||||
"fileTitle": "Nome do arquivo",
|
||||
"colorPicker": "Seletor de cores",
|
||||
"canvasBackground": "Fundo da tela",
|
||||
"drawingCanvas": "Tela de desenho",
|
||||
|
@ -61,7 +61,7 @@
|
||||
"architect": "Arquitecto",
|
||||
"artist": "Artista",
|
||||
"cartoonist": "Caricaturista",
|
||||
"fileTitle": "Título do ficheiro",
|
||||
"fileTitle": "",
|
||||
"colorPicker": "Seletor de cores",
|
||||
"canvasBackground": "Fundo da tela",
|
||||
"drawingCanvas": "Tela de desenho",
|
||||
|
@ -61,7 +61,7 @@
|
||||
"architect": "Arhitect",
|
||||
"artist": "Artist",
|
||||
"cartoonist": "Caricaturist",
|
||||
"fileTitle": "Denumirea fișierului",
|
||||
"fileTitle": "Nume de fișier",
|
||||
"colorPicker": "Selector de culoare",
|
||||
"canvasBackground": "Fundalul pânzei",
|
||||
"drawingCanvas": "Pânză pentru desenat",
|
||||
|
@ -61,7 +61,7 @@
|
||||
"architect": "Архитектор",
|
||||
"artist": "Художник",
|
||||
"cartoonist": "Карикатурист",
|
||||
"fileTitle": "Название файла",
|
||||
"fileTitle": "Имя файла",
|
||||
"colorPicker": "Выбор цвета",
|
||||
"canvasBackground": "Фон холста",
|
||||
"drawingCanvas": "Полотно",
|
||||
|
@ -61,7 +61,7 @@
|
||||
"architect": "Arkitekt",
|
||||
"artist": "Artist",
|
||||
"cartoonist": "Serietecknare",
|
||||
"fileTitle": "Filtitel",
|
||||
"fileTitle": "Filnamn",
|
||||
"colorPicker": "Färgväljare",
|
||||
"canvasBackground": "Canvas-bakgrund",
|
||||
"drawingCanvas": "Ritar canvas",
|
||||
|
@ -1,7 +1,7 @@
|
||||
{
|
||||
"labels": {
|
||||
"paste": "Yapıştır",
|
||||
"pasteCharts": "Dairesel grafik",
|
||||
"pasteCharts": "Grafikleri yapıştır",
|
||||
"selectAll": "Tümünü seç",
|
||||
"multiSelect": "Seçime öge ekle",
|
||||
"moveCanvas": "Tuvali taşı",
|
||||
@ -68,7 +68,7 @@
|
||||
"layers": "Katmanlar",
|
||||
"actions": "Eylemler",
|
||||
"language": "Dil",
|
||||
"liveCollaboration": "",
|
||||
"liveCollaboration": "Canlı ortak çalışma alanı",
|
||||
"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": "",
|
||||
"showGrid": "Izgarayı göster",
|
||||
"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": ""
|
||||
"share": "Paylaş"
|
||||
},
|
||||
"buttons": {
|
||||
"clearReset": "Tuvali sıfırla",
|
||||
@ -119,7 +119,7 @@
|
||||
"edit": "Düzenle",
|
||||
"undo": "Geri Al",
|
||||
"redo": "Yeniden yap",
|
||||
"resetLibrary": "",
|
||||
"resetLibrary": "Kütüphaneyi sıfırla",
|
||||
"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": "",
|
||||
"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.)",
|
||||
"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": "",
|
||||
"resetLibrary": ""
|
||||
"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?"
|
||||
},
|
||||
"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": ""
|
||||
"shareTitle": "Excalidraw'da canlı ortak calışma oturumuna katıl"
|
||||
},
|
||||
"errorDialog": {
|
||||
"title": "Hata"
|
||||
},
|
||||
"helpDialog": {
|
||||
"blog": "",
|
||||
"click": "",
|
||||
"curvedArrow": "",
|
||||
"curvedLine": "",
|
||||
"documentation": "",
|
||||
"drag": "",
|
||||
"blog": "Blog'umuzu okuyun",
|
||||
"click": "tıkla",
|
||||
"curvedArrow": "Eğri ok",
|
||||
"curvedLine": "Eğri çizgi",
|
||||
"documentation": "Dokümantasyon",
|
||||
"drag": "sürükle",
|
||||
"editor": "",
|
||||
"github": "",
|
||||
"howto": "",
|
||||
"or": "",
|
||||
"github": "Bir hata mı buldun? Bildir",
|
||||
"howto": "Rehberlerimizi takip edin",
|
||||
"or": "veya",
|
||||
"preventBinding": "",
|
||||
"shapes": "",
|
||||
"shortcuts": "",
|
||||
"textFinish": "",
|
||||
"textNewLine": "",
|
||||
"title": "",
|
||||
"shapes": "Şekiller",
|
||||
"shortcuts": "Klavye kısayolları",
|
||||
"textFinish": "(Metin) düzenlemeyi bitir",
|
||||
"textNewLine": "Yeni satır ekle (metin)",
|
||||
"title": "Yardım",
|
||||
"view": "",
|
||||
"zoomToFit": "",
|
||||
"zoomToSelection": ""
|
||||
"zoomToFit": "Tüm öğeleri sığdırmak için yakınlaştır",
|
||||
"zoomToSelection": "Seçime yakınlaş"
|
||||
},
|
||||
"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": "",
|
||||
"versionCopy": "",
|
||||
"versionNotAvailable": "",
|
||||
"version": "Sürüm",
|
||||
"versionCopy": "Kopyalamak için tıkla",
|
||||
"versionNotAvailable": "Sürüm mevcut değil",
|
||||
"width": "Genişlik"
|
||||
},
|
||||
"toast": {
|
||||
"copyStyles": "",
|
||||
"copyToClipboard": "",
|
||||
"copyToClipboardAsPng": "",
|
||||
"fileSaved": "",
|
||||
"fileSavedToFilename": "",
|
||||
"canvas": "",
|
||||
"selection": ""
|
||||
"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"
|
||||
}
|
||||
}
|
||||
|
@ -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": "",
|
||||
"resetLibrary": ""
|
||||
"invalidSceneUrl": "Не вдалося імпортувати сцену з наданого URL. Він або недоформований, або не містить дійсних даних Excalidraw JSON.",
|
||||
"resetLibrary": "Це призведе до очищення бібліотеки. Ви впевнені?"
|
||||
},
|
||||
"toolBar": {
|
||||
"selection": "Виділення",
|
||||
@ -201,7 +201,7 @@
|
||||
"desc_inProgressIntro": "Сесія спільної роботи над кресленням триває.",
|
||||
"desc_shareLink": "Поділіться цим посиланням з будь-ким для спільної роботи:",
|
||||
"desc_exitSession": "Зупинка сесії відключить вас від кімнати, але ви зможете продовжити роботу з полотном локально. Зверніть увагу, що це не вплине на інших людей, і вони все одно зможуть працювати над їх версією.",
|
||||
"shareTitle": ""
|
||||
"shareTitle": "Приєднатися до сеансу спільної роботи на Excalidraw"
|
||||
},
|
||||
"errorDialog": {
|
||||
"title": "Помилка"
|
||||
@ -248,10 +248,10 @@
|
||||
"toast": {
|
||||
"copyStyles": "Скопійовані стилі.",
|
||||
"copyToClipboard": "Скопіювати до буферу обміну.",
|
||||
"copyToClipboardAsPng": "",
|
||||
"copyToClipboardAsPng": "Скопійовано {{exportSelection}} до буфера обміну як PNG\n({{exportColorScheme}})",
|
||||
"fileSaved": "Файл збережено.",
|
||||
"fileSavedToFilename": "Збережено в {filename}",
|
||||
"canvas": "",
|
||||
"selection": ""
|
||||
"canvas": "полотно",
|
||||
"selection": "виділення"
|
||||
}
|
||||
}
|
||||
|
@ -61,7 +61,7 @@
|
||||
"architect": "朴素",
|
||||
"artist": "艺术",
|
||||
"cartoonist": "漫画家",
|
||||
"fileTitle": "文件标题",
|
||||
"fileTitle": "",
|
||||
"colorPicker": "调色盘",
|
||||
"canvasBackground": "画布背景",
|
||||
"drawingCanvas": "绘制 Canvas",
|
||||
|
@ -61,7 +61,7 @@
|
||||
"architect": "精確",
|
||||
"artist": "藝術",
|
||||
"cartoonist": "卡通",
|
||||
"fileTitle": "檔案標題",
|
||||
"fileTitle": "檔案名稱",
|
||||
"colorPicker": "色彩選擇工具",
|
||||
"canvasBackground": "Canvas 背景",
|
||||
"drawingCanvas": "繪圖 canvas",
|
||||
|
@ -12,12 +12,14 @@ 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.
|
||||
-->
|
||||
|
||||
## Unreleased
|
||||
## 0.5.0 (2021-03-21)
|
||||
|
||||
## 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).
|
||||
- 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.
|
||||
@ -35,6 +37,24 @@ 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)
|
||||
|
@ -28,7 +28,8 @@ If you want to load assets from a different path you can set a variable `window.
|
||||
|
||||
[Try here](https://codesandbox.io/s/excalidraw-ehlz3).
|
||||
|
||||
### Usage
|
||||
<details id="usage">
|
||||
<summary><strong>Usage</strong></summary>
|
||||
|
||||
1. If you are using a Web bundler (for instance, Webpack), you can import it as an ES6 module as shown below
|
||||
|
||||
@ -163,6 +164,8 @@ export default function App() {
|
||||
}
|
||||
```
|
||||
|
||||
To view the full example visit :point_down:
|
||||
|
||||
[](https://codesandbox.io/s/excalidraw-ehlz3?fontsize=14&hidenavigation=1&theme=dark)
|
||||
|
||||
2. To use it in a browser directly:
|
||||
@ -341,6 +344,8 @@ const excalidrawWrapper = document.getElementById("app");
|
||||
ReactDOM.render(React.createElement(App), excalidrawWrapper);
|
||||
```
|
||||
|
||||
To view the full example visit :point_down:
|
||||
|
||||
[](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.
|
||||
@ -356,7 +361,10 @@ export default function IndexPage() {
|
||||
}
|
||||
```
|
||||
|
||||
### Props
|
||||
</details>
|
||||
|
||||
<details id="props">
|
||||
<summary><strong>Props</strong></summary>
|
||||
|
||||
| Name | Type | Default | Description |
|
||||
| --- | --- | --- | --- |
|
||||
@ -376,6 +384,7 @@ export default function IndexPage() {
|
||||
| [`gridModeEnabled`](#gridModeEnabled) | boolean | | This implies if the grid mode is enabled |
|
||||
| [`libraryReturnUrl`](#libraryReturnUrl) | string | | What URL should [libraries.excalidraw.com](https://libraries.excalidraw.com) be installed to |
|
||||
| [`theme`](#theme) | `light` or `dark` | | The theme of the Excalidraw component |
|
||||
| [`name`](#name) | string | | Name of the drawing |
|
||||
|
||||
#### `width`
|
||||
|
||||
@ -526,15 +535,22 @@ 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`.
|
||||
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.
|
||||
|
||||
### `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.
|
||||
|
||||
### Extra API's
|
||||
#### `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>
|
||||
|
||||
#### `getSceneVersion`
|
||||
|
||||
@ -579,6 +595,9 @@ 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_**
|
||||
@ -627,7 +646,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)
|
||||
|
||||
**_The below APIs will be available in [next version](https://github.com/excalidraw/excalidraw/blob/master/src/packages/excalidraw/CHANGELOG.md#unreleased)_**
|
||||
</details>
|
||||
|
||||
<details id="export-utils">
|
||||
<summary><strong>Export utilities</strong></summary>
|
||||
@ -716,3 +735,4 @@ This function returns a svg with the exported elements.
|
||||
| exportWithDarkMode | boolean | false | Indicates whether to export with dark mode |
|
||||
|
||||
</details>
|
||||
</details>
|
||||
|
@ -29,6 +29,7 @@ const Excalidraw = (props: ExcalidrawProps) => {
|
||||
gridModeEnabled,
|
||||
libraryReturnUrl,
|
||||
theme,
|
||||
name,
|
||||
} = props;
|
||||
|
||||
useEffect(() => {
|
||||
@ -69,6 +70,7 @@ const Excalidraw = (props: ExcalidrawProps) => {
|
||||
gridModeEnabled={gridModeEnabled}
|
||||
libraryReturnUrl={libraryReturnUrl}
|
||||
theme={theme}
|
||||
name={name}
|
||||
/>
|
||||
</IsMobileProvider>
|
||||
</InitializeApp>
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@excalidraw/excalidraw",
|
||||
"version": "0.4.3",
|
||||
"version": "0.5.0",
|
||||
"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.2",
|
||||
"css-loader": "5.1.3",
|
||||
"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.24.3",
|
||||
"webpack": "5.27.1",
|
||||
"webpack-bundle-analyzer": "4.4.0",
|
||||
"webpack-cli": "4.5.0"
|
||||
},
|
||||
|
@ -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.2:
|
||||
version "5.1.2"
|
||||
resolved "https://registry.yarnpkg.com/css-loader/-/css-loader-5.1.2.tgz#b93dba498ec948b543b49d4fab5017205d4f5c3e"
|
||||
integrity sha512-T7vTXHSx0KrVEg/xjcl7G01RcVXpcw4OELwDPvkr7izQNny85A84dK3dqrczuEfBcu7Yg7mdTjJLSTibRUoRZg==
|
||||
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==
|
||||
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.24.3:
|
||||
version "5.24.3"
|
||||
resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.24.3.tgz#6ec0f5059f8d7c7961075fa553cfce7b7928acb3"
|
||||
integrity sha512-x7lrWZ7wlWAdyKdML6YPvfVZkhD1ICuIZGODE5SzKJjqI9A4SpqGTjGJTc6CwaHqn19gGaoOR3ONJ46nYsn9rw==
|
||||
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==
|
||||
dependencies:
|
||||
"@types/eslint-scope" "^3.7.0"
|
||||
"@types/estree" "^0.0.46"
|
||||
|
@ -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.2",
|
||||
"css-loader": "5.1.3",
|
||||
"file-loader": "6.2.0",
|
||||
"sass-loader": "11.0.1",
|
||||
"ts-loader": "8.0.18",
|
||||
"webpack": "5.24.3",
|
||||
"webpack": "5.27.1",
|
||||
"webpack-bundle-analyzer": "4.4.0",
|
||||
"webpack-cli": "4.5.0"
|
||||
},
|
||||
|
@ -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.2:
|
||||
version "5.1.2"
|
||||
resolved "https://registry.yarnpkg.com/css-loader/-/css-loader-5.1.2.tgz#b93dba498ec948b543b49d4fab5017205d4f5c3e"
|
||||
integrity sha512-T7vTXHSx0KrVEg/xjcl7G01RcVXpcw4OELwDPvkr7izQNny85A84dK3dqrczuEfBcu7Yg7mdTjJLSTibRUoRZg==
|
||||
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==
|
||||
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.24.3:
|
||||
version "5.24.3"
|
||||
resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.24.3.tgz#6ec0f5059f8d7c7961075fa553cfce7b7928acb3"
|
||||
integrity sha512-x7lrWZ7wlWAdyKdML6YPvfVZkhD1ICuIZGODE5SzKJjqI9A4SpqGTjGJTc6CwaHqn19gGaoOR3ONJ46nYsn9rw==
|
||||
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==
|
||||
dependencies:
|
||||
"@types/eslint-scope" "^3.7.0"
|
||||
"@types/estree" "^0.0.46"
|
||||
|
@ -3357,8 +3357,8 @@ Object {
|
||||
"strokeStyle": "solid",
|
||||
"strokeWidth": 1,
|
||||
"type": "rectangle",
|
||||
"version": 8,
|
||||
"versionNonce": 1116226695,
|
||||
"version": 4,
|
||||
"versionNonce": 453191,
|
||||
"width": 10,
|
||||
"x": 10,
|
||||
"y": 10,
|
||||
@ -3429,7 +3429,7 @@ Object {
|
||||
"elements": Array [
|
||||
Object {
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"backgroundColor": "#fa5252",
|
||||
"boundElementIds": null,
|
||||
"fillStyle": "hachure",
|
||||
"groupIds": Array [],
|
||||
@ -3452,78 +3452,6 @@ 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,
|
||||
@ -3552,8 +3480,8 @@ Object {
|
||||
"strokeStyle": "solid",
|
||||
"strokeWidth": 1,
|
||||
"type": "rectangle",
|
||||
"version": 8,
|
||||
"versionNonce": 1116226695,
|
||||
"version": 4,
|
||||
"versionNonce": 453191,
|
||||
"width": 10,
|
||||
"x": 10,
|
||||
"y": 10,
|
||||
@ -14745,7 +14673,7 @@ Object {
|
||||
"strokeWidth": 2,
|
||||
"type": "rectangle",
|
||||
"version": 3,
|
||||
"versionNonce": 81784553,
|
||||
"versionNonce": 1505387817,
|
||||
"width": 20,
|
||||
"x": 10,
|
||||
"y": 10,
|
||||
@ -14764,14 +14692,14 @@ Object {
|
||||
"isDeleted": false,
|
||||
"opacity": 60,
|
||||
"roughness": 2,
|
||||
"seed": 23633383,
|
||||
"seed": 238820263,
|
||||
"strokeColor": "#c92a2a",
|
||||
"strokeSharpness": "sharp",
|
||||
"strokeStyle": "dotted",
|
||||
"strokeWidth": 2,
|
||||
"type": "rectangle",
|
||||
"version": 13,
|
||||
"versionNonce": 915032327,
|
||||
"version": 9,
|
||||
"versionNonce": 1604849351,
|
||||
"width": 20,
|
||||
"x": 40,
|
||||
"y": 40,
|
||||
@ -14934,7 +14862,7 @@ Object {
|
||||
"opacity": 100,
|
||||
"roughness": 1,
|
||||
"seed": 449462985,
|
||||
"strokeColor": "#000000",
|
||||
"strokeColor": "#c92a2a",
|
||||
"strokeSharpness": "sharp",
|
||||
"strokeStyle": "solid",
|
||||
"strokeWidth": 1,
|
||||
@ -14981,6 +14909,42 @@ 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",
|
||||
@ -14988,6 +14952,29 @@ 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,
|
||||
@ -15042,9 +15029,9 @@ Object {
|
||||
},
|
||||
Object {
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"backgroundColor": "#e64980",
|
||||
"boundElementIds": null,
|
||||
"fillStyle": "hachure",
|
||||
"fillStyle": "cross-hatch",
|
||||
"groupIds": Array [],
|
||||
"height": 20,
|
||||
"id": "id1",
|
||||
@ -15055,7 +15042,7 @@ Object {
|
||||
"strokeColor": "#c92a2a",
|
||||
"strokeSharpness": "sharp",
|
||||
"strokeStyle": "solid",
|
||||
"strokeWidth": 1,
|
||||
"strokeWidth": 2,
|
||||
"type": "rectangle",
|
||||
"version": 6,
|
||||
"versionNonce": 1116226695,
|
||||
@ -15065,65 +15052,6 @@ Object {
|
||||
},
|
||||
],
|
||||
},
|
||||
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": "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": 8,
|
||||
"versionNonce": 238820263,
|
||||
"width": 20,
|
||||
"x": 40,
|
||||
"y": 40,
|
||||
},
|
||||
],
|
||||
},
|
||||
Object {
|
||||
"appState": Object {
|
||||
"editingGroupId": null,
|
||||
@ -15172,10 +15100,69 @@ Object {
|
||||
"seed": 449462985,
|
||||
"strokeColor": "#c92a2a",
|
||||
"strokeSharpness": "sharp",
|
||||
"strokeStyle": "dotted",
|
||||
"strokeWidth": 2,
|
||||
"type": "rectangle",
|
||||
"version": 7,
|
||||
"versionNonce": 1014066025,
|
||||
"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": 9,
|
||||
"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": 238820263,
|
||||
"strokeColor": "#c92a2a",
|
||||
"strokeSharpness": "sharp",
|
||||
"strokeStyle": "dotted",
|
||||
"strokeWidth": 2,
|
||||
"type": "rectangle",
|
||||
"version": 8,
|
||||
"versionNonce": 400692809,
|
||||
"width": 20,
|
||||
"x": 40,
|
||||
@ -15183,183 +15170,6 @@ Object {
|
||||
},
|
||||
],
|
||||
},
|
||||
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": "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": 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,
|
||||
@ -15405,14 +15215,14 @@ Object {
|
||||
"isDeleted": false,
|
||||
"opacity": 60,
|
||||
"roughness": 2,
|
||||
"seed": 23633383,
|
||||
"seed": 238820263,
|
||||
"strokeColor": "#c92a2a",
|
||||
"strokeSharpness": "sharp",
|
||||
"strokeStyle": "dotted",
|
||||
"strokeWidth": 2,
|
||||
"type": "rectangle",
|
||||
"version": 13,
|
||||
"versionNonce": 915032327,
|
||||
"version": 9,
|
||||
"versionNonce": 1604849351,
|
||||
"width": 20,
|
||||
"x": 40,
|
||||
"y": 40,
|
||||
@ -15448,7 +15258,7 @@ Object {
|
||||
"strokeWidth": 2,
|
||||
"type": "rectangle",
|
||||
"version": 3,
|
||||
"versionNonce": 81784553,
|
||||
"versionNonce": 1505387817,
|
||||
"width": 20,
|
||||
"x": 10,
|
||||
"y": 10,
|
||||
@ -15464,14 +15274,14 @@ Object {
|
||||
"isDeleted": false,
|
||||
"opacity": 60,
|
||||
"roughness": 2,
|
||||
"seed": 23633383,
|
||||
"seed": 238820263,
|
||||
"strokeColor": "#c92a2a",
|
||||
"strokeSharpness": "sharp",
|
||||
"strokeStyle": "dotted",
|
||||
"strokeWidth": 2,
|
||||
"type": "rectangle",
|
||||
"version": 13,
|
||||
"versionNonce": 915032327,
|
||||
"version": 9,
|
||||
"versionNonce": 1604849351,
|
||||
"width": 20,
|
||||
"x": 40,
|
||||
"y": 40,
|
||||
@ -17069,8 +16879,8 @@ Object {
|
||||
"strokeStyle": "solid",
|
||||
"strokeWidth": 1,
|
||||
"type": "rectangle",
|
||||
"version": 5,
|
||||
"versionNonce": 401146281,
|
||||
"version": 3,
|
||||
"versionNonce": 449462985,
|
||||
"width": 10,
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
@ -17141,7 +16951,7 @@ Object {
|
||||
"elements": Array [
|
||||
Object {
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"backgroundColor": "#fa5252",
|
||||
"boundElementIds": null,
|
||||
"fillStyle": "hachure",
|
||||
"groupIds": Array [],
|
||||
@ -17164,42 +16974,6 @@ 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,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
}
|
||||
`;
|
||||
@ -20607,7 +20381,7 @@ Object {
|
||||
"strokeWidth": 1,
|
||||
"type": "rectangle",
|
||||
"version": 6,
|
||||
"versionNonce": 1006504105,
|
||||
"versionNonce": 760410951,
|
||||
"width": 20,
|
||||
"x": 10,
|
||||
"y": -10,
|
||||
@ -20633,7 +20407,7 @@ Object {
|
||||
"strokeWidth": 1,
|
||||
"type": "rectangle",
|
||||
"version": 6,
|
||||
"versionNonce": 289600103,
|
||||
"versionNonce": 1006504105,
|
||||
"width": 30,
|
||||
"x": 40,
|
||||
"y": 0,
|
||||
@ -20676,8 +20450,8 @@ Object {
|
||||
"strokeStyle": "solid",
|
||||
"strokeWidth": 1,
|
||||
"type": "arrow",
|
||||
"version": 11,
|
||||
"versionNonce": 1315507081,
|
||||
"version": 9,
|
||||
"versionNonce": 81784553,
|
||||
"width": 60,
|
||||
"x": 130,
|
||||
"y": 10,
|
||||
|
@ -3,6 +3,7 @@ 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;
|
||||
|
||||
@ -29,7 +30,7 @@ describe("appState", () => {
|
||||
new Blob(
|
||||
[
|
||||
JSON.stringify({
|
||||
type: "excalidraw",
|
||||
type: EXPORT_DATA_TYPES.excalidraw,
|
||||
appState: {
|
||||
viewBackgroundColor: "#000",
|
||||
},
|
||||
|
@ -3,6 +3,7 @@ import { fireEvent, GlobalTestState, render } from "./test-utils";
|
||||
import Excalidraw from "../packages/excalidraw/index";
|
||||
import { queryByText, queryByTestId } from "@testing-library/react";
|
||||
import { GRID_SIZE } from "../constants";
|
||||
import { t } from "../i18n";
|
||||
|
||||
const { h } = window;
|
||||
|
||||
@ -104,4 +105,29 @@ describe("<Excalidraw/>", () => {
|
||||
expect(queryByTestId(container, "toggle-dark-mode")).toBe(null);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Test name prop", () => {
|
||||
it('should allow editing name when the name prop is "undefined"', async () => {
|
||||
const { container } = await render(<Excalidraw />);
|
||||
|
||||
fireEvent.click(queryByTestId(container, "export-button")!);
|
||||
const textInput: HTMLInputElement | null = document.querySelector(
|
||||
".ExportDialog__name .TextInput",
|
||||
);
|
||||
expect(textInput?.value).toContain(`${t("labels.untitled")}`);
|
||||
expect(textInput?.nodeName).toBe("INPUT");
|
||||
});
|
||||
|
||||
it('should set the name and not allow editing when the name prop is present"', async () => {
|
||||
const name = "test";
|
||||
const { container } = await render(<Excalidraw name={name} />);
|
||||
|
||||
await fireEvent.click(queryByTestId(container, "export-button")!);
|
||||
const textInput = document.querySelector(
|
||||
".ExportDialog__name .TextInput--readonly",
|
||||
);
|
||||
expect(textInput?.textContent).toEqual(name);
|
||||
expect(textInput?.nodeName).toBe("SPAN");
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -6,6 +6,7 @@ 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;
|
||||
|
||||
@ -76,7 +77,7 @@ describe("history", () => {
|
||||
new Blob(
|
||||
[
|
||||
JSON.stringify({
|
||||
type: "excalidraw",
|
||||
type: EXPORT_DATA_TYPES.excalidraw,
|
||||
appState: {
|
||||
...getDefaultAppState(),
|
||||
viewBackgroundColor: "#000",
|
||||
|
@ -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.getByText(/code/i));
|
||||
fireEvent.click(screen.getByTitle(/code/i));
|
||||
expect(h.state.currentItemFontFamily).toEqual(3); // Cascadia
|
||||
});
|
||||
|
||||
|
@ -187,6 +187,7 @@ export interface ExcalidrawProps {
|
||||
gridModeEnabled?: boolean;
|
||||
libraryReturnUrl?: string;
|
||||
theme?: "dark" | "light";
|
||||
name?: string;
|
||||
}
|
||||
|
||||
export type SceneData = {
|
||||
|
@ -131,9 +131,7 @@ export const debounce = <T extends any[]>(
|
||||
};
|
||||
ret.flush = () => {
|
||||
clearTimeout(handle);
|
||||
if (lastArgs) {
|
||||
fn(...lastArgs);
|
||||
}
|
||||
fn(...(lastArgs || []));
|
||||
};
|
||||
ret.cancel = () => {
|
||||
clearTimeout(handle);
|
||||
|
Reference in New Issue
Block a user