mirror of
https://github.com/hcengineering/platform.git
synced 2026-09-11 12:17:44 +02:00
Launch at login option for Windows Desktop. (#10105)
Signed-off-by: Denis Gladkiy <denis.gladkiy@hardcoreeng.com>
This commit is contained in:
@@ -155,6 +155,19 @@ describe('Settings', () => {
|
||||
systemUnderTest.setMinimizeToTrayEnabled(value)
|
||||
|
||||
expect(systemUnderTest.isMinimizeToTrayEnabled()).toBe(value)
|
||||
expect(systemUnderTest.isAutoLaunchEnabled()).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
describe('setAutoLaunchEnabled', () => {
|
||||
test.each([
|
||||
{ value: true, description: 'stored value is true' },
|
||||
{ value: false, description: 'stored value is false' }
|
||||
])('$description', ({ value }) => {
|
||||
systemUnderTest.setAutoLaunchEnabled(value)
|
||||
|
||||
expect(systemUnderTest.isAutoLaunchEnabled()).toBe(value)
|
||||
expect(systemUnderTest.isMinimizeToTrayEnabled()).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
@@ -15,9 +15,10 @@
|
||||
|
||||
import { BrowserWindow } from 'electron'
|
||||
import { MenuBarAction, CommandLogout, CommandSelectWorkspace, CommandOpenSettings } from '../ui/types'
|
||||
import { TrayController } from './tray'
|
||||
import { OsIntegration } from './osIntegration'
|
||||
import { IpcMessage } from '../ui/ipcMessages'
|
||||
|
||||
export function dispatchMenuBarAction (mainWindow: BrowserWindow | undefined, action: MenuBarAction, tray: TrayController | undefined): void {
|
||||
export function dispatchMenuBarAction (mainWindow: BrowserWindow | undefined, action: MenuBarAction, os: OsIntegration | undefined): void {
|
||||
if (mainWindow == null) {
|
||||
return
|
||||
}
|
||||
@@ -88,9 +89,16 @@ export function dispatchMenuBarAction (mainWindow: BrowserWindow | undefined, ac
|
||||
mainWindow.setFullScreen(!mainWindow.isFullScreen())
|
||||
break
|
||||
case 'toggle-minimize-to-tray': {
|
||||
if (tray != null) {
|
||||
const newSetting = tray.toggleMinimizeToTray()
|
||||
mainWindow.webContents.send('minimize-to-tray-setting-changed', newSetting)
|
||||
if (os != null) {
|
||||
const newSetting = os.getTray().toggleMinimizeToTray()
|
||||
mainWindow.webContents.send(IpcMessage.MinimizeToTraySettingChanged, newSetting)
|
||||
}
|
||||
break
|
||||
}
|
||||
case 'toggle-auto-launch': {
|
||||
if (os != null) {
|
||||
const newSetting = os.toggleAutoLaunch()
|
||||
mainWindow.webContents.send(IpcMessage.AutoLaunchSettingChanged, newSetting)
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
//
|
||||
// Copyright © 2025 Hardcore Engineering Inc.
|
||||
//
|
||||
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License. You may
|
||||
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
//
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import { app } from 'electron'
|
||||
import { TrayController } from './tray'
|
||||
import { Settings } from './settings'
|
||||
|
||||
export class OsIntegration {
|
||||
constructor (
|
||||
private readonly settings: Settings,
|
||||
private readonly tray: TrayController) {
|
||||
}
|
||||
|
||||
getTray (): TrayController {
|
||||
return this.tray
|
||||
}
|
||||
|
||||
toggleAutoLaunch (): boolean {
|
||||
const settings = app.getLoginItemSettings()
|
||||
const previousValue = settings.openAtLogin
|
||||
const newValue = !previousValue
|
||||
settings.openAtLogin = newValue
|
||||
app.setLoginItemSettings(settings)
|
||||
this.settings.setAutoLaunchEnabled(newValue)
|
||||
return newValue
|
||||
}
|
||||
}
|
||||
@@ -19,6 +19,7 @@ import { PackedConfig } from './config'
|
||||
export class Settings {
|
||||
private static readonly SETTINGS_KEY_SERVER = 'server'
|
||||
private static readonly SETTINGS_KEY_MINIMIZE_TO_TRAY = 'minimize-to-tray'
|
||||
private static readonly SETTINGS_KEY_AUTO_LAUNCH = 'auto-launch'
|
||||
private static readonly SETTINGS_KEY_WINDOW_BOUNDS = 'windowBounds'
|
||||
|
||||
private readonly store: Store
|
||||
@@ -41,10 +42,18 @@ export class Settings {
|
||||
return (this.store as any).get(Settings.SETTINGS_KEY_MINIMIZE_TO_TRAY) as boolean ?? false
|
||||
}
|
||||
|
||||
isAutoLaunchEnabled (): boolean {
|
||||
return (this.store as any).get(Settings.SETTINGS_KEY_AUTO_LAUNCH) as boolean ?? false
|
||||
}
|
||||
|
||||
setMinimizeToTrayEnabled (enabled: boolean): void {
|
||||
(this.store as any).set(Settings.SETTINGS_KEY_MINIMIZE_TO_TRAY, enabled)
|
||||
}
|
||||
|
||||
setAutoLaunchEnabled (enabled: boolean): void {
|
||||
(this.store as any).set(Settings.SETTINGS_KEY_AUTO_LAUNCH, enabled)
|
||||
}
|
||||
|
||||
setServerUrl (serverUrl: string): void {
|
||||
const sanitizedUrl: string = Settings.sanitizeUrl(serverUrl)
|
||||
;(this.store as any).set(Settings.SETTINGS_KEY_SERVER, sanitizedUrl)
|
||||
|
||||
+34
-27
@@ -35,6 +35,8 @@ import { readPackedConfig } from './config'
|
||||
import { Settings } from './settings'
|
||||
import { TrayController } from './tray'
|
||||
import { getFileInPublicBundledFolder } from './path'
|
||||
import { OsIntegration } from './osIntegration'
|
||||
import { IpcMessage } from '../ui/ipcMessages'
|
||||
|
||||
let isQuiting = false
|
||||
|
||||
@@ -211,7 +213,7 @@ function runTheApp (): void {
|
||||
|
||||
void (async (): Promise<void> => {
|
||||
await window.loadFile(containerPagePath)
|
||||
window.webContents.send('handle-auth', urlObj.searchParams.get('token'))
|
||||
window.webContents.send(IpcMessage.HandleAuth, urlObj.searchParams.get('token'))
|
||||
})()
|
||||
}
|
||||
})
|
||||
@@ -231,7 +233,7 @@ function runTheApp (): void {
|
||||
url: item.getURL(),
|
||||
savePath: item.getSavePath()
|
||||
}
|
||||
window.webContents.send('handle-download-item', download)
|
||||
window.webContents.send(IpcMessage.HandleDownloadItem, download)
|
||||
}
|
||||
|
||||
notifyDownloadUpdated()
|
||||
@@ -275,7 +277,7 @@ function runTheApp (): void {
|
||||
hookOpenWindow(mainWindow)
|
||||
|
||||
function minimizeToTrayIsOn (): boolean {
|
||||
return settings.isMinimizeToTrayEnabled() && trayController != null
|
||||
return settings.isMinimizeToTrayEnabled() && osIntegration != null
|
||||
}
|
||||
|
||||
mainWindow.on('close', (event: Event) => {
|
||||
@@ -299,7 +301,7 @@ function runTheApp (): void {
|
||||
})
|
||||
|
||||
function sendWindowMaximizedMessage (maximized: boolean): void {
|
||||
mainWindow?.webContents.send('window-state-changed', maximized ? 'maximized' : 'unmaximized')
|
||||
mainWindow?.webContents.send(IpcMessage.WindowStateChanged, maximized ? 'maximized' : 'unmaximized')
|
||||
}
|
||||
|
||||
mainWindow.on('focus', () => {
|
||||
@@ -313,7 +315,7 @@ function runTheApp (): void {
|
||||
|
||||
mainWindow.on('blur', () => {
|
||||
globalShortcut.unregister(CloseTabHotKey)
|
||||
mainWindow?.webContents.send('window-focus-loss')
|
||||
mainWindow?.webContents.send(IpcMessage.WindowFocusLoss)
|
||||
})
|
||||
|
||||
mainWindow.on('maximize', () => {
|
||||
@@ -361,10 +363,11 @@ function runTheApp (): void {
|
||||
}
|
||||
}
|
||||
|
||||
let trayController: TrayController | undefined
|
||||
let osIntegration: OsIntegration | undefined
|
||||
|
||||
void app.whenReady().then(() => {
|
||||
trayController = new TrayController(settings, activateWindow, quitApplication)
|
||||
const trayController = new TrayController(settings, activateWindow, quitApplication)
|
||||
osIntegration = new OsIntegration(settings, trayController)
|
||||
})
|
||||
|
||||
if (isWindows) {
|
||||
@@ -379,40 +382,40 @@ function runTheApp (): void {
|
||||
showSelectAll: false
|
||||
})
|
||||
|
||||
ipcMain.on('set-badge', (_event: any, badge: number) => {
|
||||
ipcMain.on(IpcMessage.SetBadge, (_event: any, badge: number) => {
|
||||
app.dock?.setBadge(badge > 0 ? `${badge}` : '')
|
||||
app.badgeCount = badge
|
||||
|
||||
if (isWindows && winBadge !== undefined) {
|
||||
winBadge.update(badge)
|
||||
}
|
||||
trayController?.updateTrayBadge(badge)
|
||||
osIntegration?.getTray().updateTrayBadge(badge)
|
||||
})
|
||||
|
||||
ipcMain.on('dock-bounce', (_event: any) => {
|
||||
ipcMain.on(IpcMessage.DockBounce, (_event: any) => {
|
||||
app.dock?.bounce('informational')
|
||||
})
|
||||
|
||||
ipcMain.on('send-notification', (_event: any, notificationParams: NotificationParams) => {
|
||||
ipcMain.on(IpcMessage.SendNotification, (_event: any, notificationParams: NotificationParams) => {
|
||||
if (Notification.isSupported()) {
|
||||
const notification = new Notification(notificationParams)
|
||||
|
||||
notification.on('click', () => {
|
||||
mainWindow?.show()
|
||||
mainWindow?.webContents.send('handle-notification-navigation', notificationParams)
|
||||
mainWindow?.webContents.send(IpcMessage.HandleNotificationNavigation, notificationParams)
|
||||
})
|
||||
|
||||
notification.show()
|
||||
}
|
||||
})
|
||||
|
||||
ipcMain.on('set-title', (event: any, title: string) => {
|
||||
ipcMain.on(IpcMessage.SetTitle, (event: any, title: string) => {
|
||||
const webContents = event.sender
|
||||
const window = BrowserWindow.fromWebContents(webContents)
|
||||
window?.setTitle(title)
|
||||
})
|
||||
|
||||
ipcMain.on('set-combined-config', (_event: any, config: Config) => {
|
||||
ipcMain.on(IpcMessage.SetCombinedConfig, (_event: any, config: Config) => {
|
||||
log.info('Config set: ', config)
|
||||
|
||||
setupCookieHandler(config)
|
||||
@@ -444,7 +447,7 @@ function runTheApp (): void {
|
||||
void autoUpdater.checkForUpdatesAndNotify()
|
||||
})
|
||||
|
||||
ipcMain.handle('get-main-config', (_event: any, _path: any) => {
|
||||
ipcMain.handle(IpcMessage.GetMainConfig, (_event: any, _path: any) => {
|
||||
const cfg = {
|
||||
CONFIG_URL: process.env.CONFIG_URL ?? '',
|
||||
FRONT_URL,
|
||||
@@ -454,11 +457,11 @@ function runTheApp (): void {
|
||||
}
|
||||
return cfg
|
||||
})
|
||||
ipcMain.handle('get-host', (_event: any, _path: any) => {
|
||||
ipcMain.handle(IpcMessage.GetHost, (_event: any, _path: any) => {
|
||||
return new URL(FRONT_URL).host
|
||||
})
|
||||
|
||||
ipcMain.on('set-front-cookie', function (event: any, host: string, name: string, value: string) {
|
||||
ipcMain.on(IpcMessage.SetFrontCookie, function (event: any, host: string, name: string, value: string) {
|
||||
const webContents = event.sender
|
||||
const win = BrowserWindow.fromWebContents(webContents)
|
||||
const cv: CookiesSetDetails = {
|
||||
@@ -474,11 +477,11 @@ function runTheApp (): void {
|
||||
void win?.webContents?.session.cookies.set(cv)
|
||||
})
|
||||
|
||||
ipcMain.handle('window-minimize', () => {
|
||||
ipcMain.handle(IpcMessage.WindowMinimize, () => {
|
||||
mainWindow?.minimize()
|
||||
})
|
||||
|
||||
ipcMain.handle('window-maximize', () => {
|
||||
ipcMain.handle(IpcMessage.WindowMaximize, () => {
|
||||
if (mainWindow != null) {
|
||||
if (mainWindow.isMaximized()) {
|
||||
mainWindow.unmaximize()
|
||||
@@ -488,26 +491,26 @@ function runTheApp (): void {
|
||||
}
|
||||
})
|
||||
|
||||
ipcMain.handle('window-close', () => {
|
||||
ipcMain.handle(IpcMessage.WindowClose, () => {
|
||||
mainWindow?.close()
|
||||
})
|
||||
|
||||
ipcMain.handle('get-is-os-using-dark-theme', () => {
|
||||
ipcMain.handle(IpcMessage.GetIsOsUsingDarkTheme, () => {
|
||||
return nativeTheme.shouldUseDarkColors
|
||||
})
|
||||
|
||||
ipcMain.handle('menu-action', async (_event: any, action: MenuBarAction) => {
|
||||
dispatchMenuBarAction(mainWindow, action, trayController)
|
||||
ipcMain.handle(IpcMessage.MenuAction, async (_event: any, action: MenuBarAction) => {
|
||||
dispatchMenuBarAction(mainWindow, action, osIntegration)
|
||||
})
|
||||
|
||||
if (isWindows) {
|
||||
ipcMain.on('rebuild-user-jump-list', (_event: any, spares: JumpListSpares) => {
|
||||
ipcMain.on(IpcMessage.RebuildUserJumpList, (_event: any, spares: JumpListSpares) => {
|
||||
rebuildJumpList(spares)
|
||||
})
|
||||
}
|
||||
|
||||
ipcMain.handle('get-screen-access', () => systemPreferences.getMediaAccessStatus('screen') === 'granted')
|
||||
ipcMain.handle('get-screen-sources', () => {
|
||||
ipcMain.handle(IpcMessage.GetScreenAccess, () => systemPreferences.getMediaAccessStatus('screen') === 'granted')
|
||||
ipcMain.handle(IpcMessage.GetScreenSources, () => {
|
||||
return desktopCapturer.getSources({ types: ['window', 'screen'], fetchWindowIcons: true, thumbnailSize: { width: 225, height: 135 } }).then(async sources => {
|
||||
return sources.map((source: any) => {
|
||||
return {
|
||||
@@ -523,6 +526,10 @@ function runTheApp (): void {
|
||||
return settings.isMinimizeToTrayEnabled()
|
||||
})
|
||||
|
||||
ipcMain.handle(IpcMessage.GetAutoLaunchEnabled, () => {
|
||||
return settings.isAutoLaunchEnabled()
|
||||
})
|
||||
|
||||
async function onReady (): Promise<void> {
|
||||
await createWindow()
|
||||
|
||||
@@ -605,7 +612,7 @@ function runTheApp (): void {
|
||||
return
|
||||
}
|
||||
mainWindow.setProgressBar(percent / 100)
|
||||
mainWindow.webContents.send('handle-update-download-progress', percent)
|
||||
mainWindow.webContents.send(IpcMessage.HandleUpdateDownloadProgress, percent)
|
||||
}
|
||||
|
||||
autoUpdater.on('update-downloaded', (_info: any) => {
|
||||
|
||||
+17
-1
@@ -1,3 +1,18 @@
|
||||
//
|
||||
// Copyright © 2025 Hardcore Engineering Inc.
|
||||
//
|
||||
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License. You may
|
||||
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
//
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import { loginId } from '@hcengineering/login'
|
||||
import { loveId } from '@hcengineering/love'
|
||||
import { timeId } from '@hcengineering/time'
|
||||
@@ -32,6 +47,7 @@ import { ipcMainExposed } from './typesUtils'
|
||||
import { themeStore, ThemeVariant } from '@hcengineering/theme'
|
||||
import type { Application } from '@hcengineering/workbench'
|
||||
import { isAllowedToRole } from '@hcengineering/workbench-resources'
|
||||
import { IpcMessage } from './ipcMessages'
|
||||
|
||||
function currentOsIsWindows (): boolean {
|
||||
return (window as any).windowsPlatform === true
|
||||
@@ -231,7 +247,7 @@ window.addEventListener('DOMContentLoaded', () => {
|
||||
void handleDownloadItem(item)
|
||||
})
|
||||
|
||||
ipcMain.on('start-backup', () => {
|
||||
ipcMain.on(IpcMessage.StartBackup, () => {
|
||||
// We need to obtain current token and endpoint and trigger backup
|
||||
const token = getMetadata(presentation.metadata.Token)
|
||||
const endpoint = getMetadata(presentation.metadata.Endpoint)
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
//
|
||||
// Copyright © 2025 Hardcore Engineering Inc.
|
||||
//
|
||||
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License. You may
|
||||
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
//
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
export const IpcMessage = {
|
||||
AutoLaunchSettingChanged: 'auto-launch-setting-changed',
|
||||
GetAutoLaunchEnabled: 'get-auto-launch-enabled',
|
||||
MinimizeToTraySettingChanged: 'minimize-to-tray-setting-changed',
|
||||
GetMinimizeToTrayEnabled: 'get-minimize-to-tray-enabled',
|
||||
RebuildUserJumpList: 'rebuild-user-jump-list',
|
||||
StartBackup: 'start-backup',
|
||||
CancelBackup: 'cancel-backup',
|
||||
GetScreenSources: 'get-screen-sources',
|
||||
GetScreenAccess: 'get-screen-access',
|
||||
SetFrontCookie: 'set-front-cookie',
|
||||
HandleDownloadItem: 'handle-download-item',
|
||||
SetBadge: 'set-badge',
|
||||
SetTitle: 'set-title',
|
||||
DockBounce: 'dock-bounce',
|
||||
SendNotification: 'send-notification',
|
||||
WindowMinimize: 'window-minimize',
|
||||
WindowMaximize: 'window-maximize',
|
||||
WindowClose: 'window-close',
|
||||
WindowStateChanged: 'window-state-changed',
|
||||
WindowFocusLoss: 'window-focus-loss',
|
||||
GetIsOsUsingDarkTheme: 'get-is-os-using-dark-theme',
|
||||
MenuAction: 'menu-action',
|
||||
GetMainConfig: 'get-main-config',
|
||||
SetCombinedConfig: 'set-combined-config',
|
||||
GetHost: 'get-host',
|
||||
HandleDeepLink: 'handle-deep-link',
|
||||
OnDeepLinkHandler: 'on-deep-link-handler',
|
||||
HandleNotificationNavigation: 'handle-notification-navigation',
|
||||
HandleUpdateDownloadProgress: 'handle-update-download-progress',
|
||||
HandleAuth: 'handle-auth'
|
||||
} as const
|
||||
@@ -1,3 +1,18 @@
|
||||
//
|
||||
// Copyright © 2025 Hardcore Engineering Inc.
|
||||
//
|
||||
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License. You may
|
||||
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
//
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import { formatName, getPersonByPersonId } from '@hcengineering/contact'
|
||||
import { Ref, SortingOrder, TxOperations } from '@hcengineering/core'
|
||||
import notification, {
|
||||
|
||||
@@ -1,6 +1,17 @@
|
||||
//
|
||||
// Copyright © 2023 Hardcore Engineering Inc.
|
||||
//
|
||||
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License. You may
|
||||
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
//
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import {
|
||||
Plugin,
|
||||
|
||||
+50
-33
@@ -1,11 +1,22 @@
|
||||
// preload.js
|
||||
//
|
||||
// Copyright © 2025 Hardcore Engineering Inc.
|
||||
//
|
||||
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License. You may
|
||||
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
//
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import { contextBridge, ipcRenderer } from 'electron'
|
||||
import { BrandingMap, Config, IPCMainExposed, JumpListSpares, MenuBarAction, NotificationParams } from './types'
|
||||
import { IpcMessage } from './ipcMessages'
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export function concatLink (host: string, path: string): string {
|
||||
if (!host.endsWith('/') && !path.startsWith('/')) {
|
||||
return `${host}/${path}`
|
||||
@@ -46,50 +57,50 @@ let configPromise: Promise<Config> | undefined
|
||||
|
||||
const expose: IPCMainExposed = {
|
||||
setBadge: (badge: number) => {
|
||||
ipcRenderer.send('set-badge', badge)
|
||||
ipcRenderer.send(IpcMessage.SetBadge, badge)
|
||||
},
|
||||
setTitle: (title: string) => {
|
||||
ipcRenderer.send('set-title', title)
|
||||
ipcRenderer.send(IpcMessage.SetTitle, title)
|
||||
},
|
||||
dockBounce: () => {
|
||||
ipcRenderer.send('dock-bounce')
|
||||
ipcRenderer.send(IpcMessage.DockBounce)
|
||||
},
|
||||
sendNotification: (notificationParams: NotificationParams) => {
|
||||
ipcRenderer.send('send-notification', notificationParams)
|
||||
ipcRenderer.send(IpcMessage.SendNotification, notificationParams)
|
||||
},
|
||||
|
||||
minimizeWindow: () => {
|
||||
ipcRenderer.invoke('window-minimize')
|
||||
void ipcRenderer.invoke(IpcMessage.WindowMinimize)
|
||||
},
|
||||
|
||||
maximizeWindow: () => {
|
||||
ipcRenderer.invoke('window-maximize')
|
||||
void ipcRenderer.invoke(IpcMessage.WindowMaximize)
|
||||
},
|
||||
|
||||
closeWindow: () => {
|
||||
ipcRenderer.invoke('window-close')
|
||||
void ipcRenderer.invoke(IpcMessage.WindowClose)
|
||||
},
|
||||
|
||||
onWindowStateChange: (callback) => {
|
||||
ipcRenderer.on('window-state-changed', callback)
|
||||
ipcRenderer.on(IpcMessage.WindowStateChanged, callback)
|
||||
},
|
||||
|
||||
onWindowFocusLoss: (callback) => {
|
||||
ipcRenderer.on('window-focus-loss', callback)
|
||||
ipcRenderer.on(IpcMessage.WindowFocusLoss, callback)
|
||||
},
|
||||
|
||||
isOsUsingDarkTheme: async () => {
|
||||
return await ipcRenderer.invoke('get-is-os-using-dark-theme')
|
||||
return await ipcRenderer.invoke(IpcMessage.GetIsOsUsingDarkTheme)
|
||||
},
|
||||
|
||||
executeMenuBarAction: (action: MenuBarAction) => {
|
||||
ipcRenderer.invoke('menu-action', action)
|
||||
void ipcRenderer.invoke(IpcMessage.MenuAction, action)
|
||||
},
|
||||
|
||||
config: async () => {
|
||||
if (configPromise === undefined) {
|
||||
configPromise = new Promise((resolve, reject) => {
|
||||
ipcRenderer.invoke('get-main-config').then(
|
||||
ipcRenderer.invoke(IpcMessage.GetMainConfig).then(
|
||||
async (mainConfig) => {
|
||||
const serverConfig = await loadServerConfig(concatLink(mainConfig.FRONT_URL, mainConfig.CONFIG_URL))
|
||||
const combinedConfig = {
|
||||
@@ -103,7 +114,7 @@ const expose: IPCMainExposed = {
|
||||
VERSION: mainConfig.VERSION
|
||||
}
|
||||
|
||||
ipcRenderer.send('set-combined-config', combinedConfig)
|
||||
ipcRenderer.send(IpcMessage.SetCombinedConfig, combinedConfig)
|
||||
|
||||
resolve(combinedConfig)
|
||||
},
|
||||
@@ -121,7 +132,7 @@ const expose: IPCMainExposed = {
|
||||
const branding: BrandingMap = await (
|
||||
await fetch(cfg.BRANDING_URL ?? concatLink(cfg.FRONT_URL, 'branding.json'), { keepalive: true })
|
||||
).json()
|
||||
const host = await ipcRenderer.invoke('get-host')
|
||||
const host = await ipcRenderer.invoke(IpcMessage.GetHost)
|
||||
return branding[host] ?? {}
|
||||
},
|
||||
on: (event: string, op: (...args: any[]) => void) => {
|
||||
@@ -132,58 +143,64 @@ const expose: IPCMainExposed = {
|
||||
},
|
||||
|
||||
handleDeepLink: (callback) => {
|
||||
ipcRenderer.on('handle-deep-link', (event, value) => {
|
||||
ipcRenderer.on(IpcMessage.HandleDeepLink, (event, value) => {
|
||||
try {
|
||||
if (typeof value === 'string' && value !== '') {
|
||||
callback(value)
|
||||
}
|
||||
} catch (e) {
|
||||
// Just do nothing. Nothing is ok if there is something with URL
|
||||
// Just do nothing. Nothing is ok if there is something with URL.
|
||||
}
|
||||
})
|
||||
ipcRenderer.send('on-deep-link-handler')
|
||||
ipcRenderer.send(IpcMessage.OnDeepLinkHandler)
|
||||
},
|
||||
|
||||
handleNotificationNavigation: (callback) => {
|
||||
ipcRenderer.on('handle-notification-navigation', (event, notificationParams) => {
|
||||
ipcRenderer.on(IpcMessage.HandleNotificationNavigation, (event, notificationParams) => {
|
||||
callback(notificationParams)
|
||||
})
|
||||
},
|
||||
|
||||
handleUpdateDownloadProgress: (callback) => {
|
||||
ipcRenderer.on('handle-update-download-progress', (event, value) => {
|
||||
ipcRenderer.on(IpcMessage.HandleUpdateDownloadProgress, (event, value) => {
|
||||
callback(value)
|
||||
})
|
||||
},
|
||||
|
||||
handleAuth: (callback) => {
|
||||
ipcRenderer.on('handle-auth', (event, value) => {
|
||||
ipcRenderer.on(IpcMessage.HandleAuth, (event, value) => {
|
||||
callback(value)
|
||||
})
|
||||
},
|
||||
|
||||
handleDownloadItem: (callback) => {
|
||||
ipcRenderer.on('handle-download-item', (event, value) => {
|
||||
ipcRenderer.on(IpcMessage.HandleDownloadItem, (event, value) => {
|
||||
callback(value)
|
||||
})
|
||||
},
|
||||
|
||||
async setFrontCookie (host: string, name: string, value: string): Promise<void> {
|
||||
ipcRenderer.send('set-front-cookie', host, name, value)
|
||||
ipcRenderer.send(IpcMessage.SetFrontCookie, host, name, value)
|
||||
},
|
||||
|
||||
getScreenAccess: () => ipcRenderer.invoke('get-screen-access'),
|
||||
getScreenSources: () => ipcRenderer.invoke('get-screen-sources'),
|
||||
cancelBackup: () => { ipcRenderer.send('cancel-backup') },
|
||||
startBackup: (token, endpoint, wsIds) => { ipcRenderer.send('start-backup', token, endpoint, wsIds) },
|
||||
getScreenAccess: () => ipcRenderer.invoke(IpcMessage.GetScreenAccess),
|
||||
getScreenSources: () => ipcRenderer.invoke(IpcMessage.GetScreenSources),
|
||||
cancelBackup: () => { ipcRenderer.send(IpcMessage.CancelBackup) },
|
||||
startBackup: (token, endpoint, wsIds) => { ipcRenderer.send(IpcMessage.StartBackup, token, endpoint, wsIds) },
|
||||
|
||||
rebuildJumpList: (spares: JumpListSpares) => { ipcRenderer.send('rebuild-user-jump-list', spares) },
|
||||
rebuildJumpList: (spares: JumpListSpares) => { ipcRenderer.send(IpcMessage.RebuildUserJumpList, spares) },
|
||||
|
||||
isMinimizeToTrayEnabled: async () => {
|
||||
return await ipcRenderer.invoke('get-minimize-to-tray-enabled')
|
||||
return await ipcRenderer.invoke(IpcMessage.GetMinimizeToTrayEnabled)
|
||||
},
|
||||
onMinimizeToTraySettingChanged: (callback: (enabled: boolean) => void) => {
|
||||
ipcRenderer.on('minimize-to-tray-setting-changed', (_event, enabled: boolean) => { callback(enabled) })
|
||||
ipcRenderer.on(IpcMessage.MinimizeToTraySettingChanged, (_event, enabled: boolean) => { callback(enabled) })
|
||||
},
|
||||
isAutoLaunchEnabled: async () => {
|
||||
return await ipcRenderer.invoke(IpcMessage.GetAutoLaunchEnabled)
|
||||
},
|
||||
onAutoLaunchSettingChanged: (callback: (enabled: boolean) => void) => {
|
||||
ipcRenderer.on(IpcMessage.AutoLaunchSettingChanged, (_event, enabled: boolean) => { callback(enabled) })
|
||||
}
|
||||
}
|
||||
contextBridge.exposeInMainWorld('electron', expose)
|
||||
|
||||
@@ -1,3 +1,18 @@
|
||||
//
|
||||
// Copyright © 2025 Hardcore Engineering Inc.
|
||||
//
|
||||
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License. You may
|
||||
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
//
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import log from 'electron-log'
|
||||
import love from '@hcengineering/love'
|
||||
import { setCustomCreateScreenTracks } from '@hcengineering/love-resources'
|
||||
|
||||
@@ -19,13 +19,19 @@ import { TitleBarMenuState } from './titleBarMenuState'
|
||||
import { ThemeVariant, type ThemeVariantType } from '@hcengineering/theme'
|
||||
|
||||
const ToggleMinimizeToTrayAction: MenuBarAction = 'toggle-minimize-to-tray'
|
||||
const ToggleAutoLaunchAction: MenuBarAction = 'toggle-auto-launch'
|
||||
|
||||
const LabelMinimizeToTrayEnabled = '☑ Minimize to tray'
|
||||
const LabelMinimizeToTrayDisabled = '☐ Minimize to tray'
|
||||
|
||||
const LabelAutoLaunchEnabled = '☑ Launch at Login'
|
||||
const LabelAutoLaunchDisabled = '☐ Launch at Login'
|
||||
|
||||
export async function setupTitleBarMenu (ipcMain: IPCMainExposed, root: HTMLElement): Promise<MenuBar> {
|
||||
const themeManager = new ThemeManager(ThemeVariant.Light)
|
||||
const menuManager = new MenuBarManager(root, await ipcMain.isMinimizeToTrayEnabled())
|
||||
const minimizeToTrayEnabled = await ipcMain.isMinimizeToTrayEnabled()
|
||||
const isAutoLaunchEnabled = await ipcMain.isAutoLaunchEnabled()
|
||||
const menuManager = new MenuBarManager(root, minimizeToTrayEnabled, isAutoLaunchEnabled)
|
||||
|
||||
const menuBar = menuManager.getView()
|
||||
|
||||
@@ -47,6 +53,13 @@ export async function setupTitleBarMenu (ipcMain: IPCMainExposed, root: HTMLElem
|
||||
}
|
||||
})
|
||||
|
||||
ipcMain.onAutoLaunchSettingChanged((enabled) => {
|
||||
const toggle = root.querySelector(`[data-action="${ToggleAutoLaunchAction}"]`)
|
||||
if (toggle != null) {
|
||||
toggle.textContent = enabled ? LabelAutoLaunchEnabled : LabelAutoLaunchDisabled
|
||||
}
|
||||
})
|
||||
|
||||
ipcMain.onWindowStateChange((_event, state) => {
|
||||
const maximizeButton = root.querySelector('#maximize-button')
|
||||
if (maximizeButton != null) {
|
||||
@@ -70,7 +83,7 @@ export class MenuBar {
|
||||
}
|
||||
}
|
||||
|
||||
export function buildHulyApplicationMenu (minimizeToTrayEnabled: boolean): HTMLElement {
|
||||
export function buildHulyApplicationMenu (minimizeToTrayEnabled: boolean, autoLaunchEnabled: boolean): HTMLElement {
|
||||
const menuBuilder = new MenuBuilder()
|
||||
|
||||
const MenuFileIndex = 0
|
||||
@@ -106,9 +119,12 @@ export function buildHulyApplicationMenu (minimizeToTrayEnabled: boolean): HTMLE
|
||||
.addMenuItem(MenuViewIndex, 'Toggle Fullscreen', 'toggle-fullscreen', 'F11', 'l')
|
||||
|
||||
const MenuWindowIndex = 3
|
||||
|
||||
const ToggleMinimizeToTrayLabel = minimizeToTrayEnabled ? LabelMinimizeToTrayEnabled : LabelMinimizeToTrayDisabled
|
||||
menuBuilder.addTopLevelMenu('Window', 'w')
|
||||
const ToggleAutoLaunchLabel = autoLaunchEnabled ? LabelAutoLaunchEnabled : LabelAutoLaunchDisabled
|
||||
menuBuilder.addTopLevelMenu('System', 's')
|
||||
.addMenuItem(MenuWindowIndex, ToggleMinimizeToTrayLabel, 'toggle-minimize-to-tray', undefined, 'm')
|
||||
.addMenuItem(MenuWindowIndex, ToggleAutoLaunchLabel, 'toggle-auto-launch', undefined, 'a')
|
||||
|
||||
return menuBuilder.build()
|
||||
}
|
||||
@@ -298,7 +314,11 @@ class MenuBarManager {
|
||||
private readonly StateStyleKeyboardSelected = 'desktop-app-keyboard-selected'
|
||||
private readonly StateStyleAltModeActive = 'desktop-app-alt-active'
|
||||
|
||||
constructor (private readonly root: HTMLElement, minimizeToTrayEnabled: boolean) {
|
||||
constructor (
|
||||
private readonly root: HTMLElement,
|
||||
minimizeToTrayEnabled: boolean,
|
||||
autoLaunchEnabled: boolean
|
||||
) {
|
||||
this.state = new TitleBarMenuState(
|
||||
() => this.topLevelMenus().length,
|
||||
(topLevelMenuIndex: number) => {
|
||||
@@ -307,7 +327,7 @@ class MenuBarManager {
|
||||
}
|
||||
)
|
||||
|
||||
this.view = buildHulyApplicationMenu(minimizeToTrayEnabled)
|
||||
this.view = buildHulyApplicationMenu(minimizeToTrayEnabled, autoLaunchEnabled)
|
||||
}
|
||||
|
||||
public getView (): HTMLElement {
|
||||
|
||||
+19
-1
@@ -1,3 +1,18 @@
|
||||
//
|
||||
// Copyright © 2025 Hardcore Engineering Inc.
|
||||
//
|
||||
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License. You may
|
||||
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
//
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import { DownloadItem } from '@hcengineering/desktop-downloads'
|
||||
import { ScreenSource } from '@hcengineering/love'
|
||||
import { Plugin } from '@hcengineering/platform'
|
||||
@@ -122,7 +137,8 @@ export const MenuBarActions = [
|
||||
'zoom-out',
|
||||
'restore-size',
|
||||
'toggle-fullscreen',
|
||||
'toggle-minimize-to-tray'] as const
|
||||
'toggle-minimize-to-tray',
|
||||
'toggle-auto-launch'] as const
|
||||
|
||||
export type MenuBarAction = typeof MenuBarActions[number]
|
||||
|
||||
@@ -171,6 +187,8 @@ export interface IPCMainExposed {
|
||||
|
||||
isMinimizeToTrayEnabled: () => Promise<boolean>
|
||||
onMinimizeToTraySettingChanged: (callback: (enabled: boolean) => void) => void
|
||||
isAutoLaunchEnabled: () => Promise<boolean>
|
||||
onAutoLaunchSettingChanged: (callback: (enabled: boolean) => void) => void
|
||||
}
|
||||
|
||||
export type SendCommandDelegate = (cmd: Command, ...args: any[]) => void
|
||||
|
||||
Reference in New Issue
Block a user