feat: Allow host app to update title of drawing (#3273)
* Allow updating name on updateScene * Revert "Allow updating name on updateScene" This reverts commit 4e07a608d38a585e0f3c04e26b9f5e0e404824b1. * 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:
parent
de99484a1f
commit
c3ecbcb3ab
@ -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"}
|
||||
/>
|
||||
),
|
||||
});
|
||||
|
@ -122,6 +122,7 @@ export class ActionManager implements ActionsManagerInterface {
|
||||
appState={this.getAppState()}
|
||||
updateData={updateData}
|
||||
id={id}
|
||||
appProps={this.app.props}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
import React from "react";
|
||||
import { ExcalidrawElement } from "../element/types";
|
||||
import { AppState } from "../types";
|
||||
import { AppState, ExcalidrawProps } from "../types";
|
||||
|
||||
/** if false, the action should be prevented */
|
||||
export type ActionResult =
|
||||
@ -94,6 +94,7 @@ export interface Action {
|
||||
elements: readonly ExcalidrawElement[];
|
||||
appState: AppState;
|
||||
updateData: (formData?: any) => void;
|
||||
appProps: ExcalidrawProps;
|
||||
id?: string;
|
||||
}>;
|
||||
perform: ActionFn;
|
||||
|
@ -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");
|
||||
|
@ -34,6 +34,14 @@
|
||||
|
||||
.TextInput {
|
||||
height: calc(1rem - 3px);
|
||||
|
||||
&--readonly {
|
||||
background: none;
|
||||
border: none;
|
||||
&:hover {
|
||||
background: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -257,6 +257,7 @@ export const ExportDialog = ({
|
||||
onClick={() => {
|
||||
setModalIsShown(true);
|
||||
}}
|
||||
data-testid="export-button"
|
||||
icon={exportFile}
|
||||
type="button"
|
||||
aria-label={t("buttons.export")}
|
||||
|
@ -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>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -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"]}
|
||||
|
@ -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.
|
||||
|
@ -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`
|
||||
|
@ -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>
|
||||
|
@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -187,6 +187,7 @@ export interface ExcalidrawProps {
|
||||
gridModeEnabled?: boolean;
|
||||
libraryReturnUrl?: string;
|
||||
theme?: "dark" | "light";
|
||||
name?: string;
|
||||
}
|
||||
|
||||
export type SceneData = {
|
||||
|
Loading…
x
Reference in New Issue
Block a user