UNPKG

22.2 kBJavaScriptView Raw
1"use strict";
2// *****************************************************************************
3// Copyright (C) 2017 TypeFox and others.
4//
5// This program and the accompanying materials are made available under the
6// terms of the Eclipse Public License v. 2.0 which is available at
7// http://www.eclipse.org/legal/epl-2.0.
8//
9// This Source Code may also be made available under the following Secondary
10// Licenses when the conditions for such availability set forth in the Eclipse
11// Public License v. 2.0 are satisfied: GNU General Public License, version 2
12// with the GNU Classpath Exception which is available at
13// https://www.gnu.org/software/classpath/license.html.
14//
15// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
16// *****************************************************************************
17var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
18 var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
19 if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
20 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;
21 return c > 3 && r && Object.defineProperty(target, key, r), r;
22};
23var __metadata = (this && this.__metadata) || function (k, v) {
24 if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
25};
26var __param = (this && this.__param) || function (paramIndex, decorator) {
27 return function (target, key) { decorator(target, key, paramIndex); }
28};
29Object.defineProperty(exports, "__esModule", { value: true });
30exports.CustomTitleWidget = exports.ElectronMenuContribution = exports.CustomTitleWidgetFactory = exports.ElectronMenus = exports.ElectronCommands = void 0;
31const inversify_1 = require("inversify");
32const common_1 = require("../../common");
33const browser_1 = require("../../browser");
34const electron_main_menu_factory_1 = require("./electron-main-menu-factory");
35const frontend_application_state_1 = require("../../browser/frontend-application-state");
36const frontend_application_config_provider_1 = require("../../browser/frontend-application-config-provider");
37const electron_window_preferences_1 = require("../window/electron-window-preferences");
38const browser_menu_plugin_1 = require("../../browser/menu/browser-menu-plugin");
39const window_service_1 = require("../../browser/window/window-service");
40const window_title_service_1 = require("../../browser/window/window-title-service");
41require("../../../src/electron-browser/menu/electron-menu-style.css");
42const theming_1 = require("../../browser/theming");
43var ElectronCommands;
44(function (ElectronCommands) {
45 ElectronCommands.TOGGLE_DEVELOPER_TOOLS = common_1.Command.toDefaultLocalizedCommand({
46 id: 'theia.toggleDevTools',
47 label: 'Toggle Developer Tools'
48 });
49 ElectronCommands.RELOAD = common_1.Command.toDefaultLocalizedCommand({
50 id: 'view.reload',
51 label: 'Reload Window'
52 });
53 ElectronCommands.ZOOM_IN = common_1.Command.toDefaultLocalizedCommand({
54 id: 'view.zoomIn',
55 label: 'Zoom In'
56 });
57 ElectronCommands.ZOOM_OUT = common_1.Command.toDefaultLocalizedCommand({
58 id: 'view.zoomOut',
59 label: 'Zoom Out'
60 });
61 ElectronCommands.RESET_ZOOM = common_1.Command.toDefaultLocalizedCommand({
62 id: 'view.resetZoom',
63 label: 'Reset Zoom'
64 });
65 ElectronCommands.CLOSE_WINDOW = common_1.Command.toDefaultLocalizedCommand({
66 id: 'close.window',
67 label: 'Close Window'
68 });
69 ElectronCommands.TOGGLE_FULL_SCREEN = common_1.Command.toDefaultLocalizedCommand({
70 id: 'workbench.action.toggleFullScreen',
71 category: browser_1.CommonCommands.VIEW_CATEGORY,
72 label: 'Toggle Full Screen'
73 });
74})(ElectronCommands = exports.ElectronCommands || (exports.ElectronCommands = {}));
75var ElectronMenus;
76(function (ElectronMenus) {
77 ElectronMenus.VIEW_WINDOW = [...browser_1.CommonMenus.VIEW, 'window'];
78 ElectronMenus.VIEW_ZOOM = [...browser_1.CommonMenus.VIEW_APPEARANCE_SUBMENU, '4_appearance_submenu_zoom'];
79})(ElectronMenus = exports.ElectronMenus || (exports.ElectronMenus = {}));
80(function (ElectronMenus) {
81 ElectronMenus.HELP_TOGGLE = [...browser_1.CommonMenus.HELP, 'z_toggle'];
82})(ElectronMenus = exports.ElectronMenus || (exports.ElectronMenus = {}));
83(function (ElectronMenus) {
84 ElectronMenus.FILE_CLOSE = [...browser_1.CommonMenus.FILE_CLOSE, 'window-close'];
85})(ElectronMenus = exports.ElectronMenus || (exports.ElectronMenus = {}));
86exports.CustomTitleWidgetFactory = Symbol('CustomTitleWidgetFactory');
87let ElectronMenuContribution = class ElectronMenuContribution extends browser_menu_plugin_1.BrowserMenuBarContribution {
88 constructor(factory) {
89 super(factory);
90 this.factory = factory;
91 this.titleBarStyleChangeFlag = false;
92 }
93 onStart(app) {
94 this.handleTitleBarStyling(app);
95 if (common_1.isOSX) {
96 this.attachWindowFocusListener(app);
97 }
98 // Make sure the application menu is complete, once the frontend application is ready.
99 // https://github.com/theia-ide/theia/issues/5100
100 let onStateChange = undefined;
101 const stateServiceListener = (state) => {
102 if (state === 'closing_window') {
103 if (!!onStateChange) {
104 onStateChange.dispose();
105 }
106 }
107 };
108 onStateChange = this.stateService.onStateChanged(stateServiceListener);
109 this.shell.mainPanel.onDidToggleMaximized(() => {
110 this.handleToggleMaximized();
111 });
112 this.shell.bottomPanel.onDidToggleMaximized(() => {
113 this.handleToggleMaximized();
114 });
115 this.attachMenuBarVisibilityListener();
116 this.themeService.onDidColorThemeChange(e => {
117 this.handleThemeChange(e);
118 });
119 }
120 attachWindowFocusListener(app) {
121 // OSX: Recreate the menus when changing windows.
122 // OSX only has one menu bar for all windows, so we need to swap
123 // between them as the user switches windows.
124 const disposeHandler = window.electronTheiaCore.onWindowEvent('focus', () => {
125 this.setMenu(app);
126 });
127 window.addEventListener('unload', () => disposeHandler.dispose());
128 }
129 attachMenuBarVisibilityListener() {
130 this.preferenceService.onPreferenceChanged(e => {
131 if (e.preferenceName === 'window.menuBarVisibility') {
132 this.handleFullScreen(e.newValue);
133 }
134 });
135 }
136 handleTitleBarStyling(app) {
137 this.hideTopPanel(app);
138 window.electronTheiaCore.getTitleBarStyleAtStartup().then(style => {
139 this.titleBarStyle = style;
140 this.setMenu(app);
141 this.preferenceService.ready.then(() => {
142 this.preferenceService.set('window.titleBarStyle', this.titleBarStyle, browser_1.PreferenceScope.User);
143 });
144 });
145 this.preferenceService.ready.then(() => {
146 window.electronTheiaCore.setMenuBarVisible(['classic', 'visible'].includes(this.preferenceService.get('window.menuBarVisibility', 'classic')));
147 });
148 this.preferenceService.onPreferenceChanged(change => {
149 if (change.preferenceName === 'window.titleBarStyle') {
150 if (this.titleBarStyleChangeFlag && this.titleBarStyle !== change.newValue) {
151 window.electronTheiaCore.setTitleBarStyle(change.newValue);
152 this.handleRequiredRestart();
153 }
154 this.titleBarStyleChangeFlag = true;
155 }
156 });
157 }
158 handleToggleMaximized() {
159 const preference = this.preferenceService.get('window.menuBarVisibility');
160 if (preference === 'classic') {
161 this.factory.setMenuBar();
162 }
163 }
164 /**
165 * Hides the `theia-top-panel` depending on the selected `titleBarStyle`.
166 * The `theia-top-panel` is used as the container of the main, application menu-bar for the
167 * browser. Native Electron has it's own.
168 * By default, this method is called on application `onStart`.
169 */
170 hideTopPanel(app) {
171 const itr = app.shell.children();
172 let child = itr.next();
173 while (child) {
174 // Top panel for the menu contribution is not required for native Electron title bar.
175 if (child.id === 'theia-top-panel') {
176 child.setHidden(this.titleBarStyle !== 'custom');
177 break;
178 }
179 else {
180 child = itr.next();
181 }
182 }
183 }
184 setMenu(app, electronMenu = this.factory.createElectronMenuBar()) {
185 if (!common_1.isOSX) {
186 this.hideTopPanel(app);
187 if (this.titleBarStyle === 'custom' && !this.menuBar) {
188 this.createCustomTitleBar(app);
189 return;
190 }
191 }
192 window.electronTheiaCore.setMenu(electronMenu);
193 }
194 createCustomTitleBar(app) {
195 const dragPanel = new browser_1.Widget();
196 dragPanel.id = 'theia-drag-panel';
197 app.shell.addWidget(dragPanel, { area: 'top' });
198 this.appendMenu(app.shell);
199 this.createCustomTitleWidget(app);
200 const controls = document.createElement('div');
201 controls.id = 'window-controls';
202 controls.append(this.createControlButton('minimize', () => window.electronTheiaCore.minimize()), this.createControlButton('maximize', () => window.electronTheiaCore.maximize()), this.createControlButton('restore', () => window.electronTheiaCore.unMaximize()), this.createControlButton('close', () => window.electronTheiaCore.close()));
203 app.shell.topPanel.node.append(controls);
204 this.handleWindowControls();
205 }
206 createCustomTitleWidget(app) {
207 const titleWidget = this.customTitleWidgetFactory();
208 if (titleWidget) {
209 app.shell.addWidget(titleWidget, { area: 'top' });
210 }
211 }
212 handleWindowControls() {
213 toggleControlButtons();
214 window.electronTheiaCore.onWindowEvent('maximize', toggleControlButtons);
215 window.electronTheiaCore.onWindowEvent('unmaximize', toggleControlButtons);
216 function toggleControlButtons() {
217 if (window.electronTheiaCore.isMaximized()) {
218 document.body.classList.add('maximized');
219 }
220 else {
221 document.body.classList.remove('maximized');
222 }
223 }
224 }
225 createControlButton(id, handler) {
226 const button = document.createElement('div');
227 button.id = `${id}-button`;
228 button.className = `control-button ${(0, browser_1.codicon)(`chrome-${id}`)}`;
229 button.addEventListener('click', handler);
230 return button;
231 }
232 async handleRequiredRestart() {
233 const msgNode = document.createElement('div');
234 const message = document.createElement('p');
235 message.textContent = common_1.nls.localizeByDefault('A setting has changed that requires a restart to take effect.');
236 const detail = document.createElement('p');
237 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);
238 msgNode.append(message, detail);
239 const restart = common_1.nls.localizeByDefault('Restart');
240 const dialog = new browser_1.ConfirmDialog({
241 title: restart,
242 msg: msgNode,
243 ok: restart,
244 cancel: browser_1.Dialog.CANCEL
245 });
246 if (await dialog.open()) {
247 this.windowService.setSafeToShutDown();
248 window.electronTheiaCore.restart();
249 }
250 }
251 registerCommands(registry) {
252 registry.registerCommand(ElectronCommands.TOGGLE_DEVELOPER_TOOLS, {
253 execute: () => {
254 window.electronTheiaCore.toggleDevTools();
255 }
256 });
257 registry.registerCommand(ElectronCommands.RELOAD, {
258 execute: () => this.windowService.reload()
259 });
260 registry.registerCommand(ElectronCommands.CLOSE_WINDOW, {
261 execute: () => window.electronTheiaCore.close()
262 });
263 registry.registerCommand(ElectronCommands.ZOOM_IN, {
264 execute: async () => {
265 const currentLevel = await window.electronTheiaCore.getZoomLevel();
266 // 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.
267 let zoomLevel = (Math.floor(currentLevel / electron_window_preferences_1.ZoomLevel.VARIATION) * electron_window_preferences_1.ZoomLevel.VARIATION) + electron_window_preferences_1.ZoomLevel.VARIATION;
268 if (zoomLevel > electron_window_preferences_1.ZoomLevel.MAX) {
269 zoomLevel = electron_window_preferences_1.ZoomLevel.MAX;
270 return;
271 }
272 ;
273 this.preferenceService.set('window.zoomLevel', zoomLevel, browser_1.PreferenceScope.User);
274 }
275 });
276 registry.registerCommand(ElectronCommands.ZOOM_OUT, {
277 execute: async () => {
278 const currentLevel = await window.electronTheiaCore.getZoomLevel();
279 // 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.
280 let zoomLevel = (Math.ceil(currentLevel / electron_window_preferences_1.ZoomLevel.VARIATION) * electron_window_preferences_1.ZoomLevel.VARIATION) - electron_window_preferences_1.ZoomLevel.VARIATION;
281 if (zoomLevel < electron_window_preferences_1.ZoomLevel.MIN) {
282 zoomLevel = electron_window_preferences_1.ZoomLevel.MIN;
283 return;
284 }
285 ;
286 this.preferenceService.set('window.zoomLevel', zoomLevel, browser_1.PreferenceScope.User);
287 }
288 });
289 registry.registerCommand(ElectronCommands.RESET_ZOOM, {
290 execute: () => this.preferenceService.set('window.zoomLevel', electron_window_preferences_1.ZoomLevel.DEFAULT, browser_1.PreferenceScope.User)
291 });
292 registry.registerCommand(ElectronCommands.TOGGLE_FULL_SCREEN, {
293 isEnabled: () => window.electronTheiaCore.isFullScreenable(),
294 isVisible: () => window.electronTheiaCore.isFullScreenable(),
295 execute: () => this.toggleFullScreen()
296 });
297 }
298 registerKeybindings(registry) {
299 registry.registerKeybindings({
300 command: ElectronCommands.TOGGLE_DEVELOPER_TOOLS.id,
301 keybinding: 'ctrlcmd+alt+i'
302 }, {
303 command: ElectronCommands.RELOAD.id,
304 keybinding: 'ctrlcmd+r'
305 }, {
306 command: ElectronCommands.ZOOM_IN.id,
307 keybinding: 'ctrlcmd+='
308 }, {
309 command: ElectronCommands.ZOOM_IN.id,
310 keybinding: 'ctrlcmd+add'
311 }, {
312 command: ElectronCommands.ZOOM_OUT.id,
313 keybinding: 'ctrlcmd+subtract'
314 }, {
315 command: ElectronCommands.ZOOM_OUT.id,
316 keybinding: 'ctrlcmd+-'
317 }, {
318 command: ElectronCommands.RESET_ZOOM.id,
319 keybinding: 'ctrlcmd+0'
320 }, {
321 command: ElectronCommands.CLOSE_WINDOW.id,
322 keybinding: (common_1.isOSX ? 'cmd+shift+w' : (common_1.isWindows ? 'ctrl+w' : /* Linux */ 'ctrl+q'))
323 }, {
324 command: ElectronCommands.TOGGLE_FULL_SCREEN.id,
325 keybinding: common_1.isOSX ? 'ctrl+ctrlcmd+f' : 'f11'
326 });
327 }
328 registerMenus(registry) {
329 registry.registerMenuAction(ElectronMenus.HELP_TOGGLE, {
330 commandId: ElectronCommands.TOGGLE_DEVELOPER_TOOLS.id
331 });
332 registry.registerMenuAction(ElectronMenus.VIEW_WINDOW, {
333 commandId: ElectronCommands.RELOAD.id,
334 order: 'z0'
335 });
336 registry.registerMenuAction(ElectronMenus.VIEW_ZOOM, {
337 commandId: ElectronCommands.ZOOM_IN.id,
338 order: 'z1'
339 });
340 registry.registerMenuAction(ElectronMenus.VIEW_ZOOM, {
341 commandId: ElectronCommands.ZOOM_OUT.id,
342 order: 'z2'
343 });
344 registry.registerMenuAction(ElectronMenus.VIEW_ZOOM, {
345 commandId: ElectronCommands.RESET_ZOOM.id,
346 order: 'z3'
347 });
348 registry.registerMenuAction(ElectronMenus.FILE_CLOSE, {
349 commandId: ElectronCommands.CLOSE_WINDOW.id,
350 });
351 registry.registerMenuAction(browser_1.CommonMenus.VIEW_APPEARANCE_SUBMENU_SCREEN, {
352 commandId: ElectronCommands.TOGGLE_FULL_SCREEN.id,
353 label: common_1.nls.localizeByDefault('Full Screen'),
354 order: '0'
355 });
356 }
357 toggleFullScreen() {
358 window.electronTheiaCore.toggleFullScreen();
359 const menuBarVisibility = this.preferenceService.get('window.menuBarVisibility', 'classic');
360 this.handleFullScreen(menuBarVisibility);
361 }
362 handleFullScreen(menuBarVisibility) {
363 const shouldShowTop = !window.electronTheiaCore.isFullScreen() || menuBarVisibility === 'visible';
364 if (this.titleBarStyle === 'native') {
365 window.electronTheiaCore.setMenuBarVisible(shouldShowTop);
366 }
367 else if (shouldShowTop) {
368 this.shell.topPanel.show();
369 }
370 else {
371 this.shell.topPanel.hide();
372 }
373 }
374 handleThemeChange(e) {
375 const backgroundColor = window.getComputedStyle(document.body).backgroundColor;
376 window.electronTheiaCore.setBackgroundColor(backgroundColor);
377 }
378};
379__decorate([
380 (0, inversify_1.inject)(frontend_application_state_1.FrontendApplicationStateService),
381 __metadata("design:type", frontend_application_state_1.FrontendApplicationStateService)
382], ElectronMenuContribution.prototype, "stateService", void 0);
383__decorate([
384 (0, inversify_1.inject)(window_service_1.WindowService),
385 __metadata("design:type", Object)
386], ElectronMenuContribution.prototype, "windowService", void 0);
387__decorate([
388 (0, inversify_1.inject)(theming_1.ThemeService),
389 __metadata("design:type", theming_1.ThemeService)
390], ElectronMenuContribution.prototype, "themeService", void 0);
391__decorate([
392 (0, inversify_1.inject)(exports.CustomTitleWidgetFactory),
393 __metadata("design:type", Function)
394], ElectronMenuContribution.prototype, "customTitleWidgetFactory", void 0);
395ElectronMenuContribution = __decorate([
396 (0, inversify_1.injectable)(),
397 __param(0, (0, inversify_1.inject)(electron_main_menu_factory_1.ElectronMainMenuFactory)),
398 __metadata("design:paramtypes", [electron_main_menu_factory_1.ElectronMainMenuFactory])
399], ElectronMenuContribution);
400exports.ElectronMenuContribution = ElectronMenuContribution;
401let CustomTitleWidget = class CustomTitleWidget extends browser_1.Widget {
402 constructor() {
403 super();
404 this.id = 'theia-custom-title';
405 }
406 init() {
407 this.updateTitle(this.windowTitleService.title);
408 this.windowTitleService.onDidChangeTitle(title => {
409 this.updateTitle(title);
410 });
411 }
412 onResize(msg) {
413 this.adjustTitleToCenter();
414 super.onResize(msg);
415 }
416 onAfterShow(msg) {
417 this.adjustTitleToCenter();
418 super.onAfterShow(msg);
419 }
420 updateTitle(title) {
421 this.node.textContent = title;
422 this.adjustTitleToCenter();
423 }
424 adjustTitleToCenter() {
425 const menubar = this.electronMenuContribution.menuBar;
426 if (menubar) {
427 const titleWidth = this.node.clientWidth;
428 const margin = 16;
429 const leftMarker = menubar.node.offsetLeft + menubar.node.clientWidth + margin;
430 const panelWidth = this.applicationShell.topPanel.node.clientWidth;
431 const controlsWidth = 48 * 3; // Each window button has a width of 48px
432 const rightMarker = panelWidth - controlsWidth - margin;
433 let hidden = false;
434 let relative = false;
435 this.node.style.left = '50%';
436 // The title has not enough space between the menu and the window controls
437 // So we simply hide it
438 if (rightMarker - leftMarker < titleWidth) {
439 hidden = true;
440 }
441 else if ((panelWidth - titleWidth) / 2 < leftMarker || (panelWidth + titleWidth) / 2 > rightMarker) {
442 // This indicates that the title has either hit the left (menu) or right (window controls) marker
443 relative = true;
444 this.node.style.left = `${leftMarker + (rightMarker - leftMarker - titleWidth) / 2}px`;
445 }
446 this.node.classList.toggle('hidden', hidden);
447 this.node.classList.toggle('relative', relative);
448 }
449 }
450};
451__decorate([
452 (0, inversify_1.inject)(ElectronMenuContribution),
453 __metadata("design:type", ElectronMenuContribution)
454], CustomTitleWidget.prototype, "electronMenuContribution", void 0);
455__decorate([
456 (0, inversify_1.inject)(window_title_service_1.WindowTitleService),
457 __metadata("design:type", window_title_service_1.WindowTitleService)
458], CustomTitleWidget.prototype, "windowTitleService", void 0);
459__decorate([
460 (0, inversify_1.inject)(browser_1.ApplicationShell),
461 __metadata("design:type", browser_1.ApplicationShell)
462], CustomTitleWidget.prototype, "applicationShell", void 0);
463__decorate([
464 (0, inversify_1.postConstruct)(),
465 __metadata("design:type", Function),
466 __metadata("design:paramtypes", []),
467 __metadata("design:returntype", void 0)
468], CustomTitleWidget.prototype, "init", null);
469CustomTitleWidget = __decorate([
470 (0, inversify_1.injectable)(),
471 __metadata("design:paramtypes", [])
472], CustomTitleWidget);
473exports.CustomTitleWidget = CustomTitleWidget;
474//# sourceMappingURL=electron-menu-contribution.js.map
\No newline at end of file