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>
This commit is contained in:
Arun 2021-03-20 16:08:03 +05:30 committed by GitHub
parent de99484a1f
commit c3ecbcb3ab
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 75 additions and 3 deletions

View File

@ -18,11 +18,12 @@ 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"}
/>
),
});

View File

@ -122,6 +122,7 @@ export class ActionManager implements ActionsManagerInterface {
appState={this.getAppState()}
updateData={updateData}
id={id}
appProps={this.app.props}
/>
);
}

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;

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,6 +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 +543,10 @@ class App extends React.Component<ExcalidrawProps, AppState> {
theme = this.props.theme;
}
if (typeof this.props.name !== "undefined") {
name = this.props.name;
}
this.setState(
(state) => {
// using Object.assign instead of spread to fool TS 4.2.2+ into
@ -556,6 +563,7 @@ class App extends React.Component<ExcalidrawProps, AppState> {
zenModeEnabled,
gridSize,
theme,
name,
});
},
() => {
@ -890,6 +898,13 @@ class App extends React.Component<ExcalidrawProps, AppState> {
gridSize: this.props.gridModeEnabled ? GRID_SIZE : null,
});
}
if (this.props.name && prevProps.name !== this.props.name) {
this.setState({
name: this.props.name,
});
}
document
.querySelector(".excalidraw")
?.classList.toggle("theme--dark", this.state.theme === "dark");

View File

@ -34,6 +34,14 @@
.TextInput {
height: calc(1rem - 3px);
&--readonly {
background: none;
border: none;
&:hover {
background: none;
}
}
}
}

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")}

View File

@ -7,6 +7,7 @@ type Props = {
value: string;
onChange: (value: string) => void;
label: string;
isNameEditable: boolean;
};
export class ProjectName extends Component<Props> {
@ -43,7 +44,7 @@ export class ProjectName extends Component<Props> {
};
public render() {
return (
return this.props.isNameEditable ? (
<span
suppressContentEditableWarning
ref={this.makeEditable}
@ -57,6 +58,13 @@ export class ProjectName extends Component<Props> {
>
{this.props.value}
</span>
) : (
<span
className="TextInput TextInput--readonly"
aria-label={this.props.label}
>
{this.props.value}
</span>
);
}
}

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"]}

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.

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`

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>

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,30 @@ 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 = document.querySelector(
".ExportDialog__name .TextInput",
);
expect(textInput?.textContent).toContain(`${t("labels.untitled")}`);
expect(textInput?.hasAttribute("data-type")).toBe(true);
});
it('should set the name and not allow editing when the name prop is present"', async () => {
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?.hasAttribute("data-type")).toBe(false);
});
});
});

View File

@ -187,6 +187,7 @@ export interface ExcalidrawProps {
gridModeEnabled?: boolean;
libraryReturnUrl?: string;
theme?: "dark" | "light";
name?: string;
}
export type SceneData = {