1
0
mirror of https://github.com/lensapp/lens.git synced 2025-05-20 05:10:56 +00:00

Add 'Sync with computer' theme option (#4973)

This commit is contained in:
Alex Andreev 2022-03-09 16:08:24 +03:00 committed by GitHub
parent a4954b9f8d
commit dd5dfb393d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 102 additions and 3 deletions

View File

@ -0,0 +1,8 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
export const setNativeThemeChannel = "theme:set-native-theme";
export const getNativeThemeChannel = "theme:get-native-theme";

View File

@ -29,6 +29,10 @@ jest.mock("electron", () => ({
on: jest.fn(), on: jest.fn(),
handle: jest.fn(), handle: jest.fn(),
}, },
ipcRenderer: {
on: jest.fn(),
invoke: jest.fn(),
},
})); }));
console = new Console(stdout, stderr); console = new Console(stdout, stderr);

View File

@ -56,6 +56,7 @@ import routerInjectable from "./router/router.injectable";
import shellApiRequestInjectable from "./proxy-functions/shell-api-request/shell-api-request.injectable"; import shellApiRequestInjectable from "./proxy-functions/shell-api-request/shell-api-request.injectable";
import userStoreInjectable from "../common/user-store/user-store.injectable"; import userStoreInjectable from "../common/user-store/user-store.injectable";
import trayMenuItemsInjectable from "./tray/tray-menu-items.injectable"; import trayMenuItemsInjectable from "./tray/tray-menu-items.injectable";
import { broadcastNativeThemeOnUpdate } from "./native-theme";
const di = getDi(); const di = getDi();
@ -109,6 +110,8 @@ di.runSetups().then(() => {
} }
} }
broadcastNativeThemeOnUpdate();
app.on("second-instance", (event, argv) => { app.on("second-instance", (event, argv) => {
logger.debug("second-instance message"); logger.debug("second-instance message");

View File

@ -24,6 +24,8 @@ import { onLocationChange, handleWindowAction } from "../../ipc/window";
import { openFilePickingDialogChannel } from "../../../common/ipc/dialog"; import { openFilePickingDialogChannel } from "../../../common/ipc/dialog";
import { showOpenDialog } from "../../ipc/dialog"; import { showOpenDialog } from "../../ipc/dialog";
import { windowActionHandleChannel, windowLocationChangedChannel, windowOpenAppMenuAsContextMenuChannel } from "../../../common/ipc/window"; import { windowActionHandleChannel, windowLocationChangedChannel, windowOpenAppMenuAsContextMenuChannel } from "../../../common/ipc/window";
import { getNativeColorTheme } from "../../native-theme";
import { getNativeThemeChannel } from "../../../common/ipc/native-theme";
interface Dependencies { interface Dependencies {
electronMenuItems: IComputedValue<MenuRegistration[]>; electronMenuItems: IComputedValue<MenuRegistration[]>;
@ -158,4 +160,8 @@ export const initIpcMainHandlers = ({ electronMenuItems, directoryForLensLocalSt
y: 20, y: 20,
}); });
}); });
ipcMainHandle(getNativeThemeChannel, () => {
return getNativeColorTheme();
});
}; };

18
src/main/native-theme.ts Normal file
View File

@ -0,0 +1,18 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { nativeTheme } from "electron";
import { broadcastMessage } from "../common/ipc";
import { setNativeThemeChannel } from "../common/ipc/native-theme";
export function broadcastNativeThemeOnUpdate() {
nativeTheme.on("updated", () => {
broadcastMessage(setNativeThemeChannel, getNativeColorTheme());
});
}
export function getNativeColorTheme() {
return nativeTheme.shouldUseDarkColors ? "dark" : "light";
}

View File

@ -41,6 +41,10 @@ jest.mock("electron", () => ({
on: jest.fn(), on: jest.fn(),
handle: jest.fn(), handle: jest.fn(),
}, },
ipcRenderer: {
on: jest.fn(),
invoke: jest.fn(),
},
})); }));
jest.mock("./hotbar-toggle-menu-item", () => ({ jest.mock("./hotbar-toggle-menu-item", () => ({

View File

@ -45,7 +45,10 @@ const NonInjectedApplication: React.FC<Dependencies> = ({ appPreferenceItems })
<section id="appearance"> <section id="appearance">
<SubTitle title="Theme" /> <SubTitle title="Theme" />
<Select <Select
options={themeStore.themeOptions} options={[
{ label: "Sync with computer", value: "system" },
...themeStore.themeOptions,
]}
value={userStore.colorTheme} value={userStore.colorTheme}
onChange={({ value }) => userStore.colorTheme = value} onChange={({ value }) => userStore.colorTheme = value}
themeName="lens" themeName="lens"

View File

@ -33,6 +33,10 @@ jest.mock("electron", () => ({
on: jest.fn(), on: jest.fn(),
handle: jest.fn(), handle: jest.fn(),
}, },
ipcRenderer: {
on: jest.fn(),
invoke: jest.fn(),
},
})); }));
const initialTabs: DockTab[] = [ const initialTabs: DockTab[] = [

View File

@ -36,6 +36,10 @@ jest.mock("electron", () => ({
on: jest.fn(), on: jest.fn(),
handle: jest.fn(), handle: jest.fn(),
}, },
ipcRenderer: {
on: jest.fn(),
invoke: jest.fn(),
},
})); }));
function mockLogTabViewModel(tabId: TabId, deps: Partial<LogTabViewModelDependencies>): LogTabViewModel { function mockLogTabViewModel(tabId: TabId, deps: Partial<LogTabViewModelDependencies>): LogTabViewModel {

View File

@ -26,6 +26,13 @@ const mockHotbars: { [id: string]: any } = {
}, },
}; };
jest.mock("electron", () => ({
ipcRenderer: {
on: jest.fn(),
invoke: jest.fn(),
},
}));
describe("<HotbarRemoveCommand />", () => { describe("<HotbarRemoveCommand />", () => {
let di: DiContainer; let di: DiContainer;
let render: DiRender; let render: DiRender;

View File

@ -17,6 +17,12 @@ import rendererExtensionsInjectable from "../../../extensions/renderer-extension
import { computed } from "mobx"; import { computed } from "mobx";
import type { LensRendererExtension } from "../../../extensions/lens-renderer-extension"; import type { LensRendererExtension } from "../../../extensions/lens-renderer-extension";
jest.mock("electron", () => ({
ipcRenderer: {
on: jest.fn(),
invoke: jest.fn(),
},
}));
describe("<Select />", () => { describe("<Select />", () => {
let di: DiContainer; let di: DiContainer;

View File

@ -13,6 +13,8 @@ import type { SelectOption } from "./components/select";
import type { MonacoEditorProps } from "./components/monaco-editor"; import type { MonacoEditorProps } from "./components/monaco-editor";
import { defaultTheme } from "../common/vars"; import { defaultTheme } from "../common/vars";
import { camelCase } from "lodash"; import { camelCase } from "lodash";
import { ipcRenderer } from "electron";
import { getNativeThemeChannel, setNativeThemeChannel } from "../common/ipc/native-theme";
export type ThemeId = string; export type ThemeId = string;
@ -34,6 +36,8 @@ export class ThemeStore extends Singleton {
"lens-light": lensLightThemeJson as Theme, "lens-light": lensLightThemeJson as Theme,
}); });
@observable osNativeTheme: "dark" | "light" | undefined;
@computed get activeThemeId(): ThemeId { @computed get activeThemeId(): ThemeId {
return UserStore.getInstance().colorTheme; return UserStore.getInstance().colorTheme;
} }
@ -43,7 +47,7 @@ export class ThemeStore extends Singleton {
} }
@computed get activeTheme(): Theme { @computed get activeTheme(): Theme {
return this.themes.get(this.activeThemeId) ?? this.themes.get(defaultTheme); return this.systemTheme ?? this.themes.get(this.activeThemeId) ?? this.themes.get(defaultTheme);
} }
@computed get terminalColors(): [string, string][] { @computed get terminalColors(): [string, string][] {
@ -72,11 +76,25 @@ export class ThemeStore extends Singleton {
})); }));
} }
@computed get systemTheme() {
if (this.activeThemeId == "system" && this.osNativeTheme) {
return this.themes.get(`lens-${this.osNativeTheme}`);
}
return null;
}
constructor() { constructor() {
super(); super();
makeObservable(this); makeObservable(this);
autoBind(this); autoBind(this);
this.init();
}
async init() {
await this.setNativeTheme();
this.bindNativeThemeUpdateEvent();
// auto-apply active theme // auto-apply active theme
reaction(() => ({ reaction(() => ({
@ -95,12 +113,26 @@ export class ThemeStore extends Singleton {
}); });
} }
bindNativeThemeUpdateEvent() {
ipcRenderer.on(setNativeThemeChannel, (event, theme: "dark" | "light") => {
this.osNativeTheme = theme;
this.applyTheme(theme);
});
}
async setNativeTheme() {
const theme: "dark" | "light" = await ipcRenderer.invoke(getNativeThemeChannel);
this.osNativeTheme = theme;
}
getThemeById(themeId: ThemeId): Theme { getThemeById(themeId: ThemeId): Theme {
return this.themes.get(themeId); return this.themes.get(themeId);
} }
protected applyTheme(themeId: ThemeId) { protected applyTheme(themeId: ThemeId) {
const theme = this.getThemeById(themeId); const theme = this.systemTheme ?? this.getThemeById(themeId);
const colors = Object.entries({ const colors = Object.entries({
...theme.colors, ...theme.colors,
...Object.fromEntries(this.terminalColors), ...Object.fromEntries(this.terminalColors),