Skip to content

Commit

Permalink
Add OS dark mode support (#2868)
Browse files Browse the repository at this point in the history
* Add settings for color scheme detection and themes

Default light and dark themes can still be changed.
For now its studio-light and default for core, and studio-dark and studio-light for designer.

* Add color scheme type and supporting methods

The detection of dark scheme is based on the background color at the moment.
This seems to work pretty well, but is not an ideal solution.
I think themes should at least get to override this.

* Add support for choosing light and dark theme to settings

This adds a checkbox to the theme settings that determines whether we use the OS color scheme.
If we don't (default) everything stays the same as before.
If we do, themes are rendered in two groups. One for the light themes and one for the dark themes. They can be chosen independently. None of this overrides the default theme choice.

* Add padding to the theme settings tab

Themes are still aligned by adding negative margin.
A bit of a hack, open for suggestions.

* Update theme on OS color scheme change

* Replace usages of setTheme with applyColorScheme

This makes sure that we don't override the user's choice.

* Update packages/insomnia-app/app/plugins/misc.js

Co-authored-by: Opender Singh <opender94@gmail.com>

* Remove dark mode heuristic

* Remove unused button value

* Update theme settings design

* Update packages/insomnia-app/app/ui/components/settings/theme.js

Co-authored-by: Opender Singh <opender94@gmail.com>

* Update packages/insomnia-app/app/ui/components/settings/theme.js

Co-authored-by: Opender Singh <opender94@gmail.com>

* Replace object literal lookups

Do not use object literal lookups to make code more readable

* Remove unused parameter

* Disable default theme select when auto detection is enabled

* Fix imports after rebase

* Update packages/insomnia-app/app/ui/components/modals/settings-modal.js

Co-authored-by: Opender Singh <opender94@gmail.com>

* Update packages/insomnia-app/app/ui/components/modals/settings-modal.js

Co-authored-by: Opender Singh <opender94@gmail.com>

* Remove theme header

* Disable hover animation and border on disabled theme buttons

* Clean up double negation in css

Replace :not(:disabled) with :enabled. Not sure what I was thinking there.

* Update index.js

Co-authored-by: Opender Singh <opender94@gmail.com>
Co-authored-by: Opender Singh <opender.singh@konghq.com>
  • Loading branch information
3 people committed Mar 13, 2021
1 parent 7aa56ef commit 565f389
Show file tree
Hide file tree
Showing 10 changed files with 167 additions and 19 deletions.
8 changes: 8 additions & 0 deletions packages/insomnia-app/app/common/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,14 @@ export function getAppDefaultTheme() {
return appConfig().theme;
}

export function getAppDefaultLightTheme() {
return appConfig().lightTheme;
}

export function getAppDefaultDarkTheme() {
return appConfig().darkTheme;
}

export function getAppSynopsis() {
return appConfig().synopsis;
}
Expand Down
14 changes: 13 additions & 1 deletion packages/insomnia-app/app/models/settings.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
// @flow
import type { BaseModel } from './index';
import * as db from '../common/database';
import { getAppDefaultTheme, HttpVersions, UPDATE_CHANNEL_STABLE } from '../common/constants';
import {
getAppDefaultTheme,
getAppDefaultLightTheme,
getAppDefaultDarkTheme,
HttpVersions,
UPDATE_CHANNEL_STABLE,
} from '../common/constants';
import * as hotkeys from '../common/hotkeys';
import type { HttpVersion } from '../common/constants';

Expand Down Expand Up @@ -49,6 +55,9 @@ type BaseSettings = {
proxyEnabled: boolean,
showPasswords: boolean,
theme: string,
autoDetectColorScheme: boolean,
lightTheme: string,
darkTheme: string,
timeout: number,
updateAutomatically: boolean,
updateChannel: string,
Expand Down Expand Up @@ -104,6 +113,9 @@ export function init(): BaseSettings {
proxyEnabled: false,
showPasswords: false,
theme: getAppDefaultTheme(),
autoDetectColorScheme: false,
lightTheme: getAppDefaultLightTheme(),
darkTheme: getAppDefaultDarkTheme(),
timeout: 0,
updateAutomatically: true,
updateChannel: UPDATE_CHANNEL_STABLE,
Expand Down
2 changes: 2 additions & 0 deletions packages/insomnia-app/app/plugins/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,8 @@ export type Theme = {
theme: PluginTheme,
};

export type ColorScheme = 'default' | 'light' | 'dark';

let plugins: ?Array<Plugin> = null;

let ignorePlugins: Array<string> = [];
Expand Down
27 changes: 27 additions & 0 deletions packages/insomnia-app/app/plugins/misc.js
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,33 @@ function wrapStyles(theme: string, selector: string, styles: string) {
].join('\n');
}

export function getColorScheme(settings: Settings): ColorScheme {
if (!settings.autoDetectColorScheme) {
return 'default';
}

if (window.matchMedia(`(prefers-color-scheme: light)`).matches) {
return 'light';
}

if (window.matchMedia(`(prefers-color-scheme: dark)`).matches) {
return 'dark';
}

return 'default';
}

export async function applyColorScheme(settings: Settings): Promise<void> {
const scheme = getColorScheme(settings);
if (scheme === 'light') {
await setTheme(settings.lightTheme);
} else if (scheme === 'dark') {
await setTheme(settings.darkTheme);
} else {
await setTheme(settings.theme);
}
}

export async function setTheme(themeName: string) {
if (!document) {
return;
Expand Down
45 changes: 39 additions & 6 deletions packages/insomnia-app/app/ui/components/modals/settings-modal.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import Theme from '../settings/theme';
import * as models from '../../../models/index';
import { Curl } from 'node-libcurl';
import Tooltip from '../tooltip';
import { setTheme } from '../../../plugins/misc';
import { applyColorScheme } from '../../../plugins/misc';
import * as session from '../../../account/session';
import Account from '../settings/account';
import { showModal } from './index';
Expand Down Expand Up @@ -65,11 +65,37 @@ class SettingsModal extends PureComponent {
this.modal.hide();
}

async _handleChangeTheme(theme, persist = true) {
await setTheme(theme);
async _handleChangeTheme(themeName, colorScheme, persist = true) {
const { settings } = this.props;

let patch;
switch (colorScheme) {
case 'light':
patch = { lightTheme: themeName };
break;
case 'dark':
patch = { darkTheme: themeName };
break;
case 'default':
default:
patch = { theme: themeName };
break;
}

applyColorScheme({ ...settings, ...patch });

if (persist) {
models.settings.update(this.props.settings, { theme });
await models.settings.update(settings, patch);
}
}

async _handleAutoDetectColorSchemeChange(autoDetectColorScheme, persist = true) {
const { settings } = this.props;

applyColorScheme({ ...settings, autoDetectColorScheme });

if (persist) {
models.settings.update(settings, { autoDetectColorScheme });
}
}

Expand Down Expand Up @@ -147,8 +173,15 @@ class SettingsModal extends PureComponent {
handleImportUri={this._handleImportUri}
/>
</TabPanel>
<TabPanel className="react-tabs__tab-panel scrollable">
<Theme handleChangeTheme={this._handleChangeTheme} activeTheme={settings.theme} />
<TabPanel className="react-tabs__tab-panel pad scrollable">
<Theme
handleChangeTheme={this._handleChangeTheme}
activeTheme={settings.theme}
handleAutoDetectColorSchemeChange={this._handleAutoDetectColorSchemeChange}
autoDetectColorScheme={settings.autoDetectColorScheme}
activeLightTheme={settings.lightTheme}
activeDarkTheme={settings.darkTheme}
/>
</TabPanel>
<TabPanel className="react-tabs__tab-panel pad scrollable">
<SettingsShortcuts
Expand Down
70 changes: 64 additions & 6 deletions packages/insomnia-app/app/ui/components/settings/theme.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,19 @@ import * as React from 'react';
import { autoBindMethodsForReact } from 'class-autobind-decorator';
import { AUTOBIND_CFG } from '../../../common/constants';
import Button from '../base/button';
import type { Theme as ThemeType } from '../../../plugins';
import type { Theme as ThemeType, ColorScheme } from '../../../plugins';
import { getThemes } from '../../../plugins';
import HelpTooltip from '../help-tooltip';

const THEMES_PER_ROW = 5;

type Props = {
handleChangeTheme: string => void,
handleChangeTheme: (string, ColorScheme) => void,
activeTheme: string,
handleAutoDetectColorSchemeChange: boolean => void,
autoDetectColorScheme: boolean,
activeLightTheme: string,
activeDarkTheme: string,
};

type State = {
Expand All @@ -36,8 +41,10 @@ class Theme extends React.PureComponent<Props, State> {
}

renderTheme(theme: ThemeType) {
const { handleChangeTheme, activeTheme } = this.props;
const { handleChangeTheme, activeTheme, autoDetectColorScheme } = this.props;

const isActive = activeTheme === theme.theme.name;
const disabled = autoDetectColorScheme;

return (
<div
Expand All @@ -46,8 +53,8 @@ class Theme extends React.PureComponent<Props, State> {
style={{ maxWidth: `${100 / THEMES_PER_ROW}%` }}>
<h2 className="txt-lg">{theme.theme.displayName}</h2>
<Button
onClick={handleChangeTheme}
value={theme.theme.name}
disabled={disabled}
onClick={() => handleChangeTheme(theme.theme.name, 'default')}
className={isActive ? 'active' : ''}>
<svg theme={theme.theme.name} width="100%" height="100%" viewBox="0 0 500 300">
{/*
Expand Down Expand Up @@ -127,8 +134,59 @@ class Theme extends React.PureComponent<Props, State> {
));
}

renderThemeSelect(scheme: 'light' | 'dark') {
const { activeLightTheme, activeDarkTheme, handleChangeTheme } = this.props;
const { themes } = this.state;

const activeColorTheme = scheme === 'light' ? activeLightTheme : activeDarkTheme;

return (
<div className="form-control form-control--outlined">
<label>
Preferred {scheme} color scheme
<HelpTooltip className="space-left">
Lets you choose the color scheme that is used in {scheme} mode.
</HelpTooltip>
<select
value={activeColorTheme}
onChange={e => handleChangeTheme(e.target.value, scheme)}>
{themes.map(theme => (
<option key={theme.theme.name} value={theme.theme.name}>
{theme.theme.displayName}
</option>
))}
</select>
</label>
</div>
);
}

render() {
return <div className="themes pad-top">{this.renderThemeRows()}</div>;
const { autoDetectColorScheme, handleAutoDetectColorSchemeChange } = this.props;

return (
<>
<div className="themes">{this.renderThemeRows()}</div>
<div className="form-control form-control--thin">
<label className="inline-block">
Use OS color scheme
<HelpTooltip className="space-left">
Lets you choose one theme for light mode and one for dark mode.
</HelpTooltip>
<input
type="checkbox"
name="autoDetectColorScheme"
checked={autoDetectColorScheme}
onChange={e => handleAutoDetectColorSchemeChange(e.target.checked)}
/>
</label>
</div>
<div className="form-row">
{this.renderThemeSelect('light')}
{this.renderThemeSelect('dark')}
</div>
</>
);
}
}

Expand Down
6 changes: 5 additions & 1 deletion packages/insomnia-app/app/ui/containers/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -947,7 +947,7 @@ class App extends PureComponent {
async _handleReloadPlugins() {
const { settings } = this.props;
await plugins.reloadPlugins();
await themes.setTheme(settings.theme);
await themes.applyColorScheme(settings);
templating.reload();
console.log('[plugins] reloaded');
}
Expand Down Expand Up @@ -1218,6 +1218,10 @@ class App extends PureComponent {

// Give it a bit before letting the backend know it's ready
setTimeout(() => ipcRenderer.send('window-ready'), 500);

window
.matchMedia('(prefers-color-scheme: dark)')
.addListener(async () => themes.applyColorScheme(this.props.settings));
}

componentWillUnmount() {
Expand Down
8 changes: 5 additions & 3 deletions packages/insomnia-app/app/ui/css/components/themes.less
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
display: flex;
flex-direction: row;
margin-bottom: var(--padding-lg);
margin-left: calc(var(--padding-md) * -1);
margin-right: calc(var(--padding-md) * -1);
}

.themes__theme {
Expand Down Expand Up @@ -63,12 +65,12 @@
overflow: hidden;
box-shadow: 0 0 0 1px var(--hl-sm);

&.active {
&.active:enabled {
box-shadow: 0 0 0 var(--padding-xs) var(--color-surprise);
}

&.active,
&:hover {
&.active:enabled,
&:hover:enabled {
transform: scale(1.05);
}

Expand Down
4 changes: 2 additions & 2 deletions packages/insomnia-app/app/ui/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { init as initStore } from './redux/modules';
import { init as initPlugins } from '../plugins';
import './css/index.less';
import { getAppLongName, isDevelopment } from '../common/constants';
import { setFont, setTheme } from '../plugins/misc';
import { setFont, applyColorScheme } from '../plugins/misc';
import { trackEvent } from '../common/analytics';
import * as styledComponents from 'styled-components';
import { initNewOAuthSession } from '../network/o-auth-2/misc';
Expand All @@ -29,7 +29,7 @@ document.title = getAppLongName();
if (settings.clearOAuth2SessionOnRestart) {
initNewOAuthSession();
}
await setTheme(settings.theme);
await applyColorScheme(settings);
await setFont(settings);

// Create Redux store
Expand Down
2 changes: 2 additions & 0 deletions packages/insomnia-app/config/config.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@
"icon": "https://github.com/kong/insomnia/blob/develop/packages/insomnia-app/app/icons/icon.ico?raw=true",
"changelogUrl": "https://insomnia.rest/changelog",
"theme": "default",
"lightTheme": "studio-light",
"darkTheme": "default",
"main": "main.min.js",
"githubOrg": "Kong",
"githubRepo": "insomnia",
Expand Down

0 comments on commit 565f389

Please sign in to comment.