Compare commits

...

9 Commits

Author SHA1 Message Date
Aakansha Doshi 016b054288 fix: Don't trigger onChange on initial load 2021-03-21 02:05:04 +05:30
David Luzar 94ad8eaa19 feat: support pasting file contents & always prefer system clip (#3257) 2021-03-20 20:20:47 +01:00
Aakansha Doshi 13d9374cde fix: Don't show export and delete when library is empty (#3288) 2021-03-20 21:58:37 +05:30
Aakansha Doshi efb6d0825b feat: Add label for name field and use input when editable in export dialog (#3286)
* feat: Add label for name field and use input when editable in export dialog

* fix

* review fix

* dnt allow to edit file name when view mode

* Update src/components/ProjectName.tsx

Co-authored-by: David Luzar <luzar.david@gmail.com>

Co-authored-by: David Luzar <luzar.david@gmail.com>
2021-03-20 21:57:58 +05:30
Aakansha Doshi 80a61db72f fix: overflow in textinput in export dialog (#3284)
* fix: overflow in textinput in export dialog

* use width
2021-03-20 18:21:48 +05:30
David Luzar 9a13dd8836 fix: bail on noop updates for newElementWith (#3279) 2021-03-20 13:29:53 +01:00
David Luzar cf6a5ff16b fix: state continuously updated when holding ctrl/cmd (#3283) 2021-03-20 13:28:28 +01:00
David Luzar fa8c7abf50 fix: debounce.flush not invoked if lastArgs not defined (#3281) 2021-03-20 13:15:28 +01:00
Arun c3ecbcb3ab feat: Allow host app to update title of drawing (#3273)
* Allow updating name on updateScene

* Revert "Allow updating name on updateScene"

This reverts commit 4e07a608d3.

* Make requested changes

* Make requested changes

* Remove customName from state

* Remove redundant if statement

* Add tests, update changelog and minor fixes

* remove eempty lines

* minor fixes

* no border and on hover no background change

* Give preference to name prop when initialData.appState.name is present and update specs

* minor fix

* Fix name input style in dark mode

Co-authored-by: Aakansha Doshi <aakansha1216@gmail.com>
2021-03-20 16:08:03 +05:30
25 changed files with 356 additions and 474 deletions
+4 -1
View File
@@ -18,11 +18,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
}
/>
),
});
+1
View File
@@ -122,6 +122,7 @@ export class ActionManager implements ActionsManagerInterface {
appState={this.getAppState()}
updateData={updateData}
id={id}
appProps={this.app.props}
/>
);
}
+2 -1
View File
@@ -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;
+14 -15
View File
@@ -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
+17 -3
View File
@@ -303,6 +303,7 @@ class App extends React.Component<ExcalidrawProps, AppState> {
zenModeEnabled = false,
gridModeEnabled = false,
theme = defaultAppState.theme,
name = defaultAppState.name,
} = props;
this.state = {
...defaultAppState,
@@ -314,6 +315,7 @@ class App extends React.Component<ExcalidrawProps, AppState> {
viewModeEnabled,
zenModeEnabled,
gridSize: gridModeEnabled ? GRID_SIZE : null,
name,
};
if (excalidrawRef) {
const readyPromise =
@@ -523,7 +525,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 +542,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 +562,7 @@ class App extends React.Component<ExcalidrawProps, AppState> {
zenModeEnabled,
gridSize,
theme,
name,
});
},
() => {
@@ -890,6 +897,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");
@@ -1008,7 +1022,7 @@ class App extends React.Component<ExcalidrawProps, AppState> {
// potential issues, this fixes a case where the tab isn't focused during
// init, which would trigger onChange with empty elements, which would then
// override whatever is in localStorage currently.
if (!this.state.isLoading) {
if (!this.state.isLoading && !prevState.isLoading) {
this.props.onChange?.(
this.scene.getElementsIncludingDeleted(),
this.state,
@@ -1390,7 +1404,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 });
}
+18
View File
@@ -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;
}
}
}
+1
View File
@@ -257,6 +257,7 @@ export const ExportDialog = ({
onClick={() => {
setModalIsShown(true);
}}
data-testid="export-button"
icon={exportFile}
type="button"
aria-label={t("buttons.export")}
+31 -28
View File
@@ -144,34 +144,37 @@ 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}`}
target="_excalidraw_libraries"
+30 -31
View File
@@ -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>
)}
</>
);
}
}
+1
View File
@@ -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"]}
+7 -1
View File
@@ -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;
+2 -2
View File
@@ -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;
+9 -3
View File
@@ -2,7 +2,7 @@ import decodePng from "png-chunks-extract";
import tEXt from "png-chunk-text";
import encodePng from "png-chunks-encode";
import { stringToBase64, encode, decode, base64ToString } from "./encode";
import { 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");
+5 -5
View File
@@ -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,
},
+27 -6
View File
@@ -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(),
};
};
+1 -1
View File
@@ -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",
+1
View File
@@ -18,6 +18,7 @@ Please add the latest change on the top under the correct section.
### 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).
#### BREAKING CHANGE
- `offsetLeft` and `offsetTop` props have been removed now so you have to use the `setCanvasOffsets` via `ref` to achieve the same.
+5
View File
@@ -376,6 +376,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`
@@ -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.
### `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
#### `getSceneVersion`
+2
View File
@@ -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>
@@ -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,
+2 -1
View File
@@ -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",
},
+26
View File
@@ -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");
});
});
});
+2 -1
View File
@@ -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",
+1
View File
@@ -187,6 +187,7 @@ export interface ExcalidrawProps {
gridModeEnabled?: boolean;
libraryReturnUrl?: string;
theme?: "dark" | "light";
name?: string;
}
export type SceneData = {
+1 -3
View File
@@ -131,9 +131,7 @@ export const debounce = <T extends any[]>(
};
ret.flush = () => {
clearTimeout(handle);
if (lastArgs) {
fn(...lastArgs);
}
fn(...(lastArgs || []));
};
ret.cancel = () => {
clearTimeout(handle);