UNPKG

@theia/core

Version:

Theia is a cloud & desktop IDE framework implemented in TypeScript.

477 lines • 22.6 kB
"use strict"; // ***************************************************************************** // Copyright (C) 2017 TypeFox and others. // // This program and the accompanying materials are made available under the // terms of the Eclipse Public License v. 2.0 which is available at // http://www.eclipse.org/legal/epl-2.0. // // This Source Code may also be made available under the following Secondary // Licenses when the conditions for such availability set forth in the Eclipse // Public License v. 2.0 are satisfied: GNU General Public License, version 2 // with the GNU Classpath Exception which is available at // https://www.gnu.org/software/classpath/license.html. // // SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 // ***************************************************************************** var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; return c > 3 && r && Object.defineProperty(target, key, r), r; }; var __metadata = (this && this.__metadata) || function (k, v) { if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v); }; var __param = (this && this.__param) || function (paramIndex, decorator) { return function (target, key) { decorator(target, key, paramIndex); } }; Object.defineProperty(exports, "__esModule", { value: true }); exports.CustomTitleWidget = exports.ElectronMenuContribution = exports.CustomTitleWidgetFactory = exports.ElectronMenus = exports.ElectronCommands = void 0; const electron = require("../../../electron-shared/electron"); const electronRemote = require("../../../electron-shared/@electron/remote"); const inversify_1 = require("inversify"); const common_1 = require("../../common"); const browser_1 = require("../../browser"); const electron_main_menu_factory_1 = require("./electron-main-menu-factory"); const frontend_application_state_1 = require("../../browser/frontend-application-state"); const frontend_application_config_provider_1 = require("../../browser/frontend-application-config-provider"); const electron_messages_1 = require("../../electron-common/messaging/electron-messages"); const electron_window_preferences_1 = require("../window/electron-window-preferences"); const browser_menu_plugin_1 = require("../../browser/menu/browser-menu-plugin"); const window_service_1 = require("../../browser/window/window-service"); const window_title_service_1 = require("../../browser/window/window-title-service"); require("../../../src/electron-browser/menu/electron-menu-style.css"); var ElectronCommands; (function (ElectronCommands) { ElectronCommands.TOGGLE_DEVELOPER_TOOLS = common_1.Command.toDefaultLocalizedCommand({ id: 'theia.toggleDevTools', label: 'Toggle Developer Tools' }); ElectronCommands.RELOAD = common_1.Command.toDefaultLocalizedCommand({ id: 'view.reload', label: 'Reload Window' }); ElectronCommands.ZOOM_IN = common_1.Command.toDefaultLocalizedCommand({ id: 'view.zoomIn', label: 'Zoom In' }); ElectronCommands.ZOOM_OUT = common_1.Command.toDefaultLocalizedCommand({ id: 'view.zoomOut', label: 'Zoom Out' }); ElectronCommands.RESET_ZOOM = common_1.Command.toDefaultLocalizedCommand({ id: 'view.resetZoom', label: 'Reset Zoom' }); ElectronCommands.CLOSE_WINDOW = common_1.Command.toDefaultLocalizedCommand({ id: 'close.window', label: 'Close Window' }); ElectronCommands.TOGGLE_FULL_SCREEN = common_1.Command.toDefaultLocalizedCommand({ id: 'workbench.action.toggleFullScreen', category: browser_1.CommonCommands.VIEW_CATEGORY, label: 'Toggle Full Screen' }); })(ElectronCommands = exports.ElectronCommands || (exports.ElectronCommands = {})); var ElectronMenus; (function (ElectronMenus) { ElectronMenus.VIEW_WINDOW = [...browser_1.CommonMenus.VIEW, 'window']; ElectronMenus.VIEW_ZOOM = [...browser_1.CommonMenus.VIEW_APPEARANCE_SUBMENU, '4_appearance_submenu_zoom']; })(ElectronMenus = exports.ElectronMenus || (exports.ElectronMenus = {})); (function (ElectronMenus) { ElectronMenus.HELP_TOGGLE = [...browser_1.CommonMenus.HELP, 'z_toggle']; })(ElectronMenus = exports.ElectronMenus || (exports.ElectronMenus = {})); (function (ElectronMenus) { ElectronMenus.FILE_CLOSE = [...browser_1.CommonMenus.FILE_CLOSE, 'window-close']; })(ElectronMenus = exports.ElectronMenus || (exports.ElectronMenus = {})); exports.CustomTitleWidgetFactory = Symbol('CustomTitleWidgetFactory'); let ElectronMenuContribution = class ElectronMenuContribution extends browser_menu_plugin_1.BrowserMenuBarContribution { constructor(factory) { super(factory); this.factory = factory; this.titleBarStyleChangeFlag = false; } onStart(app) { this.handleTitleBarStyling(app); if (common_1.isOSX) { this.attachWindowFocusListener(app); } // Make sure the application menu is complete, once the frontend application is ready. // https://github.com/theia-ide/theia/issues/5100 let onStateChange = undefined; const stateServiceListener = (state) => { if (state === 'closing_window') { if (!!onStateChange) { onStateChange.dispose(); } } }; onStateChange = this.stateService.onStateChanged(stateServiceListener); this.shell.mainPanel.onDidToggleMaximized(() => { this.handleToggleMaximized(); }); this.shell.bottomPanel.onDidToggleMaximized(() => { this.handleToggleMaximized(); }); this.attachMenuBarVisibilityListener(); } attachWindowFocusListener(app) { // OSX: Recreate the menus when changing windows. // OSX only has one menu bar for all windows, so we need to swap // between them as the user switches windows. const targetWindow = electronRemote.getCurrentWindow(); const callback = () => this.setMenu(app); targetWindow.on('focus', callback); window.addEventListener('unload', () => targetWindow.off('focus', callback)); } attachMenuBarVisibilityListener() { this.preferenceService.onPreferenceChanged(e => { if (e.preferenceName === 'window.menuBarVisibility') { const targetWindow = electronRemote.getCurrentWindow(); this.handleFullScreen(targetWindow, e.newValue); } }); } handleTitleBarStyling(app) { this.hideTopPanel(app); electron.ipcRenderer.on(electron_messages_1.TitleBarStyleAtStartup, (_event, style) => { this.titleBarStyle = style; this.setMenu(app); this.preferenceService.ready.then(() => { this.preferenceService.set('window.titleBarStyle', this.titleBarStyle, browser_1.PreferenceScope.User); }); }); electron.ipcRenderer.send(electron_messages_1.RequestTitleBarStyle); this.preferenceService.ready.then(() => { electronRemote.getCurrentWindow().setMenuBarVisibility(['classic', 'visible'].includes(this.preferenceService.get('window.menuBarVisibility', 'classic'))); }); this.preferenceService.onPreferenceChanged(change => { if (change.preferenceName === 'window.titleBarStyle') { if (this.titleBarStyleChangeFlag && this.titleBarStyle !== change.newValue && electronRemote.getCurrentWindow().isFocused()) { electron.ipcRenderer.send(electron_messages_1.TitleBarStyleChanged, change.newValue); this.handleRequiredRestart(); } this.titleBarStyleChangeFlag = true; } }); } handleToggleMaximized() { const preference = this.preferenceService.get('window.menuBarVisibility'); if (preference === 'classic') { this.factory.setMenuBar(); } } /** * Hides the `theia-top-panel` depending on the selected `titleBarStyle`. * The `theia-top-panel` is used as the container of the main, application menu-bar for the * browser. Native Electron has it's own. * By default, this method is called on application `onStart`. */ hideTopPanel(app) { const itr = app.shell.children(); let child = itr.next(); while (child) { // Top panel for the menu contribution is not required for native Electron title bar. if (child.id === 'theia-top-panel') { child.setHidden(this.titleBarStyle !== 'custom'); break; } else { child = itr.next(); } } } setMenu(app, electronMenu = this.factory.createElectronMenuBar(), electronWindow = electronRemote.getCurrentWindow()) { if (common_1.isOSX) { electronRemote.Menu.setApplicationMenu(electronMenu); } else { this.hideTopPanel(app); if (this.titleBarStyle === 'custom' && !this.menuBar) { this.createCustomTitleBar(app, electronWindow); } // Unix/Windows: Set the per-window menus electronWindow.setMenu(electronMenu); } } createCustomTitleBar(app, electronWindow) { const dragPanel = new browser_1.Widget(); dragPanel.id = 'theia-drag-panel'; app.shell.addWidget(dragPanel, { area: 'top' }); this.appendMenu(app.shell); this.createCustomTitleWidget(app); const controls = document.createElement('div'); controls.id = 'window-controls'; controls.append(this.createControlButton('minimize', () => electronWindow.minimize()), this.createControlButton('maximize', () => electronWindow.maximize()), this.createControlButton('restore', () => electronWindow.unmaximize()), this.createControlButton('close', () => electronWindow.close())); app.shell.topPanel.node.append(controls); this.handleWindowControls(electronWindow); } createCustomTitleWidget(app) { const titleWidget = this.customTitleWidgetFactory(); if (titleWidget) { app.shell.addWidget(titleWidget, { area: 'top' }); } } handleWindowControls(electronWindow) { toggleControlButtons(); electronWindow.on('maximize', toggleControlButtons); electronWindow.on('unmaximize', toggleControlButtons); function toggleControlButtons() { if (electronWindow.isMaximized()) { document.body.classList.add('maximized'); } else { document.body.classList.remove('maximized'); } } } createControlButton(id, handler) { const button = document.createElement('div'); button.id = `${id}-button`; button.className = `control-button ${(0, browser_1.codicon)(`chrome-${id}`)}`; button.addEventListener('click', handler); return button; } async handleRequiredRestart() { const msgNode = document.createElement('div'); const message = document.createElement('p'); message.textContent = common_1.nls.localizeByDefault('A setting has changed that requires a restart to take effect.'); const detail = document.createElement('p'); detail.textContent = common_1.nls.localizeByDefault('Press the restart button to restart {0} and enable the setting.', frontend_application_config_provider_1.FrontendApplicationConfigProvider.get().applicationName); msgNode.append(message, detail); const restart = common_1.nls.localizeByDefault('Restart'); const dialog = new browser_1.ConfirmDialog({ title: restart, msg: msgNode, ok: restart, cancel: browser_1.Dialog.CANCEL }); if (await dialog.open()) { this.windowService.setSafeToShutDown(); electron.ipcRenderer.send(electron_messages_1.Restart); } } registerCommands(registry) { const currentWindow = electronRemote.getCurrentWindow(); registry.registerCommand(ElectronCommands.TOGGLE_DEVELOPER_TOOLS, { execute: () => { const webContent = electronRemote.getCurrentWebContents(); if (!webContent.isDevToolsOpened()) { webContent.openDevTools(); } else { webContent.closeDevTools(); } } }); registry.registerCommand(ElectronCommands.RELOAD, { execute: () => this.windowService.reload() }); registry.registerCommand(ElectronCommands.CLOSE_WINDOW, { execute: () => currentWindow.close() }); registry.registerCommand(ElectronCommands.ZOOM_IN, { execute: () => { const webContents = currentWindow.webContents; // When starting at a level that is not a multiple of 0.5, increment by at most 0.5 to reach the next highest multiple of 0.5. let zoomLevel = (Math.floor(webContents.zoomLevel / electron_window_preferences_1.ZoomLevel.VARIATION) * electron_window_preferences_1.ZoomLevel.VARIATION) + electron_window_preferences_1.ZoomLevel.VARIATION; if (zoomLevel > electron_window_preferences_1.ZoomLevel.MAX) { zoomLevel = electron_window_preferences_1.ZoomLevel.MAX; return; } ; this.preferenceService.set('window.zoomLevel', zoomLevel, browser_1.PreferenceScope.User); } }); registry.registerCommand(ElectronCommands.ZOOM_OUT, { execute: () => { const webContents = currentWindow.webContents; // When starting at a level that is not a multiple of 0.5, decrement by at most 0.5 to reach the next lowest multiple of 0.5. let zoomLevel = (Math.ceil(webContents.zoomLevel / electron_window_preferences_1.ZoomLevel.VARIATION) * electron_window_preferences_1.ZoomLevel.VARIATION) - electron_window_preferences_1.ZoomLevel.VARIATION; if (zoomLevel < electron_window_preferences_1.ZoomLevel.MIN) { zoomLevel = electron_window_preferences_1.ZoomLevel.MIN; return; } ; this.preferenceService.set('window.zoomLevel', zoomLevel, browser_1.PreferenceScope.User); } }); registry.registerCommand(ElectronCommands.RESET_ZOOM, { execute: () => this.preferenceService.set('window.zoomLevel', electron_window_preferences_1.ZoomLevel.DEFAULT, browser_1.PreferenceScope.User) }); registry.registerCommand(ElectronCommands.TOGGLE_FULL_SCREEN, { isEnabled: () => currentWindow.isFullScreenable(), isVisible: () => currentWindow.isFullScreenable(), execute: () => this.toggleFullScreen(currentWindow) }); } registerKeybindings(registry) { registry.registerKeybindings({ command: ElectronCommands.TOGGLE_DEVELOPER_TOOLS.id, keybinding: 'ctrlcmd+alt+i' }, { command: ElectronCommands.RELOAD.id, keybinding: 'ctrlcmd+r' }, { command: ElectronCommands.ZOOM_IN.id, keybinding: 'ctrlcmd+=' }, { command: ElectronCommands.ZOOM_IN.id, keybinding: 'ctrlcmd+add' }, { command: ElectronCommands.ZOOM_OUT.id, keybinding: 'ctrlcmd+subtract' }, { command: ElectronCommands.ZOOM_OUT.id, keybinding: 'ctrlcmd+-' }, { command: ElectronCommands.RESET_ZOOM.id, keybinding: 'ctrlcmd+0' }, { command: ElectronCommands.CLOSE_WINDOW.id, keybinding: (common_1.isOSX ? 'cmd+shift+w' : (common_1.isWindows ? 'ctrl+w' : /* Linux */ 'ctrl+q')) }, { command: ElectronCommands.TOGGLE_FULL_SCREEN.id, keybinding: common_1.isOSX ? 'ctrl+ctrlcmd+f' : 'f11' }); } registerMenus(registry) { registry.registerMenuAction(ElectronMenus.HELP_TOGGLE, { commandId: ElectronCommands.TOGGLE_DEVELOPER_TOOLS.id }); registry.registerMenuAction(ElectronMenus.VIEW_WINDOW, { commandId: ElectronCommands.RELOAD.id, order: 'z0' }); registry.registerMenuAction(ElectronMenus.VIEW_ZOOM, { commandId: ElectronCommands.ZOOM_IN.id, order: 'z1' }); registry.registerMenuAction(ElectronMenus.VIEW_ZOOM, { commandId: ElectronCommands.ZOOM_OUT.id, order: 'z2' }); registry.registerMenuAction(ElectronMenus.VIEW_ZOOM, { commandId: ElectronCommands.RESET_ZOOM.id, order: 'z3' }); registry.registerMenuAction(ElectronMenus.FILE_CLOSE, { commandId: ElectronCommands.CLOSE_WINDOW.id, }); registry.registerMenuAction(browser_1.CommonMenus.VIEW_APPEARANCE_SUBMENU_SCREEN, { commandId: ElectronCommands.TOGGLE_FULL_SCREEN.id, label: common_1.nls.localizeByDefault('Full Screen'), order: '0' }); } toggleFullScreen(currentWindow) { currentWindow.setFullScreen(!currentWindow.isFullScreen()); const menuBarVisibility = this.preferenceService.get('window.menuBarVisibility', 'classic'); this.handleFullScreen(currentWindow, menuBarVisibility); } handleFullScreen(currentWindow, menuBarVisibility) { const shouldShowTop = !currentWindow.isFullScreen() || menuBarVisibility === 'visible'; if (this.titleBarStyle === 'native') { currentWindow.menuBarVisible = shouldShowTop; } else if (shouldShowTop) { this.shell.topPanel.show(); } else { this.shell.topPanel.hide(); } } }; __decorate([ (0, inversify_1.inject)(frontend_application_state_1.FrontendApplicationStateService), __metadata("design:type", frontend_application_state_1.FrontendApplicationStateService) ], ElectronMenuContribution.prototype, "stateService", void 0); __decorate([ (0, inversify_1.inject)(window_service_1.WindowService), __metadata("design:type", Object) ], ElectronMenuContribution.prototype, "windowService", void 0); __decorate([ (0, inversify_1.inject)(exports.CustomTitleWidgetFactory), __metadata("design:type", Function) ], ElectronMenuContribution.prototype, "customTitleWidgetFactory", void 0); ElectronMenuContribution = __decorate([ (0, inversify_1.injectable)(), __param(0, (0, inversify_1.inject)(electron_main_menu_factory_1.ElectronMainMenuFactory)), __metadata("design:paramtypes", [electron_main_menu_factory_1.ElectronMainMenuFactory]) ], ElectronMenuContribution); exports.ElectronMenuContribution = ElectronMenuContribution; let CustomTitleWidget = class CustomTitleWidget extends browser_1.Widget { constructor() { super(); this.id = 'theia-custom-title'; } init() { this.updateTitle(this.windowTitleService.title); this.windowTitleService.onDidChangeTitle(title => { this.updateTitle(title); }); } onResize(msg) { this.adjustTitleToCenter(); super.onResize(msg); } onAfterShow(msg) { this.adjustTitleToCenter(); super.onAfterShow(msg); } updateTitle(title) { this.node.textContent = title; this.adjustTitleToCenter(); } adjustTitleToCenter() { const menubar = this.electronMenuContribution.menuBar; if (menubar) { const titleWidth = this.node.clientWidth; const margin = 16; const leftMarker = menubar.node.offsetLeft + menubar.node.clientWidth + margin; const panelWidth = this.applicationShell.topPanel.node.clientWidth; const controlsWidth = 48 * 3; // Each window button has a width of 48px const rightMarker = panelWidth - controlsWidth - margin; let hidden = false; let relative = false; this.node.style.left = '50%'; // The title has not enough space between the menu and the window controls // So we simply hide it if (rightMarker - leftMarker < titleWidth) { hidden = true; } else if ((panelWidth - titleWidth) / 2 < leftMarker || (panelWidth + titleWidth) / 2 > rightMarker) { // This indicates that the title has either hit the left (menu) or right (window controls) marker relative = true; this.node.style.left = `${leftMarker + (rightMarker - leftMarker - titleWidth) / 2}px`; } this.node.classList.toggle('hidden', hidden); this.node.classList.toggle('relative', relative); } } }; __decorate([ (0, inversify_1.inject)(ElectronMenuContribution), __metadata("design:type", ElectronMenuContribution) ], CustomTitleWidget.prototype, "electronMenuContribution", void 0); __decorate([ (0, inversify_1.inject)(window_title_service_1.WindowTitleService), __metadata("design:type", window_title_service_1.WindowTitleService) ], CustomTitleWidget.prototype, "windowTitleService", void 0); __decorate([ (0, inversify_1.inject)(browser_1.ApplicationShell), __metadata("design:type", browser_1.ApplicationShell) ], CustomTitleWidget.prototype, "applicationShell", void 0); __decorate([ (0, inversify_1.postConstruct)(), __metadata("design:type", Function), __metadata("design:paramtypes", []), __metadata("design:returntype", void 0) ], CustomTitleWidget.prototype, "init", null); CustomTitleWidget = __decorate([ (0, inversify_1.injectable)(), __metadata("design:paramtypes", []) ], CustomTitleWidget); exports.CustomTitleWidget = CustomTitleWidget; //# sourceMappingURL=electron-menu-contribution.js.map