@theia/core 1.65.0-next.47 → 1.65.0-next.55
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/browser/catalog.json +33 -5
- package/lib/browser/frontend-application-module.d.ts.map +1 -1
- package/lib/browser/frontend-application-module.js +5 -1
- package/lib/browser/frontend-application-module.js.map +1 -1
- package/lib/browser/secondary-window-handler.d.ts +21 -9
- package/lib/browser/secondary-window-handler.d.ts.map +1 -1
- package/lib/browser/secondary-window-handler.js +162 -21
- package/lib/browser/secondary-window-handler.js.map +1 -1
- package/lib/browser/shell/application-shell.d.ts +6 -1
- package/lib/browser/shell/application-shell.d.ts.map +1 -1
- package/lib/browser/shell/application-shell.js +20 -4
- package/lib/browser/shell/application-shell.js.map +1 -1
- package/lib/browser/shell/theia-dock-panel.d.ts +17 -1
- package/lib/browser/shell/theia-dock-panel.d.ts.map +1 -1
- package/lib/browser/shell/theia-dock-panel.js +76 -0
- package/lib/browser/shell/theia-dock-panel.js.map +1 -1
- package/lib/browser/widgets/extractable-widget.d.ts +3 -0
- package/lib/browser/widgets/extractable-widget.d.ts.map +1 -1
- package/lib/browser/widgets/extractable-widget.js.map +1 -1
- package/lib/browser/window/default-secondary-window-service.d.ts +9 -3
- package/lib/browser/window/default-secondary-window-service.d.ts.map +1 -1
- package/lib/browser/window/default-secondary-window-service.js +52 -11
- package/lib/browser/window/default-secondary-window-service.js.map +1 -1
- package/lib/browser/window/secondary-window-service.d.ts +15 -2
- package/lib/browser/window/secondary-window-service.d.ts.map +1 -1
- package/lib/browser/window/secondary-window-service.js +12 -1
- package/lib/browser/window/secondary-window-service.js.map +1 -1
- package/lib/electron-browser/preload.d.ts.map +1 -1
- package/lib/electron-browser/preload.js +3 -0
- package/lib/electron-browser/preload.js.map +1 -1
- package/lib/electron-browser/window/electron-secondary-window-service.d.ts.map +1 -1
- package/lib/electron-browser/window/electron-secondary-window-service.js +5 -4
- package/lib/electron-browser/window/electron-secondary-window-service.js.map +1 -1
- package/lib/electron-common/electron-api.d.ts +2 -0
- package/lib/electron-common/electron-api.d.ts.map +1 -1
- package/lib/electron-common/electron-api.js +2 -1
- package/lib/electron-common/electron-api.js.map +1 -1
- package/lib/electron-main/electron-api-main.d.ts.map +1 -1
- package/lib/electron-main/electron-api-main.js +9 -0
- package/lib/electron-main/electron-api-main.js.map +1 -1
- package/lib/node/logger-cli-contribution.d.ts +7 -1
- package/lib/node/logger-cli-contribution.d.ts.map +1 -1
- package/lib/node/logger-cli-contribution.js +22 -10
- package/lib/node/logger-cli-contribution.js.map +1 -1
- package/lib/node/logger-cli-contribution.spec.js +13 -2
- package/lib/node/logger-cli-contribution.spec.js.map +1 -1
- package/package.json +4 -4
- package/src/browser/frontend-application-module.ts +5 -1
- package/src/browser/secondary-window-handler.ts +189 -29
- package/src/browser/shell/application-shell.ts +29 -7
- package/src/browser/shell/theia-dock-panel.ts +93 -1
- package/src/browser/style/dockpanel.css +7 -0
- package/src/browser/widgets/extractable-widget.ts +3 -0
- package/src/browser/window/default-secondary-window-service.ts +53 -15
- package/src/browser/window/secondary-window-service.ts +23 -2
- package/src/electron-browser/preload.ts +4 -1
- package/src/electron-browser/window/electron-secondary-window-service.ts +7 -4
- package/src/electron-common/electron-api.ts +2 -0
- package/src/electron-main/electron-api-main.ts +11 -1
- package/src/node/logger-cli-contribution.spec.ts +17 -2
- package/src/node/logger-cli-contribution.ts +18 -3
|
@@ -47,6 +47,8 @@ export class TheiaDockPanel extends DockPanel {
|
|
|
47
47
|
readonly widgetRemoved = new Signal<this, Widget>(this);
|
|
48
48
|
|
|
49
49
|
protected readonly onDidChangeCurrentEmitter = new Emitter<Title<Widget> | undefined>();
|
|
50
|
+
protected disableDND: boolean | undefined = false;
|
|
51
|
+
protected tabWithDNDDisabledStyling?: HTMLElement = undefined;
|
|
50
52
|
|
|
51
53
|
get onDidChangeCurrent(): Event<Title<Widget> | undefined> {
|
|
52
54
|
return this.onDidChangeCurrentEmitter.event;
|
|
@@ -57,6 +59,7 @@ export class TheiaDockPanel extends DockPanel {
|
|
|
57
59
|
protected readonly maximizeCallback?: (area: TheiaDockPanel) => void
|
|
58
60
|
) {
|
|
59
61
|
super(options);
|
|
62
|
+
this.disableDND = TheiaDockPanel.isTheiaDockPanelIOptions(options) && options.disableDragAndDrop;
|
|
60
63
|
this['_onCurrentChanged'] = (sender: TabBar<Widget>, args: TabBar.ICurrentChangedArgs<Widget>) => {
|
|
61
64
|
this.markAsCurrent(args.currentTitle || undefined);
|
|
62
65
|
super['_onCurrentChanged'](sender, args);
|
|
@@ -68,12 +71,72 @@ export class TheiaDockPanel extends DockPanel {
|
|
|
68
71
|
if (tabBar instanceof ToolbarAwareTabBar) {
|
|
69
72
|
tabBar.setDockPanel(this);
|
|
70
73
|
}
|
|
74
|
+
if (this.disableDND) {
|
|
75
|
+
tabBar['tabDetachRequested'].disconnect(this['_onTabDetachRequested'], this);
|
|
76
|
+
tabBar['tabDetachRequested'].connect(this.onTabDetachRequestedWithDisabledDND, this);
|
|
77
|
+
|
|
78
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any, no-null/no-null
|
|
79
|
+
let dragDataValue: any = null;
|
|
80
|
+
Object.defineProperty(tabBar, '_dragData', {
|
|
81
|
+
get: () => dragDataValue,
|
|
82
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
83
|
+
set: (value: any) => {
|
|
84
|
+
dragDataValue = value;
|
|
85
|
+
// eslint-disable-next-line no-null/no-null
|
|
86
|
+
if (value === null) {
|
|
87
|
+
this.onNullTabDragDataWithDisabledDND();
|
|
88
|
+
}
|
|
89
|
+
},
|
|
90
|
+
configurable: true
|
|
91
|
+
});
|
|
92
|
+
}
|
|
71
93
|
return tabBar;
|
|
72
94
|
};
|
|
73
95
|
this['_onTabActivateRequested'] = (sender: TabBar<Widget>, args: TabBar.ITabActivateRequestedArgs<Widget>) => {
|
|
74
96
|
this.markAsCurrent(args.title);
|
|
75
97
|
super['_onTabActivateRequested'](sender, args);
|
|
76
98
|
};
|
|
99
|
+
this['_onTabCloseRequested'] = (sender: TabBar<Widget>, args: TabBar.ITabCloseRequestedArgs<Widget>) => {
|
|
100
|
+
if (TheiaDockPanel.isTheiaDockPanelIOptions(options) && options.closeHandler !== undefined) {
|
|
101
|
+
if (options.closeHandler(sender, args)) {
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
super['_onTabCloseRequested'](sender, args);
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
protected onTabDetachRequestedWithDisabledDND(sender: TabBar<Widget>, args: TabBar.ITabDetachRequestedArgs<Widget>): void {
|
|
110
|
+
// don't process the detach request at all. We still want to support other drag starts, e.g. tab reorder
|
|
111
|
+
// provide visual feedback that DnD is disabled by adding not-allowed class
|
|
112
|
+
const tab = sender.contentNode.children[args.index] as HTMLElement;
|
|
113
|
+
if (tab) {
|
|
114
|
+
tab.classList.add('theia-drag-not-allowed');
|
|
115
|
+
this.tabWithDNDDisabledStyling = tab;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
protected onNullTabDragDataWithDisabledDND(): void {
|
|
120
|
+
if (this.tabWithDNDDisabledStyling) {
|
|
121
|
+
this.tabWithDNDDisabledStyling.classList.remove('theia-drag-not-allowed');
|
|
122
|
+
this.tabWithDNDDisabledStyling = undefined;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
override handleEvent(event: globalThis.Event): void {
|
|
127
|
+
if (this.disableDND) {
|
|
128
|
+
switch (event.type) {
|
|
129
|
+
case 'lm-dragenter':
|
|
130
|
+
case 'lm-dragleave':
|
|
131
|
+
case 'lm-dragover':
|
|
132
|
+
case 'lm-drop':
|
|
133
|
+
/* no-op */
|
|
134
|
+
break;
|
|
135
|
+
default:
|
|
136
|
+
super.handleEvent(event);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
super.handleEvent(event);
|
|
77
140
|
}
|
|
78
141
|
|
|
79
142
|
toggleMaximized(): void {
|
|
@@ -182,7 +245,36 @@ export class TheiaDockPanel extends DockPanel {
|
|
|
182
245
|
export namespace TheiaDockPanel {
|
|
183
246
|
export const Factory = Symbol('TheiaDockPanel#Factory');
|
|
184
247
|
export interface Factory {
|
|
185
|
-
(options?: DockPanel.IOptions, maximizeCallback?: (area: TheiaDockPanel) => void): TheiaDockPanel;
|
|
248
|
+
(options?: DockPanel.IOptions | TheiaDockPanel.IOptions, maximizeCallback?: (area: TheiaDockPanel) => void): TheiaDockPanel;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
export interface IOptions extends DockPanel.IOptions {
|
|
252
|
+
/** whether drag and drop for tabs should be disabled */
|
|
253
|
+
disableDragAndDrop?: boolean;
|
|
254
|
+
|
|
255
|
+
/**
|
|
256
|
+
* @param sender the tab bar
|
|
257
|
+
* @param args the widget (title)
|
|
258
|
+
* @returns true if the request was handled by this handler, false if the tabbar should handle the request
|
|
259
|
+
*/
|
|
260
|
+
closeHandler?: (sender: TabBar<Widget>, args: TabBar.ITabCloseRequestedArgs<Widget>) => boolean;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
export function isTheiaDockPanelIOptions(options: DockPanel.IOptions | undefined): options is IOptions {
|
|
264
|
+
if (options === undefined) {
|
|
265
|
+
return false;
|
|
266
|
+
}
|
|
267
|
+
if ('disableDragAndDrop' in options) {
|
|
268
|
+
if (options.disableDragAndDrop !== undefined && typeof options.disableDragAndDrop !== 'boolean') {
|
|
269
|
+
return false;
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
if ('closeHandler' in options) {
|
|
273
|
+
if (options.closeHandler !== undefined && typeof options.closeHandler !== 'function') {
|
|
274
|
+
return false;
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
return true;
|
|
186
278
|
}
|
|
187
279
|
|
|
188
280
|
export interface AddOptions extends DockPanel.IAddOptions {
|
|
@@ -84,3 +84,10 @@
|
|
|
84
84
|
.lm-DockPanel-overlay.lm-mod-root-bottom {
|
|
85
85
|
background: var(--theia-panel-dropBackground);
|
|
86
86
|
}
|
|
87
|
+
|
|
88
|
+
.lm-TabBar-tab.theia-drag-not-allowed {
|
|
89
|
+
cursor: not-allowed !important;
|
|
90
|
+
background-color: var(--theia-errorBackground) !important;
|
|
91
|
+
opacity: 0.8;
|
|
92
|
+
transition: background-color 0.3s ease;
|
|
93
|
+
}
|
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
|
|
15
15
|
// *****************************************************************************
|
|
16
16
|
|
|
17
|
+
import { ApplicationShell } from '../shell';
|
|
17
18
|
import { Widget } from './widget';
|
|
18
19
|
|
|
19
20
|
/**
|
|
@@ -24,6 +25,8 @@ export interface ExtractableWidget extends Widget {
|
|
|
24
25
|
isExtractable: boolean;
|
|
25
26
|
/** The secondary window that the window was extracted to or `undefined` if it is not yet extracted. */
|
|
26
27
|
secondaryWindow: Window | undefined;
|
|
28
|
+
/** Stores the area which contained the widget before being extracted. This is undefined if the widget wasn't extracted or if the area could not be determined */
|
|
29
|
+
previousArea?: ApplicationShell.Area;
|
|
27
30
|
}
|
|
28
31
|
|
|
29
32
|
export namespace ExtractableWidget {
|
|
@@ -14,13 +14,14 @@
|
|
|
14
14
|
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
|
|
15
15
|
// *****************************************************************************
|
|
16
16
|
import { inject, injectable, postConstruct } from 'inversify';
|
|
17
|
-
import { SecondaryWindowService } from './secondary-window-service';
|
|
17
|
+
import { SecondaryWindow, SecondaryWindowService } from './secondary-window-service';
|
|
18
18
|
import { WindowService } from './window-service';
|
|
19
|
-
import { ExtractableWidget } from '../widgets';
|
|
19
|
+
import { ExtractableWidget, Widget } from '../widgets';
|
|
20
20
|
import { ApplicationShell } from '../shell';
|
|
21
21
|
import { Saveable } from '../saveable';
|
|
22
22
|
import { Emitter, environment, Event, PreferenceService } from '../../common';
|
|
23
23
|
import { SaveableService } from '../saveable-service';
|
|
24
|
+
import { getAllWidgetsFromSecondaryWindow, getDefaultRestoreArea } from '../secondary-window-handler';
|
|
24
25
|
|
|
25
26
|
@injectable()
|
|
26
27
|
export class DefaultSecondaryWindowService implements SecondaryWindowService {
|
|
@@ -28,6 +29,8 @@ export class DefaultSecondaryWindowService implements SecondaryWindowService {
|
|
|
28
29
|
readonly onWindowOpened: Event<Window> = this.onWindowOpenedEmitter.event;
|
|
29
30
|
protected readonly onWindowClosedEmitter = new Emitter<Window>;
|
|
30
31
|
readonly onWindowClosed: Event<Window> = this.onWindowClosedEmitter.event;
|
|
32
|
+
protected readonly beforeWidgetRestoreEmitter = new Emitter<[Widget, Window]>;
|
|
33
|
+
readonly beforeWidgetRestore: Event<[Widget, Window]> = this.beforeWidgetRestoreEmitter.event;
|
|
31
34
|
// secondary-window.html is part of Theia's generated code. It is generated by dev-packages/application-manager/src/generator/frontend-generator.ts
|
|
32
35
|
protected static SECONDARY_WINDOW_URL = 'secondary-window.html';
|
|
33
36
|
|
|
@@ -92,7 +95,7 @@ export class DefaultSecondaryWindowService implements SecondaryWindowService {
|
|
|
92
95
|
});
|
|
93
96
|
}
|
|
94
97
|
|
|
95
|
-
createSecondaryWindow(widget: ExtractableWidget, shell: ApplicationShell): Window | undefined {
|
|
98
|
+
createSecondaryWindow(widget: ExtractableWidget, shell: ApplicationShell): Window | SecondaryWindow | undefined {
|
|
96
99
|
const [height, width, left, top] = this.findSecondaryWindowCoordinates(widget);
|
|
97
100
|
let options = `popup=1,width=${width},height=${height},left=${left},top=${top}`;
|
|
98
101
|
if (this.preferenceService.get('window.secondaryWindowAlwaysOnTop')) {
|
|
@@ -104,21 +107,19 @@ export class DefaultSecondaryWindowService implements SecondaryWindowService {
|
|
|
104
107
|
this.onWindowOpenedEmitter.fire(newWindow);
|
|
105
108
|
newWindow.addEventListener('DOMContentLoaded', () => {
|
|
106
109
|
newWindow.addEventListener('beforeunload', evt => {
|
|
107
|
-
const
|
|
108
|
-
const
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
110
|
+
const widgets = getAllWidgetsFromSecondaryWindow(newWindow) ?? [widget];
|
|
111
|
+
for (const w of widgets) {
|
|
112
|
+
const saveable = Saveable.get(w);
|
|
113
|
+
const wouldLoseState = !!saveable && saveable.dirty && this.saveResourceService.autoSave === 'off';
|
|
114
|
+
if (wouldLoseState) {
|
|
115
|
+
evt.returnValue = '';
|
|
116
|
+
evt.preventDefault();
|
|
117
|
+
return 'non-empty';
|
|
118
|
+
}
|
|
113
119
|
}
|
|
114
120
|
}, { capture: true });
|
|
115
121
|
|
|
116
122
|
newWindow.addEventListener('unload', () => {
|
|
117
|
-
const saveable = Saveable.get(widget);
|
|
118
|
-
shell.closeWidget(widget.id, {
|
|
119
|
-
save: !!saveable && saveable.dirty && this.saveResourceService.autoSave !== 'off'
|
|
120
|
-
});
|
|
121
|
-
|
|
122
123
|
const extIndex = this.secondaryWindows.indexOf(newWindow);
|
|
123
124
|
if (extIndex > -1) {
|
|
124
125
|
this.onWindowClosedEmitter.fire(newWindow);
|
|
@@ -128,12 +129,13 @@ export class DefaultSecondaryWindowService implements SecondaryWindowService {
|
|
|
128
129
|
this.windowCreated(newWindow, widget, shell);
|
|
129
130
|
});
|
|
130
131
|
}
|
|
132
|
+
(newWindow as SecondaryWindow).rootWidget = undefined;
|
|
131
133
|
return newWindow;
|
|
132
134
|
}
|
|
133
135
|
|
|
134
136
|
protected windowCreated(newWindow: Window, widget: ExtractableWidget, shell: ApplicationShell): void {
|
|
135
137
|
newWindow.addEventListener('unload', () => {
|
|
136
|
-
|
|
138
|
+
this.restoreWidgets(newWindow, widget, shell);
|
|
137
139
|
});
|
|
138
140
|
}
|
|
139
141
|
|
|
@@ -199,4 +201,40 @@ export class DefaultSecondaryWindowService implements SecondaryWindowService {
|
|
|
199
201
|
protected nextWindowId(): string {
|
|
200
202
|
return `${this.prefix}-secondaryWindow-${this.nextId++}`;
|
|
201
203
|
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Restore the widgets back to the main window. SecondaryWindowHandler needs to get informated about this.
|
|
207
|
+
*/
|
|
208
|
+
protected async restoreWidgets(newWindow: Window, extractableWidget: ExtractableWidget, shell: ApplicationShell): Promise<boolean> {
|
|
209
|
+
const widgets = getAllWidgetsFromSecondaryWindow(newWindow) ?? new Set([extractableWidget]);
|
|
210
|
+
const defaultRestoreArea = getDefaultRestoreArea(newWindow);
|
|
211
|
+
|
|
212
|
+
let allMovedOrDisposed = true;
|
|
213
|
+
for (const widget of widgets) {
|
|
214
|
+
if (widget.isDisposed) {
|
|
215
|
+
continue;
|
|
216
|
+
}
|
|
217
|
+
try {
|
|
218
|
+
const preferredRestoreArea = ExtractableWidget.is(widget) ? widget.previousArea : defaultRestoreArea;
|
|
219
|
+
const area = (preferredRestoreArea === undefined || preferredRestoreArea === 'top' || preferredRestoreArea === 'secondaryWindow') ? 'main' : preferredRestoreArea;
|
|
220
|
+
// fire removed event before adding it to shell
|
|
221
|
+
this.beforeWidgetRestoreEmitter.fire([widget, newWindow]);
|
|
222
|
+
// reset ExtractableWidget properties before moving back so that handler evaluation is correct immediately
|
|
223
|
+
if (ExtractableWidget.is(widget)) {
|
|
224
|
+
widget.secondaryWindow = undefined;
|
|
225
|
+
widget.previousArea = undefined;
|
|
226
|
+
}
|
|
227
|
+
await shell.addWidget(widget, { area });
|
|
228
|
+
await shell.activateWidget(widget.id);
|
|
229
|
+
} catch (e) {
|
|
230
|
+
// we can't move back, close instead
|
|
231
|
+
// otherwise the window will just stay open with no way to close it
|
|
232
|
+
await shell.closeWidget(widget.id);
|
|
233
|
+
if (!widget.isDisposed) {
|
|
234
|
+
allMovedOrDisposed = false;
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
return allMovedOrDisposed;
|
|
239
|
+
}
|
|
202
240
|
}
|
|
@@ -16,7 +16,27 @@
|
|
|
16
16
|
|
|
17
17
|
import { Event } from '../../common';
|
|
18
18
|
import { ApplicationShell } from '../shell';
|
|
19
|
-
import {
|
|
19
|
+
import { TheiaDockPanel } from '../shell/theia-dock-panel';
|
|
20
|
+
import { ExtractableWidget, TabBar, Widget } from '../widgets';
|
|
21
|
+
|
|
22
|
+
export abstract class SecondaryWindowRootWidget extends Widget {
|
|
23
|
+
secondaryWindow: Window | SecondaryWindow;
|
|
24
|
+
defaultRestoreArea?: ApplicationShell.Area;
|
|
25
|
+
abstract widgets: ReadonlyArray<Widget>;
|
|
26
|
+
abstract addWidget(widget: Widget, disposeCallback: () => void, options?: TheiaDockPanel.AddOptions): void;
|
|
27
|
+
getTabBar?(widget: Widget): TabBar<Widget> | undefined;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface SecondaryWindow extends Window {
|
|
31
|
+
rootWidget: SecondaryWindowRootWidget | undefined;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function isSecondaryWindow(window: unknown): window is SecondaryWindow {
|
|
35
|
+
if (!window) {
|
|
36
|
+
return false;
|
|
37
|
+
}
|
|
38
|
+
return typeof window === 'object' && 'rootWidget' in window;
|
|
39
|
+
}
|
|
20
40
|
|
|
21
41
|
export const SecondaryWindowService = Symbol('SecondaryWindowService');
|
|
22
42
|
|
|
@@ -33,9 +53,10 @@ export interface SecondaryWindowService {
|
|
|
33
53
|
* @param onClose optional callback that is invoked when the secondary window is closed
|
|
34
54
|
* @returns the created window or `undefined` if it could not be created
|
|
35
55
|
*/
|
|
36
|
-
createSecondaryWindow(widget: ExtractableWidget, shell: ApplicationShell): Window | undefined;
|
|
56
|
+
createSecondaryWindow(widget: ExtractableWidget, shell: ApplicationShell): SecondaryWindow | Window | undefined;
|
|
37
57
|
readonly onWindowOpened: Event<Window>;
|
|
38
58
|
readonly onWindowClosed: Event<Window>;
|
|
59
|
+
readonly beforeWidgetRestore: Event<[Widget, Window]>;
|
|
39
60
|
|
|
40
61
|
/** Handles focussing the given secondary window in the browser and on Electron. */
|
|
41
62
|
focus(win: Window): void;
|
|
@@ -27,7 +27,7 @@ import {
|
|
|
27
27
|
CHANNEL_REQUEST_RELOAD, CHANNEL_APP_STATE_CHANGED, CHANNEL_SHOW_ITEM_IN_FOLDER, CHANNEL_READ_CLIPBOARD, CHANNEL_WRITE_CLIPBOARD,
|
|
28
28
|
CHANNEL_KEYBOARD_LAYOUT_CHANGED, CHANNEL_IPC_CONNECTION, InternalMenuDto, CHANNEL_REQUEST_SECONDARY_CLOSE, CHANNEL_SET_BACKGROUND_COLOR,
|
|
29
29
|
CHANNEL_WC_METADATA, CHANNEL_ABOUT_TO_CLOSE, CHANNEL_OPEN_WITH_SYSTEM_APP,
|
|
30
|
-
CHANNEL_OPEN_URL, CHANNEL_SET_THEME
|
|
30
|
+
CHANNEL_OPEN_URL, CHANNEL_SET_THEME, CHANNEL_OPEN_DEVTOOLS_FOR_WINDOW
|
|
31
31
|
} from '../electron-common/electron-api';
|
|
32
32
|
|
|
33
33
|
// eslint-disable-next-line import/no-extraneous-dependencies
|
|
@@ -200,6 +200,9 @@ const api: TheiaCoreAPI = {
|
|
|
200
200
|
toggleDevTools: function (): void {
|
|
201
201
|
ipcRenderer.send(CHANNEL_TOGGLE_DEVTOOLS);
|
|
202
202
|
},
|
|
203
|
+
openDevToolsForWindow: function (windowName: string): void {
|
|
204
|
+
ipcRenderer.send(CHANNEL_OPEN_DEVTOOLS_FOR_WINDOW, windowName);
|
|
205
|
+
},
|
|
203
206
|
getZoomLevel: function (): Promise<number> {
|
|
204
207
|
return ipcRenderer.invoke(CHANNEL_GET_ZOOM_LEVEL);
|
|
205
208
|
},
|
|
@@ -48,10 +48,13 @@ export class ElectronSecondaryWindowService extends DefaultSecondaryWindowServic
|
|
|
48
48
|
|
|
49
49
|
protected override windowCreated(newWindow: Window, widget: ExtractableWidget, shell: ApplicationShell): void {
|
|
50
50
|
window.electronTheiaCore.setMenuBarVisible(false, newWindow.name);
|
|
51
|
-
window.electronTheiaCore.setSecondaryWindowCloseRequestHandler(newWindow.name, () => this.canClose(widget, shell));
|
|
51
|
+
window.electronTheiaCore.setSecondaryWindowCloseRequestHandler(newWindow.name, () => this.canClose(widget, shell, newWindow));
|
|
52
|
+
|
|
53
|
+
// Below code may be used to debug contents of secondary window
|
|
54
|
+
// window.electronTheiaCore.openDevToolsForWindow(newWindow.name);
|
|
52
55
|
}
|
|
53
|
-
private async canClose(
|
|
54
|
-
|
|
55
|
-
return widget.isDisposed;
|
|
56
|
+
private async canClose(extractableWidget: ExtractableWidget, shell: ApplicationShell, newWindow: Window): Promise<boolean> {
|
|
57
|
+
return this.restoreWidgets(newWindow, extractableWidget, shell);
|
|
56
58
|
}
|
|
59
|
+
|
|
57
60
|
}
|
|
@@ -83,6 +83,7 @@ export interface TheiaCoreAPI {
|
|
|
83
83
|
setSecondaryWindowCloseRequestHandler(windowName: string, handler: () => Promise<boolean>): void;
|
|
84
84
|
|
|
85
85
|
toggleDevTools(): void;
|
|
86
|
+
openDevToolsForWindow(windowName: string): void;
|
|
86
87
|
getZoomLevel(): Promise<number>;
|
|
87
88
|
setZoomLevel(desired: number): void;
|
|
88
89
|
|
|
@@ -141,6 +142,7 @@ export const CHANNEL_OPEN_URL = 'OpenUrl';
|
|
|
141
142
|
export const CHANNEL_UNMAXIMIZE = 'UnMaximize';
|
|
142
143
|
export const CHANNEL_ON_WINDOW_EVENT = 'OnWindowEvent';
|
|
143
144
|
export const CHANNEL_TOGGLE_DEVTOOLS = 'ToggleDevtools';
|
|
145
|
+
export const CHANNEL_OPEN_DEVTOOLS_FOR_WINDOW = 'OpenDevtoolsForWindow';
|
|
144
146
|
export const CHANNEL_GET_ZOOM_LEVEL = 'GetZoomLevel';
|
|
145
147
|
export const CHANNEL_SET_ZOOM_LEVEL = 'SetZoomLevel';
|
|
146
148
|
export const CHANNEL_IS_FULL_SCREENABLE = 'IsFullScreenable';
|
|
@@ -56,7 +56,8 @@ import {
|
|
|
56
56
|
CHANNEL_ABOUT_TO_CLOSE,
|
|
57
57
|
CHANNEL_OPEN_WITH_SYSTEM_APP,
|
|
58
58
|
CHANNEL_OPEN_URL,
|
|
59
|
-
CHANNEL_SET_THEME
|
|
59
|
+
CHANNEL_SET_THEME,
|
|
60
|
+
CHANNEL_OPEN_DEVTOOLS_FOR_WINDOW
|
|
60
61
|
} from '../electron-common/electron-api';
|
|
61
62
|
import { ElectronMainApplication, ElectronMainApplicationContribution } from './electron-main-application';
|
|
62
63
|
import { Disposable, DisposableCollection, isOSX, MaybePromise } from '../common';
|
|
@@ -207,6 +208,15 @@ export class TheiaMainApi implements ElectronMainApplicationContribution {
|
|
|
207
208
|
event.sender.toggleDevTools();
|
|
208
209
|
});
|
|
209
210
|
|
|
211
|
+
ipcMain.on(CHANNEL_OPEN_DEVTOOLS_FOR_WINDOW, (event, windowName: string) => {
|
|
212
|
+
const electronWindow = BrowserWindow.getAllWindows().find(win => win.webContents.mainFrame.name === windowName);
|
|
213
|
+
if (electronWindow) {
|
|
214
|
+
electronWindow.webContents.openDevTools();
|
|
215
|
+
} else {
|
|
216
|
+
console.warn(`There is no known window '${windowName}'. Thus, the devtools could not be opened.`);
|
|
217
|
+
}
|
|
218
|
+
});
|
|
219
|
+
|
|
210
220
|
ipcMain.on(CHANNEL_SET_ZOOM_LEVEL, (event, zoomLevel: number) => {
|
|
211
221
|
event.sender.setZoomLevel(zoomLevel);
|
|
212
222
|
});
|
|
@@ -22,17 +22,30 @@ import { ContainerModule, Container } from 'inversify';
|
|
|
22
22
|
import { LogLevel } from '../common/logger';
|
|
23
23
|
import { LogLevelCliContribution } from './logger-cli-contribution';
|
|
24
24
|
import * as sinon from 'sinon';
|
|
25
|
+
import { Disposable, DisposableCollection } from '../common';
|
|
25
26
|
|
|
26
27
|
// Allow creating temporary files, but remove them when we are done.
|
|
27
28
|
const track = temp.track();
|
|
28
29
|
|
|
29
30
|
let cli: LogLevelCliContribution;
|
|
30
31
|
let consoleErrorSpy: sinon.SinonSpy;
|
|
32
|
+
let container: Container;
|
|
33
|
+
let toDisposeAfter: DisposableCollection;
|
|
31
34
|
|
|
32
35
|
describe('log-level-cli-contribution', () => {
|
|
33
36
|
|
|
37
|
+
before(() => {
|
|
38
|
+
toDisposeAfter = new DisposableCollection(
|
|
39
|
+
Disposable.create(() => track.cleanupSync())
|
|
40
|
+
);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
after(() => {
|
|
44
|
+
toDisposeAfter.dispose();
|
|
45
|
+
});
|
|
46
|
+
|
|
34
47
|
beforeEach(() => {
|
|
35
|
-
|
|
48
|
+
container = new Container();
|
|
36
49
|
|
|
37
50
|
const module = new ContainerModule(bind => {
|
|
38
51
|
bind(LogLevelCliContribution).toSelf().inSingletonScope();
|
|
@@ -47,8 +60,10 @@ describe('log-level-cli-contribution', () => {
|
|
|
47
60
|
consoleErrorSpy = sinon.spy(console, 'error');
|
|
48
61
|
});
|
|
49
62
|
|
|
50
|
-
afterEach(() => {
|
|
63
|
+
afterEach(async () => {
|
|
51
64
|
consoleErrorSpy.restore();
|
|
65
|
+
await cli.dispose();
|
|
66
|
+
container.unload();
|
|
52
67
|
});
|
|
53
68
|
|
|
54
69
|
it('should use --log-level flag', async () => {
|
|
@@ -19,9 +19,10 @@ import { injectable } from 'inversify';
|
|
|
19
19
|
import { LogLevel } from '../common/logger';
|
|
20
20
|
import { CliContribution } from './cli';
|
|
21
21
|
import * as fs from 'fs-extra';
|
|
22
|
-
import { subscribe } from '@parcel/watcher';
|
|
22
|
+
import { AsyncSubscription, subscribe } from '@parcel/watcher';
|
|
23
23
|
import { Event, Emitter } from '../common/event';
|
|
24
24
|
import * as path from 'path';
|
|
25
|
+
import { Disposable, DisposableCollection } from '../common';
|
|
25
26
|
|
|
26
27
|
/** Maps logger names to log levels. */
|
|
27
28
|
export interface LogLevels {
|
|
@@ -34,9 +35,11 @@ export interface LogLevels {
|
|
|
34
35
|
* what the log level per logger should be.
|
|
35
36
|
*/
|
|
36
37
|
@injectable()
|
|
37
|
-
export class LogLevelCliContribution implements CliContribution {
|
|
38
|
+
export class LogLevelCliContribution implements CliContribution, Disposable {
|
|
38
39
|
|
|
39
40
|
protected _logLevels: LogLevels = {};
|
|
41
|
+
protected asyncSubscriptions: AsyncSubscription[] = [];
|
|
42
|
+
protected toDispose = new DisposableCollection();
|
|
40
43
|
|
|
41
44
|
/**
|
|
42
45
|
* Log level to use for loggers not specified in `logLevels`.
|
|
@@ -59,6 +62,10 @@ export class LogLevelCliContribution implements CliContribution {
|
|
|
59
62
|
return this._logFile;
|
|
60
63
|
}
|
|
61
64
|
|
|
65
|
+
constructor() {
|
|
66
|
+
this.toDispose.push(this.logConfigChangedEvent);
|
|
67
|
+
}
|
|
68
|
+
|
|
62
69
|
configure(conf: yargs.Argv): void {
|
|
63
70
|
conf.option('log-level', {
|
|
64
71
|
description: 'Sets the default log level',
|
|
@@ -129,7 +136,7 @@ export class LogLevelCliContribution implements CliContribution {
|
|
|
129
136
|
|
|
130
137
|
protected async watchLogConfigFile(filename: string): Promise<void> {
|
|
131
138
|
const dir = path.dirname(filename);
|
|
132
|
-
await subscribe(dir, async (err, events) => {
|
|
139
|
+
const subscription = await subscribe(dir, async (err, events) => {
|
|
133
140
|
if (err) {
|
|
134
141
|
console.log(`Error during log file watching ${filename}: ${err}`);
|
|
135
142
|
return;
|
|
@@ -150,6 +157,14 @@ export class LogLevelCliContribution implements CliContribution {
|
|
|
150
157
|
console.error(`Error reading log config file ${filename}: ${e}`);
|
|
151
158
|
}
|
|
152
159
|
});
|
|
160
|
+
this.asyncSubscriptions.push(subscription);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
async dispose(): Promise<void> {
|
|
164
|
+
for (const sub of this.asyncSubscriptions) {
|
|
165
|
+
sub.unsubscribe();
|
|
166
|
+
}
|
|
167
|
+
this.toDispose.dispose();
|
|
153
168
|
}
|
|
154
169
|
|
|
155
170
|
protected async slurpLogConfigFile(filename: string): Promise<void> {
|