@theia/messages 1.48.1 → 1.48.3
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 +30 -30
- package/lib/browser/messages-frontend-module.d.ts +4 -4
- package/lib/browser/messages-frontend-module.js +41 -41
- package/lib/browser/notification-center-component.d.ts +18 -18
- package/lib/browser/notification-center-component.js +67 -67
- package/lib/browser/notification-component.d.ts +15 -15
- package/lib/browser/notification-component.js +88 -88
- package/lib/browser/notification-content-renderer.d.ts +5 -5
- package/lib/browser/notification-content-renderer.js +35 -35
- package/lib/browser/notification-content-renderer.spec.d.ts +1 -1
- package/lib/browser/notification-content-renderer.spec.js +41 -41
- package/lib/browser/notification-preferences.d.ts +11 -11
- package/lib/browser/notification-preferences.js +46 -46
- package/lib/browser/notification-toasts-component.d.ts +18 -18
- package/lib/browser/notification-toasts-component.js +48 -48
- package/lib/browser/notifications-commands.d.ts +7 -7
- package/lib/browser/notifications-commands.js +47 -47
- package/lib/browser/notifications-contribution.d.ts +21 -21
- package/lib/browser/notifications-contribution.js +203 -203
- package/lib/browser/notifications-manager.d.ts +71 -71
- package/lib/browser/notifications-manager.js +274 -274
- package/lib/browser/notifications-renderer.d.ts +13 -13
- package/lib/browser/notifications-renderer.js +67 -67
- package/package.json +4 -4
- package/src/browser/messages-frontend-module.ts +42 -42
- package/src/browser/notification-center-component.tsx +95 -95
- package/src/browser/notification-component.tsx +128 -128
- package/src/browser/notification-content-renderer.spec.ts +73 -73
- package/src/browser/notification-content-renderer.ts +31 -31
- package/src/browser/notification-preferences.ts +58 -58
- package/src/browser/notification-toasts-component.tsx +67 -67
- package/src/browser/notifications-commands.ts +50 -50
- package/src/browser/notifications-contribution.ts +218 -218
- package/src/browser/notifications-manager.ts +305 -305
- package/src/browser/notifications-renderer.tsx +61 -61
- package/src/browser/style/index.css +17 -17
- package/src/browser/style/notifications.css +283 -283
|
@@ -1,305 +1,305 @@
|
|
|
1
|
-
// *****************************************************************************
|
|
2
|
-
// Copyright (C) 2019 TypeFox and others.
|
|
3
|
-
//
|
|
4
|
-
// This program and the accompanying materials are made available under the
|
|
5
|
-
// terms of the Eclipse Public License v. 2.0 which is available at
|
|
6
|
-
// http://www.eclipse.org/legal/epl-2.0.
|
|
7
|
-
//
|
|
8
|
-
// This Source Code may also be made available under the following Secondary
|
|
9
|
-
// Licenses when the conditions for such availability set forth in the Eclipse
|
|
10
|
-
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
|
|
11
|
-
// with the GNU Classpath Exception which is available at
|
|
12
|
-
// https://www.gnu.org/software/classpath/license.html.
|
|
13
|
-
//
|
|
14
|
-
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
|
|
15
|
-
// *****************************************************************************
|
|
16
|
-
|
|
17
|
-
import { injectable, inject, postConstruct } from '@theia/core/shared/inversify';
|
|
18
|
-
import { MessageClient, MessageType, Message as PlainMessage, ProgressMessage, ProgressUpdate, CancellationToken } from '@theia/core/lib/common';
|
|
19
|
-
import { deepClone } from '@theia/core/lib/common/objects';
|
|
20
|
-
import { Emitter } from '@theia/core';
|
|
21
|
-
import { Deferred } from '@theia/core/lib/common/promise-util';
|
|
22
|
-
import { Md5 } from 'ts-md5';
|
|
23
|
-
import throttle = require('@theia/core/shared/lodash.throttle');
|
|
24
|
-
import { NotificationPreferences } from './notification-preferences';
|
|
25
|
-
import { ContextKeyService, ContextKey } from '@theia/core/lib/browser/context-key-service';
|
|
26
|
-
import { OpenerService } from '@theia/core/lib/browser';
|
|
27
|
-
import URI from '@theia/core/lib/common/uri';
|
|
28
|
-
import { NotificationContentRenderer } from './notification-content-renderer';
|
|
29
|
-
|
|
30
|
-
export interface NotificationUpdateEvent {
|
|
31
|
-
readonly notifications: Notification[];
|
|
32
|
-
readonly toasts: Notification[];
|
|
33
|
-
readonly visibilityState: Notification.Visibility;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
export interface Notification {
|
|
37
|
-
messageId: string;
|
|
38
|
-
message: string;
|
|
39
|
-
source?: string;
|
|
40
|
-
expandable: boolean;
|
|
41
|
-
collapsed: boolean;
|
|
42
|
-
type: Notification.Type;
|
|
43
|
-
actions: string[];
|
|
44
|
-
progress?: number;
|
|
45
|
-
}
|
|
46
|
-
export namespace Notification {
|
|
47
|
-
export type Visibility = 'hidden' | 'toasts' | 'center';
|
|
48
|
-
export type Type = 'info' | 'warning' | 'error' | 'progress';
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
@injectable()
|
|
52
|
-
export class NotificationManager extends MessageClient {
|
|
53
|
-
|
|
54
|
-
@inject(NotificationPreferences)
|
|
55
|
-
protected readonly preferences: NotificationPreferences;
|
|
56
|
-
|
|
57
|
-
@inject(ContextKeyService)
|
|
58
|
-
protected readonly contextKeyService: ContextKeyService;
|
|
59
|
-
|
|
60
|
-
@inject(OpenerService)
|
|
61
|
-
protected readonly openerService: OpenerService;
|
|
62
|
-
|
|
63
|
-
@inject(NotificationContentRenderer)
|
|
64
|
-
protected readonly contentRenderer: NotificationContentRenderer;
|
|
65
|
-
|
|
66
|
-
protected readonly onUpdatedEmitter = new Emitter<NotificationUpdateEvent>();
|
|
67
|
-
readonly onUpdated = this.onUpdatedEmitter.event;
|
|
68
|
-
protected readonly fireUpdatedEvent = throttle(() => {
|
|
69
|
-
const notifications = deepClone(Array.from(this.notifications.values()).filter((notification: Notification) =>
|
|
70
|
-
notification.message
|
|
71
|
-
));
|
|
72
|
-
const toasts = deepClone(Array.from(this.toasts.values()).filter((toast: Notification) =>
|
|
73
|
-
toast.message
|
|
74
|
-
));
|
|
75
|
-
const visibilityState = this.visibilityState;
|
|
76
|
-
this.onUpdatedEmitter.fire({ notifications, toasts, visibilityState });
|
|
77
|
-
}, 250, { leading: true, trailing: true });
|
|
78
|
-
|
|
79
|
-
protected readonly deferredResults = new Map<string, Deferred<string | undefined>>();
|
|
80
|
-
protected readonly notifications = new Map<string, Notification>();
|
|
81
|
-
protected readonly toasts = new Map<string, Notification>();
|
|
82
|
-
|
|
83
|
-
protected notificationToastsVisibleKey: ContextKey<boolean>;
|
|
84
|
-
protected notificationCenterVisibleKey: ContextKey<boolean>;
|
|
85
|
-
protected notificationsVisible: ContextKey<boolean>;
|
|
86
|
-
|
|
87
|
-
@postConstruct()
|
|
88
|
-
protected init(): void {
|
|
89
|
-
this.doInit();
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
protected async doInit(): Promise<void> {
|
|
93
|
-
this.notificationToastsVisibleKey = this.contextKeyService.createKey<boolean>('notificationToastsVisible', false);
|
|
94
|
-
this.notificationCenterVisibleKey = this.contextKeyService.createKey<boolean>('notificationCenterVisible', false);
|
|
95
|
-
this.notificationsVisible = this.contextKeyService.createKey<boolean>('notificationsVisible', false);
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
protected updateContextKeys(): void {
|
|
99
|
-
this.notificationToastsVisibleKey.set(this.toastsVisible);
|
|
100
|
-
this.notificationCenterVisibleKey.set(this.centerVisible);
|
|
101
|
-
this.notificationsVisible.set(this.toastsVisible || this.centerVisible);
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
get toastsVisible(): boolean {
|
|
105
|
-
return this.visibilityState === 'toasts';
|
|
106
|
-
}
|
|
107
|
-
get centerVisible(): boolean {
|
|
108
|
-
return this.visibilityState === 'center';
|
|
109
|
-
}
|
|
110
|
-
protected visibilityState: Notification.Visibility = 'hidden';
|
|
111
|
-
protected setVisibilityState(newState: Notification.Visibility): void {
|
|
112
|
-
const changed = this.visibilityState !== newState;
|
|
113
|
-
this.visibilityState = newState;
|
|
114
|
-
if (changed) {
|
|
115
|
-
this.fireUpdatedEvent();
|
|
116
|
-
this.updateContextKeys();
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
hideCenter(): void {
|
|
121
|
-
this.setVisibilityState('hidden');
|
|
122
|
-
}
|
|
123
|
-
showCenter(): void {
|
|
124
|
-
this.setVisibilityState('center');
|
|
125
|
-
}
|
|
126
|
-
toggleCenter(): void {
|
|
127
|
-
this.setVisibilityState(this.centerVisible ? 'hidden' : 'center');
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
accept(notification: Notification | string, action: string | undefined): void {
|
|
131
|
-
const messageId = this.getId(notification);
|
|
132
|
-
if (!messageId) {
|
|
133
|
-
return;
|
|
134
|
-
}
|
|
135
|
-
this.notifications.delete(messageId);
|
|
136
|
-
this.toasts.delete(messageId);
|
|
137
|
-
const result = this.deferredResults.get(messageId);
|
|
138
|
-
if (!result) {
|
|
139
|
-
return;
|
|
140
|
-
}
|
|
141
|
-
this.deferredResults.delete(messageId);
|
|
142
|
-
if ((this.centerVisible && !this.notifications.size) || (this.toastsVisible && !this.toasts.size)) {
|
|
143
|
-
this.setVisibilityState('hidden');
|
|
144
|
-
}
|
|
145
|
-
result.resolve(action);
|
|
146
|
-
this.fireUpdatedEvent();
|
|
147
|
-
}
|
|
148
|
-
protected find(notification: Notification | string): Notification | undefined {
|
|
149
|
-
return typeof notification === 'string' ? this.notifications.get(notification) : notification;
|
|
150
|
-
}
|
|
151
|
-
protected getId(notification: Notification | string): string {
|
|
152
|
-
return typeof notification === 'string' ? notification : notification.messageId;
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
hide(): void {
|
|
156
|
-
if (this.toastsVisible) {
|
|
157
|
-
this.toasts.clear();
|
|
158
|
-
}
|
|
159
|
-
this.setVisibilityState('hidden');
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
clearAll(): void {
|
|
163
|
-
this.setVisibilityState('hidden');
|
|
164
|
-
Array.from(this.notifications.values()).forEach(n => this.clear(n));
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
clear(notification: Notification | string): void {
|
|
168
|
-
this.accept(notification, undefined);
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
toggleExpansion(notificationId: string): void {
|
|
172
|
-
const notification = this.find(notificationId);
|
|
173
|
-
if (!notification) {
|
|
174
|
-
return;
|
|
175
|
-
}
|
|
176
|
-
notification.collapsed = !notification.collapsed;
|
|
177
|
-
this.fireUpdatedEvent();
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
override showMessage(plainMessage: PlainMessage): Promise<string | undefined> {
|
|
181
|
-
const messageId = this.getMessageId(plainMessage);
|
|
182
|
-
|
|
183
|
-
let notification = this.notifications.get(messageId);
|
|
184
|
-
if (!notification) {
|
|
185
|
-
const message = this.contentRenderer.renderMessage(plainMessage.text);
|
|
186
|
-
const type = this.toNotificationType(plainMessage.type);
|
|
187
|
-
const actions = Array.from(new Set(plainMessage.actions));
|
|
188
|
-
const source = plainMessage.source;
|
|
189
|
-
const expandable = this.isExpandable(message, source, actions);
|
|
190
|
-
const collapsed = expandable;
|
|
191
|
-
notification = { messageId, message, type, actions, expandable, collapsed };
|
|
192
|
-
this.notifications.set(messageId, notification);
|
|
193
|
-
}
|
|
194
|
-
const result = this.deferredResults.get(messageId) || new Deferred<string | undefined>();
|
|
195
|
-
this.deferredResults.set(messageId, result);
|
|
196
|
-
|
|
197
|
-
if (!this.centerVisible) {
|
|
198
|
-
this.toasts.delete(messageId);
|
|
199
|
-
this.toasts.set(messageId, notification);
|
|
200
|
-
this.startHideTimeout(messageId, this.getTimeout(plainMessage));
|
|
201
|
-
this.setVisibilityState('toasts');
|
|
202
|
-
}
|
|
203
|
-
this.fireUpdatedEvent();
|
|
204
|
-
return result.promise;
|
|
205
|
-
}
|
|
206
|
-
protected hideTimeouts = new Map<string, number>();
|
|
207
|
-
protected startHideTimeout(messageId: string, timeout: number): void {
|
|
208
|
-
if (timeout > 0) {
|
|
209
|
-
this.hideTimeouts.set(messageId, window.setTimeout(() => {
|
|
210
|
-
this.hideToast(messageId);
|
|
211
|
-
}, timeout));
|
|
212
|
-
}
|
|
213
|
-
}
|
|
214
|
-
protected hideToast(messageId: string): void {
|
|
215
|
-
this.toasts.delete(messageId);
|
|
216
|
-
if (this.toastsVisible && !this.toasts.size) {
|
|
217
|
-
this.setVisibilityState('hidden');
|
|
218
|
-
} else {
|
|
219
|
-
this.fireUpdatedEvent();
|
|
220
|
-
}
|
|
221
|
-
}
|
|
222
|
-
protected getTimeout(plainMessage: PlainMessage): number {
|
|
223
|
-
if (plainMessage.actions && plainMessage.actions.length > 0) {
|
|
224
|
-
// Ignore the timeout if at least one action is set, and we wait for user interaction.
|
|
225
|
-
return 0;
|
|
226
|
-
}
|
|
227
|
-
return plainMessage.options && plainMessage.options.timeout || this.preferences['notification.timeout'];
|
|
228
|
-
}
|
|
229
|
-
protected isExpandable(message: string, source: string | undefined, actions: string[]): boolean {
|
|
230
|
-
if (!actions.length && source) {
|
|
231
|
-
return true;
|
|
232
|
-
}
|
|
233
|
-
return message.length > 500;
|
|
234
|
-
}
|
|
235
|
-
protected toNotificationType(type?: MessageType): Notification.Type {
|
|
236
|
-
switch (type) {
|
|
237
|
-
case MessageType.Error:
|
|
238
|
-
return 'error';
|
|
239
|
-
case MessageType.Warning:
|
|
240
|
-
return 'warning';
|
|
241
|
-
case MessageType.Progress:
|
|
242
|
-
return 'progress';
|
|
243
|
-
default:
|
|
244
|
-
return 'info';
|
|
245
|
-
}
|
|
246
|
-
}
|
|
247
|
-
protected getMessageId(m: PlainMessage): string {
|
|
248
|
-
return String(Md5.hashStr(`[${m.type}] ${m.text} : ${(m.actions || []).join(' | ')};`));
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
override async showProgress(messageId: string, plainMessage: ProgressMessage, cancellationToken: CancellationToken): Promise<string | undefined> {
|
|
252
|
-
let notification = this.notifications.get(messageId);
|
|
253
|
-
if (!notification) {
|
|
254
|
-
const message = this.contentRenderer.renderMessage(plainMessage.text);
|
|
255
|
-
const type = this.toNotificationType(plainMessage.type);
|
|
256
|
-
const actions = Array.from(new Set(plainMessage.actions));
|
|
257
|
-
const source = plainMessage.source;
|
|
258
|
-
const expandable = this.isExpandable(message, source, actions);
|
|
259
|
-
const collapsed = expandable;
|
|
260
|
-
notification = { messageId, message, type, actions, expandable, collapsed };
|
|
261
|
-
this.notifications.set(messageId, notification);
|
|
262
|
-
|
|
263
|
-
notification.progress = 0;
|
|
264
|
-
cancellationToken.onCancellationRequested(() => {
|
|
265
|
-
this.accept(messageId, ProgressMessage.Cancel);
|
|
266
|
-
});
|
|
267
|
-
}
|
|
268
|
-
const result = this.deferredResults.get(messageId) || new Deferred<string | undefined>();
|
|
269
|
-
this.deferredResults.set(messageId, result);
|
|
270
|
-
|
|
271
|
-
if (!this.centerVisible) {
|
|
272
|
-
this.toasts.set(messageId, notification);
|
|
273
|
-
this.setVisibilityState('toasts');
|
|
274
|
-
}
|
|
275
|
-
this.fireUpdatedEvent();
|
|
276
|
-
return result.promise;
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
override async reportProgress(messageId: string, update: ProgressUpdate, originalMessage: ProgressMessage, cancellationToken: CancellationToken): Promise<void> {
|
|
280
|
-
const notification = this.find(messageId);
|
|
281
|
-
if (!notification) {
|
|
282
|
-
return;
|
|
283
|
-
}
|
|
284
|
-
if (cancellationToken.isCancellationRequested) {
|
|
285
|
-
this.clear(messageId);
|
|
286
|
-
} else {
|
|
287
|
-
const textMessage = originalMessage.text && update.message ? `${originalMessage.text}: ${update.message}` : originalMessage.text || update?.message;
|
|
288
|
-
if (textMessage) {
|
|
289
|
-
notification.message = this.contentRenderer.renderMessage(textMessage);
|
|
290
|
-
}
|
|
291
|
-
notification.progress = this.toPlainProgress(update) || notification.progress;
|
|
292
|
-
}
|
|
293
|
-
this.fireUpdatedEvent();
|
|
294
|
-
}
|
|
295
|
-
protected toPlainProgress(update: ProgressUpdate): number | undefined {
|
|
296
|
-
return update.work && Math.min(update.work.done / update.work.total * 100, 100);
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
async openLink(link: string): Promise<void> {
|
|
300
|
-
const uri = new URI(link);
|
|
301
|
-
const opener = await this.openerService.getOpener(uri);
|
|
302
|
-
opener.open(uri);
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
}
|
|
1
|
+
// *****************************************************************************
|
|
2
|
+
// Copyright (C) 2019 TypeFox and others.
|
|
3
|
+
//
|
|
4
|
+
// This program and the accompanying materials are made available under the
|
|
5
|
+
// terms of the Eclipse Public License v. 2.0 which is available at
|
|
6
|
+
// http://www.eclipse.org/legal/epl-2.0.
|
|
7
|
+
//
|
|
8
|
+
// This Source Code may also be made available under the following Secondary
|
|
9
|
+
// Licenses when the conditions for such availability set forth in the Eclipse
|
|
10
|
+
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
|
|
11
|
+
// with the GNU Classpath Exception which is available at
|
|
12
|
+
// https://www.gnu.org/software/classpath/license.html.
|
|
13
|
+
//
|
|
14
|
+
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
|
|
15
|
+
// *****************************************************************************
|
|
16
|
+
|
|
17
|
+
import { injectable, inject, postConstruct } from '@theia/core/shared/inversify';
|
|
18
|
+
import { MessageClient, MessageType, Message as PlainMessage, ProgressMessage, ProgressUpdate, CancellationToken } from '@theia/core/lib/common';
|
|
19
|
+
import { deepClone } from '@theia/core/lib/common/objects';
|
|
20
|
+
import { Emitter } from '@theia/core';
|
|
21
|
+
import { Deferred } from '@theia/core/lib/common/promise-util';
|
|
22
|
+
import { Md5 } from 'ts-md5';
|
|
23
|
+
import throttle = require('@theia/core/shared/lodash.throttle');
|
|
24
|
+
import { NotificationPreferences } from './notification-preferences';
|
|
25
|
+
import { ContextKeyService, ContextKey } from '@theia/core/lib/browser/context-key-service';
|
|
26
|
+
import { OpenerService } from '@theia/core/lib/browser';
|
|
27
|
+
import URI from '@theia/core/lib/common/uri';
|
|
28
|
+
import { NotificationContentRenderer } from './notification-content-renderer';
|
|
29
|
+
|
|
30
|
+
export interface NotificationUpdateEvent {
|
|
31
|
+
readonly notifications: Notification[];
|
|
32
|
+
readonly toasts: Notification[];
|
|
33
|
+
readonly visibilityState: Notification.Visibility;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export interface Notification {
|
|
37
|
+
messageId: string;
|
|
38
|
+
message: string;
|
|
39
|
+
source?: string;
|
|
40
|
+
expandable: boolean;
|
|
41
|
+
collapsed: boolean;
|
|
42
|
+
type: Notification.Type;
|
|
43
|
+
actions: string[];
|
|
44
|
+
progress?: number;
|
|
45
|
+
}
|
|
46
|
+
export namespace Notification {
|
|
47
|
+
export type Visibility = 'hidden' | 'toasts' | 'center';
|
|
48
|
+
export type Type = 'info' | 'warning' | 'error' | 'progress';
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
@injectable()
|
|
52
|
+
export class NotificationManager extends MessageClient {
|
|
53
|
+
|
|
54
|
+
@inject(NotificationPreferences)
|
|
55
|
+
protected readonly preferences: NotificationPreferences;
|
|
56
|
+
|
|
57
|
+
@inject(ContextKeyService)
|
|
58
|
+
protected readonly contextKeyService: ContextKeyService;
|
|
59
|
+
|
|
60
|
+
@inject(OpenerService)
|
|
61
|
+
protected readonly openerService: OpenerService;
|
|
62
|
+
|
|
63
|
+
@inject(NotificationContentRenderer)
|
|
64
|
+
protected readonly contentRenderer: NotificationContentRenderer;
|
|
65
|
+
|
|
66
|
+
protected readonly onUpdatedEmitter = new Emitter<NotificationUpdateEvent>();
|
|
67
|
+
readonly onUpdated = this.onUpdatedEmitter.event;
|
|
68
|
+
protected readonly fireUpdatedEvent = throttle(() => {
|
|
69
|
+
const notifications = deepClone(Array.from(this.notifications.values()).filter((notification: Notification) =>
|
|
70
|
+
notification.message
|
|
71
|
+
));
|
|
72
|
+
const toasts = deepClone(Array.from(this.toasts.values()).filter((toast: Notification) =>
|
|
73
|
+
toast.message
|
|
74
|
+
));
|
|
75
|
+
const visibilityState = this.visibilityState;
|
|
76
|
+
this.onUpdatedEmitter.fire({ notifications, toasts, visibilityState });
|
|
77
|
+
}, 250, { leading: true, trailing: true });
|
|
78
|
+
|
|
79
|
+
protected readonly deferredResults = new Map<string, Deferred<string | undefined>>();
|
|
80
|
+
protected readonly notifications = new Map<string, Notification>();
|
|
81
|
+
protected readonly toasts = new Map<string, Notification>();
|
|
82
|
+
|
|
83
|
+
protected notificationToastsVisibleKey: ContextKey<boolean>;
|
|
84
|
+
protected notificationCenterVisibleKey: ContextKey<boolean>;
|
|
85
|
+
protected notificationsVisible: ContextKey<boolean>;
|
|
86
|
+
|
|
87
|
+
@postConstruct()
|
|
88
|
+
protected init(): void {
|
|
89
|
+
this.doInit();
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
protected async doInit(): Promise<void> {
|
|
93
|
+
this.notificationToastsVisibleKey = this.contextKeyService.createKey<boolean>('notificationToastsVisible', false);
|
|
94
|
+
this.notificationCenterVisibleKey = this.contextKeyService.createKey<boolean>('notificationCenterVisible', false);
|
|
95
|
+
this.notificationsVisible = this.contextKeyService.createKey<boolean>('notificationsVisible', false);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
protected updateContextKeys(): void {
|
|
99
|
+
this.notificationToastsVisibleKey.set(this.toastsVisible);
|
|
100
|
+
this.notificationCenterVisibleKey.set(this.centerVisible);
|
|
101
|
+
this.notificationsVisible.set(this.toastsVisible || this.centerVisible);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
get toastsVisible(): boolean {
|
|
105
|
+
return this.visibilityState === 'toasts';
|
|
106
|
+
}
|
|
107
|
+
get centerVisible(): boolean {
|
|
108
|
+
return this.visibilityState === 'center';
|
|
109
|
+
}
|
|
110
|
+
protected visibilityState: Notification.Visibility = 'hidden';
|
|
111
|
+
protected setVisibilityState(newState: Notification.Visibility): void {
|
|
112
|
+
const changed = this.visibilityState !== newState;
|
|
113
|
+
this.visibilityState = newState;
|
|
114
|
+
if (changed) {
|
|
115
|
+
this.fireUpdatedEvent();
|
|
116
|
+
this.updateContextKeys();
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
hideCenter(): void {
|
|
121
|
+
this.setVisibilityState('hidden');
|
|
122
|
+
}
|
|
123
|
+
showCenter(): void {
|
|
124
|
+
this.setVisibilityState('center');
|
|
125
|
+
}
|
|
126
|
+
toggleCenter(): void {
|
|
127
|
+
this.setVisibilityState(this.centerVisible ? 'hidden' : 'center');
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
accept(notification: Notification | string, action: string | undefined): void {
|
|
131
|
+
const messageId = this.getId(notification);
|
|
132
|
+
if (!messageId) {
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
this.notifications.delete(messageId);
|
|
136
|
+
this.toasts.delete(messageId);
|
|
137
|
+
const result = this.deferredResults.get(messageId);
|
|
138
|
+
if (!result) {
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
this.deferredResults.delete(messageId);
|
|
142
|
+
if ((this.centerVisible && !this.notifications.size) || (this.toastsVisible && !this.toasts.size)) {
|
|
143
|
+
this.setVisibilityState('hidden');
|
|
144
|
+
}
|
|
145
|
+
result.resolve(action);
|
|
146
|
+
this.fireUpdatedEvent();
|
|
147
|
+
}
|
|
148
|
+
protected find(notification: Notification | string): Notification | undefined {
|
|
149
|
+
return typeof notification === 'string' ? this.notifications.get(notification) : notification;
|
|
150
|
+
}
|
|
151
|
+
protected getId(notification: Notification | string): string {
|
|
152
|
+
return typeof notification === 'string' ? notification : notification.messageId;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
hide(): void {
|
|
156
|
+
if (this.toastsVisible) {
|
|
157
|
+
this.toasts.clear();
|
|
158
|
+
}
|
|
159
|
+
this.setVisibilityState('hidden');
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
clearAll(): void {
|
|
163
|
+
this.setVisibilityState('hidden');
|
|
164
|
+
Array.from(this.notifications.values()).forEach(n => this.clear(n));
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
clear(notification: Notification | string): void {
|
|
168
|
+
this.accept(notification, undefined);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
toggleExpansion(notificationId: string): void {
|
|
172
|
+
const notification = this.find(notificationId);
|
|
173
|
+
if (!notification) {
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
notification.collapsed = !notification.collapsed;
|
|
177
|
+
this.fireUpdatedEvent();
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
override showMessage(plainMessage: PlainMessage): Promise<string | undefined> {
|
|
181
|
+
const messageId = this.getMessageId(plainMessage);
|
|
182
|
+
|
|
183
|
+
let notification = this.notifications.get(messageId);
|
|
184
|
+
if (!notification) {
|
|
185
|
+
const message = this.contentRenderer.renderMessage(plainMessage.text);
|
|
186
|
+
const type = this.toNotificationType(plainMessage.type);
|
|
187
|
+
const actions = Array.from(new Set(plainMessage.actions));
|
|
188
|
+
const source = plainMessage.source;
|
|
189
|
+
const expandable = this.isExpandable(message, source, actions);
|
|
190
|
+
const collapsed = expandable;
|
|
191
|
+
notification = { messageId, message, type, actions, expandable, collapsed };
|
|
192
|
+
this.notifications.set(messageId, notification);
|
|
193
|
+
}
|
|
194
|
+
const result = this.deferredResults.get(messageId) || new Deferred<string | undefined>();
|
|
195
|
+
this.deferredResults.set(messageId, result);
|
|
196
|
+
|
|
197
|
+
if (!this.centerVisible) {
|
|
198
|
+
this.toasts.delete(messageId);
|
|
199
|
+
this.toasts.set(messageId, notification);
|
|
200
|
+
this.startHideTimeout(messageId, this.getTimeout(plainMessage));
|
|
201
|
+
this.setVisibilityState('toasts');
|
|
202
|
+
}
|
|
203
|
+
this.fireUpdatedEvent();
|
|
204
|
+
return result.promise;
|
|
205
|
+
}
|
|
206
|
+
protected hideTimeouts = new Map<string, number>();
|
|
207
|
+
protected startHideTimeout(messageId: string, timeout: number): void {
|
|
208
|
+
if (timeout > 0) {
|
|
209
|
+
this.hideTimeouts.set(messageId, window.setTimeout(() => {
|
|
210
|
+
this.hideToast(messageId);
|
|
211
|
+
}, timeout));
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
protected hideToast(messageId: string): void {
|
|
215
|
+
this.toasts.delete(messageId);
|
|
216
|
+
if (this.toastsVisible && !this.toasts.size) {
|
|
217
|
+
this.setVisibilityState('hidden');
|
|
218
|
+
} else {
|
|
219
|
+
this.fireUpdatedEvent();
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
protected getTimeout(plainMessage: PlainMessage): number {
|
|
223
|
+
if (plainMessage.actions && plainMessage.actions.length > 0) {
|
|
224
|
+
// Ignore the timeout if at least one action is set, and we wait for user interaction.
|
|
225
|
+
return 0;
|
|
226
|
+
}
|
|
227
|
+
return plainMessage.options && plainMessage.options.timeout || this.preferences['notification.timeout'];
|
|
228
|
+
}
|
|
229
|
+
protected isExpandable(message: string, source: string | undefined, actions: string[]): boolean {
|
|
230
|
+
if (!actions.length && source) {
|
|
231
|
+
return true;
|
|
232
|
+
}
|
|
233
|
+
return message.length > 500;
|
|
234
|
+
}
|
|
235
|
+
protected toNotificationType(type?: MessageType): Notification.Type {
|
|
236
|
+
switch (type) {
|
|
237
|
+
case MessageType.Error:
|
|
238
|
+
return 'error';
|
|
239
|
+
case MessageType.Warning:
|
|
240
|
+
return 'warning';
|
|
241
|
+
case MessageType.Progress:
|
|
242
|
+
return 'progress';
|
|
243
|
+
default:
|
|
244
|
+
return 'info';
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
protected getMessageId(m: PlainMessage): string {
|
|
248
|
+
return String(Md5.hashStr(`[${m.type}] ${m.text} : ${(m.actions || []).join(' | ')};`));
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
override async showProgress(messageId: string, plainMessage: ProgressMessage, cancellationToken: CancellationToken): Promise<string | undefined> {
|
|
252
|
+
let notification = this.notifications.get(messageId);
|
|
253
|
+
if (!notification) {
|
|
254
|
+
const message = this.contentRenderer.renderMessage(plainMessage.text);
|
|
255
|
+
const type = this.toNotificationType(plainMessage.type);
|
|
256
|
+
const actions = Array.from(new Set(plainMessage.actions));
|
|
257
|
+
const source = plainMessage.source;
|
|
258
|
+
const expandable = this.isExpandable(message, source, actions);
|
|
259
|
+
const collapsed = expandable;
|
|
260
|
+
notification = { messageId, message, type, actions, expandable, collapsed };
|
|
261
|
+
this.notifications.set(messageId, notification);
|
|
262
|
+
|
|
263
|
+
notification.progress = 0;
|
|
264
|
+
cancellationToken.onCancellationRequested(() => {
|
|
265
|
+
this.accept(messageId, ProgressMessage.Cancel);
|
|
266
|
+
});
|
|
267
|
+
}
|
|
268
|
+
const result = this.deferredResults.get(messageId) || new Deferred<string | undefined>();
|
|
269
|
+
this.deferredResults.set(messageId, result);
|
|
270
|
+
|
|
271
|
+
if (!this.centerVisible) {
|
|
272
|
+
this.toasts.set(messageId, notification);
|
|
273
|
+
this.setVisibilityState('toasts');
|
|
274
|
+
}
|
|
275
|
+
this.fireUpdatedEvent();
|
|
276
|
+
return result.promise;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
override async reportProgress(messageId: string, update: ProgressUpdate, originalMessage: ProgressMessage, cancellationToken: CancellationToken): Promise<void> {
|
|
280
|
+
const notification = this.find(messageId);
|
|
281
|
+
if (!notification) {
|
|
282
|
+
return;
|
|
283
|
+
}
|
|
284
|
+
if (cancellationToken.isCancellationRequested) {
|
|
285
|
+
this.clear(messageId);
|
|
286
|
+
} else {
|
|
287
|
+
const textMessage = originalMessage.text && update.message ? `${originalMessage.text}: ${update.message}` : originalMessage.text || update?.message;
|
|
288
|
+
if (textMessage) {
|
|
289
|
+
notification.message = this.contentRenderer.renderMessage(textMessage);
|
|
290
|
+
}
|
|
291
|
+
notification.progress = this.toPlainProgress(update) || notification.progress;
|
|
292
|
+
}
|
|
293
|
+
this.fireUpdatedEvent();
|
|
294
|
+
}
|
|
295
|
+
protected toPlainProgress(update: ProgressUpdate): number | undefined {
|
|
296
|
+
return update.work && Math.min(update.work.done / update.work.total * 100, 100);
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
async openLink(link: string): Promise<void> {
|
|
300
|
+
const uri = new URI(link);
|
|
301
|
+
const opener = await this.openerService.getOpener(uri);
|
|
302
|
+
opener.open(uri);
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
}
|