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:
parent
a4954b9f8d
commit
dd5dfb393d
8
src/common/ipc/native-theme.ts
Normal file
8
src/common/ipc/native-theme.ts
Normal 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";
|
||||||
@ -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);
|
||||||
|
|||||||
@ -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");
|
||||||
|
|
||||||
|
|||||||
@ -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
18
src/main/native-theme.ts
Normal 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";
|
||||||
|
}
|
||||||
@ -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", () => ({
|
||||||
|
|||||||
@ -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"
|
||||||
|
|||||||
@ -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[] = [
|
||||||
|
|||||||
@ -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 {
|
||||||
|
|||||||
@ -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;
|
||||||
|
|||||||
@ -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;
|
||||||
|
|||||||
@ -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),
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user