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 installExtension, { REDUX_DEVTOOLS } from 'electron-devtools-installer'
|
||||||
|
|
||||||
import { registerIpc } from './ipc'
|
import { registerIpc } from './ipc'
|
||||||
|
import { configManager } from './services/ConfigManager'
|
||||||
import { registerZoomShortcut } from './services/ShortcutService'
|
import { registerZoomShortcut } from './services/ShortcutService'
|
||||||
import { TrayService } from './services/TrayService'
|
import { TrayService } from './services/TrayService'
|
||||||
import { windowService } from './services/WindowService'
|
import { windowService } from './services/WindowService'
|
||||||
@ -64,6 +65,12 @@ if (!app.requestSingleInstanceLock()) {
|
|||||||
app.isQuitting = true
|
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
|
// 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.
|
// 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)
|
configManager.setLanguage(language)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// tray
|
||||||
|
ipcMain.handle('app:set-tray', (_, isActive: boolean) => {
|
||||||
|
configManager.setTray(isActive)
|
||||||
|
})
|
||||||
|
|
||||||
// theme
|
// theme
|
||||||
ipcMain.handle('app:set-theme', (_, theme: ThemeMode) => {
|
ipcMain.handle('app:set-theme', (_, theme: ThemeMode) => {
|
||||||
configManager.setTheme(theme)
|
configManager.setTheme(theme)
|
||||||
|
|||||||
@ -4,6 +4,7 @@ import Store from 'electron-store'
|
|||||||
|
|
||||||
export class ConfigManager {
|
export class ConfigManager {
|
||||||
private store: Store
|
private store: Store
|
||||||
|
private subscribers: Map<string, Array<(newValue: any) => void>> = new Map()
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.store = new Store()
|
this.store = new Store()
|
||||||
@ -24,6 +25,39 @@ export class ConfigManager {
|
|||||||
setTheme(theme: ThemeMode) {
|
setTheme(theme: ThemeMode) {
|
||||||
this.store.set('theme', theme)
|
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()
|
export const configManager = new ConfigManager()
|
||||||
|
|||||||
@ -10,7 +10,8 @@ export class TrayService {
|
|||||||
private tray: Tray | null = null
|
private tray: Tray | null = null
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.createTray()
|
this.updateTray()
|
||||||
|
this.watchTrayChanges()
|
||||||
}
|
}
|
||||||
|
|
||||||
private createTray() {
|
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() {
|
private quit() {
|
||||||
app.quit()
|
app.quit()
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
import { is } from '@electron-toolkit/utils'
|
import { is } from '@electron-toolkit/utils'
|
||||||
|
import { isTilingWindowManager } from '@main/utils/is-tiling-window-manager'
|
||||||
import { app, BrowserWindow, Menu, MenuItem, shell } from 'electron'
|
import { app, BrowserWindow, Menu, MenuItem, shell } from 'electron'
|
||||||
import windowStateKeeper from 'electron-window-state'
|
import windowStateKeeper from 'electron-window-state'
|
||||||
import { join } from 'path'
|
import { join } from 'path'
|
||||||
@ -166,6 +167,10 @@ export class WindowService {
|
|||||||
|
|
||||||
private setupWindowLifecycleEvents(mainWindow: BrowserWindow) {
|
private setupWindowLifecycleEvents(mainWindow: BrowserWindow) {
|
||||||
mainWindow.on('close', (event) => {
|
mainWindow.on('close', (event) => {
|
||||||
|
if (!configManager.isTray() && isTilingWindowManager()) {
|
||||||
|
app.quit()
|
||||||
|
}
|
||||||
|
|
||||||
if (!app.isQuitting) {
|
if (!app.isQuitting) {
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
mainWindow.hide()
|
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
|
openWebsite: (url: string) => void
|
||||||
setProxy: (proxy: string | undefined) => void
|
setProxy: (proxy: string | undefined) => void
|
||||||
setLanguage: (theme: LanguageVarious) => void
|
setLanguage: (theme: LanguageVarious) => void
|
||||||
|
setTray: (isActive: boolean) => void
|
||||||
setTheme: (theme: 'light' | 'dark') => void
|
setTheme: (theme: 'light' | 'dark') => void
|
||||||
minApp: (options: { url: string; windowOptions?: Electron.BrowserWindowConstructorOptions }) => void
|
minApp: (options: { url: string; windowOptions?: Electron.BrowserWindowConstructorOptions }) => void
|
||||||
reload: () => void
|
reload: () => void
|
||||||
|
|||||||
@ -9,6 +9,7 @@ const api = {
|
|||||||
setProxy: (proxy: string) => ipcRenderer.invoke('app:proxy', proxy),
|
setProxy: (proxy: string) => ipcRenderer.invoke('app:proxy', proxy),
|
||||||
checkForUpdate: () => ipcRenderer.invoke('app:check-for-update'),
|
checkForUpdate: () => ipcRenderer.invoke('app:check-for-update'),
|
||||||
setLanguage: (lang: string) => ipcRenderer.invoke('app:set-language', lang),
|
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),
|
setTheme: (theme: 'light' | 'dark') => ipcRenderer.invoke('app:set-theme', theme),
|
||||||
openWebsite: (url: string) => ipcRenderer.invoke('open:website', url),
|
openWebsite: (url: string) => ipcRenderer.invoke('open:website', url),
|
||||||
minApp: (url: string) => ipcRenderer.invoke('minapp', url),
|
minApp: (url: string) => ipcRenderer.invoke('minapp', url),
|
||||||
|
|||||||
@ -4,6 +4,7 @@ import {
|
|||||||
setSendMessageShortcut as _setSendMessageShortcut,
|
setSendMessageShortcut as _setSendMessageShortcut,
|
||||||
setTheme,
|
setTheme,
|
||||||
setTopicPosition,
|
setTopicPosition,
|
||||||
|
setTray,
|
||||||
setWindowStyle
|
setWindowStyle
|
||||||
} from '@renderer/store/settings'
|
} from '@renderer/store/settings'
|
||||||
import { ThemeMode } from '@renderer/types'
|
import { ThemeMode } from '@renderer/types'
|
||||||
@ -17,6 +18,9 @@ export function useSettings() {
|
|||||||
setSendMessageShortcut(shortcut: SendMessageShortcut) {
|
setSendMessageShortcut(shortcut: SendMessageShortcut) {
|
||||||
dispatch(_setSendMessageShortcut(shortcut))
|
dispatch(_setSendMessageShortcut(shortcut))
|
||||||
},
|
},
|
||||||
|
setTray(isActive: boolean) {
|
||||||
|
dispatch(setTray(isActive))
|
||||||
|
},
|
||||||
setTheme(theme: ThemeMode) {
|
setTheme(theme: ThemeMode) {
|
||||||
dispatch(setTheme(theme))
|
dispatch(setTheme(theme))
|
||||||
},
|
},
|
||||||
|
|||||||
@ -336,6 +336,7 @@
|
|||||||
"about.license.button": "License",
|
"about.license.button": "License",
|
||||||
"about.contact.button": "Email",
|
"about.contact.button": "Email",
|
||||||
"proxy.title": "Proxy Address",
|
"proxy.title": "Proxy Address",
|
||||||
|
"tray.title": "Minimize to tray instead of closing",
|
||||||
"theme.title": "Theme",
|
"theme.title": "Theme",
|
||||||
"theme.dark": "Dark",
|
"theme.dark": "Dark",
|
||||||
"theme.light": "Light",
|
"theme.light": "Light",
|
||||||
|
|||||||
@ -324,6 +324,7 @@
|
|||||||
"about.license.button": "查看",
|
"about.license.button": "查看",
|
||||||
"about.contact.button": "邮件",
|
"about.contact.button": "邮件",
|
||||||
"proxy.title": "代理地址",
|
"proxy.title": "代理地址",
|
||||||
|
"tray.title": "最小化到托盘而不是关闭",
|
||||||
"theme.title": "主题",
|
"theme.title": "主题",
|
||||||
"theme.dark": "深色主题",
|
"theme.dark": "深色主题",
|
||||||
"theme.light": "浅色主题",
|
"theme.light": "浅色主题",
|
||||||
|
|||||||
@ -324,6 +324,7 @@
|
|||||||
"about.license.button": "查看",
|
"about.license.button": "查看",
|
||||||
"about.contact.button": "郵件",
|
"about.contact.button": "郵件",
|
||||||
"proxy.title": "代理地址",
|
"proxy.title": "代理地址",
|
||||||
|
"tray.title": "最小化到系統列而不是關閉",
|
||||||
"theme.title": "主題",
|
"theme.title": "主題",
|
||||||
"theme.dark": "深色主題",
|
"theme.dark": "深色主題",
|
||||||
"theme.light": "淺色主題",
|
"theme.light": "淺色主題",
|
||||||
|
|||||||
@ -16,17 +16,24 @@ const GeneralSettings: FC = () => {
|
|||||||
const {
|
const {
|
||||||
language,
|
language,
|
||||||
proxyUrl: storeProxyUrl,
|
proxyUrl: storeProxyUrl,
|
||||||
|
setTheme,
|
||||||
theme,
|
theme,
|
||||||
|
setTray,
|
||||||
|
tray,
|
||||||
windowStyle,
|
windowStyle,
|
||||||
topicPosition,
|
topicPosition,
|
||||||
showTopicTime,
|
showTopicTime,
|
||||||
clickAssistantToShowTopic,
|
clickAssistantToShowTopic,
|
||||||
setTheme,
|
|
||||||
setWindowStyle,
|
setWindowStyle,
|
||||||
setTopicPosition
|
setTopicPosition
|
||||||
} = useSettings()
|
} = useSettings()
|
||||||
const [proxyUrl, setProxyUrl] = useState<string | undefined>(storeProxyUrl)
|
const [proxyUrl, setProxyUrl] = useState<string | undefined>(storeProxyUrl)
|
||||||
|
|
||||||
|
const updateTray = (value: boolean) => {
|
||||||
|
setTray(value)
|
||||||
|
window.api.setTray(value)
|
||||||
|
}
|
||||||
|
|
||||||
const dispatch = useAppDispatch()
|
const dispatch = useAppDispatch()
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
|
|
||||||
@ -121,6 +128,11 @@ const GeneralSettings: FC = () => {
|
|||||||
/>
|
/>
|
||||||
</SettingRow>
|
</SettingRow>
|
||||||
<SettingDivider />
|
<SettingDivider />
|
||||||
|
<SettingRow>
|
||||||
|
<SettingRowTitle>{t('settings.tray.title')}</SettingRowTitle>
|
||||||
|
<Switch checked={tray} onChange={(checked) => updateTray(checked)} />
|
||||||
|
</SettingRow>
|
||||||
|
<SettingDivider />
|
||||||
{topicPosition === 'left' && (
|
{topicPosition === 'left' && (
|
||||||
<>
|
<>
|
||||||
<SettingRow style={{ minHeight: 32 }}>
|
<SettingRow style={{ minHeight: 32 }}>
|
||||||
|
|||||||
@ -13,6 +13,7 @@ export interface SettingsState {
|
|||||||
showMessageDivider: boolean
|
showMessageDivider: boolean
|
||||||
messageFont: 'system' | 'serif'
|
messageFont: 'system' | 'serif'
|
||||||
showInputEstimatedTokens: boolean
|
showInputEstimatedTokens: boolean
|
||||||
|
tray: boolean
|
||||||
theme: ThemeMode
|
theme: ThemeMode
|
||||||
windowStyle: 'transparent' | 'opaque'
|
windowStyle: 'transparent' | 'opaque'
|
||||||
fontSize: number
|
fontSize: number
|
||||||
@ -99,6 +100,9 @@ const settingsSlice = createSlice({
|
|||||||
setShowInputEstimatedTokens: (state, action: PayloadAction<boolean>) => {
|
setShowInputEstimatedTokens: (state, action: PayloadAction<boolean>) => {
|
||||||
state.showInputEstimatedTokens = action.payload
|
state.showInputEstimatedTokens = action.payload
|
||||||
},
|
},
|
||||||
|
setTray: (state, action: PayloadAction<boolean>) => {
|
||||||
|
state.tray = action.payload
|
||||||
|
},
|
||||||
setTheme: (state, action: PayloadAction<ThemeMode>) => {
|
setTheme: (state, action: PayloadAction<ThemeMode>) => {
|
||||||
state.theme = action.payload
|
state.theme = action.payload
|
||||||
},
|
},
|
||||||
@ -166,6 +170,7 @@ export const {
|
|||||||
setShowMessageDivider,
|
setShowMessageDivider,
|
||||||
setMessageFont,
|
setMessageFont,
|
||||||
setShowInputEstimatedTokens,
|
setShowInputEstimatedTokens,
|
||||||
|
setTray,
|
||||||
setTheme,
|
setTheme,
|
||||||
setFontSize,
|
setFontSize,
|
||||||
setWindowStyle,
|
setWindowStyle,
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user