feat: 新增主题|多语言配置
This commit is contained in:
7
src/helpers/ipc/context-exposer.ts
Normal file
7
src/helpers/ipc/context-exposer.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import { exposeThemeContext } from "./theme/theme-context";
|
||||
import { exposeWindowContext } from "./window/window-context";
|
||||
|
||||
export default function exposeContexts() {
|
||||
exposeWindowContext();
|
||||
exposeThemeContext();
|
||||
}
|
||||
8
src/helpers/ipc/listeners-register.ts
Normal file
8
src/helpers/ipc/listeners-register.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
import { BrowserWindow } from "electron";
|
||||
import { addThemeEventListeners } from "./theme/theme-listeners";
|
||||
import { addWindowEventListeners } from "./window/window-listeners";
|
||||
|
||||
export default function registerListeners(mainWindow: BrowserWindow) {
|
||||
addWindowEventListeners(mainWindow);
|
||||
addThemeEventListeners();
|
||||
}
|
||||
5
src/helpers/ipc/theme/theme-channels.ts
Normal file
5
src/helpers/ipc/theme/theme-channels.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
export const THEME_MODE_CURRENT_CHANNEL = "theme-mode:current";
|
||||
export const THEME_MODE_TOGGLE_CHANNEL = "theme-mode:toggle";
|
||||
export const THEME_MODE_DARK_CHANNEL = "theme-mode:dark";
|
||||
export const THEME_MODE_LIGHT_CHANNEL = "theme-mode:light";
|
||||
export const THEME_MODE_SYSTEM_CHANNEL = "theme-mode:system";
|
||||
19
src/helpers/ipc/theme/theme-context.ts
Normal file
19
src/helpers/ipc/theme/theme-context.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import {
|
||||
THEME_MODE_CURRENT_CHANNEL,
|
||||
THEME_MODE_DARK_CHANNEL,
|
||||
THEME_MODE_LIGHT_CHANNEL,
|
||||
THEME_MODE_SYSTEM_CHANNEL,
|
||||
THEME_MODE_TOGGLE_CHANNEL,
|
||||
} from "./theme-channels";
|
||||
|
||||
export function exposeThemeContext() {
|
||||
const { contextBridge, ipcRenderer } = window.require("electron");
|
||||
|
||||
contextBridge.exposeInMainWorld("themeMode", {
|
||||
current: () => ipcRenderer.invoke(THEME_MODE_CURRENT_CHANNEL),
|
||||
toggle: () => ipcRenderer.invoke(THEME_MODE_TOGGLE_CHANNEL),
|
||||
dark: () => ipcRenderer.invoke(THEME_MODE_DARK_CHANNEL),
|
||||
light: () => ipcRenderer.invoke(THEME_MODE_LIGHT_CHANNEL),
|
||||
system: () => ipcRenderer.invoke(THEME_MODE_SYSTEM_CHANNEL),
|
||||
});
|
||||
}
|
||||
37
src/helpers/ipc/theme/window-channels.ts
Normal file
37
src/helpers/ipc/theme/window-channels.ts
Normal file
@@ -0,0 +1,37 @@
|
||||
import { nativeTheme } from "electron";
|
||||
import { ipcMain } from "electron";
|
||||
import {
|
||||
THEME_MODE_CURRENT_CHANNEL,
|
||||
THEME_MODE_DARK_CHANNEL,
|
||||
THEME_MODE_LIGHT_CHANNEL,
|
||||
THEME_MODE_SYSTEM_CHANNEL,
|
||||
THEME_MODE_TOGGLE_CHANNEL,
|
||||
} from "./theme-channels";
|
||||
|
||||
export function addThemeEventListeners() {
|
||||
ipcMain.handle(THEME_MODE_CURRENT_CHANNEL, () => nativeTheme.themeSource);
|
||||
|
||||
ipcMain.handle(THEME_MODE_TOGGLE_CHANNEL, () => {
|
||||
if (nativeTheme.shouldUseDarkColors) {
|
||||
nativeTheme.themeSource = "light";
|
||||
} else {
|
||||
nativeTheme.themeSource = "dark";
|
||||
}
|
||||
return nativeTheme.shouldUseDarkColors;
|
||||
});
|
||||
|
||||
ipcMain.handle(
|
||||
THEME_MODE_DARK_CHANNEL,
|
||||
() => (nativeTheme.themeSource = "dark")
|
||||
);
|
||||
|
||||
ipcMain.handle(
|
||||
THEME_MODE_LIGHT_CHANNEL,
|
||||
() => (nativeTheme.themeSource = "light")
|
||||
);
|
||||
|
||||
ipcMain.handle(THEME_MODE_SYSTEM_CHANNEL, () => {
|
||||
nativeTheme.themeSource = "system";
|
||||
return nativeTheme.shouldUseDarkColors;
|
||||
});
|
||||
}
|
||||
3
src/helpers/ipc/window/window-channels.ts
Normal file
3
src/helpers/ipc/window/window-channels.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
export const WIN_MINIMIZE_CHANNEL = "window:minimize";
|
||||
export const WIN_MAXIMIZE_CHANNEL = "window:maximize";
|
||||
export const WIN_CLOSE_CHANNEL = "window:close";
|
||||
15
src/helpers/ipc/window/window-context.ts
Normal file
15
src/helpers/ipc/window/window-context.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import {
|
||||
WIN_MINIMIZE_CHANNEL,
|
||||
WIN_MAXIMIZE_CHANNEL,
|
||||
WIN_CLOSE_CHANNEL,
|
||||
} from "./window-channels";
|
||||
|
||||
export function exposeWindowContext() {
|
||||
const { contextBridge, ipcRenderer } = window.require("electron");
|
||||
|
||||
contextBridge.exposeInMainWorld("electronWindow", {
|
||||
minimize: () => ipcRenderer.invoke(WIN_MINIMIZE_CHANNEL),
|
||||
maximize: () => ipcRenderer.invoke(WIN_MAXIMIZE_CHANNEL),
|
||||
close: () => ipcRenderer.invoke(WIN_CLOSE_CHANNEL),
|
||||
});
|
||||
}
|
||||
22
src/helpers/ipc/window/window-listeners.ts
Normal file
22
src/helpers/ipc/window/window-listeners.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import { BrowserWindow, ipcMain } from "electron";
|
||||
import {
|
||||
WIN_CLOSE_CHANNEL,
|
||||
WIN_MAXIMIZE_CHANNEL,
|
||||
WIN_MINIMIZE_CHANNEL,
|
||||
} from "./window-channels";
|
||||
|
||||
export function addWindowEventListeners(mainWindow: BrowserWindow) {
|
||||
ipcMain.handle(WIN_MINIMIZE_CHANNEL, () => {
|
||||
mainWindow.minimize();
|
||||
});
|
||||
ipcMain.handle(WIN_MAXIMIZE_CHANNEL, () => {
|
||||
if (mainWindow.isMaximized()) {
|
||||
mainWindow.unmaximize();
|
||||
} else {
|
||||
mainWindow.maximize();
|
||||
}
|
||||
});
|
||||
ipcMain.handle(WIN_CLOSE_CHANNEL, () => {
|
||||
mainWindow.close();
|
||||
});
|
||||
}
|
||||
19
src/helpers/language_helper.ts
Normal file
19
src/helpers/language_helper.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import type { i18n } from "i18next";
|
||||
|
||||
const languageLocalStorageKey = "lang";
|
||||
|
||||
export function setAppLanguage(lang: string, i18n: i18n) {
|
||||
localStorage.setItem(languageLocalStorageKey, lang);
|
||||
i18n.changeLanguage(lang);
|
||||
document.documentElement.lang = lang;
|
||||
}
|
||||
|
||||
export function updateAppLanguage(i18n: i18n) {
|
||||
const localLang = localStorage.getItem(languageLocalStorageKey);
|
||||
if (!localLang) {
|
||||
return;
|
||||
}
|
||||
|
||||
i18n.changeLanguage(localLang);
|
||||
document.documentElement.lang = localLang;
|
||||
}
|
||||
64
src/helpers/theme_helpers.ts
Normal file
64
src/helpers/theme_helpers.ts
Normal file
@@ -0,0 +1,64 @@
|
||||
import { ThemeMode } from "@types/theme-mode";
|
||||
|
||||
const THEME_KEY = "theme";
|
||||
|
||||
export interface ThemePreferences {
|
||||
system: ThemeMode;
|
||||
local: ThemeMode | null;
|
||||
}
|
||||
|
||||
export async function getCurrentTheme(): Promise<ThemePreferences> {
|
||||
const currentTheme = await window.themeMode.current();
|
||||
const localTheme = localStorage.getItem(THEME_KEY) as ThemeMode | null;
|
||||
|
||||
return {
|
||||
system: currentTheme,
|
||||
local: localTheme,
|
||||
};
|
||||
}
|
||||
|
||||
export async function setTheme(newTheme: ThemeMode) {
|
||||
switch (newTheme) {
|
||||
case "dark":
|
||||
await window.themeMode.dark();
|
||||
updateDocumentTheme(true);
|
||||
break;
|
||||
case "light":
|
||||
await window.themeMode.light();
|
||||
updateDocumentTheme(false);
|
||||
break;
|
||||
case "system": {
|
||||
const isDarkMode = await window.themeMode.system();
|
||||
updateDocumentTheme(isDarkMode);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
localStorage.setItem(THEME_KEY, newTheme);
|
||||
}
|
||||
|
||||
export async function toggleTheme() {
|
||||
const isDarkMode = await window.themeMode.toggle();
|
||||
const newTheme = isDarkMode ? "dark" : "light";
|
||||
|
||||
updateDocumentTheme(isDarkMode);
|
||||
localStorage.setItem(THEME_KEY, newTheme);
|
||||
}
|
||||
|
||||
export async function syncThemeWithLocal() {
|
||||
const { local } = await getCurrentTheme();
|
||||
if (!local) {
|
||||
setTheme("system");
|
||||
return;
|
||||
}
|
||||
|
||||
await setTheme(local);
|
||||
}
|
||||
|
||||
function updateDocumentTheme(isDarkMode: boolean) {
|
||||
if (!isDarkMode) {
|
||||
document.documentElement.classList.remove("dark");
|
||||
} else {
|
||||
document.documentElement.classList.add("dark");
|
||||
}
|
||||
}
|
||||
11
src/helpers/window_helpers.ts
Normal file
11
src/helpers/window_helpers.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
export async function minimizeWindow() {
|
||||
await window.electronWindow.minimize();
|
||||
}
|
||||
|
||||
export async function maximizeWindow() {
|
||||
await window.electronWindow.maximize();
|
||||
}
|
||||
|
||||
export async function closeWindow() {
|
||||
await window.electronWindow.close();
|
||||
}
|
||||
@@ -1,11 +1,12 @@
|
||||
import { app, BrowserWindow } from "electron";
|
||||
import registerListeners from "./helpers/ipc/listeners-register";
|
||||
import path from "node:path";
|
||||
import started from "electron-squirrel-startup";
|
||||
// import started from "electron-squirrel-startup";
|
||||
|
||||
// Handle creating/removing shortcuts on Windows when installing/uninstalling.
|
||||
if (started) {
|
||||
app.quit();
|
||||
}
|
||||
// if (started) {
|
||||
// app.quit();
|
||||
// }
|
||||
|
||||
const createWindow = () => {
|
||||
// Create the browser window.
|
||||
|
||||
25
src/types.d.ts
vendored
Normal file
25
src/types.d.ts
vendored
Normal file
@@ -0,0 +1,25 @@
|
||||
// This allows TypeScript to pick up the magic constants that's auto-generated by Forge's Vite
|
||||
// plugin that tells the Electron app where to look for the Vite-bundled app code (depending on
|
||||
// whether you're running in development or production).
|
||||
declare const MAIN_WINDOW_VITE_DEV_SERVER_URL: string;
|
||||
declare const MAIN_WINDOW_VITE_NAME: string;
|
||||
|
||||
// Preload types
|
||||
interface ThemeModeContext {
|
||||
toggle: () => Promise<boolean>;
|
||||
dark: () => Promise<void>;
|
||||
light: () => Promise<void>;
|
||||
system: () => Promise<boolean>;
|
||||
current: () => Promise<"dark" | "light" | "system">;
|
||||
}
|
||||
|
||||
interface ElectronWindow {
|
||||
minimize: () => Promise<void>;
|
||||
maximize: () => Promise<void>;
|
||||
close: () => Promise<void>;
|
||||
}
|
||||
|
||||
declare interface Window {
|
||||
themeMode: ThemeModeContext;
|
||||
electronWindow: ElectronWindow;
|
||||
}
|
||||
1
src/types/theme-mode.ts
Normal file
1
src/types/theme-mode.ts
Normal file
@@ -0,0 +1 @@
|
||||
export type ThemeMode = "dark" | "light" | "system";
|
||||
@@ -18,7 +18,8 @@
|
||||
"@/*": ["./src/*"],
|
||||
"@stores/*": ["./src/stores/*"],
|
||||
"@utils/*": ["./src/utils/*"],
|
||||
"@api/*": ["./src/api/*"]
|
||||
"@api/*": ["./src/api/*"],
|
||||
"@types/*": ["./src/types/*"]
|
||||
},
|
||||
"outDir": "dist",
|
||||
"moduleResolution": "bundler",
|
||||
|
||||
@@ -9,6 +9,7 @@ export default defineConfig({
|
||||
"@stores": resolve(__dirname, "./src/stores"),
|
||||
"@utils": resolve(__dirname, "./src/utils"),
|
||||
"@api": resolve(__dirname, "./src/api"),
|
||||
"@types": resolve(__dirname, "./src/types"),
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
@@ -17,6 +17,7 @@ export default defineConfig({
|
||||
"@stores": resolve(__dirname, "./src/stores"),
|
||||
"@utils": resolve(__dirname, "./src/utils"),
|
||||
"@api": resolve(__dirname, "./src/api"),
|
||||
"@types": resolve(__dirname, "./src/types"),
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user