feat: added settings for minimize to tray instead of closing

This commit is contained in:
injurka 2024-11-11 17:26:44 +04:00 committed by 亢奋猫
parent 422baa848b
commit 3311f8cdef
14 changed files with 111 additions and 2 deletions

View File

@ -3,6 +3,7 @@ import { app, BrowserWindow } from 'electron'
import installExtension, { REDUX_DEVTOOLS } from 'electron-devtools-installer'
import { registerIpc } from './ipc'
import { configManager } from './services/ConfigManager'
import { registerZoomShortcut } from './services/ShortcutService'
import { TrayService } from './services/TrayService'
import { windowService } from './services/WindowService'
@ -64,6 +65,12 @@ if (!app.requestSingleInstanceLock()) {
app.isQuitting = true
})
app.on('window-all-closed', () => {
if (!configManager.isTray()) {
app.quit()
}
})
// In this file you can include the rest of your app"s specific main process
// code. You can also put them in separate files and require them here.
}

View File

@ -35,6 +35,11 @@ export function registerIpc(mainWindow: BrowserWindow, app: Electron.App) {
configManager.setLanguage(language)
})
// tray
ipcMain.handle('app:set-tray', (_, isActive: boolean) => {
configManager.setTray(isActive)
})
// theme
ipcMain.handle('app:set-theme', (_, theme: ThemeMode) => {
configManager.setTheme(theme)

View File

@ -4,6 +4,7 @@ import Store from 'electron-store'
export class ConfigManager {
private store: Store
private subscribers: Map<string, Array<(newValue: any) => void>> = new Map()
constructor() {
this.store = new Store()
@ -24,6 +25,39 @@ export class ConfigManager {
setTheme(theme: ThemeMode) {
this.store.set('theme', theme)
}
isTray(): boolean {
return !!this.store.get('tray', false)
}
setTray(value: boolean) {
this.store.set('tray', value)
this.notifySubscribers('tray', value)
}
subscribe<T>(key: string, callback: (newValue: T) => void) {
if (!this.subscribers.has(key)) {
this.subscribers.set(key, [])
}
this.subscribers.get(key)!.push(callback)
}
unsubscribe<T>(key: string, callback: (newValue: T) => void) {
const subscribers = this.subscribers.get(key)
if (subscribers) {
this.subscribers.set(
key,
subscribers.filter((subscriber) => subscriber !== callback)
)
}
}
private notifySubscribers<T>(key: string, newValue: T) {
const subscribers = this.subscribers.get(key)
if (subscribers) {
subscribers.forEach((subscriber) => subscriber(newValue))
}
}
}
export const configManager = new ConfigManager()

View File

@ -10,7 +10,8 @@ export class TrayService {
private tray: Tray | null = null
constructor() {
this.createTray()
this.updateTray()
this.watchTrayChanges()
}
private createTray() {
@ -69,6 +70,25 @@ export class TrayService {
})
}
private updateTray() {
if (configManager.isTray()) {
this.createTray()
} else {
this.destroyTray()
}
}
private destroyTray() {
if (this.tray) {
this.tray.destroy()
this.tray = null
}
}
private watchTrayChanges() {
configManager.subscribe<boolean>('tray', () => this.updateTray())
}
private quit() {
app.quit()
}

View File

@ -1,4 +1,5 @@
import { is } from '@electron-toolkit/utils'
import { isTilingWindowManager } from '@main/utils/is-tiling-window-manager'
import { app, BrowserWindow, Menu, MenuItem, shell } from 'electron'
import windowStateKeeper from 'electron-window-state'
import { join } from 'path'
@ -166,6 +167,10 @@ export class WindowService {
private setupWindowLifecycleEvents(mainWindow: BrowserWindow) {
mainWindow.on('close', (event) => {
if (!configManager.isTray() && isTilingWindowManager()) {
app.quit()
}
if (!app.isQuitting) {
event.preventDefault()
mainWindow.hide()

View File

@ -0,0 +1,12 @@
function isTilingWindowManager() {
if (process.platform !== 'linux') {
return false
}
const desktopEnv = process.env.XDG_CURRENT_DESKTOP?.toLowerCase()
const tilingSystems = ['hyprland', 'i3', 'sway', 'bspwm', 'dwm', 'awesome', 'qtile', 'herbstluftwm', 'xmonad']
return tilingSystems.some((system) => desktopEnv?.includes(system))
}
export { isTilingWindowManager }

View File

@ -19,6 +19,7 @@ declare global {
openWebsite: (url: string) => void
setProxy: (proxy: string | undefined) => void
setLanguage: (theme: LanguageVarious) => void
setTray: (isActive: boolean) => void
setTheme: (theme: 'light' | 'dark') => void
minApp: (options: { url: string; windowOptions?: Electron.BrowserWindowConstructorOptions }) => void
reload: () => void

View File

@ -9,6 +9,7 @@ const api = {
setProxy: (proxy: string) => ipcRenderer.invoke('app:proxy', proxy),
checkForUpdate: () => ipcRenderer.invoke('app:check-for-update'),
setLanguage: (lang: string) => ipcRenderer.invoke('app:set-language', lang),
setTray: (isActive: boolean) => ipcRenderer.invoke('app:set-tray', isActive),
setTheme: (theme: 'light' | 'dark') => ipcRenderer.invoke('app:set-theme', theme),
openWebsite: (url: string) => ipcRenderer.invoke('open:website', url),
minApp: (url: string) => ipcRenderer.invoke('minapp', url),

View File

@ -4,6 +4,7 @@ import {
setSendMessageShortcut as _setSendMessageShortcut,
setTheme,
setTopicPosition,
setTray,
setWindowStyle
} from '@renderer/store/settings'
import { ThemeMode } from '@renderer/types'
@ -17,6 +18,9 @@ export function useSettings() {
setSendMessageShortcut(shortcut: SendMessageShortcut) {
dispatch(_setSendMessageShortcut(shortcut))
},
setTray(isActive: boolean) {
dispatch(setTray(isActive))
},
setTheme(theme: ThemeMode) {
dispatch(setTheme(theme))
},

View File

@ -336,6 +336,7 @@
"about.license.button": "License",
"about.contact.button": "Email",
"proxy.title": "Proxy Address",
"tray.title": "Minimize to tray instead of closing",
"theme.title": "Theme",
"theme.dark": "Dark",
"theme.light": "Light",

View File

@ -324,6 +324,7 @@
"about.license.button": "查看",
"about.contact.button": "邮件",
"proxy.title": "代理地址",
"tray.title": "最小化到托盘而不是关闭",
"theme.title": "主题",
"theme.dark": "深色主题",
"theme.light": "浅色主题",

View File

@ -324,6 +324,7 @@
"about.license.button": "查看",
"about.contact.button": "郵件",
"proxy.title": "代理地址",
"tray.title": "最小化到系統列而不是關閉",
"theme.title": "主題",
"theme.dark": "深色主題",
"theme.light": "淺色主題",

View File

@ -16,17 +16,24 @@ const GeneralSettings: FC = () => {
const {
language,
proxyUrl: storeProxyUrl,
setTheme,
theme,
setTray,
tray,
windowStyle,
topicPosition,
showTopicTime,
clickAssistantToShowTopic,
setTheme,
setWindowStyle,
setTopicPosition
} = useSettings()
const [proxyUrl, setProxyUrl] = useState<string | undefined>(storeProxyUrl)
const updateTray = (value: boolean) => {
setTray(value)
window.api.setTray(value)
}
const dispatch = useAppDispatch()
const { t } = useTranslation()
@ -121,6 +128,11 @@ const GeneralSettings: FC = () => {
/>
</SettingRow>
<SettingDivider />
<SettingRow>
<SettingRowTitle>{t('settings.tray.title')}</SettingRowTitle>
<Switch checked={tray} onChange={(checked) => updateTray(checked)} />
</SettingRow>
<SettingDivider />
{topicPosition === 'left' && (
<>
<SettingRow style={{ minHeight: 32 }}>

View File

@ -13,6 +13,7 @@ export interface SettingsState {
showMessageDivider: boolean
messageFont: 'system' | 'serif'
showInputEstimatedTokens: boolean
tray: boolean
theme: ThemeMode
windowStyle: 'transparent' | 'opaque'
fontSize: number
@ -99,6 +100,9 @@ const settingsSlice = createSlice({
setShowInputEstimatedTokens: (state, action: PayloadAction<boolean>) => {
state.showInputEstimatedTokens = action.payload
},
setTray: (state, action: PayloadAction<boolean>) => {
state.tray = action.payload
},
setTheme: (state, action: PayloadAction<ThemeMode>) => {
state.theme = action.payload
},
@ -166,6 +170,7 @@ export const {
setShowMessageDivider,
setMessageFont,
setShowInputEstimatedTokens,
setTray,
setTheme,
setFontSize,
setWindowStyle,