mirror of
https://github.com/hcengineering/platform.git
synced 2026-09-27 03:54:57 +02:00
Signed-off-by: Denis Gladkiy <denis.gladkiy@hardcoreeng.com>
600 lines
20 KiB
TypeScript
600 lines
20 KiB
TypeScript
//
|
|
// 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 { IPCMainExposed, MenuBarAction } from './types'
|
|
import { isMenuBarAction } from './typesUtils'
|
|
import { TitleBarMenuState } from './titleBarMenuState'
|
|
import { 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()
|
|
const minimizeToTrayEnabled = await ipcMain.isMinimizeToTrayEnabled()
|
|
const isAutoLaunchEnabled = await ipcMain.isAutoLaunchEnabled()
|
|
const menuManager = new MenuBarManager(root, minimizeToTrayEnabled, isAutoLaunchEnabled)
|
|
|
|
const menuBar = menuManager.getView()
|
|
|
|
const menuContainer = root.querySelector('.desktop-app-menu-container')
|
|
if (menuContainer != null) {
|
|
const existingMenuBar = menuContainer.querySelector('.desktop-app-menu-bar')
|
|
if (existingMenuBar != null) {
|
|
existingMenuBar.remove()
|
|
}
|
|
menuContainer.appendChild(menuBar)
|
|
}
|
|
|
|
menuManager.attachEventListeners(ipcMain)
|
|
|
|
ipcMain.onMinimizeToTraySettingChanged((enabled) => {
|
|
const toggle = root.querySelector(`[data-action="${ToggleMinimizeToTrayAction}"]`)
|
|
if (toggle != null) {
|
|
toggle.textContent = enabled ? LabelMinimizeToTrayEnabled : LabelMinimizeToTrayDisabled
|
|
}
|
|
})
|
|
|
|
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) {
|
|
if (state === 'maximized') {
|
|
maximizeButton.textContent = '❐'
|
|
} else {
|
|
maximizeButton.textContent = '□'
|
|
}
|
|
}
|
|
})
|
|
|
|
return new MenuBar(themeManager)
|
|
}
|
|
|
|
export class MenuBar {
|
|
private static readonly lastUsedThemeKey = 'last-used-theme'
|
|
|
|
constructor (private readonly theme: ThemeManager) {
|
|
}
|
|
|
|
public setTheme (theme: ThemeVariantType): void {
|
|
localStorage.setItem(MenuBar.lastUsedThemeKey, theme)
|
|
this.theme.setTheme(theme)
|
|
}
|
|
|
|
public lastUsedThemeIsUnknown (): boolean {
|
|
return localStorage.getItem(MenuBar.lastUsedThemeKey) == null
|
|
}
|
|
}
|
|
|
|
export function buildHulyApplicationMenu (minimizeToTrayEnabled: boolean, autoLaunchEnabled: boolean): HTMLElement {
|
|
const menuBuilder = new MenuBuilder()
|
|
|
|
const MenuFileIndex = 0
|
|
menuBuilder.addTopLevelMenu('File', 'f')
|
|
.addMenuItem(MenuFileIndex, 'Settings', 'settings', undefined, 's')
|
|
.addMenuItem(MenuFileIndex, 'Select Workspace', 'select-workspace', undefined, 'w')
|
|
.addMenuItem(MenuFileIndex, 'Logout', 'logout', undefined, 'l')
|
|
.addSeparator(MenuFileIndex)
|
|
.addMenuItem(MenuFileIndex, 'Exit', 'exit', 'Alt+F4', 'x')
|
|
|
|
const MenuEditIndex = 1
|
|
menuBuilder.addTopLevelMenu('Edit', 'e')
|
|
.addMenuItem(MenuEditIndex, 'Undo', 'undo', 'Ctrl+Z', 'u')
|
|
.addMenuItem(MenuEditIndex, 'Redo', 'redo', 'Ctrl+Y', 'r')
|
|
.addSeparator(MenuEditIndex)
|
|
.addMenuItem(MenuEditIndex, 'Cut', 'cut', 'Ctrl+X', 't')
|
|
.addMenuItem(MenuEditIndex, 'Copy', 'copy', 'Ctrl+C', 'c')
|
|
.addMenuItem(MenuEditIndex, 'Paste', 'paste', 'Ctrl+V', 'p')
|
|
.addMenuItem(MenuEditIndex, 'Delete', 'delete', 'Delete', 'd')
|
|
.addSeparator(MenuEditIndex)
|
|
.addMenuItem(MenuEditIndex, 'Select All', 'select-all', 'Ctrl+A', 'a')
|
|
|
|
const MenuViewIndex = 2
|
|
menuBuilder.addTopLevelMenu('View', 'v')
|
|
.addMenuItem(MenuViewIndex, 'Reload', 'reload', 'Ctrl+R', 'r')
|
|
.addMenuItem(MenuViewIndex, 'Force Reload', 'force-reload', 'Ctrl+Shift+R', 'o')
|
|
.addMenuItem(MenuViewIndex, 'Toggle Developer Tools', 'toggle-devtools', 'Ctrl+Shift+I', 'd')
|
|
.addSeparator(MenuViewIndex)
|
|
.addMenuItem(MenuViewIndex, 'Zoom In', 'zoom-in', 'Ctrl+\'+\'', 'i')
|
|
.addMenuItem(MenuViewIndex, 'Zoom Out', 'zoom-out', 'Ctrl+\'-\'', 'u')
|
|
.addMenuItem(MenuViewIndex, 'Actual Size', 'restore-size', 'Ctrl+0', 'a')
|
|
.addSeparator(MenuViewIndex)
|
|
.addMenuItem(MenuViewIndex, 'Toggle Fullscreen', 'toggle-fullscreen', 'F11', 'l')
|
|
|
|
const MenuWindowIndex = 3
|
|
|
|
const ToggleMinimizeToTrayLabel = minimizeToTrayEnabled ? LabelMinimizeToTrayEnabled : LabelMinimizeToTrayDisabled
|
|
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()
|
|
}
|
|
|
|
class ThemeManager {
|
|
private static readonly domThemeKey = 'data-theme'
|
|
|
|
public setTheme (theme: ThemeVariantType): void {
|
|
ThemeManager.setTheme(theme)
|
|
}
|
|
|
|
public static setTheme (theme: ThemeVariantType): void {
|
|
document.body.setAttribute(ThemeManager.domThemeKey, theme)
|
|
}
|
|
}
|
|
|
|
interface MenuItem {
|
|
type: 'item' | 'separator'
|
|
label?: string
|
|
action?: MenuBarAction
|
|
shortcut?: string
|
|
acceleratorChar?: string
|
|
}
|
|
|
|
interface TopLevelMenu {
|
|
label: string
|
|
accelerator: string
|
|
subMenus: MenuItem[]
|
|
}
|
|
|
|
export class MenuBuilder {
|
|
private readonly menus: TopLevelMenu[] = []
|
|
|
|
public addTopLevelMenu (label: string, accelerator: string): this {
|
|
const menu: TopLevelMenu = {
|
|
label,
|
|
accelerator: accelerator.toLowerCase(),
|
|
subMenus: []
|
|
}
|
|
this.menus.push(menu)
|
|
return this
|
|
}
|
|
|
|
public addMenuItem (
|
|
topLevelMenuIndex: number,
|
|
label: string,
|
|
action: MenuBarAction,
|
|
shortcut?: string,
|
|
acceleratorChar: string | undefined = undefined
|
|
): this {
|
|
if (topLevelMenuIndex >= 0 && topLevelMenuIndex < this.menus.length) {
|
|
const item: MenuItem = {
|
|
type: 'item',
|
|
label,
|
|
action,
|
|
shortcut,
|
|
acceleratorChar: (acceleratorChar || label.charAt(0)).toLowerCase()
|
|
}
|
|
this.menus[topLevelMenuIndex].subMenus.push(item)
|
|
}
|
|
return this
|
|
}
|
|
|
|
public addSeparator (topLevelMenuIndex: number): this {
|
|
if (topLevelMenuIndex >= 0 && topLevelMenuIndex < this.menus.length) {
|
|
this.menus[topLevelMenuIndex].subMenus.push({ type: 'separator' })
|
|
}
|
|
return this
|
|
}
|
|
|
|
public build (): HTMLElement {
|
|
const menuBar = document.createElement('ul')
|
|
menuBar.className = 'desktop-app-menu-bar'
|
|
|
|
this.menus.forEach((topLevelMenu) => {
|
|
const topLevelMenuView = document.createElement('li')
|
|
topLevelMenuView.className = 'desktop-app-menu-item'
|
|
|
|
const menuButton = document.createElement('button')
|
|
menuButton.className = 'desktop-app-top-menu-button'
|
|
menuButton.dataset.menu = topLevelMenu.label.toLowerCase()
|
|
menuButton.dataset.accelerator = topLevelMenu.accelerator
|
|
|
|
const acceleratorSpan = document.createElement('span')
|
|
acceleratorSpan.className = 'desktop-app-accelerator'
|
|
acceleratorSpan.dataset.menu = topLevelMenu.label.toLowerCase()
|
|
|
|
const labelText = topLevelMenu.label
|
|
const acceleratorIndex = labelText.toLowerCase().indexOf(topLevelMenu.accelerator)
|
|
|
|
if (acceleratorIndex === 0) {
|
|
acceleratorSpan.textContent = topLevelMenu.accelerator.toUpperCase()
|
|
menuButton.appendChild(acceleratorSpan)
|
|
menuButton.appendChild(document.createTextNode(labelText.substring(1)))
|
|
} else if (acceleratorIndex > 0) {
|
|
acceleratorSpan.textContent = topLevelMenu.accelerator.toLowerCase()
|
|
menuButton.appendChild(document.createTextNode(labelText.substring(0, acceleratorIndex)))
|
|
menuButton.appendChild(acceleratorSpan)
|
|
menuButton.appendChild(document.createTextNode(labelText.substring(acceleratorIndex + 1)))
|
|
} else {
|
|
menuButton.textContent = labelText
|
|
}
|
|
|
|
const dropdown = document.createElement('div')
|
|
dropdown.className = 'desktop-app-dropdown-menu'
|
|
dropdown.id = `${topLevelMenu.label.toLowerCase()}-menu`
|
|
|
|
topLevelMenu.subMenus.forEach(item => {
|
|
if (item.type === 'separator') {
|
|
const separator = document.createElement('div')
|
|
separator.className = 'desktop-app-dropdown-separator'
|
|
dropdown.appendChild(separator)
|
|
} else if (item.type === 'item' && item.label != null) {
|
|
const menuItemButton = document.createElement('button')
|
|
menuItemButton.className = 'desktop-app-dropdown-item'
|
|
menuItemButton.dataset.accelerator = item.acceleratorChar
|
|
menuItemButton.dataset.action = item.action
|
|
|
|
const labelSpan = document.createElement('span')
|
|
|
|
const itemAcceleratorSpan = document.createElement('span')
|
|
itemAcceleratorSpan.className = 'desktop-app-accelerator'
|
|
|
|
if (item.acceleratorChar) {
|
|
const labelParts = this.splitLabelByAccelerator(item.label, item.acceleratorChar)
|
|
|
|
const actualChar = item.label.charAt(
|
|
item.label.toLowerCase().indexOf(item.acceleratorChar.toLowerCase())
|
|
)
|
|
itemAcceleratorSpan.textContent = actualChar
|
|
|
|
if (labelParts.before) {
|
|
labelSpan.appendChild(document.createTextNode(labelParts.before))
|
|
}
|
|
labelSpan.appendChild(itemAcceleratorSpan)
|
|
if (labelParts.after) {
|
|
labelSpan.appendChild(document.createTextNode(labelParts.after))
|
|
}
|
|
}
|
|
|
|
menuItemButton.appendChild(labelSpan)
|
|
|
|
if (item.shortcut) {
|
|
const shortcutSpan = document.createElement('span')
|
|
shortcutSpan.className = 'desktop-app-shortcut'
|
|
shortcutSpan.textContent = item.shortcut
|
|
menuItemButton.appendChild(shortcutSpan)
|
|
}
|
|
|
|
dropdown.appendChild(menuItemButton)
|
|
}
|
|
})
|
|
|
|
topLevelMenuView.appendChild(menuButton)
|
|
topLevelMenuView.appendChild(dropdown)
|
|
menuBar.appendChild(topLevelMenuView)
|
|
})
|
|
|
|
return menuBar
|
|
}
|
|
|
|
private splitLabelByAccelerator (label: string, acceleratorChar: string): { before: string, after: string } {
|
|
const index = label.toLowerCase().indexOf(acceleratorChar.toLowerCase())
|
|
if (index === -1) {
|
|
return { before: label, after: '' }
|
|
}
|
|
return {
|
|
before: label.substring(0, index),
|
|
after: label.substring(index + 1)
|
|
}
|
|
}
|
|
}
|
|
|
|
class MenuBarManager {
|
|
private readonly state: TitleBarMenuState
|
|
private readonly view: HTMLElement
|
|
|
|
private altPressed: boolean = false
|
|
private controlKeysActivated: boolean = false
|
|
|
|
private readonly TopMenuStyle = '.desktop-app-top-menu-button'
|
|
private readonly DropdownMenuStyle = '.desktop-app-dropdown-menu'
|
|
private readonly DropdownItemStyle = '.desktop-app-dropdown-item'
|
|
private readonly MenuItemStyle = '.desktop-app-menu-item'
|
|
|
|
private readonly StateStyleAltMode = 'desktop-app-alt-mode'
|
|
private readonly StateStyleKeyboardSelected = 'desktop-app-keyboard-selected'
|
|
private readonly StateStyleAltModeActive = 'desktop-app-alt-active'
|
|
|
|
constructor (
|
|
private readonly root: HTMLElement,
|
|
minimizeToTrayEnabled: boolean,
|
|
autoLaunchEnabled: boolean
|
|
) {
|
|
this.state = new TitleBarMenuState(
|
|
() => this.topLevelMenus().length,
|
|
(topLevelMenuIndex: number) => {
|
|
const children = this.childrenOfTopLevelMenu(topLevelMenuIndex)
|
|
return children.length
|
|
}
|
|
)
|
|
|
|
this.view = buildHulyApplicationMenu(minimizeToTrayEnabled, autoLaunchEnabled)
|
|
}
|
|
|
|
public getView (): HTMLElement {
|
|
return this.view
|
|
}
|
|
|
|
private topLevelMenus (): NodeListOf<HTMLButtonElement> {
|
|
return this.root.querySelectorAll<HTMLButtonElement>(this.TopMenuStyle)
|
|
}
|
|
|
|
private onButtonClick (id: string, callback: () => void): void {
|
|
const button = this.root.querySelector(`#${id}`)
|
|
if (button) {
|
|
button.addEventListener('click', callback)
|
|
}
|
|
}
|
|
|
|
public attachEventListeners (ipcMain: IPCMainExposed): void {
|
|
this.onButtonClick('minimize-button', () => {
|
|
ipcMain.minimizeWindow()
|
|
})
|
|
|
|
this.onButtonClick('maximize-button', () => {
|
|
ipcMain.maximizeWindow()
|
|
})
|
|
|
|
this.onButtonClick('close-button', () => {
|
|
ipcMain.closeWindow()
|
|
})
|
|
|
|
document.addEventListener('keydown', (e) => { this.handleKeyDown(ipcMain, e) })
|
|
document.addEventListener('keyup', (e) => { this.handleKeyUp(e) })
|
|
|
|
this.topLevelMenus().forEach((button, index) => {
|
|
button.addEventListener('click', (e) => { this.handleTopLevelMenuButtonClick(e, index) })
|
|
})
|
|
|
|
document.addEventListener('click', (e) => { this.handleDocumentClick(e) })
|
|
|
|
document.querySelectorAll<HTMLButtonElement>(this.DropdownItemStyle + '[data-action]').forEach(item => {
|
|
item.addEventListener('click', () => { this.handleMenuButtonClick(ipcMain, item) })
|
|
})
|
|
|
|
ipcMain.onWindowFocusLoss(() => {
|
|
this.state.exitAltMode()
|
|
this.altPressed = false
|
|
this.controlKeysActivated = false
|
|
this.renderState()
|
|
})
|
|
}
|
|
|
|
private renderState (): void {
|
|
if (this.state.isAltModeActive) {
|
|
this.root.classList.add(this.StateStyleAltModeActive)
|
|
} else {
|
|
this.root.classList.remove(this.StateStyleAltModeActive)
|
|
}
|
|
|
|
this.root.querySelectorAll<HTMLElement>(this.DropdownMenuStyle).forEach(menu => {
|
|
menu.style.display = 'none'
|
|
})
|
|
|
|
const topLevelMenus = this.topLevelMenus()
|
|
|
|
topLevelMenus.forEach((button, index) => {
|
|
button.classList.remove(this.StateStyleAltMode)
|
|
|
|
if (index === this.state.FocusedTopLevelMenuIndex) {
|
|
button.classList.add(this.StateStyleAltMode)
|
|
button.focus()
|
|
|
|
if (this.state.isTopLevelMenuExpanded && button.dataset.menu) {
|
|
const menuType = button.dataset.menu
|
|
const dropdown: HTMLElement | null = this.root.querySelector(`#${menuType}-menu`)
|
|
if (dropdown) {
|
|
dropdown.style.display = 'block'
|
|
}
|
|
}
|
|
} else {
|
|
button.blur()
|
|
}
|
|
})
|
|
|
|
if (this.state.FocusedTopLevelMenuIndex != null && this.state.isTopLevelMenuExpanded) {
|
|
const candidates = this.childrenOfTopLevelMenu(this.state.FocusedTopLevelMenuIndex)
|
|
candidates.forEach((menu, index) => {
|
|
if (index === this.state.FocusedChildMenuIndex) {
|
|
menu.classList.add(this.StateStyleKeyboardSelected)
|
|
} else {
|
|
menu.classList.remove(this.StateStyleKeyboardSelected)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
private childrenOfTopLevelMenu (index: number): NodeListOf<HTMLElement> {
|
|
const topLevelMenus = this.topLevelMenus()
|
|
const menuButton = topLevelMenus[index]
|
|
const menuType = menuButton.dataset.menu
|
|
const dropdown = this.root.querySelector(`#${menuType}-menu`)
|
|
if (dropdown) {
|
|
return dropdown.querySelectorAll<HTMLElement>(this.DropdownItemStyle)
|
|
}
|
|
return document.createDocumentFragment().querySelectorAll<HTMLElement>('*')
|
|
}
|
|
|
|
private executeMenuAction (ipcMain: IPCMainExposed, action: MenuBarAction): void {
|
|
try {
|
|
ipcMain.executeMenuBarAction(action)
|
|
} catch (error) {
|
|
console.error('error executing action:', error)
|
|
}
|
|
}
|
|
|
|
private handleKeyDown (ipcMain: IPCMainExposed, e: KeyboardEvent): void {
|
|
if (e.altKey) {
|
|
this.altPressed = true
|
|
}
|
|
|
|
if (e.shiftKey || e.ctrlKey || e.metaKey) {
|
|
if (this.altPressed) {
|
|
this.controlKeysActivated = true
|
|
return
|
|
}
|
|
}
|
|
|
|
if (e.altKey) {
|
|
if (this.state.isAltModeActive) {
|
|
if (this.state.FocusedTopLevelMenuIndex != null) {
|
|
this.state.closeAll()
|
|
this.renderState()
|
|
return
|
|
}
|
|
} else {
|
|
this.state.enterAltMode(null)
|
|
this.renderState()
|
|
}
|
|
}
|
|
|
|
switch (e.key) {
|
|
case 'ArrowLeft':
|
|
this.state.moveFocusHorizontal(-1)
|
|
this.renderState()
|
|
break
|
|
|
|
case 'ArrowRight':
|
|
this.state.moveFocusHorizontal(+1)
|
|
this.renderState()
|
|
break
|
|
|
|
case 'ArrowDown':
|
|
this.state.moveFocusVertical(+1)
|
|
this.renderState()
|
|
break
|
|
|
|
case 'ArrowUp':
|
|
this.state.moveFocusVertical(-1)
|
|
this.renderState()
|
|
break
|
|
}
|
|
|
|
switch (e.key) {
|
|
case 'Escape':
|
|
this.state.defocus()
|
|
this.renderState()
|
|
break
|
|
|
|
case 'Enter':
|
|
case ' ':
|
|
if (this.state.FocusedTopLevelMenuIndex != null) {
|
|
if (this.state.isTopLevelMenuExpanded) {
|
|
if (this.state.FocusedChildMenuIndex != null) {
|
|
const children = this.childrenOfTopLevelMenu(this.state.FocusedTopLevelMenuIndex)
|
|
const focused = children[this.state.FocusedChildMenuIndex]
|
|
this.state.closeAll()
|
|
this.renderState()
|
|
if (focused.dataset.action && isMenuBarAction(focused.dataset.action)) {
|
|
this.executeMenuAction(ipcMain, focused.dataset.action)
|
|
}
|
|
this.renderState()
|
|
}
|
|
}
|
|
}
|
|
break
|
|
|
|
default: {
|
|
const key = e.key.toLowerCase()
|
|
if (!this.state.isAltModeActive) {
|
|
return
|
|
}
|
|
|
|
if (this.state.isTopLevelMenuExpanded && this.state.FocusedTopLevelMenuIndex != null) {
|
|
const children = this.childrenOfTopLevelMenu(this.state.FocusedTopLevelMenuIndex)
|
|
for (let i = 0; i < children.length; i++) {
|
|
if (children[i].dataset.accelerator === key) {
|
|
const action = children[i].dataset.action
|
|
if (action) {
|
|
if (isMenuBarAction(action)) {
|
|
this.executeMenuAction(ipcMain, action)
|
|
}
|
|
this.state.closeAll()
|
|
this.renderState()
|
|
return
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
const menuButtons = this.root.querySelectorAll<HTMLButtonElement>(this.TopMenuStyle + '[data-accelerator]')
|
|
for (let i = 0; i < menuButtons.length; i++) {
|
|
if (menuButtons[i].dataset.accelerator === key) {
|
|
this.state.expandTopLevelMenu(i)
|
|
this.state.focusChildMenu()
|
|
this.renderState()
|
|
return
|
|
}
|
|
}
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
private handleKeyUp (e: KeyboardEvent): void {
|
|
if (e.key === 'Alt') {
|
|
if (this.controlKeysActivated) {
|
|
this.state.exitAltMode()
|
|
this.renderState()
|
|
} else {
|
|
if (this.state.FocusedTopLevelMenuIndex == null) {
|
|
this.state.enterAltMode(0)
|
|
this.renderState()
|
|
}
|
|
}
|
|
this.controlKeysActivated = false
|
|
this.altPressed = false
|
|
}
|
|
}
|
|
|
|
private handleMenuButtonClick (ipcMain: IPCMainExposed, item: HTMLButtonElement): void {
|
|
const action = item.dataset.action
|
|
if (action) {
|
|
if (isMenuBarAction(action)) {
|
|
this.executeMenuAction(ipcMain, action)
|
|
}
|
|
this.state.closeAll()
|
|
this.renderState()
|
|
}
|
|
}
|
|
|
|
private handleTopLevelMenuButtonClick (_e: Event, index: number): void {
|
|
this.state.expandTopLevelMenu(index)
|
|
this.renderState()
|
|
}
|
|
|
|
private handleDocumentClick (e: Event): void {
|
|
const target = e.target as HTMLElement
|
|
if (!target.closest(this.MenuItemStyle)) {
|
|
this.state.closeAll()
|
|
this.renderState()
|
|
}
|
|
}
|
|
}
|