Compare commits
9 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 016b054288 | |||
| 94ad8eaa19 | |||
| 13d9374cde | |||
| efb6d0825b | |||
| 80a61db72f | |||
| 9a13dd8836 | |||
| cf6a5ff16b | |||
| fa8c7abf50 | |||
| c3ecbcb3ab |
@@ -18,11 +18,14 @@ export const actionChangeProjectName = register({
|
|||||||
trackEvent("change", "title");
|
trackEvent("change", "title");
|
||||||
return { appState: { ...appState, name: value }, commitToHistory: false };
|
return { appState: { ...appState, name: value }, commitToHistory: false };
|
||||||
},
|
},
|
||||||
PanelComponent: ({ appState, updateData }) => (
|
PanelComponent: ({ appState, updateData, appProps }) => (
|
||||||
<ProjectName
|
<ProjectName
|
||||||
label={t("labels.fileTitle")}
|
label={t("labels.fileTitle")}
|
||||||
value={appState.name || "Unnamed"}
|
value={appState.name || "Unnamed"}
|
||||||
onChange={(name: string) => updateData(name)}
|
onChange={(name: string) => updateData(name)}
|
||||||
|
isNameEditable={
|
||||||
|
typeof appProps.name === "undefined" && !appState.viewModeEnabled
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -122,6 +122,7 @@ export class ActionManager implements ActionsManagerInterface {
|
|||||||
appState={this.getAppState()}
|
appState={this.getAppState()}
|
||||||
updateData={updateData}
|
updateData={updateData}
|
||||||
id={id}
|
id={id}
|
||||||
|
appProps={this.app.props}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { ExcalidrawElement } from "../element/types";
|
import { ExcalidrawElement } from "../element/types";
|
||||||
import { AppState } from "../types";
|
import { AppState, ExcalidrawProps } from "../types";
|
||||||
|
|
||||||
/** if false, the action should be prevented */
|
/** if false, the action should be prevented */
|
||||||
export type ActionResult =
|
export type ActionResult =
|
||||||
@@ -94,6 +94,7 @@ export interface Action {
|
|||||||
elements: readonly ExcalidrawElement[];
|
elements: readonly ExcalidrawElement[];
|
||||||
appState: AppState;
|
appState: AppState;
|
||||||
updateData: (formData?: any) => void;
|
updateData: (formData?: any) => void;
|
||||||
|
appProps: ExcalidrawProps;
|
||||||
id?: string;
|
id?: string;
|
||||||
}>;
|
}>;
|
||||||
perform: ActionFn;
|
perform: ActionFn;
|
||||||
|
|||||||
+14
-15
@@ -7,12 +7,10 @@ import { AppState } from "./types";
|
|||||||
import { SVG_EXPORT_TAG } from "./scene/export";
|
import { SVG_EXPORT_TAG } from "./scene/export";
|
||||||
import { tryParseSpreadsheet, Spreadsheet, VALID_SPREADSHEET } from "./charts";
|
import { tryParseSpreadsheet, Spreadsheet, VALID_SPREADSHEET } from "./charts";
|
||||||
import { canvasToBlob } from "./data/blob";
|
import { canvasToBlob } from "./data/blob";
|
||||||
|
import { EXPORT_DATA_TYPES } from "./constants";
|
||||||
const TYPE_ELEMENTS = "excalidraw/elements";
|
|
||||||
|
|
||||||
type ElementsClipboard = {
|
type ElementsClipboard = {
|
||||||
type: typeof TYPE_ELEMENTS;
|
type: typeof EXPORT_DATA_TYPES.excalidrawClipboard;
|
||||||
created: number;
|
|
||||||
elements: ExcalidrawElement[];
|
elements: ExcalidrawElement[];
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -31,8 +29,16 @@ export const probablySupportsClipboardBlob =
|
|||||||
"ClipboardItem" in window &&
|
"ClipboardItem" in window &&
|
||||||
"toBlob" in HTMLCanvasElement.prototype;
|
"toBlob" in HTMLCanvasElement.prototype;
|
||||||
|
|
||||||
const isElementsClipboard = (contents: any): contents is ElementsClipboard => {
|
const clipboardContainsElements = (
|
||||||
if (contents?.type === TYPE_ELEMENTS) {
|
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 true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
@@ -43,8 +49,7 @@ export const copyToClipboard = async (
|
|||||||
appState: AppState,
|
appState: AppState,
|
||||||
) => {
|
) => {
|
||||||
const contents: ElementsClipboard = {
|
const contents: ElementsClipboard = {
|
||||||
type: TYPE_ELEMENTS,
|
type: EXPORT_DATA_TYPES.excalidrawClipboard,
|
||||||
created: Date.now(),
|
|
||||||
elements: getSelectedElements(elements, appState),
|
elements: getSelectedElements(elements, appState),
|
||||||
};
|
};
|
||||||
const json = JSON.stringify(contents);
|
const json = JSON.stringify(contents);
|
||||||
@@ -131,15 +136,9 @@ export const parseClipboard = async (
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const systemClipboardData = JSON.parse(systemClipboard);
|
const systemClipboardData = JSON.parse(systemClipboard);
|
||||||
// system clipboard elements are newer than in-app clipboard
|
if (clipboardContainsElements(systemClipboardData)) {
|
||||||
if (
|
|
||||||
isElementsClipboard(systemClipboardData) &&
|
|
||||||
(!appClipboardData?.created ||
|
|
||||||
appClipboardData.created < systemClipboardData.created)
|
|
||||||
) {
|
|
||||||
return { elements: systemClipboardData.elements };
|
return { elements: systemClipboardData.elements };
|
||||||
}
|
}
|
||||||
// in-app clipboard is newer than system clipboard
|
|
||||||
return appClipboardData;
|
return appClipboardData;
|
||||||
} catch {
|
} catch {
|
||||||
// system clipboard doesn't contain excalidraw elements → return plaintext
|
// system clipboard doesn't contain excalidraw elements → return plaintext
|
||||||
|
|||||||
+17
-3
@@ -303,6 +303,7 @@ class App extends React.Component<ExcalidrawProps, AppState> {
|
|||||||
zenModeEnabled = false,
|
zenModeEnabled = false,
|
||||||
gridModeEnabled = false,
|
gridModeEnabled = false,
|
||||||
theme = defaultAppState.theme,
|
theme = defaultAppState.theme,
|
||||||
|
name = defaultAppState.name,
|
||||||
} = props;
|
} = props;
|
||||||
this.state = {
|
this.state = {
|
||||||
...defaultAppState,
|
...defaultAppState,
|
||||||
@@ -314,6 +315,7 @@ class App extends React.Component<ExcalidrawProps, AppState> {
|
|||||||
viewModeEnabled,
|
viewModeEnabled,
|
||||||
zenModeEnabled,
|
zenModeEnabled,
|
||||||
gridSize: gridModeEnabled ? GRID_SIZE : null,
|
gridSize: gridModeEnabled ? GRID_SIZE : null,
|
||||||
|
name,
|
||||||
};
|
};
|
||||||
if (excalidrawRef) {
|
if (excalidrawRef) {
|
||||||
const readyPromise =
|
const readyPromise =
|
||||||
@@ -523,7 +525,7 @@ class App extends React.Component<ExcalidrawProps, AppState> {
|
|||||||
let zenModeEnabled = actionResult?.appState?.zenModeEnabled || false;
|
let zenModeEnabled = actionResult?.appState?.zenModeEnabled || false;
|
||||||
let gridSize = actionResult?.appState?.gridSize || null;
|
let gridSize = actionResult?.appState?.gridSize || null;
|
||||||
let theme = actionResult?.appState?.theme || "light";
|
let theme = actionResult?.appState?.theme || "light";
|
||||||
|
let name = actionResult?.appState?.name ?? this.state.name;
|
||||||
if (typeof this.props.viewModeEnabled !== "undefined") {
|
if (typeof this.props.viewModeEnabled !== "undefined") {
|
||||||
viewModeEnabled = this.props.viewModeEnabled;
|
viewModeEnabled = this.props.viewModeEnabled;
|
||||||
}
|
}
|
||||||
@@ -540,6 +542,10 @@ class App extends React.Component<ExcalidrawProps, AppState> {
|
|||||||
theme = this.props.theme;
|
theme = this.props.theme;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (typeof this.props.name !== "undefined") {
|
||||||
|
name = this.props.name;
|
||||||
|
}
|
||||||
|
|
||||||
this.setState(
|
this.setState(
|
||||||
(state) => {
|
(state) => {
|
||||||
// using Object.assign instead of spread to fool TS 4.2.2+ into
|
// using Object.assign instead of spread to fool TS 4.2.2+ into
|
||||||
@@ -556,6 +562,7 @@ class App extends React.Component<ExcalidrawProps, AppState> {
|
|||||||
zenModeEnabled,
|
zenModeEnabled,
|
||||||
gridSize,
|
gridSize,
|
||||||
theme,
|
theme,
|
||||||
|
name,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
() => {
|
() => {
|
||||||
@@ -890,6 +897,13 @@ class App extends React.Component<ExcalidrawProps, AppState> {
|
|||||||
gridSize: this.props.gridModeEnabled ? GRID_SIZE : null,
|
gridSize: this.props.gridModeEnabled ? GRID_SIZE : null,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this.props.name && prevProps.name !== this.props.name) {
|
||||||
|
this.setState({
|
||||||
|
name: this.props.name,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
document
|
document
|
||||||
.querySelector(".excalidraw")
|
.querySelector(".excalidraw")
|
||||||
?.classList.toggle("theme--dark", this.state.theme === "dark");
|
?.classList.toggle("theme--dark", this.state.theme === "dark");
|
||||||
@@ -1008,7 +1022,7 @@ class App extends React.Component<ExcalidrawProps, AppState> {
|
|||||||
// potential issues, this fixes a case where the tab isn't focused during
|
// potential issues, this fixes a case where the tab isn't focused during
|
||||||
// init, which would trigger onChange with empty elements, which would then
|
// init, which would trigger onChange with empty elements, which would then
|
||||||
// override whatever is in localStorage currently.
|
// override whatever is in localStorage currently.
|
||||||
if (!this.state.isLoading) {
|
if (!this.state.isLoading && !prevState.isLoading) {
|
||||||
this.props.onChange?.(
|
this.props.onChange?.(
|
||||||
this.scene.getElementsIncludingDeleted(),
|
this.scene.getElementsIncludingDeleted(),
|
||||||
this.state,
|
this.state,
|
||||||
@@ -1390,7 +1404,7 @@ class App extends React.Component<ExcalidrawProps, AppState> {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (event[KEYS.CTRL_OR_CMD]) {
|
if (event[KEYS.CTRL_OR_CMD] && this.state.isBindingEnabled) {
|
||||||
this.setState({ isBindingEnabled: false });
|
this.setState({ isBindingEnabled: false });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -31,9 +31,27 @@
|
|||||||
.ExportDialog__name {
|
.ExportDialog__name {
|
||||||
grid-column: project-name;
|
grid-column: project-name;
|
||||||
margin: auto;
|
margin: auto;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
.TextInput {
|
.TextInput {
|
||||||
height: calc(1rem - 3px);
|
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={() => {
|
onClick={() => {
|
||||||
setModalIsShown(true);
|
setModalIsShown(true);
|
||||||
}}
|
}}
|
||||||
|
data-testid="export-button"
|
||||||
icon={exportFile}
|
icon={exportFile}
|
||||||
type="button"
|
type="button"
|
||||||
aria-label={t("buttons.export")}
|
aria-label={t("buttons.export")}
|
||||||
|
|||||||
+31
-28
@@ -144,34 +144,37 @@ const LibraryMenuItems = ({
|
|||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<ToolButton
|
{!!library.length && (
|
||||||
key="export"
|
<>
|
||||||
type="button"
|
<ToolButton
|
||||||
title={t("buttons.export")}
|
key="export"
|
||||||
aria-label={t("buttons.export")}
|
type="button"
|
||||||
icon={exportFile}
|
title={t("buttons.export")}
|
||||||
onClick={() => {
|
aria-label={t("buttons.export")}
|
||||||
saveLibraryAsJSON()
|
icon={exportFile}
|
||||||
.catch(muteFSAbortError)
|
onClick={() => {
|
||||||
.catch((error) => {
|
saveLibraryAsJSON()
|
||||||
setAppState({ errorMessage: error.message });
|
.catch(muteFSAbortError)
|
||||||
});
|
.catch((error) => {
|
||||||
}}
|
setAppState({ errorMessage: error.message });
|
||||||
/>
|
});
|
||||||
<ToolButton
|
}}
|
||||||
key="reset"
|
/>
|
||||||
type="button"
|
<ToolButton
|
||||||
title={t("buttons.resetLibrary")}
|
key="reset"
|
||||||
aria-label={t("buttons.resetLibrary")}
|
type="button"
|
||||||
icon={trash}
|
title={t("buttons.resetLibrary")}
|
||||||
onClick={() => {
|
aria-label={t("buttons.resetLibrary")}
|
||||||
if (window.confirm(t("alerts.resetLibrary"))) {
|
icon={trash}
|
||||||
Library.resetLibrary();
|
onClick={() => {
|
||||||
setLibraryItems([]);
|
if (window.confirm(t("alerts.resetLibrary"))) {
|
||||||
}
|
Library.resetLibrary();
|
||||||
}}
|
setLibraryItems([]);
|
||||||
/>
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
<a
|
<a
|
||||||
href={`https://libraries.excalidraw.com?referrer=${referrer}`}
|
href={`https://libraries.excalidraw.com?referrer=${referrer}`}
|
||||||
target="_excalidraw_libraries"
|
target="_excalidraw_libraries"
|
||||||
|
|||||||
@@ -1,25 +1,26 @@
|
|||||||
import "./TextInput.scss";
|
import "./TextInput.scss";
|
||||||
|
|
||||||
import React, { Component } from "react";
|
import React, { Component } from "react";
|
||||||
import { selectNode, removeSelection } from "../utils";
|
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
value: string;
|
value: string;
|
||||||
onChange: (value: string) => void;
|
onChange: (value: string) => void;
|
||||||
label: string;
|
label: string;
|
||||||
|
isNameEditable: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export class ProjectName extends Component<Props> {
|
type State = {
|
||||||
private handleFocus = (event: React.FocusEvent<HTMLElement>) => {
|
fileName: string;
|
||||||
selectNode(event.currentTarget);
|
};
|
||||||
|
export class ProjectName extends Component<Props, State> {
|
||||||
|
state = {
|
||||||
|
fileName: this.props.value,
|
||||||
};
|
};
|
||||||
|
private handleBlur = (event: any) => {
|
||||||
private handleBlur = (event: React.FocusEvent<HTMLElement>) => {
|
const value = event.target.value;
|
||||||
const value = event.currentTarget.innerText.trim();
|
|
||||||
if (value !== this.props.value) {
|
if (value !== this.props.value) {
|
||||||
this.props.onChange(value);
|
this.props.onChange(value);
|
||||||
}
|
}
|
||||||
removeSelection();
|
|
||||||
};
|
};
|
||||||
|
|
||||||
private handleKeyDown = (event: React.KeyboardEvent<HTMLElement>) => {
|
private handleKeyDown = (event: React.KeyboardEvent<HTMLElement>) => {
|
||||||
@@ -31,32 +32,30 @@ export class ProjectName extends Component<Props> {
|
|||||||
event.currentTarget.blur();
|
event.currentTarget.blur();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
private makeEditable = (editable: HTMLSpanElement | null) => {
|
|
||||||
if (!editable) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
editable.contentEditable = "plaintext-only";
|
|
||||||
} catch {
|
|
||||||
editable.contentEditable = "true";
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
public render() {
|
public render() {
|
||||||
return (
|
return (
|
||||||
<span
|
<>
|
||||||
suppressContentEditableWarning
|
<label htmlFor="file-name">
|
||||||
ref={this.makeEditable}
|
{`${this.props.label}${this.props.isNameEditable ? "" : ":"}`}
|
||||||
data-type="wysiwyg"
|
</label>
|
||||||
className="TextInput"
|
{this.props.isNameEditable ? (
|
||||||
role="textbox"
|
<input
|
||||||
aria-label={this.props.label}
|
className="TextInput"
|
||||||
onBlur={this.handleBlur}
|
onBlur={this.handleBlur}
|
||||||
onKeyDown={this.handleKeyDown}
|
onKeyDown={this.handleKeyDown}
|
||||||
onFocus={this.handleFocus}
|
id="file-name"
|
||||||
>
|
value={this.state.fileName}
|
||||||
{this.props.value}
|
onChange={(event) =>
|
||||||
</span>
|
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,
|
"ToolIcon--selected": props.selected,
|
||||||
},
|
},
|
||||||
)}
|
)}
|
||||||
|
data-testid={props["data-testid"]}
|
||||||
hidden={props.hidden}
|
hidden={props.hidden}
|
||||||
title={props.title}
|
title={props.title}
|
||||||
aria-label={props["aria-label"]}
|
aria-label={props["aria-label"]}
|
||||||
|
|||||||
+7
-1
@@ -84,9 +84,15 @@ export const MIME_TYPES = {
|
|||||||
excalidrawlib: "application/vnd.excalidrawlib+json",
|
excalidrawlib: "application/vnd.excalidrawlib+json",
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const EXPORT_DATA_TYPES = {
|
||||||
|
excalidraw: "excalidraw",
|
||||||
|
excalidrawClipboard: "excalidraw/clipboard",
|
||||||
|
excalidrawLibrary: "excalidrawlib",
|
||||||
|
} as const;
|
||||||
|
|
||||||
export const STORAGE_KEYS = {
|
export const STORAGE_KEYS = {
|
||||||
LOCAL_STORAGE_LIBRARY: "excalidraw-library",
|
LOCAL_STORAGE_LIBRARY: "excalidraw-library",
|
||||||
};
|
} as const;
|
||||||
|
|
||||||
// time in milliseconds
|
// time in milliseconds
|
||||||
export const TAP_TWICE_TIMEOUT = 300;
|
export const TAP_TWICE_TIMEOUT = 300;
|
||||||
|
|||||||
+2
-2
@@ -1,5 +1,5 @@
|
|||||||
import { cleanAppStateForExport } from "../appState";
|
import { cleanAppStateForExport } from "../appState";
|
||||||
import { MIME_TYPES } from "../constants";
|
import { EXPORT_DATA_TYPES, MIME_TYPES } from "../constants";
|
||||||
import { clearElementsForExport } from "../element";
|
import { clearElementsForExport } from "../element";
|
||||||
import { CanvasError } from "../errors";
|
import { CanvasError } from "../errors";
|
||||||
import { t } from "../i18n";
|
import { t } from "../i18n";
|
||||||
@@ -121,7 +121,7 @@ export const loadFromBlob = async (
|
|||||||
export const loadLibraryFromBlob = async (blob: Blob) => {
|
export const loadLibraryFromBlob = async (blob: Blob) => {
|
||||||
const contents = await parseFileContents(blob);
|
const contents = await parseFileContents(blob);
|
||||||
const data: LibraryData = JSON.parse(contents);
|
const data: LibraryData = JSON.parse(contents);
|
||||||
if (data.type !== "excalidrawlib") {
|
if (data.type !== EXPORT_DATA_TYPES.excalidrawLibrary) {
|
||||||
throw new Error(t("alerts.couldNotLoadInvalidFile"));
|
throw new Error(t("alerts.couldNotLoadInvalidFile"));
|
||||||
}
|
}
|
||||||
return data;
|
return data;
|
||||||
|
|||||||
+9
-3
@@ -2,7 +2,7 @@ import decodePng from "png-chunks-extract";
|
|||||||
import tEXt from "png-chunk-text";
|
import tEXt from "png-chunk-text";
|
||||||
import encodePng from "png-chunks-encode";
|
import encodePng from "png-chunks-encode";
|
||||||
import { stringToBase64, encode, decode, base64ToString } from "./encode";
|
import { stringToBase64, encode, decode, base64ToString } from "./encode";
|
||||||
import { MIME_TYPES } from "../constants";
|
import { EXPORT_DATA_TYPES, MIME_TYPES } from "../constants";
|
||||||
|
|
||||||
// -----------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------
|
||||||
// PNG
|
// PNG
|
||||||
@@ -67,7 +67,10 @@ export const decodePngMetadata = async (blob: Blob) => {
|
|||||||
const encodedData = JSON.parse(metadata.text);
|
const encodedData = JSON.parse(metadata.text);
|
||||||
if (!("encoded" in encodedData)) {
|
if (!("encoded" in encodedData)) {
|
||||||
// legacy, un-encoded scene JSON
|
// 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;
|
return metadata.text;
|
||||||
}
|
}
|
||||||
throw new Error("FAILED");
|
throw new Error("FAILED");
|
||||||
@@ -115,7 +118,10 @@ export const decodeSvgMetadata = async ({ svg }: { svg: string }) => {
|
|||||||
const encodedData = JSON.parse(json);
|
const encodedData = JSON.parse(json);
|
||||||
if (!("encoded" in encodedData)) {
|
if (!("encoded" in encodedData)) {
|
||||||
// legacy, un-encoded scene JSON
|
// legacy, un-encoded scene JSON
|
||||||
if ("type" in encodedData && encodedData.type === "excalidraw") {
|
if (
|
||||||
|
"type" in encodedData &&
|
||||||
|
encodedData.type === EXPORT_DATA_TYPES.excalidraw
|
||||||
|
) {
|
||||||
return json;
|
return json;
|
||||||
}
|
}
|
||||||
throw new Error("FAILED");
|
throw new Error("FAILED");
|
||||||
|
|||||||
+5
-5
@@ -1,6 +1,6 @@
|
|||||||
import { fileOpen, fileSave } from "browser-fs-access";
|
import { fileOpen, fileSave } from "browser-fs-access";
|
||||||
import { cleanAppStateForExport } from "../appState";
|
import { cleanAppStateForExport } from "../appState";
|
||||||
import { MIME_TYPES } from "../constants";
|
import { EXPORT_DATA_TYPES, MIME_TYPES } from "../constants";
|
||||||
import { clearElementsForExport } from "../element";
|
import { clearElementsForExport } from "../element";
|
||||||
import { ExcalidrawElement } from "../element/types";
|
import { ExcalidrawElement } from "../element/types";
|
||||||
import { AppState } from "../types";
|
import { AppState } from "../types";
|
||||||
@@ -14,7 +14,7 @@ export const serializeAsJSON = (
|
|||||||
): string =>
|
): string =>
|
||||||
JSON.stringify(
|
JSON.stringify(
|
||||||
{
|
{
|
||||||
type: "excalidraw",
|
type: EXPORT_DATA_TYPES.excalidraw,
|
||||||
version: 2,
|
version: 2,
|
||||||
source: window.location.origin,
|
source: window.location.origin,
|
||||||
elements: clearElementsForExport(elements),
|
elements: clearElementsForExport(elements),
|
||||||
@@ -69,7 +69,7 @@ export const isValidExcalidrawData = (data?: {
|
|||||||
appState?: any;
|
appState?: any;
|
||||||
}): data is ImportedDataState => {
|
}): data is ImportedDataState => {
|
||||||
return (
|
return (
|
||||||
data?.type === "excalidraw" &&
|
data?.type === EXPORT_DATA_TYPES.excalidraw &&
|
||||||
(!data.elements ||
|
(!data.elements ||
|
||||||
(Array.isArray(data.elements) &&
|
(Array.isArray(data.elements) &&
|
||||||
(!data.appState || typeof data.appState === "object")))
|
(!data.appState || typeof data.appState === "object")))
|
||||||
@@ -80,7 +80,7 @@ export const isValidLibrary = (json: any) => {
|
|||||||
return (
|
return (
|
||||||
typeof json === "object" &&
|
typeof json === "object" &&
|
||||||
json &&
|
json &&
|
||||||
json.type === "excalidrawlib" &&
|
json.type === EXPORT_DATA_TYPES.excalidrawLibrary &&
|
||||||
json.version === 1
|
json.version === 1
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@@ -89,7 +89,7 @@ export const saveLibraryAsJSON = async () => {
|
|||||||
const library = await Library.loadLibrary();
|
const library = await Library.loadLibrary();
|
||||||
const serialized = JSON.stringify(
|
const serialized = JSON.stringify(
|
||||||
{
|
{
|
||||||
type: "excalidrawlib",
|
type: EXPORT_DATA_TYPES.excalidrawLibrary,
|
||||||
version: 1,
|
version: 1,
|
||||||
library,
|
library,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -87,9 +87,30 @@ export const mutateElement = <TElement extends Mutable<ExcalidrawElement>>(
|
|||||||
export const newElementWith = <TElement extends ExcalidrawElement>(
|
export const newElementWith = <TElement extends ExcalidrawElement>(
|
||||||
element: TElement,
|
element: TElement,
|
||||||
updates: ElementUpdate<TElement>,
|
updates: ElementUpdate<TElement>,
|
||||||
): TElement => ({
|
): TElement => {
|
||||||
...element,
|
let didChange = false;
|
||||||
...updates,
|
for (const key in updates) {
|
||||||
version: element.version + 1,
|
const value = (updates as any)[key];
|
||||||
versionNonce: randomInteger(),
|
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(),
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|||||||
+1
-1
@@ -61,7 +61,7 @@
|
|||||||
"architect": "Architect",
|
"architect": "Architect",
|
||||||
"artist": "Artist",
|
"artist": "Artist",
|
||||||
"cartoonist": "Cartoonist",
|
"cartoonist": "Cartoonist",
|
||||||
"fileTitle": "File title",
|
"fileTitle": "File name",
|
||||||
"colorPicker": "Color picker",
|
"colorPicker": "Color picker",
|
||||||
"canvasBackground": "Canvas background",
|
"canvasBackground": "Canvas background",
|
||||||
"drawingCanvas": "Drawing canvas",
|
"drawingCanvas": "Drawing canvas",
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ Please add the latest change on the top under the correct section.
|
|||||||
|
|
||||||
### Features
|
### Features
|
||||||
|
|
||||||
|
- 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).
|
- Export API `setCanvasOffsets` via `ref` to set the offsets for Excalidraw[#3265](https://github.com/excalidraw/excalidraw/pull/3265).
|
||||||
#### BREAKING CHANGE
|
#### BREAKING CHANGE
|
||||||
- `offsetLeft` and `offsetTop` props have been removed now so you have to use the `setCanvasOffsets` via `ref` to achieve the same.
|
- `offsetLeft` and `offsetTop` props have been removed now so you have to use the `setCanvasOffsets` via `ref` to achieve the same.
|
||||||
|
|||||||
@@ -376,6 +376,7 @@ export default function IndexPage() {
|
|||||||
| [`gridModeEnabled`](#gridModeEnabled) | boolean | | This implies if the grid mode is enabled |
|
| [`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 |
|
| [`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 |
|
| [`theme`](#theme) | `light` or `dark` | | The theme of the Excalidraw component |
|
||||||
|
| [`name`](#name) | string | | Name of the drawing |
|
||||||
|
|
||||||
#### `width`
|
#### `width`
|
||||||
|
|
||||||
@@ -534,6 +535,10 @@ If supplied, this URL will be used when user tries to install a library from [li
|
|||||||
|
|
||||||
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.
|
This prop controls Excalidraw's theme. When supplied, the value takes precedence over `intialData.appState.theme`, the theme will be fully controlled by the host app, and users won't be able to toggle it from within the app.
|
||||||
|
|
||||||
|
### `name`
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
### Extra API's
|
### Extra API's
|
||||||
|
|
||||||
#### `getSceneVersion`
|
#### `getSceneVersion`
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ const Excalidraw = (props: ExcalidrawProps) => {
|
|||||||
gridModeEnabled,
|
gridModeEnabled,
|
||||||
libraryReturnUrl,
|
libraryReturnUrl,
|
||||||
theme,
|
theme,
|
||||||
|
name,
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -69,6 +70,7 @@ const Excalidraw = (props: ExcalidrawProps) => {
|
|||||||
gridModeEnabled={gridModeEnabled}
|
gridModeEnabled={gridModeEnabled}
|
||||||
libraryReturnUrl={libraryReturnUrl}
|
libraryReturnUrl={libraryReturnUrl}
|
||||||
theme={theme}
|
theme={theme}
|
||||||
|
name={name}
|
||||||
/>
|
/>
|
||||||
</IsMobileProvider>
|
</IsMobileProvider>
|
||||||
</InitializeApp>
|
</InitializeApp>
|
||||||
|
|||||||
@@ -3357,8 +3357,8 @@ Object {
|
|||||||
"strokeStyle": "solid",
|
"strokeStyle": "solid",
|
||||||
"strokeWidth": 1,
|
"strokeWidth": 1,
|
||||||
"type": "rectangle",
|
"type": "rectangle",
|
||||||
"version": 8,
|
"version": 4,
|
||||||
"versionNonce": 1116226695,
|
"versionNonce": 453191,
|
||||||
"width": 10,
|
"width": 10,
|
||||||
"x": 10,
|
"x": 10,
|
||||||
"y": 10,
|
"y": 10,
|
||||||
@@ -3429,7 +3429,7 @@ Object {
|
|||||||
"elements": Array [
|
"elements": Array [
|
||||||
Object {
|
Object {
|
||||||
"angle": 0,
|
"angle": 0,
|
||||||
"backgroundColor": "transparent",
|
"backgroundColor": "#fa5252",
|
||||||
"boundElementIds": null,
|
"boundElementIds": null,
|
||||||
"fillStyle": "hachure",
|
"fillStyle": "hachure",
|
||||||
"groupIds": Array [],
|
"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 {
|
Object {
|
||||||
"appState": Object {
|
"appState": Object {
|
||||||
"editingGroupId": null,
|
"editingGroupId": null,
|
||||||
@@ -3552,8 +3480,8 @@ Object {
|
|||||||
"strokeStyle": "solid",
|
"strokeStyle": "solid",
|
||||||
"strokeWidth": 1,
|
"strokeWidth": 1,
|
||||||
"type": "rectangle",
|
"type": "rectangle",
|
||||||
"version": 8,
|
"version": 4,
|
||||||
"versionNonce": 1116226695,
|
"versionNonce": 453191,
|
||||||
"width": 10,
|
"width": 10,
|
||||||
"x": 10,
|
"x": 10,
|
||||||
"y": 10,
|
"y": 10,
|
||||||
@@ -14745,7 +14673,7 @@ Object {
|
|||||||
"strokeWidth": 2,
|
"strokeWidth": 2,
|
||||||
"type": "rectangle",
|
"type": "rectangle",
|
||||||
"version": 3,
|
"version": 3,
|
||||||
"versionNonce": 81784553,
|
"versionNonce": 1505387817,
|
||||||
"width": 20,
|
"width": 20,
|
||||||
"x": 10,
|
"x": 10,
|
||||||
"y": 10,
|
"y": 10,
|
||||||
@@ -14764,14 +14692,14 @@ Object {
|
|||||||
"isDeleted": false,
|
"isDeleted": false,
|
||||||
"opacity": 60,
|
"opacity": 60,
|
||||||
"roughness": 2,
|
"roughness": 2,
|
||||||
"seed": 23633383,
|
"seed": 238820263,
|
||||||
"strokeColor": "#c92a2a",
|
"strokeColor": "#c92a2a",
|
||||||
"strokeSharpness": "sharp",
|
"strokeSharpness": "sharp",
|
||||||
"strokeStyle": "dotted",
|
"strokeStyle": "dotted",
|
||||||
"strokeWidth": 2,
|
"strokeWidth": 2,
|
||||||
"type": "rectangle",
|
"type": "rectangle",
|
||||||
"version": 13,
|
"version": 9,
|
||||||
"versionNonce": 915032327,
|
"versionNonce": 1604849351,
|
||||||
"width": 20,
|
"width": 20,
|
||||||
"x": 40,
|
"x": 40,
|
||||||
"y": 40,
|
"y": 40,
|
||||||
@@ -14934,7 +14862,7 @@ Object {
|
|||||||
"opacity": 100,
|
"opacity": 100,
|
||||||
"roughness": 1,
|
"roughness": 1,
|
||||||
"seed": 449462985,
|
"seed": 449462985,
|
||||||
"strokeColor": "#000000",
|
"strokeColor": "#c92a2a",
|
||||||
"strokeSharpness": "sharp",
|
"strokeSharpness": "sharp",
|
||||||
"strokeStyle": "solid",
|
"strokeStyle": "solid",
|
||||||
"strokeWidth": 1,
|
"strokeWidth": 1,
|
||||||
@@ -14981,6 +14909,42 @@ Object {
|
|||||||
"x": 10,
|
"x": 10,
|
||||||
"y": 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 {
|
Object {
|
||||||
"angle": 0,
|
"angle": 0,
|
||||||
"backgroundColor": "transparent",
|
"backgroundColor": "transparent",
|
||||||
@@ -14988,6 +14952,29 @@ Object {
|
|||||||
"fillStyle": "hachure",
|
"fillStyle": "hachure",
|
||||||
"groupIds": Array [],
|
"groupIds": Array [],
|
||||||
"height": 20,
|
"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",
|
"id": "id1",
|
||||||
"isDeleted": false,
|
"isDeleted": false,
|
||||||
"opacity": 100,
|
"opacity": 100,
|
||||||
@@ -15042,9 +15029,9 @@ Object {
|
|||||||
},
|
},
|
||||||
Object {
|
Object {
|
||||||
"angle": 0,
|
"angle": 0,
|
||||||
"backgroundColor": "transparent",
|
"backgroundColor": "#e64980",
|
||||||
"boundElementIds": null,
|
"boundElementIds": null,
|
||||||
"fillStyle": "hachure",
|
"fillStyle": "cross-hatch",
|
||||||
"groupIds": Array [],
|
"groupIds": Array [],
|
||||||
"height": 20,
|
"height": 20,
|
||||||
"id": "id1",
|
"id": "id1",
|
||||||
@@ -15055,7 +15042,7 @@ Object {
|
|||||||
"strokeColor": "#c92a2a",
|
"strokeColor": "#c92a2a",
|
||||||
"strokeSharpness": "sharp",
|
"strokeSharpness": "sharp",
|
||||||
"strokeStyle": "solid",
|
"strokeStyle": "solid",
|
||||||
"strokeWidth": 1,
|
"strokeWidth": 2,
|
||||||
"type": "rectangle",
|
"type": "rectangle",
|
||||||
"version": 6,
|
"version": 6,
|
||||||
"versionNonce": 1116226695,
|
"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 {
|
Object {
|
||||||
"appState": Object {
|
"appState": Object {
|
||||||
"editingGroupId": null,
|
"editingGroupId": null,
|
||||||
@@ -15172,10 +15100,69 @@ Object {
|
|||||||
"seed": 449462985,
|
"seed": 449462985,
|
||||||
"strokeColor": "#c92a2a",
|
"strokeColor": "#c92a2a",
|
||||||
"strokeSharpness": "sharp",
|
"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",
|
"strokeStyle": "solid",
|
||||||
"strokeWidth": 1,
|
"strokeWidth": 1,
|
||||||
"type": "rectangle",
|
"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,
|
"versionNonce": 400692809,
|
||||||
"width": 20,
|
"width": 20,
|
||||||
"x": 40,
|
"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 {
|
Object {
|
||||||
"appState": Object {
|
"appState": Object {
|
||||||
"editingGroupId": null,
|
"editingGroupId": null,
|
||||||
@@ -15405,14 +15215,14 @@ Object {
|
|||||||
"isDeleted": false,
|
"isDeleted": false,
|
||||||
"opacity": 60,
|
"opacity": 60,
|
||||||
"roughness": 2,
|
"roughness": 2,
|
||||||
"seed": 23633383,
|
"seed": 238820263,
|
||||||
"strokeColor": "#c92a2a",
|
"strokeColor": "#c92a2a",
|
||||||
"strokeSharpness": "sharp",
|
"strokeSharpness": "sharp",
|
||||||
"strokeStyle": "dotted",
|
"strokeStyle": "dotted",
|
||||||
"strokeWidth": 2,
|
"strokeWidth": 2,
|
||||||
"type": "rectangle",
|
"type": "rectangle",
|
||||||
"version": 13,
|
"version": 9,
|
||||||
"versionNonce": 915032327,
|
"versionNonce": 1604849351,
|
||||||
"width": 20,
|
"width": 20,
|
||||||
"x": 40,
|
"x": 40,
|
||||||
"y": 40,
|
"y": 40,
|
||||||
@@ -15448,7 +15258,7 @@ Object {
|
|||||||
"strokeWidth": 2,
|
"strokeWidth": 2,
|
||||||
"type": "rectangle",
|
"type": "rectangle",
|
||||||
"version": 3,
|
"version": 3,
|
||||||
"versionNonce": 81784553,
|
"versionNonce": 1505387817,
|
||||||
"width": 20,
|
"width": 20,
|
||||||
"x": 10,
|
"x": 10,
|
||||||
"y": 10,
|
"y": 10,
|
||||||
@@ -15464,14 +15274,14 @@ Object {
|
|||||||
"isDeleted": false,
|
"isDeleted": false,
|
||||||
"opacity": 60,
|
"opacity": 60,
|
||||||
"roughness": 2,
|
"roughness": 2,
|
||||||
"seed": 23633383,
|
"seed": 238820263,
|
||||||
"strokeColor": "#c92a2a",
|
"strokeColor": "#c92a2a",
|
||||||
"strokeSharpness": "sharp",
|
"strokeSharpness": "sharp",
|
||||||
"strokeStyle": "dotted",
|
"strokeStyle": "dotted",
|
||||||
"strokeWidth": 2,
|
"strokeWidth": 2,
|
||||||
"type": "rectangle",
|
"type": "rectangle",
|
||||||
"version": 13,
|
"version": 9,
|
||||||
"versionNonce": 915032327,
|
"versionNonce": 1604849351,
|
||||||
"width": 20,
|
"width": 20,
|
||||||
"x": 40,
|
"x": 40,
|
||||||
"y": 40,
|
"y": 40,
|
||||||
@@ -17069,8 +16879,8 @@ Object {
|
|||||||
"strokeStyle": "solid",
|
"strokeStyle": "solid",
|
||||||
"strokeWidth": 1,
|
"strokeWidth": 1,
|
||||||
"type": "rectangle",
|
"type": "rectangle",
|
||||||
"version": 5,
|
"version": 3,
|
||||||
"versionNonce": 401146281,
|
"versionNonce": 449462985,
|
||||||
"width": 10,
|
"width": 10,
|
||||||
"x": 0,
|
"x": 0,
|
||||||
"y": 0,
|
"y": 0,
|
||||||
@@ -17141,7 +16951,7 @@ Object {
|
|||||||
"elements": Array [
|
"elements": Array [
|
||||||
Object {
|
Object {
|
||||||
"angle": 0,
|
"angle": 0,
|
||||||
"backgroundColor": "transparent",
|
"backgroundColor": "#fa5252",
|
||||||
"boundElementIds": null,
|
"boundElementIds": null,
|
||||||
"fillStyle": "hachure",
|
"fillStyle": "hachure",
|
||||||
"groupIds": Array [],
|
"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,
|
"strokeWidth": 1,
|
||||||
"type": "rectangle",
|
"type": "rectangle",
|
||||||
"version": 6,
|
"version": 6,
|
||||||
"versionNonce": 1006504105,
|
"versionNonce": 760410951,
|
||||||
"width": 20,
|
"width": 20,
|
||||||
"x": 10,
|
"x": 10,
|
||||||
"y": -10,
|
"y": -10,
|
||||||
@@ -20633,7 +20407,7 @@ Object {
|
|||||||
"strokeWidth": 1,
|
"strokeWidth": 1,
|
||||||
"type": "rectangle",
|
"type": "rectangle",
|
||||||
"version": 6,
|
"version": 6,
|
||||||
"versionNonce": 289600103,
|
"versionNonce": 1006504105,
|
||||||
"width": 30,
|
"width": 30,
|
||||||
"x": 40,
|
"x": 40,
|
||||||
"y": 0,
|
"y": 0,
|
||||||
@@ -20676,8 +20450,8 @@ Object {
|
|||||||
"strokeStyle": "solid",
|
"strokeStyle": "solid",
|
||||||
"strokeWidth": 1,
|
"strokeWidth": 1,
|
||||||
"type": "arrow",
|
"type": "arrow",
|
||||||
"version": 11,
|
"version": 9,
|
||||||
"versionNonce": 1315507081,
|
"versionNonce": 81784553,
|
||||||
"width": 60,
|
"width": 60,
|
||||||
"x": 130,
|
"x": 130,
|
||||||
"y": 10,
|
"y": 10,
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { render, waitFor } from "./test-utils";
|
|||||||
import ExcalidrawApp from "../excalidraw-app";
|
import ExcalidrawApp from "../excalidraw-app";
|
||||||
import { API } from "./helpers/api";
|
import { API } from "./helpers/api";
|
||||||
import { getDefaultAppState } from "../appState";
|
import { getDefaultAppState } from "../appState";
|
||||||
|
import { EXPORT_DATA_TYPES } from "../constants";
|
||||||
|
|
||||||
const { h } = window;
|
const { h } = window;
|
||||||
|
|
||||||
@@ -29,7 +30,7 @@ describe("appState", () => {
|
|||||||
new Blob(
|
new Blob(
|
||||||
[
|
[
|
||||||
JSON.stringify({
|
JSON.stringify({
|
||||||
type: "excalidraw",
|
type: EXPORT_DATA_TYPES.excalidraw,
|
||||||
appState: {
|
appState: {
|
||||||
viewBackgroundColor: "#000",
|
viewBackgroundColor: "#000",
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { fireEvent, GlobalTestState, render } from "./test-utils";
|
|||||||
import Excalidraw from "../packages/excalidraw/index";
|
import Excalidraw from "../packages/excalidraw/index";
|
||||||
import { queryByText, queryByTestId } from "@testing-library/react";
|
import { queryByText, queryByTestId } from "@testing-library/react";
|
||||||
import { GRID_SIZE } from "../constants";
|
import { GRID_SIZE } from "../constants";
|
||||||
|
import { t } from "../i18n";
|
||||||
|
|
||||||
const { h } = window;
|
const { h } = window;
|
||||||
|
|
||||||
@@ -104,4 +105,29 @@ describe("<Excalidraw/>", () => {
|
|||||||
expect(queryByTestId(container, "toggle-dark-mode")).toBe(null);
|
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 { getDefaultAppState } from "../appState";
|
||||||
import { waitFor } from "@testing-library/react";
|
import { waitFor } from "@testing-library/react";
|
||||||
import { createUndoAction, createRedoAction } from "../actions/actionHistory";
|
import { createUndoAction, createRedoAction } from "../actions/actionHistory";
|
||||||
|
import { EXPORT_DATA_TYPES } from "../constants";
|
||||||
|
|
||||||
const { h } = window;
|
const { h } = window;
|
||||||
|
|
||||||
@@ -76,7 +77,7 @@ describe("history", () => {
|
|||||||
new Blob(
|
new Blob(
|
||||||
[
|
[
|
||||||
JSON.stringify({
|
JSON.stringify({
|
||||||
type: "excalidraw",
|
type: EXPORT_DATA_TYPES.excalidraw,
|
||||||
appState: {
|
appState: {
|
||||||
...getDefaultAppState(),
|
...getDefaultAppState(),
|
||||||
viewBackgroundColor: "#000",
|
viewBackgroundColor: "#000",
|
||||||
|
|||||||
@@ -187,6 +187,7 @@ export interface ExcalidrawProps {
|
|||||||
gridModeEnabled?: boolean;
|
gridModeEnabled?: boolean;
|
||||||
libraryReturnUrl?: string;
|
libraryReturnUrl?: string;
|
||||||
theme?: "dark" | "light";
|
theme?: "dark" | "light";
|
||||||
|
name?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type SceneData = {
|
export type SceneData = {
|
||||||
|
|||||||
+1
-3
@@ -131,9 +131,7 @@ export const debounce = <T extends any[]>(
|
|||||||
};
|
};
|
||||||
ret.flush = () => {
|
ret.flush = () => {
|
||||||
clearTimeout(handle);
|
clearTimeout(handle);
|
||||||
if (lastArgs) {
|
fn(...(lastArgs || []));
|
||||||
fn(...lastArgs);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
ret.cancel = () => {
|
ret.cancel = () => {
|
||||||
clearTimeout(handle);
|
clearTimeout(handle);
|
||||||
|
|||||||
Reference in New Issue
Block a user