From b3250c068e90df5209b1d8947a582593933f21bf Mon Sep 17 00:00:00 2001 From: FaberVitale Date: Mon, 13 Sep 2021 22:53:06 +0200 Subject: [PATCH 1/5] feat(redux-devtools-app): use `prefers-color-scheme` to set `theme.light` if user has not set a preferred theme --- .../src/browser/extension/devpanel/index.tsx | 18 +++++++++++-- .../src/browser/extension/window/index.tsx | 11 +++++++- .../redux-devtools-app/src/actions/index.ts | 25 +++++++++++++++++-- .../src/constants/actionTypes.ts | 2 ++ packages/redux-devtools-app/src/index.tsx | 19 ++++++++++++-- .../redux-devtools-app/src/reducers/theme.ts | 16 +++++++++++- .../src/utils/media-queries.ts | 13 ++++++++++ 7 files changed, 96 insertions(+), 8 deletions(-) create mode 100644 packages/redux-devtools-app/src/utils/media-queries.ts diff --git a/extension/src/browser/extension/devpanel/index.tsx b/extension/src/browser/extension/devpanel/index.tsx index 658612b6cc..80ea824974 100644 --- a/extension/src/browser/extension/devpanel/index.tsx +++ b/extension/src/browser/extension/devpanel/index.tsx @@ -8,7 +8,10 @@ import configureStore from '../../../app/stores/panelStore'; import '../../views/devpanel.pug'; import { Action, Store } from 'redux'; -import { StoreAction } from '@redux-devtools/app/lib/actions'; +import { + applyMediaFeaturesPreferences, + StoreAction, +} from '@redux-devtools/app/lib/actions'; import { PanelMessage } from '../../../app/middlewares/api'; import { StoreStateWithoutSocket } from '../../../app/reducers/panel'; import { PersistGate } from 'redux-persist/integration/react'; @@ -33,9 +36,20 @@ function renderDevTools() { unmountComponentAtNode(node!); clearTimeout(naTimeout); ({ store, persistor } = configureStore(position, bgConnection)); + + const onBeforeLift = () => { + if (store) { + store.dispatch(applyMediaFeaturesPreferences()); + } + }; + render( - + , diff --git a/extension/src/browser/extension/window/index.tsx b/extension/src/browser/extension/window/index.tsx index a49aa01a8f..bc52758863 100644 --- a/extension/src/browser/extension/window/index.tsx +++ b/extension/src/browser/extension/window/index.tsx @@ -8,6 +8,7 @@ import configureStore from '../../../app/stores/windowStore'; import { MonitorMessage } from '../../../app/middlewares/api'; import '../../views/window.pug'; +import { applyMediaFeaturesPreferences } from '@redux-devtools/app/lib/actions'; const position = location.hash; @@ -25,9 +26,17 @@ chrome.runtime.getBackgroundPage((window) => { bg.onMessage.addListener(update); update(); + const onBeforeLift = () => { + localStore.dispatch(applyMediaFeaturesPreferences()); + }; + render( - + , diff --git a/packages/redux-devtools-app/src/actions/index.ts b/packages/redux-devtools-app/src/actions/index.ts index c66cb0fb00..663f132e76 100644 --- a/packages/redux-devtools-app/src/actions/index.ts +++ b/packages/redux-devtools-app/src/actions/index.ts @@ -3,6 +3,7 @@ import { AuthStates, States } from 'socketcluster-client/lib/scclientsocket'; import { CHANGE_SECTION, CHANGE_THEME, + APPLY_MEDIA_FEATURES_PREFERENCES, SELECT_INSTANCE, SELECT_MONITOR, UPDATE_MONITOR_STATE, @@ -44,9 +45,9 @@ import { import { Action } from 'redux'; import { Features, State } from '../reducers/instances'; import { MonitorStateMonitorState } from '../reducers/monitor'; -import { LiftedAction } from '@redux-devtools/core'; +import { LiftedAction, LiftedState } from '@redux-devtools/core'; import { Data } from '../reducers/reports'; -import { LiftedState } from '@redux-devtools/core'; +import { prefersDarkColorScheme } from '../utils/media-queries'; let monitorReducer: ( monitorProps: unknown, @@ -77,10 +78,29 @@ export interface ChangeThemeAction { readonly scheme: Scheme; readonly dark: boolean; } + +export interface ApplyMediaFeaturesPreferencesAction { + readonly type: typeof APPLY_MEDIA_FEATURES_PREFERENCES; + readonly prefersDarkColorScheme: boolean; +} + export function changeTheme(data: ChangeThemeData): ChangeThemeAction { return { type: CHANGE_THEME, ...data.formData }; } +/** + * @see https://developer.mozilla.org/en-US/docs/Web/CSS/Media_Queries/Using_media_queries#media_features + */ +export function applyMediaFeaturesPreferences( + payload?: Partial> +): ApplyMediaFeaturesPreferencesAction { + return { + prefersDarkColorScheme: prefersDarkColorScheme(), + ...payload, + type: APPLY_MEDIA_FEATURES_PREFERENCES, + }; +} + export interface InitMonitorAction { type: '@@INIT_MONITOR'; newMonitorState: unknown; @@ -564,6 +584,7 @@ export interface ErrorAction { export type StoreActionWithoutUpdateStateOrLiftedAction = | ChangeSectionAction | ChangeThemeAction + | ApplyMediaFeaturesPreferencesAction | MonitorActionAction | SelectInstanceAction | SelectMonitorAction diff --git a/packages/redux-devtools-app/src/constants/actionTypes.ts b/packages/redux-devtools-app/src/constants/actionTypes.ts index 98b1aa38ae..fa9e0ee718 100644 --- a/packages/redux-devtools-app/src/constants/actionTypes.ts +++ b/packages/redux-devtools-app/src/constants/actionTypes.ts @@ -1,5 +1,7 @@ export const CHANGE_SECTION = 'main/CHANGE_SECTION'; export const CHANGE_THEME = 'main/CHANGE_THEME'; +export const APPLY_MEDIA_FEATURES_PREFERENCES = + 'main/APPLY_MEDIA_FEATURES_PREFERENCES'; export const UPDATE_STATE = 'devTools/UPDATE_STATE'; export const SET_STATE = 'devTools/SET_STATE'; diff --git a/packages/redux-devtools-app/src/index.tsx b/packages/redux-devtools-app/src/index.tsx index abb0fc56c0..6d2d8b81bb 100644 --- a/packages/redux-devtools-app/src/index.tsx +++ b/packages/redux-devtools-app/src/index.tsx @@ -7,7 +7,7 @@ import configureStore from './store/configureStore'; import { CONNECT_REQUEST } from './constants/socketActionTypes'; import App from './containers/App'; import { StoreState } from './reducers'; -import { StoreAction } from './actions'; +import { StoreAction, applyMediaFeaturesPreferences } from './actions'; class Root extends Component { store?: Store; @@ -27,11 +27,26 @@ class Root extends Component { this.persistor = persistor; } + /** + * @hidden + * @private + */ + private _checkMediaFeaturesPreferences = () => { + if (this.store) { + this.store.dispatch(applyMediaFeaturesPreferences()); + } + }; + render() { if (!this.store) return null; + return ( - + diff --git a/packages/redux-devtools-app/src/reducers/theme.ts b/packages/redux-devtools-app/src/reducers/theme.ts index 293e9992ae..55d6d40547 100644 --- a/packages/redux-devtools-app/src/reducers/theme.ts +++ b/packages/redux-devtools-app/src/reducers/theme.ts @@ -1,11 +1,12 @@ import { Scheme, Theme } from '@redux-devtools/ui'; -import { CHANGE_THEME } from '../constants/actionTypes'; +import { CHANGE_THEME, APPLY_MEDIA_FEATURES_PREFERENCES, } from '../constants/actionTypes'; import { StoreAction } from '../actions'; export interface ThemeState { readonly theme: Theme; readonly scheme: Scheme; readonly light: boolean; + readonly latestChangeBy?: string; } export default function theme( @@ -21,7 +22,20 @@ export default function theme( theme: action.theme, scheme: action.scheme, light: !action.dark, + latestChangeBy: CHANGE_THEME, }; } + + if ( + action.type === APPLY_MEDIA_FEATURES_PREFERENCES && + state.latestChangeBy !== CHANGE_THEME + ) { + return { + ...state, + light: !action.prefersDarkColorScheme, + latestChangeBy: APPLY_MEDIA_FEATURES_PREFERENCES, + }; + } + return state; } diff --git a/packages/redux-devtools-app/src/utils/media-queries.ts b/packages/redux-devtools-app/src/utils/media-queries.ts new file mode 100644 index 0000000000..a965492ce0 --- /dev/null +++ b/packages/redux-devtools-app/src/utils/media-queries.ts @@ -0,0 +1,13 @@ +/** + * @see https://developer.mozilla.org/en-US/docs/Web/CSS/@media/prefers-color-scheme + */ +export function prefersDarkColorScheme(): boolean { + if ( + typeof window !== 'undefined' && + typeof window.matchMedia === 'function' + ) { + return window.matchMedia('(prefers-color-scheme: dark)').matches; + } + + return false; +} From fb976f0207adeec044b17f73aa3db63f747a866a Mon Sep 17 00:00:00 2001 From: FaberVitale Date: Tue, 14 Sep 2021 16:03:15 +0200 Subject: [PATCH 2/5] chore(@redux-devtools/app): fix lint error Error message: 0:0 error Parsing error: "parserOptions.project" has been set for @typescript-eslint/parser. The file does not match your project config: test/__mocks__/styleMock.ts. The file must be included in at least one of the projects provided --- packages/redux-devtools-app/jest.config.js | 2 +- packages/redux-devtools-app/test/__mocks__/styleMock.js | 1 + packages/redux-devtools-app/test/__mocks__/styleMock.ts | 1 - 3 files changed, 2 insertions(+), 2 deletions(-) create mode 100644 packages/redux-devtools-app/test/__mocks__/styleMock.js delete mode 100644 packages/redux-devtools-app/test/__mocks__/styleMock.ts diff --git a/packages/redux-devtools-app/jest.config.js b/packages/redux-devtools-app/jest.config.js index a1f53444e6..7e5b0b2866 100644 --- a/packages/redux-devtools-app/jest.config.js +++ b/packages/redux-devtools-app/jest.config.js @@ -3,6 +3,6 @@ module.exports = { setupFilesAfterEnv: ['/test/setup.ts'], testEnvironment: 'jsdom', moduleNameMapper: { - '\\.css$': '/test/__mocks__/styleMock.ts', + '\\.css$': '/test/__mocks__/styleMock.js', }, }; diff --git a/packages/redux-devtools-app/test/__mocks__/styleMock.js b/packages/redux-devtools-app/test/__mocks__/styleMock.js new file mode 100644 index 0000000000..f053ebf797 --- /dev/null +++ b/packages/redux-devtools-app/test/__mocks__/styleMock.js @@ -0,0 +1 @@ +module.exports = {}; diff --git a/packages/redux-devtools-app/test/__mocks__/styleMock.ts b/packages/redux-devtools-app/test/__mocks__/styleMock.ts deleted file mode 100644 index ff8b4c5632..0000000000 --- a/packages/redux-devtools-app/test/__mocks__/styleMock.ts +++ /dev/null @@ -1 +0,0 @@ -export default {}; From fb1ec166d385ada5da0175f640a1c49242966e6f Mon Sep 17 00:00:00 2001 From: FaberVitale Date: Sun, 24 Oct 2021 11:31:14 +0200 Subject: [PATCH 3/5] chore: run prettier --- packages/redux-devtools-app/src/reducers/theme.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/redux-devtools-app/src/reducers/theme.ts b/packages/redux-devtools-app/src/reducers/theme.ts index 55d6d40547..bb3aa8a99f 100644 --- a/packages/redux-devtools-app/src/reducers/theme.ts +++ b/packages/redux-devtools-app/src/reducers/theme.ts @@ -1,5 +1,8 @@ import { Scheme, Theme } from '@redux-devtools/ui'; -import { CHANGE_THEME, APPLY_MEDIA_FEATURES_PREFERENCES, } from '../constants/actionTypes'; +import { + CHANGE_THEME, + APPLY_MEDIA_FEATURES_PREFERENCES, +} from '../constants/actionTypes'; import { StoreAction } from '../actions'; export interface ThemeState { From 482674b83b0e1d2488523d347ae26e57b36d6c08 Mon Sep 17 00:00:00 2001 From: FaberVitale Date: Sat, 30 Oct 2021 18:31:01 +0200 Subject: [PATCH 4/5] feat(app): add theme color dropdown --- extension/src/app/containers/App.tsx | 4 +++- .../redux-devtools-app/src/actions/index.ts | 22 +++++++++++++++--- .../src/components/Settings/Themes.tsx | 13 ++++++++--- .../redux-devtools-app/src/reducers/theme.ts | 23 +++++++++++++++---- 4 files changed, 50 insertions(+), 12 deletions(-) diff --git a/extension/src/app/containers/App.tsx b/extension/src/app/containers/App.tsx index e9b46b878a..e19dddb354 100644 --- a/extension/src/app/containers/App.tsx +++ b/extension/src/app/containers/App.tsx @@ -62,10 +62,12 @@ class App extends Component { function mapStateToProps(state: StoreState) { const instances = state.instances; const id = getActiveInstance(instances); + const { themeColorPreference, ...themeData } = state.theme; + return { options: instances.options[id], section: state.section, - theme: state.theme, + theme: themeData, notification: state.notification, }; } diff --git a/packages/redux-devtools-app/src/actions/index.ts b/packages/redux-devtools-app/src/actions/index.ts index 663f132e76..6773371403 100644 --- a/packages/redux-devtools-app/src/actions/index.ts +++ b/packages/redux-devtools-app/src/actions/index.ts @@ -48,6 +48,7 @@ import { MonitorStateMonitorState } from '../reducers/monitor'; import { LiftedAction, LiftedState } from '@redux-devtools/core'; import { Data } from '../reducers/reports'; import { prefersDarkColorScheme } from '../utils/media-queries'; +import { ThemeColorPreference } from '../reducers/theme'; let monitorReducer: ( monitorProps: unknown, @@ -67,9 +68,9 @@ export function changeSection(section: string): ChangeSectionAction { interface ChangeThemeFormData { readonly theme: Theme; readonly scheme: Scheme; - readonly dark: boolean; + readonly themeColorPreference: ThemeColorPreference; } -interface ChangeThemeData { +export interface ChangeThemeData { readonly formData: ChangeThemeFormData; } export interface ChangeThemeAction { @@ -77,6 +78,7 @@ export interface ChangeThemeAction { readonly theme: Theme; readonly scheme: Scheme; readonly dark: boolean; + readonly themeColorPreference: ThemeColorPreference; } export interface ApplyMediaFeaturesPreferencesAction { @@ -85,7 +87,21 @@ export interface ApplyMediaFeaturesPreferencesAction { } export function changeTheme(data: ChangeThemeData): ChangeThemeAction { - return { type: CHANGE_THEME, ...data.formData }; + const { themeColorPreference } = data.formData; + let dark: boolean; + + switch (themeColorPreference) { + case 'light': + dark = false; + break; + case 'dark': + dark = true; + break; + default: + dark = prefersDarkColorScheme(); + } + + return { type: CHANGE_THEME, ...data.formData, dark }; } /** diff --git a/packages/redux-devtools-app/src/components/Settings/Themes.tsx b/packages/redux-devtools-app/src/components/Settings/Themes.tsx index 326a22b335..5854b53a79 100644 --- a/packages/redux-devtools-app/src/components/Settings/Themes.tsx +++ b/packages/redux-devtools-app/src/components/Settings/Themes.tsx @@ -4,6 +4,10 @@ import { Container, Form } from '@redux-devtools/ui'; import { listSchemes, listThemes } from '@redux-devtools/ui/lib/utils/theme'; import { changeTheme } from '../../actions'; import { StoreState } from '../../reducers'; +import { + defaultThemeColorPreference, + themeColorPreferences, +} from '../../reducers/theme'; type StateProps = ReturnType; type DispatchProps = ResolveThunks; @@ -15,7 +19,8 @@ export class Themes extends Component { const formData = { theme: theme.theme, scheme: theme.scheme, - dark: !theme.light, + themeColorPreference: + theme.themeColorPreference ?? defaultThemeColorPreference, }; return ( @@ -33,8 +38,10 @@ export class Themes extends Component { type: 'string', enum: listSchemes(), }, - dark: { - type: 'boolean', + themeColorPreference: { + title: 'theme color', + type: 'string', + enum: themeColorPreferences as unknown as string[], }, }, }} diff --git a/packages/redux-devtools-app/src/reducers/theme.ts b/packages/redux-devtools-app/src/reducers/theme.ts index bb3aa8a99f..6148f277cb 100644 --- a/packages/redux-devtools-app/src/reducers/theme.ts +++ b/packages/redux-devtools-app/src/reducers/theme.ts @@ -1,15 +1,25 @@ -import { Scheme, Theme } from '@redux-devtools/ui'; +import { Theme, Scheme } from '@redux-devtools/ui'; import { CHANGE_THEME, APPLY_MEDIA_FEATURES_PREFERENCES, } from '../constants/actionTypes'; import { StoreAction } from '../actions'; +export const defaultThemeColorPreference = 'default'; + +export const themeColorPreferences = [ + defaultThemeColorPreference, + 'light', + 'dark', +] as const; + +export type ThemeColorPreference = typeof themeColorPreferences[number]; + export interface ThemeState { readonly theme: Theme; readonly scheme: Scheme; readonly light: boolean; - readonly latestChangeBy?: string; + readonly themeColorPreference?: ThemeColorPreference; } export default function theme( @@ -17,6 +27,7 @@ export default function theme( theme: 'default' as const, scheme: 'default' as const, light: true, + themeColorPreference: defaultThemeColorPreference, }, action: StoreAction ) { @@ -25,18 +36,20 @@ export default function theme( theme: action.theme, scheme: action.scheme, light: !action.dark, - latestChangeBy: CHANGE_THEME, + themeColorPreference: action.themeColorPreference, }; } if ( action.type === APPLY_MEDIA_FEATURES_PREFERENCES && - state.latestChangeBy !== CHANGE_THEME + (!state.themeColorPreference || + state.themeColorPreference === defaultThemeColorPreference) ) { return { ...state, + themeColorPreference: + state.themeColorPreference ?? defaultThemeColorPreference, light: !action.prefersDarkColorScheme, - latestChangeBy: APPLY_MEDIA_FEATURES_PREFERENCES, }; } From 09f7a0cbccf7b37d034f3a42d962a8e58a099c16 Mon Sep 17 00:00:00 2001 From: FaberVitale Date: Sun, 31 Oct 2021 09:52:09 +0100 Subject: [PATCH 5/5] refactor(rename): system preference option from default to auto --- packages/redux-devtools-app/src/reducers/theme.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/redux-devtools-app/src/reducers/theme.ts b/packages/redux-devtools-app/src/reducers/theme.ts index 6148f277cb..c801dcb8d3 100644 --- a/packages/redux-devtools-app/src/reducers/theme.ts +++ b/packages/redux-devtools-app/src/reducers/theme.ts @@ -5,7 +5,7 @@ import { } from '../constants/actionTypes'; import { StoreAction } from '../actions'; -export const defaultThemeColorPreference = 'default'; +export const defaultThemeColorPreference = 'auto'; export const themeColorPreferences = [ defaultThemeColorPreference,