@theia/core
Version:
Theia is a cloud & desktop IDE framework implemented in TypeScript.
477 lines • 22.6 kB
JavaScript
// *****************************************************************************
// 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
;