Compare commits

..

14 Commits

Author SHA1 Message Date
Arun Kumar
eb206cc932 Fix name input style in dark mode 2021-03-20 16:02:15 +05:30
Aakansha Doshi
16c287c848 Give preference to name prop when initialData.appState.name is present and update specs 2021-03-20 14:25:02 +05:30
Aakansha Doshi
78024873e5 no border and on hover no background change 2021-03-20 13:52:15 +05:30
Aakansha Doshi
4e41bd9dbb Merge remote-tracking branch 'origin/master' into updatescene-name 2021-03-20 13:43:01 +05:30
Aakansha Doshi
edc23b854f minor fixes 2021-03-20 13:41:12 +05:30
Arun Kumar
4843c49556 remove eempty lines 2021-03-20 01:15:01 +05:30
Arun Kumar
d565413082 Add tests, update changelog and minor fixes 2021-03-20 01:07:49 +05:30
Aakansha Doshi
dcda7184d0 Merge remote-tracking branch 'origin/master' into updatescene-name 2021-03-20 00:24:24 +05:30
Arun Kumar
8d413670c8 Remove redundant if statement 2021-03-19 01:32:14 +05:30
Arun Kumar
f774452124 Remove customName from state 2021-03-19 01:30:27 +05:30
Arun Kumar
db4ed1ecb1 Make requested changes 2021-03-18 22:49:37 +05:30
Arun Kumar
489f45b910 Make requested changes 2021-03-18 16:36:12 +05:30
Arun Kumar
a17be085b0 Revert "Allow updating name on updateScene"
This reverts commit 4e07a608d3.
2021-03-18 16:06:28 +05:30
Arun Kumar
4e07a608d3 Allow updating name on updateScene 2021-03-18 01:04:02 +05:30
18 changed files with 441 additions and 251 deletions

View File

@@ -23,9 +23,7 @@ export const actionChangeProjectName = register({
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={ isNameEditable={typeof appProps.name === "undefined"}
typeof appProps.name === "undefined" && !appState.viewModeEnabled
}
/> />
), ),
}); });

View File

@@ -7,10 +7,12 @@ 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 EXPORT_DATA_TYPES.excalidrawClipboard; type: typeof TYPE_ELEMENTS;
created: number;
elements: ExcalidrawElement[]; elements: ExcalidrawElement[];
}; };
@@ -29,16 +31,8 @@ export const probablySupportsClipboardBlob =
"ClipboardItem" in window && "ClipboardItem" in window &&
"toBlob" in HTMLCanvasElement.prototype; "toBlob" in HTMLCanvasElement.prototype;
const clipboardContainsElements = ( const isElementsClipboard = (contents: any): contents is ElementsClipboard => {
contents: any, if (contents?.type === TYPE_ELEMENTS) {
): 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;
@@ -49,7 +43,8 @@ export const copyToClipboard = async (
appState: AppState, appState: AppState,
) => { ) => {
const contents: ElementsClipboard = { const contents: ElementsClipboard = {
type: EXPORT_DATA_TYPES.excalidrawClipboard, type: TYPE_ELEMENTS,
created: Date.now(),
elements: getSelectedElements(elements, appState), elements: getSelectedElements(elements, appState),
}; };
const json = JSON.stringify(contents); const json = JSON.stringify(contents);
@@ -136,9 +131,15 @@ export const parseClipboard = async (
try { try {
const systemClipboardData = JSON.parse(systemClipboard); const systemClipboardData = JSON.parse(systemClipboard);
if (clipboardContainsElements(systemClipboardData)) { // system clipboard elements are newer than in-app clipboard
if (
isElementsClipboard(systemClipboardData) &&
(!appClipboardData?.created ||
appClipboardData.created < systemClipboardData.created)
) {
return { elements: systemClipboardData.elements }; 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

View File

@@ -525,7 +525,8 @@ 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; 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;
} }
@@ -1022,7 +1023,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 && !prevState.isLoading) { if (!this.state.isLoading) {
this.props.onChange?.( this.props.onChange?.(
this.scene.getElementsIncludingDeleted(), this.scene.getElementsIncludingDeleted(),
this.state, this.state,
@@ -1404,7 +1405,7 @@ class App extends React.Component<ExcalidrawProps, AppState> {
return; return;
} }
if (event[KEYS.CTRL_OR_CMD] && this.state.isBindingEnabled) { if (event[KEYS.CTRL_OR_CMD]) {
this.setState({ isBindingEnabled: false }); this.setState({ isBindingEnabled: false });
} }

View File

@@ -31,16 +31,9 @@
.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 { &--readonly {
background: none; background: none;
@@ -48,9 +41,6 @@
&:hover { &:hover {
background: none; background: none;
} }
width: auto;
max-width: 200px;
padding-left: 2px;
} }
} }
} }

View File

@@ -144,8 +144,6 @@ const LibraryMenuItems = ({
}); });
}} }}
/> />
{!!library.length && (
<>
<ToolButton <ToolButton
key="export" key="export"
type="button" type="button"
@@ -173,8 +171,7 @@ const LibraryMenuItems = ({
} }
}} }}
/> />
</>
)}
<a <a
href={`https://libraries.excalidraw.com?referrer=${referrer}`} href={`https://libraries.excalidraw.com?referrer=${referrer}`}
target="_excalidraw_libraries" target="_excalidraw_libraries"

View File

@@ -1,6 +1,7 @@
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;
@@ -9,18 +10,17 @@ type Props = {
isNameEditable: boolean; isNameEditable: boolean;
}; };
type State = { export class ProjectName extends Component<Props> {
fileName: string; private handleFocus = (event: React.FocusEvent<HTMLElement>) => {
}; selectNode(event.currentTarget);
export class ProjectName extends Component<Props, State> {
state = {
fileName: this.props.value,
}; };
private handleBlur = (event: any) => {
const value = event.target.value; private handleBlur = (event: React.FocusEvent<HTMLElement>) => {
const value = event.currentTarget.innerText.trim();
if (value !== this.props.value) { 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>) => {
@@ -32,30 +32,39 @@ export class ProjectName extends Component<Props, State> {
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 this.props.isNameEditable ? (
<> <span
<label htmlFor="file-name"> suppressContentEditableWarning
{`${this.props.label}${this.props.isNameEditable ? "" : ":"}`} ref={this.makeEditable}
</label> data-type="wysiwyg"
{this.props.isNameEditable ? (
<input
className="TextInput" className="TextInput"
role="textbox"
aria-label={this.props.label}
onBlur={this.handleBlur} onBlur={this.handleBlur}
onKeyDown={this.handleKeyDown} onKeyDown={this.handleKeyDown}
id="file-name" onFocus={this.handleFocus}
value={this.state.fileName} >
onChange={(event) => {this.props.value}
this.setState({ fileName: event.target.value }) </span>
} ) : (
/> <span
) : ( className="TextInput TextInput--readonly"
<span className="TextInput TextInput--readonly" id="file-name"> aria-label={this.props.label}
>
{this.props.value} {this.props.value}
</span> </span>
)}
</>
); );
} }
} }

View File

@@ -84,15 +84,9 @@ 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;

View File

@@ -1,5 +1,5 @@
import { cleanAppStateForExport } from "../appState"; import { cleanAppStateForExport } from "../appState";
import { EXPORT_DATA_TYPES, MIME_TYPES } from "../constants"; import { 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 !== EXPORT_DATA_TYPES.excalidrawLibrary) { if (data.type !== "excalidrawlib") {
throw new Error(t("alerts.couldNotLoadInvalidFile")); throw new Error(t("alerts.couldNotLoadInvalidFile"));
} }
return data; return data;

View File

@@ -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 { EXPORT_DATA_TYPES, MIME_TYPES } from "../constants"; import { MIME_TYPES } from "../constants";
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
// PNG // PNG
@@ -67,10 +67,7 @@ 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 ( if ("type" in encodedData && encodedData.type === "excalidraw") {
"type" in encodedData &&
encodedData.type === EXPORT_DATA_TYPES.excalidraw
) {
return metadata.text; return metadata.text;
} }
throw new Error("FAILED"); throw new Error("FAILED");
@@ -118,10 +115,7 @@ 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 ( if ("type" in encodedData && encodedData.type === "excalidraw") {
"type" in encodedData &&
encodedData.type === EXPORT_DATA_TYPES.excalidraw
) {
return json; return json;
} }
throw new Error("FAILED"); throw new Error("FAILED");

View File

@@ -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 { EXPORT_DATA_TYPES, MIME_TYPES } from "../constants"; import { 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: EXPORT_DATA_TYPES.excalidraw, type: "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 === EXPORT_DATA_TYPES.excalidraw && data?.type === "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 === EXPORT_DATA_TYPES.excalidrawLibrary && json.type === "excalidrawlib" &&
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: EXPORT_DATA_TYPES.excalidrawLibrary, type: "excalidrawlib",
version: 1, version: 1,
library, library,
}, },

View File

@@ -87,30 +87,9 @@ 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 => ({
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, ...element,
...updates, ...updates,
version: element.version + 1, version: element.version + 1,
versionNonce: randomInteger(), versionNonce: randomInteger(),
}; });
};

View File

@@ -61,7 +61,7 @@
"architect": "Architect", "architect": "Architect",
"artist": "Artist", "artist": "Artist",
"cartoonist": "Cartoonist", "cartoonist": "Cartoonist",
"fileTitle": "File name", "fileTitle": "File title",
"colorPicker": "Color picker", "colorPicker": "Color picker",
"canvasBackground": "Canvas background", "canvasBackground": "Canvas background",
"drawingCanvas": "Drawing canvas", "drawingCanvas": "Drawing canvas",

View File

@@ -18,7 +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). - Add `name` prop which indicates the name of the drawing which will be used when exporting the drawing. When supplied, the value takes precedence over `intialData.appState.name`, the `name` will be fully controlled by host app and the users won't be able to edit from within Excalidraw [#3273](https://github.com/excalidraw/excalidraw/pull/3273).
- Export API `setCanvasOffsets` via `ref` to set the offsets for Excalidraw[#3265](https://github.com/excalidraw/excalidraw/pull/3265). - 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.

View File

@@ -3357,8 +3357,8 @@ Object {
"strokeStyle": "solid", "strokeStyle": "solid",
"strokeWidth": 1, "strokeWidth": 1,
"type": "rectangle", "type": "rectangle",
"version": 4, "version": 8,
"versionNonce": 453191, "versionNonce": 1116226695,
"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": "#fa5252", "backgroundColor": "transparent",
"boundElementIds": null, "boundElementIds": null,
"fillStyle": "hachure", "fillStyle": "hachure",
"groupIds": Array [], "groupIds": Array [],
@@ -3452,6 +3452,78 @@ Object {
}, },
], ],
}, },
Object {
"appState": Object {
"editingGroupId": null,
"editingLinearElement": null,
"name": "Untitled-201933152653",
"selectedElementIds": Object {
"id0": true,
},
"viewBackgroundColor": "#ffffff",
},
"elements": Array [
Object {
"angle": 0,
"backgroundColor": "#fa5252",
"boundElementIds": null,
"fillStyle": "hachure",
"groupIds": Array [],
"height": 10,
"id": "id0",
"isDeleted": false,
"opacity": 100,
"roughness": 1,
"seed": 337897,
"strokeColor": "#000000",
"strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
"version": 5,
"versionNonce": 401146281,
"width": 10,
"x": 10,
"y": 10,
},
],
},
Object {
"appState": Object {
"editingGroupId": null,
"editingLinearElement": null,
"name": "Untitled-201933152653",
"selectedElementIds": Object {
"id0": true,
},
"viewBackgroundColor": "#ffffff",
},
"elements": Array [
Object {
"angle": 0,
"backgroundColor": "#fa5252",
"boundElementIds": null,
"fillStyle": "hachure",
"groupIds": Array [],
"height": 10,
"id": "id0",
"isDeleted": false,
"opacity": 100,
"roughness": 1,
"seed": 337897,
"strokeColor": "#000000",
"strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
"version": 6,
"versionNonce": 2019559783,
"width": 10,
"x": 10,
"y": 10,
},
],
},
Object { Object {
"appState": Object { "appState": Object {
"editingGroupId": null, "editingGroupId": null,
@@ -3480,8 +3552,8 @@ Object {
"strokeStyle": "solid", "strokeStyle": "solid",
"strokeWidth": 1, "strokeWidth": 1,
"type": "rectangle", "type": "rectangle",
"version": 4, "version": 8,
"versionNonce": 453191, "versionNonce": 1116226695,
"width": 10, "width": 10,
"x": 10, "x": 10,
"y": 10, "y": 10,
@@ -14673,7 +14745,7 @@ Object {
"strokeWidth": 2, "strokeWidth": 2,
"type": "rectangle", "type": "rectangle",
"version": 3, "version": 3,
"versionNonce": 1505387817, "versionNonce": 81784553,
"width": 20, "width": 20,
"x": 10, "x": 10,
"y": 10, "y": 10,
@@ -14692,14 +14764,14 @@ Object {
"isDeleted": false, "isDeleted": false,
"opacity": 60, "opacity": 60,
"roughness": 2, "roughness": 2,
"seed": 238820263, "seed": 23633383,
"strokeColor": "#c92a2a", "strokeColor": "#c92a2a",
"strokeSharpness": "sharp", "strokeSharpness": "sharp",
"strokeStyle": "dotted", "strokeStyle": "dotted",
"strokeWidth": 2, "strokeWidth": 2,
"type": "rectangle", "type": "rectangle",
"version": 9, "version": 13,
"versionNonce": 1604849351, "versionNonce": 915032327,
"width": 20, "width": 20,
"x": 40, "x": 40,
"y": 40, "y": 40,
@@ -14862,7 +14934,7 @@ Object {
"opacity": 100, "opacity": 100,
"roughness": 1, "roughness": 1,
"seed": 449462985, "seed": 449462985,
"strokeColor": "#c92a2a", "strokeColor": "#000000",
"strokeSharpness": "sharp", "strokeSharpness": "sharp",
"strokeStyle": "solid", "strokeStyle": "solid",
"strokeWidth": 1, "strokeWidth": 1,
@@ -14909,42 +14981,6 @@ 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",
@@ -14952,29 +14988,6 @@ 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,
@@ -15029,9 +15042,9 @@ Object {
}, },
Object { Object {
"angle": 0, "angle": 0,
"backgroundColor": "#e64980", "backgroundColor": "transparent",
"boundElementIds": null, "boundElementIds": null,
"fillStyle": "cross-hatch", "fillStyle": "hachure",
"groupIds": Array [], "groupIds": Array [],
"height": 20, "height": 20,
"id": "id1", "id": "id1",
@@ -15042,7 +15055,7 @@ Object {
"strokeColor": "#c92a2a", "strokeColor": "#c92a2a",
"strokeSharpness": "sharp", "strokeSharpness": "sharp",
"strokeStyle": "solid", "strokeStyle": "solid",
"strokeWidth": 2, "strokeWidth": 1,
"type": "rectangle", "type": "rectangle",
"version": 6, "version": 6,
"versionNonce": 1116226695, "versionNonce": 1116226695,
@@ -15090,7 +15103,7 @@ Object {
"angle": 0, "angle": 0,
"backgroundColor": "#e64980", "backgroundColor": "#e64980",
"boundElementIds": null, "boundElementIds": null,
"fillStyle": "cross-hatch", "fillStyle": "hachure",
"groupIds": Array [], "groupIds": Array [],
"height": 20, "height": 20,
"id": "id1", "id": "id1",
@@ -15100,11 +15113,11 @@ Object {
"seed": 449462985, "seed": 449462985,
"strokeColor": "#c92a2a", "strokeColor": "#c92a2a",
"strokeSharpness": "sharp", "strokeSharpness": "sharp",
"strokeStyle": "dotted", "strokeStyle": "solid",
"strokeWidth": 2, "strokeWidth": 1,
"type": "rectangle", "type": "rectangle",
"version": 7, "version": 8,
"versionNonce": 1014066025, "versionNonce": 238820263,
"width": 20, "width": 20,
"x": 40, "x": 40,
"y": 40, "y": 40,
@@ -15155,14 +15168,14 @@ Object {
"id": "id1", "id": "id1",
"isDeleted": false, "isDeleted": false,
"opacity": 100, "opacity": 100,
"roughness": 2, "roughness": 1,
"seed": 238820263, "seed": 449462985,
"strokeColor": "#c92a2a", "strokeColor": "#c92a2a",
"strokeSharpness": "sharp", "strokeSharpness": "sharp",
"strokeStyle": "dotted", "strokeStyle": "solid",
"strokeWidth": 2, "strokeWidth": 1,
"type": "rectangle", "type": "rectangle",
"version": 8, "version": 9,
"versionNonce": 400692809, "versionNonce": 400692809,
"width": 20, "width": 20,
"x": 40, "x": 40,
@@ -15213,16 +15226,193 @@ Object {
"height": 20, "height": 20,
"id": "id1", "id": "id1",
"isDeleted": false, "isDeleted": false,
"opacity": 60, "opacity": 100,
"roughness": 2, "roughness": 1,
"seed": 238820263, "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", "strokeColor": "#c92a2a",
"strokeSharpness": "sharp", "strokeSharpness": "sharp",
"strokeStyle": "dotted", "strokeStyle": "dotted",
"strokeWidth": 2, "strokeWidth": 2,
"type": "rectangle", "type": "rectangle",
"version": 9, "version": 11,
"versionNonce": 1604849351, "versionNonce": 1505387817,
"width": 20,
"x": 40,
"y": 40,
},
],
},
Object {
"appState": Object {
"editingGroupId": null,
"editingLinearElement": null,
"name": "Untitled-201933152653",
"selectedElementIds": Object {
"id1": true,
},
"viewBackgroundColor": "#ffffff",
},
"elements": Array [
Object {
"angle": 0,
"backgroundColor": "transparent",
"boundElementIds": null,
"fillStyle": "hachure",
"groupIds": Array [],
"height": 20,
"id": "id0",
"isDeleted": false,
"opacity": 100,
"roughness": 1,
"seed": 337897,
"strokeColor": "#000000",
"strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
"version": 2,
"versionNonce": 1278240551,
"width": 20,
"x": 10,
"y": 10,
},
Object {
"angle": 0,
"backgroundColor": "#e64980",
"boundElementIds": null,
"fillStyle": "cross-hatch",
"groupIds": Array [],
"height": 20,
"id": "id1",
"isDeleted": false,
"opacity": 100,
"roughness": 2,
"seed": 23633383,
"strokeColor": "#c92a2a",
"strokeSharpness": "sharp",
"strokeStyle": "dotted",
"strokeWidth": 2,
"type": "rectangle",
"version": 12,
"versionNonce": 493213705,
"width": 20,
"x": 40,
"y": 40,
},
],
},
Object {
"appState": Object {
"editingGroupId": null,
"editingLinearElement": null,
"name": "Untitled-201933152653",
"selectedElementIds": Object {
"id1": true,
},
"viewBackgroundColor": "#ffffff",
},
"elements": Array [
Object {
"angle": 0,
"backgroundColor": "transparent",
"boundElementIds": null,
"fillStyle": "hachure",
"groupIds": Array [],
"height": 20,
"id": "id0",
"isDeleted": false,
"opacity": 100,
"roughness": 1,
"seed": 337897,
"strokeColor": "#000000",
"strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
"version": 2,
"versionNonce": 1278240551,
"width": 20,
"x": 10,
"y": 10,
},
Object {
"angle": 0,
"backgroundColor": "#e64980",
"boundElementIds": null,
"fillStyle": "cross-hatch",
"groupIds": Array [],
"height": 20,
"id": "id1",
"isDeleted": false,
"opacity": 60,
"roughness": 2,
"seed": 23633383,
"strokeColor": "#c92a2a",
"strokeSharpness": "sharp",
"strokeStyle": "dotted",
"strokeWidth": 2,
"type": "rectangle",
"version": 13,
"versionNonce": 915032327,
"width": 20, "width": 20,
"x": 40, "x": 40,
"y": 40, "y": 40,
@@ -15258,7 +15448,7 @@ Object {
"strokeWidth": 2, "strokeWidth": 2,
"type": "rectangle", "type": "rectangle",
"version": 3, "version": 3,
"versionNonce": 1505387817, "versionNonce": 81784553,
"width": 20, "width": 20,
"x": 10, "x": 10,
"y": 10, "y": 10,
@@ -15274,14 +15464,14 @@ Object {
"isDeleted": false, "isDeleted": false,
"opacity": 60, "opacity": 60,
"roughness": 2, "roughness": 2,
"seed": 238820263, "seed": 23633383,
"strokeColor": "#c92a2a", "strokeColor": "#c92a2a",
"strokeSharpness": "sharp", "strokeSharpness": "sharp",
"strokeStyle": "dotted", "strokeStyle": "dotted",
"strokeWidth": 2, "strokeWidth": 2,
"type": "rectangle", "type": "rectangle",
"version": 9, "version": 13,
"versionNonce": 1604849351, "versionNonce": 915032327,
"width": 20, "width": 20,
"x": 40, "x": 40,
"y": 40, "y": 40,
@@ -16879,8 +17069,8 @@ Object {
"strokeStyle": "solid", "strokeStyle": "solid",
"strokeWidth": 1, "strokeWidth": 1,
"type": "rectangle", "type": "rectangle",
"version": 3, "version": 5,
"versionNonce": 449462985, "versionNonce": 401146281,
"width": 10, "width": 10,
"x": 0, "x": 0,
"y": 0, "y": 0,
@@ -16951,7 +17141,7 @@ Object {
"elements": Array [ "elements": Array [
Object { Object {
"angle": 0, "angle": 0,
"backgroundColor": "#fa5252", "backgroundColor": "transparent",
"boundElementIds": null, "boundElementIds": null,
"fillStyle": "hachure", "fillStyle": "hachure",
"groupIds": Array [], "groupIds": Array [],
@@ -16974,6 +17164,42 @@ Object {
}, },
], ],
}, },
Object {
"appState": Object {
"editingGroupId": null,
"editingLinearElement": null,
"name": "Untitled-201933152653",
"selectedElementIds": Object {
"id0": true,
},
"viewBackgroundColor": "#ffffff",
},
"elements": Array [
Object {
"angle": 0,
"backgroundColor": "#fa5252",
"boundElementIds": null,
"fillStyle": "hachure",
"groupIds": Array [],
"height": 10,
"id": "id0",
"isDeleted": false,
"opacity": 100,
"roughness": 1,
"seed": 337897,
"strokeColor": "#000000",
"strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
"version": 5,
"versionNonce": 401146281,
"width": 10,
"x": 0,
"y": 0,
},
],
},
], ],
} }
`; `;
@@ -20381,7 +20607,7 @@ Object {
"strokeWidth": 1, "strokeWidth": 1,
"type": "rectangle", "type": "rectangle",
"version": 6, "version": 6,
"versionNonce": 760410951, "versionNonce": 1006504105,
"width": 20, "width": 20,
"x": 10, "x": 10,
"y": -10, "y": -10,
@@ -20407,7 +20633,7 @@ Object {
"strokeWidth": 1, "strokeWidth": 1,
"type": "rectangle", "type": "rectangle",
"version": 6, "version": 6,
"versionNonce": 1006504105, "versionNonce": 289600103,
"width": 30, "width": 30,
"x": 40, "x": 40,
"y": 0, "y": 0,
@@ -20450,8 +20676,8 @@ Object {
"strokeStyle": "solid", "strokeStyle": "solid",
"strokeWidth": 1, "strokeWidth": 1,
"type": "arrow", "type": "arrow",
"version": 9, "version": 11,
"versionNonce": 81784553, "versionNonce": 1315507081,
"width": 60, "width": 60,
"x": 130, "x": 130,
"y": 10, "y": 10,

View File

@@ -3,7 +3,6 @@ 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;
@@ -30,7 +29,7 @@ describe("appState", () => {
new Blob( new Blob(
[ [
JSON.stringify({ JSON.stringify({
type: EXPORT_DATA_TYPES.excalidraw, type: "excalidraw",
appState: { appState: {
viewBackgroundColor: "#000", viewBackgroundColor: "#000",
}, },

View File

@@ -111,11 +111,11 @@ describe("<Excalidraw/>", () => {
const { container } = await render(<Excalidraw />); const { container } = await render(<Excalidraw />);
fireEvent.click(queryByTestId(container, "export-button")!); fireEvent.click(queryByTestId(container, "export-button")!);
const textInput: HTMLInputElement | null = document.querySelector( const textInput = document.querySelector(
".ExportDialog__name .TextInput", ".ExportDialog__name .TextInput",
); );
expect(textInput?.value).toContain(`${t("labels.untitled")}`); expect(textInput?.textContent).toContain(`${t("labels.untitled")}`);
expect(textInput?.nodeName).toBe("INPUT"); expect(textInput?.hasAttribute("data-type")).toBe(true);
}); });
it('should set the name and not allow editing when the name prop is present"', async () => { it('should set the name and not allow editing when the name prop is present"', async () => {
@@ -124,10 +124,11 @@ describe("<Excalidraw/>", () => {
await fireEvent.click(queryByTestId(container, "export-button")!); await fireEvent.click(queryByTestId(container, "export-button")!);
const textInput = document.querySelector( const textInput = document.querySelector(
".ExportDialog__name .TextInput--readonly", ".ExportDialog__name .TextInput",
); );
expect(textInput?.textContent).toEqual(name); expect(textInput?.textContent).toEqual(name);
expect(textInput?.nodeName).toBe("SPAN");
expect(textInput?.hasAttribute("data-type")).toBe(false);
}); });
}); });
}); });

View File

@@ -6,7 +6,6 @@ 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;
@@ -77,7 +76,7 @@ describe("history", () => {
new Blob( new Blob(
[ [
JSON.stringify({ JSON.stringify({
type: EXPORT_DATA_TYPES.excalidraw, type: "excalidraw",
appState: { appState: {
...getDefaultAppState(), ...getDefaultAppState(),
viewBackgroundColor: "#000", viewBackgroundColor: "#000",

View File

@@ -131,7 +131,9 @@ export const debounce = <T extends any[]>(
}; };
ret.flush = () => { ret.flush = () => {
clearTimeout(handle); clearTimeout(handle);
fn(...(lastArgs || [])); if (lastArgs) {
fn(...lastArgs);
}
}; };
ret.cancel = () => { ret.cancel = () => {
clearTimeout(handle); clearTimeout(handle);