fix: default light theme splash 🔧 (#5660)

Co-authored-by: dwelle <luzar.david@gmail.com>
Co-authored-by: Aakansha Doshi <aakansha1216@gmail.com>
This commit is contained in:
Abdullah Adeel 2022-09-16 18:59:03 +05:00 committed by GitHub
parent ec4b3d913e
commit 7eaf47c9d4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 97 additions and 52 deletions

View File

@ -137,7 +137,6 @@ export class ActionManager {
*/
renderAction = (name: ActionName, data?: PanelComponentProps["data"]) => {
const canvasActions = this.app.props.UIOptions.canvasActions;
if (
this.actions[name] &&
"PanelComponent" in this.actions[name] &&

View File

@ -552,10 +552,6 @@ class App extends React.Component<AppProps, AppState> {
typeof this.props?.zenModeEnabled === "undefined" &&
this.state.zenModeEnabled
}
showThemeBtn={
typeof this.props?.theme === "undefined" &&
this.props.UIOptions.canvasActions.theme
}
libraryReturnUrl={this.props.libraryReturnUrl}
UIOptions={this.props.UIOptions}
focusContainer={this.focusContainer}
@ -645,7 +641,8 @@ class App extends React.Component<AppProps, AppState> {
let viewModeEnabled = actionResult?.appState?.viewModeEnabled || false;
let zenModeEnabled = actionResult?.appState?.zenModeEnabled || false;
let gridSize = actionResult?.appState?.gridSize || null;
let theme = actionResult?.appState?.theme || THEME.LIGHT;
const theme =
actionResult?.appState?.theme || this.props.theme || THEME.LIGHT;
let name = actionResult?.appState?.name ?? this.state.name;
if (typeof this.props.viewModeEnabled !== "undefined") {
viewModeEnabled = this.props.viewModeEnabled;
@ -659,10 +656,6 @@ class App extends React.Component<AppProps, AppState> {
gridSize = this.props.gridModeEnabled ? GRID_SIZE : null;
}
if (typeof this.props.theme !== "undefined") {
theme = this.props.theme;
}
if (typeof this.props.name !== "undefined") {
name = this.props.name;
}
@ -755,6 +748,9 @@ class App extends React.Component<AppProps, AppState> {
);
}
if (this.props.theme) {
this.setState({ theme: this.props.theme });
}
if (!this.state.isLoading) {
this.setState({ isLoading: true });
}
@ -784,6 +780,7 @@ class App extends React.Component<AppProps, AppState> {
const scene = restore(initialData, null, null);
scene.appState = {
...scene.appState,
theme: this.props.theme || scene.appState.theme,
// we're falling back to current (pre-init) state when deciding
// whether to open the library, to handle a case where we
// update the state outside of initialData (e.g. when loading the app

View File

@ -1,20 +1,12 @@
import React from "react";
import { ActionManager } from "../actions/manager";
import { AppState } from "../types";
export const BackgroundPickerAndDarkModeToggle = ({
appState,
setAppState,
actionManager,
showThemeBtn,
}: {
actionManager: ActionManager;
appState: AppState;
setAppState: React.Component<any, AppState>["setState"];
showThemeBtn: boolean;
}) => (
<div style={{ display: "flex" }}>
{actionManager.renderAction("changeViewBackgroundColor")}
{showThemeBtn && actionManager.renderAction("toggleTheme")}
{actionManager.renderAction("toggleTheme")}
</div>
);

View File

@ -2,10 +2,12 @@ import React, { useEffect, useState } from "react";
import { LoadingMessage } from "./LoadingMessage";
import { defaultLang, Language, languages, setLanguage } from "../i18n";
import { Theme } from "../element/types";
interface Props {
langCode: Language["code"];
children: React.ReactElement;
theme?: Theme;
}
export const InitializeApp = (props: Props) => {
@ -21,5 +23,5 @@ export const InitializeApp = (props: Props) => {
updateLang();
}, [props.langCode]);
return loading ? <LoadingMessage /> : props.children;
return loading ? <LoadingMessage theme={props.theme} /> : props.children;
};

View File

@ -53,7 +53,6 @@ interface LayerUIProps {
onPenModeToggle: () => void;
onInsertElements: (elements: readonly NonDeletedExcalidrawElement[]) => void;
showExitZenModeBtn: boolean;
showThemeBtn: boolean;
langCode: Language["code"];
isCollaborating: boolean;
renderTopRightUI?: ExcalidrawProps["renderTopRightUI"];
@ -78,7 +77,6 @@ const LayerUI = ({
onPenModeToggle,
onInsertElements,
showExitZenModeBtn,
showThemeBtn,
isCollaborating,
renderTopRightUI,
renderCustomFooter,
@ -209,12 +207,7 @@ const LayerUI = ({
/>
)}
</Stack.Row>
<BackgroundPickerAndDarkModeToggle
appState={appState}
actionManager={actionManager}
setAppState={setAppState}
showThemeBtn={showThemeBtn}
/>
<BackgroundPickerAndDarkModeToggle actionManager={actionManager} />
{appState.fileHandle && (
<>{actionManager.renderAction("saveToActiveFile")}</>
)}
@ -424,7 +417,6 @@ const LayerUI = ({
canvas={canvas}
isCollaborating={isCollaborating}
renderCustomFooter={renderCustomFooter}
showThemeBtn={showThemeBtn}
onImageAction={onImageAction}
renderTopRightUI={renderTopRightUI}
renderCustomStats={renderCustomStats}

View File

@ -1,8 +1,14 @@
import { t } from "../i18n";
import { useState, useEffect } from "react";
import Spinner from "./Spinner";
import clsx from "clsx";
import { THEME } from "../constants";
import { Theme } from "../element/types";
export const LoadingMessage: React.FC<{ delay?: number }> = ({ delay }) => {
export const LoadingMessage: React.FC<{ delay?: number; theme?: Theme }> = ({
delay,
theme,
}) => {
const [isWaiting, setIsWaiting] = useState(!!delay);
useEffect(() => {
@ -20,7 +26,11 @@ export const LoadingMessage: React.FC<{ delay?: number }> = ({ delay }) => {
}
return (
<div className="LoadingMessage">
<div
className={clsx("LoadingMessage", {
"LoadingMessage--dark": theme === THEME.DARK,
})}
>
<div>
<Spinner />
</div>

View File

@ -38,7 +38,6 @@ type MobileMenuProps = {
isMobile: boolean,
appState: AppState,
) => JSX.Element | null;
showThemeBtn: boolean;
onImageAction: (data: { insertOnCanvasDirectly: boolean }) => void;
renderTopRightUI?: (
isMobile: boolean,
@ -61,7 +60,6 @@ export const MobileMenu = ({
canvas,
isCollaborating,
renderCustomFooter,
showThemeBtn,
onImageAction,
renderTopRightUI,
renderCustomStats,
@ -171,14 +169,7 @@ export const MobileMenu = ({
onClick={onCollabButtonClick}
/>
)}
{
<BackgroundPickerAndDarkModeToggle
actionManager={actionManager}
appState={appState}
setAppState={setAppState}
showThemeBtn={showThemeBtn}
/>
}
{<BackgroundPickerAndDarkModeToggle actionManager={actionManager} />}
</>
);
};

View File

@ -149,7 +149,7 @@ export const DEFAULT_UI_OPTIONS: AppProps["UIOptions"] = {
export: { saveFileToDisk: true },
loadScene: true,
saveToActiveFile: true,
theme: true,
toggleTheme: null,
saveAsImage: true,
},
};

View File

@ -1,3 +1,5 @@
@import "open-color/open-color.scss";
.visually-hidden {
position: absolute !important;
height: 1px;
@ -30,3 +32,8 @@
font-size: 0.8em;
}
}
.LoadingMessage--dark {
background-color: $oc-black;
color: $oc-white;
}

View File

@ -34,6 +34,7 @@ export const STORAGE_KEYS = {
LOCAL_STORAGE_APP_STATE: "excalidraw-state",
LOCAL_STORAGE_COLLAB: "excalidraw-collab",
LOCAL_STORAGE_LIBRARY: "excalidraw-library",
LOCAL_STORAGE_THEME: "excalidraw-theme",
VERSION_DATA_STATE: "version-dataState",
VERSION_FILES: "version-files",
} as const;

View File

@ -9,6 +9,7 @@ import {
APP_NAME,
COOKIES,
EVENT,
THEME,
TITLE_TIMEOUT,
VERSION_TIMEOUT,
} from "../constants";
@ -17,6 +18,7 @@ import {
ExcalidrawElement,
FileId,
NonDeletedExcalidrawElement,
Theme,
} from "../element/types";
import { useCallbackRefState } from "../hooks/useCallbackRefState";
import { t } from "../i18n";
@ -512,6 +514,18 @@ const ExcalidrawWrapper = () => {
languageDetector.cacheUserLanguage(langCode);
}, [langCode]);
const [theme, setTheme] = useState<Theme>(
() =>
localStorage.getItem(STORAGE_KEYS.LOCAL_STORAGE_THEME) ||
// FIXME migration from old LS scheme. Can be removed later. #5660
importFromLocalStorage().appState?.theme ||
THEME.LIGHT,
);
useEffect(() => {
localStorage.setItem(STORAGE_KEYS.LOCAL_STORAGE_THEME, theme);
}, [theme]);
const onChange = (
elements: readonly ExcalidrawElement[],
appState: AppState,
@ -521,6 +535,8 @@ const ExcalidrawWrapper = () => {
collabAPI.syncElements(elements);
}
setTheme(appState.theme);
// this check is redundant, but since this is a hot path, it's best
// not to evaludate the nested expression every time
if (!LocalData.isSavePaused()) {
@ -710,6 +726,7 @@ const ExcalidrawWrapper = () => {
onPointerUpdate={collabAPI?.onPointerUpdate}
UIOptions={{
canvasActions: {
toggleTheme: true,
export: {
onExportToBackend,
renderCustomUI: (elements, appState, files) => {
@ -739,6 +756,7 @@ const ExcalidrawWrapper = () => {
handleKeyboardGlobally={true}
onLibraryChange={onLibraryChange}
autoFocus={true}
theme={theme}
/>
{excalidrawAPI && <Collab excalidrawAPI={excalidrawAPI} />}
{errorMessage && (

View File

@ -17,11 +17,13 @@ Please add the latest change on the top under the correct section.
#### Features
- Support [theme](https://github.com/excalidraw/excalidraw/blob/master/src/packages/excalidraw/README.md#theme) to be semi-controlled [#5660](https://github.com/excalidraw/excalidraw/pull/5660).
- Added support for storing [`customData`](https://github.com/excalidraw/excalidraw/blob/master/src/packages/excalidraw/README.md#storing-custom-data-to-excalidraw-elements) on Excalidraw elements [#5592].
- Added `exportPadding?: number;` to [exportToCanvas](https://github.com/excalidraw/excalidraw/blob/master/src/packages/excalidraw/README.md#exporttocanvas) and [exportToBlob](https://github.com/excalidraw/excalidraw/blob/master/src/packages/excalidraw/README.md#exporttoblob). The default value of the padding is 10.
#### Breaking Changes
- `props.UIOptions.canvasActions.theme` is now renamed to `props.UIOptions.canvasActions.toggleTheme` [#5660](https://github.com/excalidraw/excalidraw/pull/5660).
- `setToastMessage` API is now renamed to `setToast` API and the function signature is also updated [#5427](https://github.com/excalidraw/excalidraw/pull/5427). You can also pass `duration` and `closable` attributes along with `message`.
## 0.12.0 (2022-07-07)

View File

@ -637,7 +637,9 @@ If supplied, this URL will be used when user tries to install a library from [li
#### `theme`
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. You can use [`THEME`](#THEME-1) to specify the theme.
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 unless `UIOptions.canvasActions.toggleTheme` is set to `true`, in which case the `theme` prop will control Excalidraw's default theme with ability to allow theme switching (you must take care of updating the `theme` prop when you detect a change to `appState.theme` from the [onChange](#onChange) callback).
You can use [`THEME`](#THEME-1) to specify the theme.
#### `name`
@ -660,7 +662,7 @@ This prop can be used to customise UI of Excalidraw. Currently we support custom
| `export` | false &#124; [exportOpts](#exportOpts) | <pre>{ saveFileToDisk: true }</pre> | This prop allows to customize the UI inside the export dialog. By default it shows the "saveFileToDisk". If this prop is `false` the export button will not be rendered. For more details visit [`exportOpts`](#exportOpts). |
| `loadScene` | boolean | true | Implies whether to show `Load button` |
| `saveToActiveFile` | boolean | true | Implies whether to show `Save button` to save to current file |
| `theme` | boolean | true | Implies whether to show `Theme toggle` |
| `toggleTheme` | boolean &#124; null | null | Implies whether to show `Theme toggle`. When defined as `boolean`, takes precedence over [`props.theme`](#theme) to show `Theme toggle` |
| `saveAsImage` | boolean | true | Implies whether to show `Save as image button` |
##### `dockedSidebarBreakpoint`

View File

@ -56,6 +56,13 @@ const ExcalidrawBase = (props: ExcalidrawProps) => {
DEFAULT_UI_OPTIONS.canvasActions.export.saveFileToDisk;
}
if (
UIOptions.canvasActions.toggleTheme === null &&
typeof theme === "undefined"
) {
UIOptions.canvasActions.toggleTheme = true;
}
useEffect(() => {
// Block pinch-zooming on iOS outside of the content area
const handleTouchMove = (event: TouchEvent) => {
@ -75,7 +82,7 @@ const ExcalidrawBase = (props: ExcalidrawProps) => {
}, []);
return (
<InitializeApp langCode={langCode}>
<InitializeApp langCode={langCode} theme={theme}>
<Provider unstable_createStore={() => jotaiStore} scope={jotaiScope}>
<App
onChange={onChange}

View File

@ -88,21 +88,42 @@ describe("<Excalidraw/>", () => {
});
describe("Test theme prop", () => {
it('should show the dark mode toggle when the theme prop is "undefined"', async () => {
it("should show the theme toggle by default", async () => {
const { container } = await render(<Excalidraw />);
expect(h.state.theme).toBe(THEME.LIGHT);
const darkModeToggle = queryByTestId(container, "toggle-dark-mode");
expect(darkModeToggle).toBeTruthy();
});
it('should not show the dark mode toggle when the theme prop is not "undefined"', async () => {
it("should not show theme toggle when the theme prop is defined", async () => {
const { container } = await render(<Excalidraw theme="dark" />);
expect(h.state.theme).toBe(THEME.DARK);
expect(queryByTestId(container, "toggle-dark-mode")).toBe(null);
});
it("should show theme mode toggle when `UIOptions.canvasActions.toggleTheme` is true", async () => {
const { container } = await render(
<Excalidraw
theme={THEME.DARK}
UIOptions={{ canvasActions: { toggleTheme: true } }}
/>,
);
expect(h.state.theme).toBe(THEME.DARK);
const darkModeToggle = queryByTestId(container, "toggle-dark-mode");
expect(darkModeToggle).toBeTruthy();
});
it("should not show theme toggle when `UIOptions.canvasActions.toggleTheme` is false", async () => {
const { container } = await render(
<Excalidraw
UIOptions={{ canvasActions: { toggleTheme: false } }}
theme={THEME.DARK}
/>,
);
expect(h.state.theme).toBe(THEME.DARK);
const darkModeToggle = queryByTestId(container, "toggle-dark-mode");
expect(darkModeToggle).toBeFalsy();
});
});
describe("Test name prop", () => {
@ -214,7 +235,7 @@ describe("<Excalidraw/>", () => {
it("should hide the theme toggle when theme is false", async () => {
const { container } = await render(
<Excalidraw UIOptions={{ canvasActions: { theme: false } }} />,
<Excalidraw UIOptions={{ canvasActions: { toggleTheme: false } }} />,
);
expect(queryByTestId(container, "toggle-dark-mode")).toBeNull();

View File

@ -344,13 +344,17 @@ export type ExportOpts = {
) => JSX.Element;
};
// NOTE at the moment, if action name coressponds to canvasAction prop, its
// truthiness value will determine whether the action is rendered or not
// (see manager renderAction). We also override canvasAction values in
// excalidraw package index.tsx.
type CanvasActions = {
changeViewBackgroundColor?: boolean;
clearCanvas?: boolean;
export?: false | ExportOpts;
loadScene?: boolean;
saveToActiveFile?: boolean;
theme?: boolean;
toggleTheme?: boolean | null;
saveAsImage?: boolean;
};