feat: added settings for minimize to tray instead of closing
This commit is contained in:
parent
422baa848b
commit
3311f8cdef
@ -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.
|
||||
}
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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()
|
||||
|
||||
@ -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()
|
||||
}
|
||||
|
||||
@ -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()
|
||||
|
||||
12
src/main/utils/is-tiling-window-manager.ts
Normal file
12
src/main/utils/is-tiling-window-manager.ts
Normal 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 }
|
||||
1
src/preload/index.d.ts
vendored
1
src/preload/index.d.ts
vendored
@ -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
|
||||
|
||||
@ -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),
|
||||
|
||||
@ -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))
|
||||
},
|
||||
|
||||
@ -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",
|
||||
|
||||
@ -324,6 +324,7 @@
|
||||
"about.license.button": "查看",
|
||||
"about.contact.button": "邮件",
|
||||
"proxy.title": "代理地址",
|
||||
"tray.title": "最小化到托盘而不是关闭",
|
||||
"theme.title": "主题",
|
||||
"theme.dark": "深色主题",
|
||||
"theme.light": "浅色主题",
|
||||
|
||||
@ -324,6 +324,7 @@
|
||||
"about.license.button": "查看",
|
||||
"about.contact.button": "郵件",
|
||||
"proxy.title": "代理地址",
|
||||
"tray.title": "最小化到系統列而不是關閉",
|
||||
"theme.title": "主題",
|
||||
"theme.dark": "深色主題",
|
||||
"theme.light": "淺色主題",
|
||||
|
||||
@ -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 }}>
|
||||
|
||||
@ -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,
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user