@theia/core 1.39.0-next.1 → 1.39.0-next.11
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/README.md +6 -6
- package/lib/browser/dialogs.d.ts +15 -9
- package/lib/browser/dialogs.d.ts.map +1 -1
- package/lib/browser/dialogs.js +67 -54
- package/lib/browser/dialogs.js.map +1 -1
- package/lib/browser/saveable.d.ts.map +1 -1
- package/lib/browser/saveable.js +4 -2
- package/lib/browser/saveable.js.map +1 -1
- package/lib/browser/secondary-window-handler.d.ts +2 -2
- package/lib/browser/secondary-window-handler.d.ts.map +1 -1
- package/lib/browser/secondary-window-handler.js +8 -34
- package/lib/browser/secondary-window-handler.js.map +1 -1
- package/lib/browser/styling-service.d.ts +5 -2
- package/lib/browser/styling-service.d.ts.map +1 -1
- package/lib/browser/styling-service.js +15 -5
- package/lib/browser/styling-service.js.map +1 -1
- package/lib/browser/widgets/widget.d.ts +1 -0
- package/lib/browser/widgets/widget.d.ts.map +1 -1
- package/lib/browser/widgets/widget.js +7 -3
- package/lib/browser/widgets/widget.js.map +1 -1
- package/lib/browser/window/default-secondary-window-service.d.ts +5 -3
- package/lib/browser/window/default-secondary-window-service.d.ts.map +1 -1
- package/lib/browser/window/default-secondary-window-service.js +62 -18
- package/lib/browser/window/default-secondary-window-service.js.map +1 -1
- package/lib/browser/window/secondary-window-service.d.ts +3 -1
- package/lib/browser/window/secondary-window-service.d.ts.map +1 -1
- package/lib/browser/window/secondary-window-service.js.map +1 -1
- package/lib/common/message-rpc/rpc-message-encoder.d.ts +3 -3
- package/lib/common/message-rpc/rpc-message-encoder.d.ts.map +1 -1
- package/lib/common/message-rpc/rpc-message-encoder.js +2 -2
- package/lib/common/message-rpc/rpc-message-encoder.js.map +1 -1
- package/lib/common/message-rpc/rpc-protocol.d.ts +1 -1
- package/lib/common/message-rpc/rpc-protocol.d.ts.map +1 -1
- package/lib/common/message-rpc/rpc-protocol.js +8 -4
- package/lib/common/message-rpc/rpc-protocol.js.map +1 -1
- package/lib/common/messaging/proxy-factory.d.ts +6 -6
- package/lib/common/messaging/proxy-factory.d.ts.map +1 -1
- package/lib/common/messaging/proxy-factory.js +11 -10
- package/lib/common/messaging/proxy-factory.js.map +1 -1
- package/lib/electron-browser/preload.d.ts.map +1 -1
- package/lib/electron-browser/preload.js +20 -15
- package/lib/electron-browser/preload.js.map +1 -1
- package/lib/electron-browser/window/electron-secondary-window-service.d.ts +3 -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 +7 -2
- 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 +1 -0
- package/lib/electron-main/electron-api-main.d.ts.map +1 -1
- package/lib/electron-main/electron-api-main.js +16 -0
- package/lib/electron-main/electron-api-main.js.map +1 -1
- package/lib/electron-main/theia-electron-window.d.ts +1 -0
- package/lib/electron-main/theia-electron-window.d.ts.map +1 -1
- package/lib/electron-main/theia-electron-window.js +32 -0
- package/lib/electron-main/theia-electron-window.js.map +1 -1
- package/package.json +4 -4
- package/src/browser/dialogs.ts +69 -52
- package/src/browser/saveable.ts +4 -2
- package/src/browser/secondary-window-handler.ts +7 -38
- package/src/browser/styling-service.ts +17 -6
- package/src/browser/widgets/widget.ts +4 -0
- package/src/browser/window/default-secondary-window-service.ts +67 -18
- package/src/browser/window/secondary-window-service.ts +4 -1
- package/src/common/message-rpc/rpc-message-encoder.ts +4 -4
- package/src/common/message-rpc/rpc-protocol.ts +8 -4
- package/src/common/messaging/proxy-factory.ts +13 -15
- package/src/electron-browser/preload.ts +21 -1
- package/src/electron-browser/window/electron-secondary-window-service.ts +8 -2
- package/src/electron-common/electron-api.ts +4 -0
- package/src/electron-main/electron-api-main.ts +19 -1
- package/src/electron-main/theia-electron-window.ts +30 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@theia/core",
|
|
3
|
-
"version": "1.39.0-next.
|
|
3
|
+
"version": "1.39.0-next.11+c42adc25d",
|
|
4
4
|
"description": "Theia is a cloud & desktop IDE framework implemented in TypeScript.",
|
|
5
5
|
"main": "lib/common/index.js",
|
|
6
6
|
"typings": "lib/common/index.d.ts",
|
|
@@ -16,8 +16,8 @@
|
|
|
16
16
|
"@phosphor/signaling": "1",
|
|
17
17
|
"@phosphor/virtualdom": "1",
|
|
18
18
|
"@phosphor/widgets": "1",
|
|
19
|
-
"@theia/application-package": "1.39.0-next.
|
|
20
|
-
"@theia/request": "1.39.0-next.
|
|
19
|
+
"@theia/application-package": "1.39.0-next.11+c42adc25d",
|
|
20
|
+
"@theia/request": "1.39.0-next.11+c42adc25d",
|
|
21
21
|
"@types/body-parser": "^1.16.4",
|
|
22
22
|
"@types/cookie": "^0.3.3",
|
|
23
23
|
"@types/dompurify": "^2.2.2",
|
|
@@ -206,5 +206,5 @@
|
|
|
206
206
|
"nyc": {
|
|
207
207
|
"extends": "../../configs/nyc.json"
|
|
208
208
|
},
|
|
209
|
-
"gitHead": "
|
|
209
|
+
"gitHead": "c42adc25db04d38e9d300a56b9f91ba9f46601f3"
|
|
210
210
|
}
|
package/src/browser/dialogs.ts
CHANGED
|
@@ -84,10 +84,9 @@ export class DialogOverlayService implements FrontendApplicationContribution {
|
|
|
84
84
|
|
|
85
85
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
86
86
|
protected readonly dialogs: AbstractDialog<any>[] = [];
|
|
87
|
+
protected readonly documents: Document[] = [];
|
|
87
88
|
|
|
88
89
|
constructor() {
|
|
89
|
-
addKeyListener(document.body, Key.ENTER, e => this.handleEnter(e));
|
|
90
|
-
addKeyListener(document.body, Key.ESCAPE, e => this.handleEscape(e));
|
|
91
90
|
}
|
|
92
91
|
|
|
93
92
|
initialize(): void {
|
|
@@ -101,6 +100,11 @@ export class DialogOverlayService implements FrontendApplicationContribution {
|
|
|
101
100
|
|
|
102
101
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
103
102
|
push(dialog: AbstractDialog<any>): Disposable {
|
|
103
|
+
if (this.documents.findIndex(document => document === dialog.node.ownerDocument) < 0) {
|
|
104
|
+
addKeyListener(dialog.node.ownerDocument.body, Key.ENTER, e => this.handleEnter(e));
|
|
105
|
+
addKeyListener(dialog.node.ownerDocument.body, Key.ESCAPE, e => this.handleEscape(e));
|
|
106
|
+
this.documents.push(dialog.node.ownerDocument);
|
|
107
|
+
}
|
|
104
108
|
this.dialogs.unshift(dialog);
|
|
105
109
|
return Disposable.create(() => {
|
|
106
110
|
const index = this.dialogs.indexOf(dialog);
|
|
@@ -147,9 +151,10 @@ export abstract class AbstractDialog<T> extends BaseWidget {
|
|
|
147
151
|
protected activeElement: HTMLElement | undefined;
|
|
148
152
|
|
|
149
153
|
constructor(
|
|
150
|
-
|
|
154
|
+
protected readonly props: DialogProps,
|
|
155
|
+
options?: Widget.IOptions
|
|
151
156
|
) {
|
|
152
|
-
super();
|
|
157
|
+
super(options);
|
|
153
158
|
this.id = 'theia-dialog-shell';
|
|
154
159
|
this.addClass('dialogOverlay');
|
|
155
160
|
this.toDispose.push(Disposable.create(() => {
|
|
@@ -157,7 +162,7 @@ export abstract class AbstractDialog<T> extends BaseWidget {
|
|
|
157
162
|
Widget.detach(this);
|
|
158
163
|
}
|
|
159
164
|
}));
|
|
160
|
-
const container =
|
|
165
|
+
const container = this.node.ownerDocument.createElement('div');
|
|
161
166
|
container.classList.add('dialogBlock');
|
|
162
167
|
if (props.maxWidth === undefined) {
|
|
163
168
|
container.setAttribute('style', 'max-width: none');
|
|
@@ -166,31 +171,31 @@ export abstract class AbstractDialog<T> extends BaseWidget {
|
|
|
166
171
|
}
|
|
167
172
|
this.node.appendChild(container);
|
|
168
173
|
|
|
169
|
-
const titleContentNode =
|
|
174
|
+
const titleContentNode = this.node.ownerDocument.createElement('div');
|
|
170
175
|
titleContentNode.classList.add('dialogTitle');
|
|
171
176
|
container.appendChild(titleContentNode);
|
|
172
177
|
|
|
173
|
-
this.titleNode =
|
|
178
|
+
this.titleNode = this.node.ownerDocument.createElement('div');
|
|
174
179
|
this.titleNode.textContent = props.title;
|
|
175
180
|
titleContentNode.appendChild(this.titleNode);
|
|
176
181
|
|
|
177
|
-
this.closeCrossNode =
|
|
182
|
+
this.closeCrossNode = this.node.ownerDocument.createElement('i');
|
|
178
183
|
this.closeCrossNode.classList.add(...codiconArray('close'));
|
|
179
184
|
this.closeCrossNode.classList.add('closeButton');
|
|
180
185
|
titleContentNode.appendChild(this.closeCrossNode);
|
|
181
186
|
|
|
182
|
-
this.contentNode =
|
|
187
|
+
this.contentNode = this.node.ownerDocument.createElement('div');
|
|
183
188
|
this.contentNode.classList.add('dialogContent');
|
|
184
189
|
if (props.wordWrap !== undefined) {
|
|
185
190
|
this.contentNode.setAttribute('style', `word-wrap: ${props.wordWrap}`);
|
|
186
191
|
}
|
|
187
192
|
container.appendChild(this.contentNode);
|
|
188
193
|
|
|
189
|
-
this.controlPanel =
|
|
194
|
+
this.controlPanel = this.node.ownerDocument.createElement('div');
|
|
190
195
|
this.controlPanel.classList.add('dialogControl');
|
|
191
196
|
container.appendChild(this.controlPanel);
|
|
192
197
|
|
|
193
|
-
this.errorMessageNode =
|
|
198
|
+
this.errorMessageNode = this.node.ownerDocument.createElement('div');
|
|
194
199
|
this.errorMessageNode.classList.add('error');
|
|
195
200
|
this.errorMessageNode.setAttribute('style', 'flex: 2');
|
|
196
201
|
this.controlPanel.appendChild(this.errorMessageNode);
|
|
@@ -199,17 +204,18 @@ export abstract class AbstractDialog<T> extends BaseWidget {
|
|
|
199
204
|
}
|
|
200
205
|
|
|
201
206
|
protected appendCloseButton(text: string = Dialog.CANCEL): HTMLButtonElement {
|
|
202
|
-
this.closeButton = this.
|
|
203
|
-
this.controlPanel.appendChild(this.closeButton);
|
|
204
|
-
this.closeButton.classList.add('secondary');
|
|
205
|
-
return this.closeButton;
|
|
207
|
+
return this.closeButton = this.appendButton(text, false);
|
|
206
208
|
}
|
|
207
209
|
|
|
208
210
|
protected appendAcceptButton(text: string = Dialog.OK): HTMLButtonElement {
|
|
209
|
-
this.acceptButton = this.
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
211
|
+
return this.acceptButton = this.appendButton(text, true);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
protected appendButton(text: string, primary: boolean): HTMLButtonElement {
|
|
215
|
+
const button = this.createButton(text);
|
|
216
|
+
this.controlPanel.appendChild(button);
|
|
217
|
+
button.classList.add(primary ? 'main' : 'secondary');
|
|
218
|
+
return button;
|
|
213
219
|
}
|
|
214
220
|
|
|
215
221
|
protected createButton(text: string): HTMLButtonElement {
|
|
@@ -254,7 +260,7 @@ export abstract class AbstractDialog<T> extends BaseWidget {
|
|
|
254
260
|
if (this.resolve) {
|
|
255
261
|
return Promise.reject(new Error('The dialog is already opened.'));
|
|
256
262
|
}
|
|
257
|
-
this.activeElement =
|
|
263
|
+
this.activeElement = this.node.ownerDocument.activeElement as HTMLElement;
|
|
258
264
|
return new Promise<T | undefined>((resolve, reject) => {
|
|
259
265
|
this.resolve = resolve;
|
|
260
266
|
this.reject = reject;
|
|
@@ -263,7 +269,7 @@ export abstract class AbstractDialog<T> extends BaseWidget {
|
|
|
263
269
|
this.reject = undefined;
|
|
264
270
|
}));
|
|
265
271
|
|
|
266
|
-
Widget.attach(this,
|
|
272
|
+
Widget.attach(this, this.node.ownerDocument.body);
|
|
267
273
|
this.activate();
|
|
268
274
|
});
|
|
269
275
|
}
|
|
@@ -351,8 +357,12 @@ export abstract class AbstractDialog<T> extends BaseWidget {
|
|
|
351
357
|
}
|
|
352
358
|
|
|
353
359
|
@injectable()
|
|
354
|
-
export class
|
|
360
|
+
export class MessageDialogProps extends DialogProps {
|
|
355
361
|
readonly msg: string | HTMLElement;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
@injectable()
|
|
365
|
+
export class ConfirmDialogProps extends MessageDialogProps {
|
|
356
366
|
readonly cancel?: string;
|
|
357
367
|
readonly ok?: string;
|
|
358
368
|
}
|
|
@@ -383,7 +393,7 @@ export class ConfirmDialog extends AbstractDialog<boolean> {
|
|
|
383
393
|
|
|
384
394
|
protected createMessageNode(msg: string | HTMLElement): HTMLElement {
|
|
385
395
|
if (typeof msg === 'string') {
|
|
386
|
-
const messageNode =
|
|
396
|
+
const messageNode = this.node.ownerDocument.createElement('div');
|
|
387
397
|
messageNode.textContent = msg;
|
|
388
398
|
return messageNode;
|
|
389
399
|
}
|
|
@@ -401,48 +411,52 @@ export async function confirmExit(): Promise<boolean> {
|
|
|
401
411
|
return safeToExit === true;
|
|
402
412
|
}
|
|
403
413
|
|
|
404
|
-
export class ConfirmSaveDialogProps extends
|
|
414
|
+
export class ConfirmSaveDialogProps extends MessageDialogProps {
|
|
415
|
+
readonly cancel: string;
|
|
416
|
+
readonly dontSave: string;
|
|
405
417
|
readonly save: string;
|
|
406
|
-
performSave: () => Promise<void>;
|
|
407
418
|
}
|
|
408
419
|
|
|
409
|
-
|
|
420
|
+
// Dialog prompting the user to confirm whether they wish to save changes or not
|
|
421
|
+
export class ConfirmSaveDialog extends AbstractDialog<boolean | undefined> {
|
|
422
|
+
protected result?: boolean = false;
|
|
410
423
|
|
|
411
|
-
protected saveButton: HTMLButtonElement | undefined;
|
|
412
424
|
constructor(
|
|
413
425
|
@inject(ConfirmSaveDialogProps) protected override readonly props: ConfirmSaveDialogProps
|
|
414
426
|
) {
|
|
415
427
|
super(props);
|
|
428
|
+
// Append message and buttons to the dialog
|
|
416
429
|
this.contentNode.appendChild(this.createMessageNode(this.props.msg));
|
|
417
|
-
|
|
418
|
-
this.
|
|
419
|
-
|
|
420
|
-
if (typeof child !== 'undefined') {
|
|
421
|
-
this.controlPanel.appendChild(child);
|
|
422
|
-
}
|
|
423
|
-
});
|
|
424
|
-
this.appendSaveButton(props.save).addEventListener('click', async () => {
|
|
425
|
-
await props.performSave();
|
|
426
|
-
this.acceptButton?.click();
|
|
427
|
-
});
|
|
430
|
+
this.closeButton = this.appendButtonAndSetResult(props.cancel, false);
|
|
431
|
+
this.appendButtonAndSetResult(props.dontSave, false, false);
|
|
432
|
+
this.acceptButton = this.appendButtonAndSetResult(props.save, true, true);
|
|
428
433
|
}
|
|
429
434
|
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
this.controlPanel.appendChild(this.saveButton);
|
|
433
|
-
this.saveButton.classList.add('main');
|
|
434
|
-
return this.saveButton;
|
|
435
|
+
get value(): boolean | undefined {
|
|
436
|
+
return this.result;
|
|
435
437
|
}
|
|
436
438
|
|
|
437
|
-
protected
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
439
|
+
protected createMessageNode(msg: string | HTMLElement): HTMLElement {
|
|
440
|
+
if (typeof msg === 'string') {
|
|
441
|
+
const messageNode = document.createElement('div');
|
|
442
|
+
messageNode.textContent = msg;
|
|
443
|
+
return messageNode;
|
|
441
444
|
}
|
|
445
|
+
return msg;
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
protected appendButtonAndSetResult(text: string, primary: boolean, result?: boolean): HTMLButtonElement {
|
|
449
|
+
const button = this.appendButton(text, primary);
|
|
450
|
+
button.addEventListener('click', () => {
|
|
451
|
+
this.result = result;
|
|
452
|
+
this.accept();
|
|
453
|
+
});
|
|
454
|
+
return button;
|
|
442
455
|
}
|
|
443
456
|
|
|
444
457
|
}
|
|
445
458
|
|
|
459
|
+
// Asks the user to confirm whether they want to exit with or without saving the changes
|
|
446
460
|
export async function confirmExitWithOrWithoutSaving(captionsToSave: string[], performSave: () => Promise<void>): Promise<boolean> {
|
|
447
461
|
const div: HTMLElement = document.createElement('div');
|
|
448
462
|
div.innerText = nls.localizeByDefault("Your changes will be lost if you don't save them.");
|
|
@@ -458,15 +472,18 @@ export async function confirmExitWithOrWithoutSaving(captionsToSave: string[], p
|
|
|
458
472
|
});
|
|
459
473
|
span.appendChild(document.createElement('br'));
|
|
460
474
|
div.appendChild(span);
|
|
461
|
-
const
|
|
475
|
+
const result = await new ConfirmSaveDialog({
|
|
462
476
|
title: nls.localizeByDefault('Do you want to save the changes to the following {0} files?', captionsToSave.length),
|
|
463
477
|
msg: div,
|
|
464
|
-
|
|
478
|
+
dontSave: nls.localizeByDefault("Don't Save"),
|
|
465
479
|
save: nls.localizeByDefault('Save All'),
|
|
466
|
-
cancel: Dialog.CANCEL
|
|
467
|
-
performSave: performSave
|
|
480
|
+
cancel: Dialog.CANCEL
|
|
468
481
|
}).open();
|
|
469
|
-
|
|
482
|
+
|
|
483
|
+
if (result) {
|
|
484
|
+
await performSave();
|
|
485
|
+
}
|
|
486
|
+
return result !== undefined;
|
|
470
487
|
} else {
|
|
471
488
|
// fallback if not passed with an empty caption-list.
|
|
472
489
|
return confirmExit();
|
package/src/browser/saveable.ts
CHANGED
|
@@ -287,14 +287,16 @@ export class ShouldSaveDialog extends AbstractDialog<boolean> {
|
|
|
287
287
|
constructor(widget: Widget) {
|
|
288
288
|
super({
|
|
289
289
|
title: nls.localizeByDefault('Do you want to save the changes you made to {0}?', widget.title.label || widget.title.caption)
|
|
290
|
+
}, {
|
|
291
|
+
node: widget.node.ownerDocument.createElement('div')
|
|
290
292
|
});
|
|
291
293
|
|
|
292
|
-
const messageNode =
|
|
294
|
+
const messageNode = this.node.ownerDocument.createElement('div');
|
|
293
295
|
messageNode.textContent = nls.localizeByDefault("Your changes will be lost if you don't save them.");
|
|
294
296
|
messageNode.setAttribute('style', 'flex: 1 100%; padding-bottom: calc(var(--theia-ui-padding)*3);');
|
|
295
297
|
this.contentNode.appendChild(messageNode);
|
|
296
|
-
this.dontSaveButton = this.appendDontSaveButton();
|
|
297
298
|
this.appendCloseButton();
|
|
299
|
+
this.dontSaveButton = this.appendDontSaveButton();
|
|
298
300
|
this.appendAcceptButton(nls.localizeByDefault('Save'));
|
|
299
301
|
}
|
|
300
302
|
|
|
@@ -23,6 +23,7 @@ import { Emitter } from '../common/event';
|
|
|
23
23
|
import { SecondaryWindowService } from './window/secondary-window-service';
|
|
24
24
|
import { KeybindingRegistry } from './keybinding';
|
|
25
25
|
import { ColorApplicationContribution } from './color-application-contribution';
|
|
26
|
+
import { StylingService } from './styling-service';
|
|
26
27
|
|
|
27
28
|
/** Widget to be contained directly in a secondary window. */
|
|
28
29
|
class SecondaryWindowRootWidget extends Widget {
|
|
@@ -50,8 +51,6 @@ class SecondaryWindowRootWidget extends Widget {
|
|
|
50
51
|
*/
|
|
51
52
|
@injectable()
|
|
52
53
|
export class SecondaryWindowHandler {
|
|
53
|
-
/** List of currently open secondary windows. Window references should be removed once the window is closed. */
|
|
54
|
-
protected readonly secondaryWindows: Window[] = [];
|
|
55
54
|
/** List of widgets in secondary windows. */
|
|
56
55
|
protected readonly _widgets: ExtractableWidget[] = [];
|
|
57
56
|
|
|
@@ -63,6 +62,9 @@ export class SecondaryWindowHandler {
|
|
|
63
62
|
@inject(ColorApplicationContribution)
|
|
64
63
|
protected colorAppContribution: ColorApplicationContribution;
|
|
65
64
|
|
|
65
|
+
@inject(StylingService)
|
|
66
|
+
protected stylingService: StylingService;
|
|
67
|
+
|
|
66
68
|
protected readonly onDidAddWidgetEmitter = new Emitter<Widget>();
|
|
67
69
|
/** Subscribe to get notified when a widget is added to this handler, i.e. the widget was moved to an secondary window . */
|
|
68
70
|
readonly onDidAddWidget = this.onDidAddWidgetEmitter.event;
|
|
@@ -95,33 +97,6 @@ export class SecondaryWindowHandler {
|
|
|
95
97
|
return;
|
|
96
98
|
}
|
|
97
99
|
this.applicationShell = shell;
|
|
98
|
-
|
|
99
|
-
// Set up messaging with secondary windows
|
|
100
|
-
window.addEventListener('message', (event: MessageEvent) => {
|
|
101
|
-
console.trace('Message on main window', event);
|
|
102
|
-
if (event.data.fromSecondary) {
|
|
103
|
-
console.trace('Message comes from secondary window');
|
|
104
|
-
return;
|
|
105
|
-
}
|
|
106
|
-
if (event.data.fromMain) {
|
|
107
|
-
console.trace('Message has mainWindow marker, therefore ignore it');
|
|
108
|
-
return;
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
// Filter setImmediate messages. Do not forward because these come in with very high frequency.
|
|
112
|
-
// They are not needed in secondary windows because these messages are just a work around
|
|
113
|
-
// to make setImmediate work in the main window: https://developer.mozilla.org/en-US/docs/Web/API/Window/setImmediate
|
|
114
|
-
if (typeof event.data === 'string' && event.data.startsWith('setImmediate')) {
|
|
115
|
-
return;
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
console.trace('Delegate main window message to secondary windows', event);
|
|
119
|
-
this.secondaryWindows.forEach(secondaryWindow => {
|
|
120
|
-
if (!secondaryWindow.window.closed) {
|
|
121
|
-
secondaryWindow.window.postMessage({ ...event.data, fromMain: true }, '*');
|
|
122
|
-
}
|
|
123
|
-
});
|
|
124
|
-
});
|
|
125
100
|
}
|
|
126
101
|
|
|
127
102
|
/**
|
|
@@ -139,21 +114,13 @@ export class SecondaryWindowHandler {
|
|
|
139
114
|
return;
|
|
140
115
|
}
|
|
141
116
|
|
|
142
|
-
const newWindow = this.secondaryWindowService.createSecondaryWindow(
|
|
143
|
-
this.applicationShell.closeWidget(widget.id);
|
|
144
|
-
const extIndex = this.secondaryWindows.indexOf(closed);
|
|
145
|
-
if (extIndex > -1) {
|
|
146
|
-
this.secondaryWindows.splice(extIndex, 1);
|
|
147
|
-
}
|
|
148
|
-
});
|
|
117
|
+
const newWindow = this.secondaryWindowService.createSecondaryWindow(widget, this.applicationShell);
|
|
149
118
|
|
|
150
119
|
if (!newWindow) {
|
|
151
120
|
this.messageService.error('The widget could not be moved to a secondary window because the window creation failed. Please make sure to allow popups.');
|
|
152
121
|
return;
|
|
153
122
|
}
|
|
154
123
|
|
|
155
|
-
this.secondaryWindows.push(newWindow);
|
|
156
|
-
|
|
157
124
|
const mainWindowTitle = document.title;
|
|
158
125
|
newWindow.onload = () => {
|
|
159
126
|
this.keybindings.registerEventListeners(newWindow);
|
|
@@ -168,6 +135,7 @@ export class SecondaryWindowHandler {
|
|
|
168
135
|
return;
|
|
169
136
|
}
|
|
170
137
|
const unregisterWithColorContribution = this.colorAppContribution.registerWindow(newWindow);
|
|
138
|
+
const unregisterWithStylingService = this.stylingService.registerWindow(newWindow);
|
|
171
139
|
|
|
172
140
|
widget.secondaryWindow = newWindow;
|
|
173
141
|
const rootWidget = new SecondaryWindowRootWidget();
|
|
@@ -182,6 +150,7 @@ export class SecondaryWindowHandler {
|
|
|
182
150
|
// Close the window if the widget is disposed, e.g. by a command closing all widgets.
|
|
183
151
|
widget.disposed.connect(() => {
|
|
184
152
|
unregisterWithColorContribution.dispose();
|
|
153
|
+
unregisterWithStylingService.dispose();
|
|
185
154
|
this.removeWidget(widget);
|
|
186
155
|
if (!newWindow.closed) {
|
|
187
156
|
newWindow.close();
|
|
@@ -21,6 +21,7 @@ import { ColorRegistry } from './color-registry';
|
|
|
21
21
|
import { DecorationStyle } from './decoration-style';
|
|
22
22
|
import { FrontendApplicationContribution } from './frontend-application';
|
|
23
23
|
import { ThemeService } from './theming';
|
|
24
|
+
import { Disposable } from '../common';
|
|
24
25
|
|
|
25
26
|
export const StylingParticipant = Symbol('StylingParticipant');
|
|
26
27
|
|
|
@@ -40,8 +41,7 @@ export interface CssStyleCollector {
|
|
|
40
41
|
|
|
41
42
|
@injectable()
|
|
42
43
|
export class StylingService implements FrontendApplicationContribution {
|
|
43
|
-
|
|
44
|
-
protected cssElement = DecorationStyle.createStyleElement('contributedColorTheme');
|
|
44
|
+
protected cssElements = new Map<Window, HTMLStyleElement>();
|
|
45
45
|
|
|
46
46
|
@inject(ThemeService)
|
|
47
47
|
protected readonly themeService: ThemeService;
|
|
@@ -53,11 +53,22 @@ export class StylingService implements FrontendApplicationContribution {
|
|
|
53
53
|
protected readonly themingParticipants: ContributionProvider<StylingParticipant>;
|
|
54
54
|
|
|
55
55
|
onStart(): void {
|
|
56
|
-
this.
|
|
57
|
-
this.themeService.onDidColorThemeChange(e => this.
|
|
56
|
+
this.registerWindow(window);
|
|
57
|
+
this.themeService.onDidColorThemeChange(e => this.applyStylingToWindows(e.newTheme));
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
registerWindow(win: Window): Disposable {
|
|
61
|
+
const cssElement = DecorationStyle.createStyleElement('contributedColorTheme', win.document.head);
|
|
62
|
+
this.cssElements.set(win, cssElement);
|
|
63
|
+
this.applyStyling(this.themeService.getCurrentTheme(), cssElement);
|
|
64
|
+
return Disposable.create(() => this.cssElements.delete(win));
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
protected applyStylingToWindows(theme: Theme): void {
|
|
68
|
+
this.cssElements.forEach(cssElement => this.applyStyling(theme, cssElement));
|
|
58
69
|
}
|
|
59
70
|
|
|
60
|
-
protected applyStyling(theme: Theme): void {
|
|
71
|
+
protected applyStyling(theme: Theme, cssElement: HTMLStyleElement): void {
|
|
61
72
|
const rules: string[] = [];
|
|
62
73
|
const colorTheme: ColorTheme = {
|
|
63
74
|
type: theme.type,
|
|
@@ -71,6 +82,6 @@ export class StylingService implements FrontendApplicationContribution {
|
|
|
71
82
|
themingParticipant.registerThemeStyle(colorTheme, styleCollector);
|
|
72
83
|
}
|
|
73
84
|
const fullCss = rules.join('\n');
|
|
74
|
-
|
|
85
|
+
cssElement.innerText = fullCss;
|
|
75
86
|
}
|
|
76
87
|
}
|
|
@@ -115,6 +115,10 @@ export class BaseWidget extends Widget {
|
|
|
115
115
|
protected scrollBar?: PerfectScrollbar;
|
|
116
116
|
protected scrollOptions?: PerfectScrollbar.Options;
|
|
117
117
|
|
|
118
|
+
constructor(options?: Widget.IOptions) {
|
|
119
|
+
super(options);
|
|
120
|
+
}
|
|
121
|
+
|
|
118
122
|
override dispose(): void {
|
|
119
123
|
if (this.isDisposed) {
|
|
120
124
|
return;
|
|
@@ -16,6 +16,9 @@
|
|
|
16
16
|
import { inject, injectable, postConstruct } from 'inversify';
|
|
17
17
|
import { SecondaryWindowService } from './secondary-window-service';
|
|
18
18
|
import { WindowService } from './window-service';
|
|
19
|
+
import { ExtractableWidget } from '../widgets';
|
|
20
|
+
import { ApplicationShell } from '../shell';
|
|
21
|
+
import { Saveable } from '../saveable';
|
|
19
22
|
|
|
20
23
|
@injectable()
|
|
21
24
|
export class DefaultSecondaryWindowService implements SecondaryWindowService {
|
|
@@ -37,6 +40,33 @@ export class DefaultSecondaryWindowService implements SecondaryWindowService {
|
|
|
37
40
|
|
|
38
41
|
@postConstruct()
|
|
39
42
|
init(): void {
|
|
43
|
+
// Set up messaging with secondary windows
|
|
44
|
+
window.addEventListener('message', (event: MessageEvent) => {
|
|
45
|
+
console.trace('Message on main window', event);
|
|
46
|
+
if (event.data.fromSecondary) {
|
|
47
|
+
console.trace('Message comes from secondary window');
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
if (event.data.fromMain) {
|
|
51
|
+
console.trace('Message has mainWindow marker, therefore ignore it');
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Filter setImmediate messages. Do not forward because these come in with very high frequency.
|
|
56
|
+
// They are not needed in secondary windows because these messages are just a work around
|
|
57
|
+
// to make setImmediate work in the main window: https://developer.mozilla.org/en-US/docs/Web/API/Window/setImmediate
|
|
58
|
+
if (typeof event.data === 'string' && event.data.startsWith('setImmediate')) {
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
console.trace('Delegate main window message to secondary windows', event);
|
|
63
|
+
this.secondaryWindows.forEach(secondaryWindow => {
|
|
64
|
+
if (!secondaryWindow.window.closed) {
|
|
65
|
+
secondaryWindow.window.postMessage({ ...event.data, fromMain: true }, '*');
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
});
|
|
69
|
+
|
|
40
70
|
// Close all open windows when the main window is closed.
|
|
41
71
|
this.windowService.onUnload(() => {
|
|
42
72
|
// Iterate backwards because calling window.close might remove the window from the array
|
|
@@ -46,33 +76,52 @@ export class DefaultSecondaryWindowService implements SecondaryWindowService {
|
|
|
46
76
|
});
|
|
47
77
|
}
|
|
48
78
|
|
|
49
|
-
createSecondaryWindow(
|
|
50
|
-
const win = this.doCreateSecondaryWindow(
|
|
79
|
+
createSecondaryWindow(widget: ExtractableWidget, shell: ApplicationShell): Window | undefined {
|
|
80
|
+
const win = this.doCreateSecondaryWindow(widget, shell);
|
|
51
81
|
if (win) {
|
|
52
82
|
this.secondaryWindows.push(win);
|
|
83
|
+
win.addEventListener('close', () => {
|
|
84
|
+
const extIndex = this.secondaryWindows.indexOf(win);
|
|
85
|
+
if (extIndex > -1) {
|
|
86
|
+
this.secondaryWindows.splice(extIndex, 1);
|
|
87
|
+
};
|
|
88
|
+
});
|
|
53
89
|
}
|
|
54
90
|
return win;
|
|
55
91
|
}
|
|
56
92
|
|
|
57
|
-
protected
|
|
58
|
-
const
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
win.addEventListener('unload', () => {
|
|
63
|
-
this.handleWindowClosed(win, onClose);
|
|
64
|
-
});
|
|
65
|
-
});
|
|
93
|
+
protected findWindow<T>(windowName: string): Window | undefined {
|
|
94
|
+
for (const w of this.secondaryWindows) {
|
|
95
|
+
if (w.name === windowName) {
|
|
96
|
+
return w;
|
|
97
|
+
}
|
|
66
98
|
}
|
|
67
|
-
return
|
|
99
|
+
return undefined;
|
|
68
100
|
}
|
|
69
101
|
|
|
70
|
-
protected
|
|
71
|
-
const
|
|
72
|
-
if (
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
102
|
+
protected doCreateSecondaryWindow(widget: ExtractableWidget, shell: ApplicationShell): Window | undefined {
|
|
103
|
+
const newWindow = window.open(DefaultSecondaryWindowService.SECONDARY_WINDOW_URL, this.nextWindowId(), 'popup') ?? undefined;
|
|
104
|
+
if (newWindow) {
|
|
105
|
+
newWindow.addEventListener('DOMContentLoaded', () => {
|
|
106
|
+
newWindow.addEventListener('beforeunload', evt => {
|
|
107
|
+
const saveable = Saveable.get(widget);
|
|
108
|
+
const wouldLoseState = !!saveable && saveable.dirty && saveable.autoSave === 'off';
|
|
109
|
+
if (wouldLoseState) {
|
|
110
|
+
evt.returnValue = '';
|
|
111
|
+
evt.preventDefault();
|
|
112
|
+
return 'non-empty';
|
|
113
|
+
}
|
|
114
|
+
}, { capture: true });
|
|
115
|
+
|
|
116
|
+
newWindow.addEventListener('close', () => {
|
|
117
|
+
const saveable = Saveable.get(widget);
|
|
118
|
+
shell.closeWidget(widget.id, {
|
|
119
|
+
save: !!saveable && saveable.dirty && saveable.autoSave !== 'off'
|
|
120
|
+
});
|
|
121
|
+
});
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
return newWindow;
|
|
76
125
|
}
|
|
77
126
|
|
|
78
127
|
focus(win: Window): void {
|
|
@@ -14,6 +14,9 @@
|
|
|
14
14
|
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
|
|
15
15
|
// *****************************************************************************
|
|
16
16
|
|
|
17
|
+
import { ApplicationShell } from '../shell';
|
|
18
|
+
import { ExtractableWidget } from '../widgets';
|
|
19
|
+
|
|
17
20
|
export const SecondaryWindowService = Symbol('SecondaryWindowService');
|
|
18
21
|
|
|
19
22
|
/**
|
|
@@ -29,7 +32,7 @@ export interface SecondaryWindowService {
|
|
|
29
32
|
* @param onClose optional callback that is invoked when the secondary window is closed
|
|
30
33
|
* @returns the created window or `undefined` if it could not be created
|
|
31
34
|
*/
|
|
32
|
-
createSecondaryWindow(
|
|
35
|
+
createSecondaryWindow(widget: ExtractableWidget, shell: ApplicationShell): Window | undefined;
|
|
33
36
|
|
|
34
37
|
/** Handles focussing the given secondary window in the browser and on Electron. */
|
|
35
38
|
focus(win: Window): void;
|
|
@@ -51,7 +51,7 @@ export interface RequestMessage {
|
|
|
51
51
|
|
|
52
52
|
export interface NotificationMessage {
|
|
53
53
|
type: RpcMessageType.Notification;
|
|
54
|
-
id
|
|
54
|
+
id?: number;
|
|
55
55
|
method: string;
|
|
56
56
|
args: any[];
|
|
57
57
|
}
|
|
@@ -111,7 +111,7 @@ export interface RpcMessageDecoder {
|
|
|
111
111
|
export interface RpcMessageEncoder {
|
|
112
112
|
cancel(buf: WriteBuffer, requestId: number): void;
|
|
113
113
|
|
|
114
|
-
notification(buf: WriteBuffer,
|
|
114
|
+
notification(buf: WriteBuffer, method: string, args: any[], id?: number): void
|
|
115
115
|
|
|
116
116
|
request(buf: WriteBuffer, requestId: number, method: string, args: any[]): void
|
|
117
117
|
|
|
@@ -130,8 +130,8 @@ export class MsgPackMessageEncoder implements RpcMessageEncoder {
|
|
|
130
130
|
cancel(buf: WriteBuffer, requestId: number): void {
|
|
131
131
|
this.encode<CancelMessage>(buf, { type: RpcMessageType.Cancel, id: requestId });
|
|
132
132
|
}
|
|
133
|
-
notification(buf: WriteBuffer,
|
|
134
|
-
this.encode<NotificationMessage>(buf, { type: RpcMessageType.Notification,
|
|
133
|
+
notification(buf: WriteBuffer, method: string, args: any[], id?: number): void {
|
|
134
|
+
this.encode<NotificationMessage>(buf, { type: RpcMessageType.Notification, method, args, id });
|
|
135
135
|
}
|
|
136
136
|
request(buf: WriteBuffer, requestId: number, method: string, args: any[]): void {
|
|
137
137
|
this.encode<RequestMessage>(buf, { type: RpcMessageType.Request, id: requestId, method, args });
|
|
@@ -77,7 +77,11 @@ export class RpcProtocol {
|
|
|
77
77
|
this.encoder = options.encoder ?? new MsgPackMessageEncoder();
|
|
78
78
|
this.decoder = options.decoder ?? new MsgPackMessageDecoder();
|
|
79
79
|
this.toDispose.push(this.onNotificationEmitter);
|
|
80
|
-
channel.onClose(
|
|
80
|
+
channel.onClose(event => {
|
|
81
|
+
this.pendingRequests.forEach(pending => pending.reject(new Error(event.reason)));
|
|
82
|
+
this.pendingRequests.clear();
|
|
83
|
+
this.toDispose.dispose();
|
|
84
|
+
});
|
|
81
85
|
this.toDispose.push(channel.onMessage(readBuffer => this.handleMessage(this.decoder.parse(readBuffer()))));
|
|
82
86
|
this.mode = options.mode ?? 'default';
|
|
83
87
|
|
|
@@ -98,7 +102,7 @@ export class RpcProtocol {
|
|
|
98
102
|
return;
|
|
99
103
|
}
|
|
100
104
|
case RpcMessageType.Notification: {
|
|
101
|
-
this.handleNotify(message.
|
|
105
|
+
this.handleNotify(message.method, message.args, message.id);
|
|
102
106
|
return;
|
|
103
107
|
}
|
|
104
108
|
}
|
|
@@ -179,7 +183,7 @@ export class RpcProtocol {
|
|
|
179
183
|
}
|
|
180
184
|
|
|
181
185
|
const output = this.channel.getWriteBuffer();
|
|
182
|
-
this.encoder.notification(output,
|
|
186
|
+
this.encoder.notification(output, method, args, this.nextMessageId++);
|
|
183
187
|
output.commit();
|
|
184
188
|
}
|
|
185
189
|
|
|
@@ -226,7 +230,7 @@ export class RpcProtocol {
|
|
|
226
230
|
}
|
|
227
231
|
}
|
|
228
232
|
|
|
229
|
-
protected async handleNotify(
|
|
233
|
+
protected async handleNotify(method: string, args: any[], id?: number): Promise<void> {
|
|
230
234
|
if (this.toDispose.disposed) {
|
|
231
235
|
return;
|
|
232
236
|
}
|