From 7eaf47c9d41a33a6230d8c3a16b5087fc720dcfb Mon Sep 17 00:00:00 2001 From: Abdullah Adeel <64294045+mabdullahadeel@users.noreply.github.com> Date: Fri, 16 Sep 2022 18:59:03 +0500 Subject: [PATCH] =?UTF-8?q?fix:=20default=20light=20theme=20splash=20?= =?UTF-8?q?=F0=9F=94=A7=20(#5660)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: dwelle Co-authored-by: Aakansha Doshi --- src/actions/manager.tsx | 1 - src/components/App.tsx | 15 ++++----- .../BackgroundPickerAndDarkModeToggle.tsx | 10 +----- src/components/InitializeApp.tsx | 4 ++- src/components/LayerUI.tsx | 10 +----- src/components/LoadingMessage.tsx | 14 ++++++-- src/components/MobileMenu.tsx | 11 +------ src/constants.ts | 2 +- src/css/app.scss | 7 ++++ src/excalidraw-app/app_constants.ts | 1 + src/excalidraw-app/index.tsx | 18 ++++++++++ src/packages/excalidraw/CHANGELOG.md | 2 ++ src/packages/excalidraw/README.md | 6 ++-- src/packages/excalidraw/index.tsx | 9 ++++- src/tests/packages/excalidraw.test.tsx | 33 +++++++++++++++---- src/types.ts | 6 +++- 16 files changed, 97 insertions(+), 52 deletions(-) diff --git a/src/actions/manager.tsx b/src/actions/manager.tsx index 3e353373..e2655e50 100644 --- a/src/actions/manager.tsx +++ b/src/actions/manager.tsx @@ -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] && diff --git a/src/components/App.tsx b/src/components/App.tsx index 0ecbe27e..2e8d8cb3 100644 --- a/src/components/App.tsx +++ b/src/components/App.tsx @@ -552,10 +552,6 @@ class App extends React.Component { 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 { 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 { 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 { ); } + 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 { 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 diff --git a/src/components/BackgroundPickerAndDarkModeToggle.tsx b/src/components/BackgroundPickerAndDarkModeToggle.tsx index 07c1c060..603a99a9 100644 --- a/src/components/BackgroundPickerAndDarkModeToggle.tsx +++ b/src/components/BackgroundPickerAndDarkModeToggle.tsx @@ -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["setState"]; - showThemeBtn: boolean; }) => (
{actionManager.renderAction("changeViewBackgroundColor")} - {showThemeBtn && actionManager.renderAction("toggleTheme")} + {actionManager.renderAction("toggleTheme")}
); diff --git a/src/components/InitializeApp.tsx b/src/components/InitializeApp.tsx index c1941512..af4961fa 100644 --- a/src/components/InitializeApp.tsx +++ b/src/components/InitializeApp.tsx @@ -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 ? : props.children; + return loading ? : props.children; }; diff --git a/src/components/LayerUI.tsx b/src/components/LayerUI.tsx index d52c68d4..69155821 100644 --- a/src/components/LayerUI.tsx +++ b/src/components/LayerUI.tsx @@ -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 = ({ /> )} - + {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} diff --git a/src/components/LoadingMessage.tsx b/src/components/LoadingMessage.tsx index 83fe8040..12439615 100644 --- a/src/components/LoadingMessage.tsx +++ b/src/components/LoadingMessage.tsx @@ -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 ( -
+
diff --git a/src/components/MobileMenu.tsx b/src/components/MobileMenu.tsx index a2054754..1e1fdb57 100644 --- a/src/components/MobileMenu.tsx +++ b/src/components/MobileMenu.tsx @@ -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} /> )} - { - - } + {} ); }; diff --git a/src/constants.ts b/src/constants.ts index b1638f39..57496c4d 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -149,7 +149,7 @@ export const DEFAULT_UI_OPTIONS: AppProps["UIOptions"] = { export: { saveFileToDisk: true }, loadScene: true, saveToActiveFile: true, - theme: true, + toggleTheme: null, saveAsImage: true, }, }; diff --git a/src/css/app.scss b/src/css/app.scss index a8c93074..9d7fc819 100644 --- a/src/css/app.scss +++ b/src/css/app.scss @@ -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; +} diff --git a/src/excalidraw-app/app_constants.ts b/src/excalidraw-app/app_constants.ts index e56f7b24..55047ddf 100644 --- a/src/excalidraw-app/app_constants.ts +++ b/src/excalidraw-app/app_constants.ts @@ -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; diff --git a/src/excalidraw-app/index.tsx b/src/excalidraw-app/index.tsx index ebe6ed8e..25f758f3 100644 --- a/src/excalidraw-app/index.tsx +++ b/src/excalidraw-app/index.tsx @@ -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( + () => + 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 && } {errorMessage && ( diff --git a/src/packages/excalidraw/CHANGELOG.md b/src/packages/excalidraw/CHANGELOG.md index d239d25f..4f886ba5 100644 --- a/src/packages/excalidraw/CHANGELOG.md +++ b/src/packages/excalidraw/CHANGELOG.md @@ -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) diff --git a/src/packages/excalidraw/README.md b/src/packages/excalidraw/README.md index 1d09fb5b..5a188224 100644 --- a/src/packages/excalidraw/README.md +++ b/src/packages/excalidraw/README.md @@ -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 | [exportOpts](#exportOpts) |
{ saveFileToDisk: true }
| 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 | 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` diff --git a/src/packages/excalidraw/index.tsx b/src/packages/excalidraw/index.tsx index c7b6f37b..3a978b44 100644 --- a/src/packages/excalidraw/index.tsx +++ b/src/packages/excalidraw/index.tsx @@ -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 ( - + jotaiStore} scope={jotaiScope}> ", () => { }); 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(); 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(); 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( + , + ); + 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( + , + ); + 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("", () => { it("should hide the theme toggle when theme is false", async () => { const { container } = await render( - , + , ); expect(queryByTestId(container, "toggle-dark-mode")).toBeNull(); diff --git a/src/types.ts b/src/types.ts index db6647f1..0cfed3af 100644 --- a/src/types.ts +++ b/src/types.ts @@ -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; };