UNPKG

19.4 kBJavaScriptView Raw
1"use strict";
2// *****************************************************************************
3// Copyright (C) 2017 TypeFox and others.
4//
5// This program and the accompanying materials are made available under the
6// terms of the Eclipse Public License v. 2.0 which is available at
7// http://www.eclipse.org/legal/epl-2.0.
8//
9// This Source Code may also be made available under the following Secondary
10// Licenses when the conditions for such availability set forth in the Eclipse
11// Public License v. 2.0 are satisfied: GNU General Public License, version 2
12// with the GNU Classpath Exception which is available at
13// https://www.gnu.org/software/classpath/license.html.
14//
15// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
16// *****************************************************************************
17var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
18 var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
19 if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
20 else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
21 return c > 3 && r && Object.defineProperty(target, key, r), r;
22};
23var __metadata = (this && this.__metadata) || function (k, v) {
24 if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
25};
26var __param = (this && this.__param) || function (paramIndex, decorator) {
27 return function (target, key) { decorator(target, key, paramIndex); }
28};
29var DialogOverlayService_1;
30Object.defineProperty(exports, "__esModule", { value: true });
31exports.SingleTextInputDialog = exports.SingleTextInputDialogProps = exports.confirmExitWithOrWithoutSaving = exports.ConfirmSaveDialog = exports.ConfirmSaveDialogProps = exports.confirmExit = exports.ConfirmDialog = exports.ConfirmDialogProps = exports.MessageDialogProps = exports.AbstractDialog = exports.DialogOverlayService = exports.Dialog = exports.DialogError = exports.DialogProps = void 0;
32const inversify_1 = require("inversify");
33const common_1 = require("../common");
34const keys_1 = require("./keyboard/keys");
35const widgets_1 = require("./widgets");
36let DialogProps = class DialogProps {
37};
38DialogProps = __decorate([
39 (0, inversify_1.injectable)()
40], DialogProps);
41exports.DialogProps = DialogProps;
42var DialogError;
43(function (DialogError) {
44 function getResult(error) {
45 if (typeof error === 'string') {
46 return !error.length;
47 }
48 if (typeof error === 'boolean') {
49 return error;
50 }
51 return error.result;
52 }
53 DialogError.getResult = getResult;
54 function getMessage(error) {
55 if (typeof error === 'string') {
56 return error;
57 }
58 if (typeof error === 'boolean') {
59 return '';
60 }
61 return error.message;
62 }
63 DialogError.getMessage = getMessage;
64})(DialogError = exports.DialogError || (exports.DialogError = {}));
65var Dialog;
66(function (Dialog) {
67 Dialog.YES = common_1.nls.localizeByDefault('Yes');
68 Dialog.NO = common_1.nls.localizeByDefault('No');
69 Dialog.OK = common_1.nls.localizeByDefault('OK');
70 Dialog.CANCEL = common_1.nls.localizeByDefault('Cancel');
71})(Dialog = exports.Dialog || (exports.Dialog = {}));
72let DialogOverlayService = DialogOverlayService_1 = class DialogOverlayService {
73 constructor() {
74 // eslint-disable-next-line @typescript-eslint/no-explicit-any
75 this.dialogs = [];
76 this.documents = [];
77 }
78 static get() {
79 return DialogOverlayService_1.INSTANCE;
80 }
81 initialize() {
82 DialogOverlayService_1.INSTANCE = this;
83 }
84 // eslint-disable-next-line @typescript-eslint/no-explicit-any
85 get currentDialog() {
86 return this.dialogs[0];
87 }
88 // eslint-disable-next-line @typescript-eslint/no-explicit-any
89 push(dialog) {
90 if (this.documents.findIndex(document => document === dialog.node.ownerDocument) < 0) {
91 (0, widgets_1.addKeyListener)(dialog.node.ownerDocument.body, keys_1.Key.ENTER, e => this.handleEnter(e));
92 (0, widgets_1.addKeyListener)(dialog.node.ownerDocument.body, keys_1.Key.ESCAPE, e => this.handleEscape(e));
93 this.documents.push(dialog.node.ownerDocument);
94 }
95 this.dialogs.unshift(dialog);
96 return common_1.Disposable.create(() => {
97 const index = this.dialogs.indexOf(dialog);
98 if (index > -1) {
99 this.dialogs.splice(index, 1);
100 }
101 });
102 }
103 handleEscape(event) {
104 const dialog = this.currentDialog;
105 if (dialog) {
106 return dialog['handleEscape'](event);
107 }
108 return false;
109 }
110 handleEnter(event) {
111 const dialog = this.currentDialog;
112 if (dialog) {
113 return dialog['handleEnter'](event);
114 }
115 return false;
116 }
117};
118DialogOverlayService = DialogOverlayService_1 = __decorate([
119 (0, inversify_1.injectable)(),
120 __metadata("design:paramtypes", [])
121], DialogOverlayService);
122exports.DialogOverlayService = DialogOverlayService;
123let AbstractDialog = class AbstractDialog extends widgets_1.BaseWidget {
124 constructor(props, options) {
125 super(options);
126 this.props = props;
127 this.validateCancellationSource = new common_1.CancellationTokenSource();
128 this.acceptCancellationSource = new common_1.CancellationTokenSource();
129 this.id = 'theia-dialog-shell';
130 this.addClass('dialogOverlay');
131 this.toDispose.push(common_1.Disposable.create(() => {
132 if (this.reject) {
133 widgets_1.Widget.detach(this);
134 }
135 }));
136 const container = this.node.ownerDocument.createElement('div');
137 container.classList.add('dialogBlock');
138 if (props.maxWidth === undefined) {
139 container.setAttribute('style', 'max-width: none');
140 }
141 else if (props.maxWidth < 400) {
142 container.setAttribute('style', `max-width: ${props.maxWidth}px; min-width: 0px`);
143 }
144 else {
145 container.setAttribute('style', `max-width: ${props.maxWidth}px`);
146 }
147 this.node.appendChild(container);
148 const titleContentNode = this.node.ownerDocument.createElement('div');
149 titleContentNode.classList.add('dialogTitle');
150 container.appendChild(titleContentNode);
151 this.titleNode = this.node.ownerDocument.createElement('div');
152 this.titleNode.textContent = props.title;
153 titleContentNode.appendChild(this.titleNode);
154 this.closeCrossNode = this.node.ownerDocument.createElement('i');
155 this.closeCrossNode.classList.add(...(0, widgets_1.codiconArray)('close', true));
156 this.closeCrossNode.classList.add('closeButton');
157 titleContentNode.appendChild(this.closeCrossNode);
158 this.contentNode = this.node.ownerDocument.createElement('div');
159 this.contentNode.classList.add('dialogContent');
160 if (props.wordWrap !== undefined) {
161 this.contentNode.setAttribute('style', `word-wrap: ${props.wordWrap}`);
162 }
163 container.appendChild(this.contentNode);
164 this.controlPanel = this.node.ownerDocument.createElement('div');
165 this.controlPanel.classList.add('dialogControl');
166 container.appendChild(this.controlPanel);
167 this.errorMessageNode = this.node.ownerDocument.createElement('div');
168 this.errorMessageNode.classList.add('error');
169 this.errorMessageNode.setAttribute('style', 'flex: 2');
170 this.controlPanel.appendChild(this.errorMessageNode);
171 this.update();
172 }
173 appendCloseButton(text = Dialog.CANCEL) {
174 return this.closeButton = this.appendButton(text, false);
175 }
176 appendAcceptButton(text = Dialog.OK) {
177 return this.acceptButton = this.appendButton(text, true);
178 }
179 appendButton(text, primary) {
180 const button = this.createButton(text);
181 this.controlPanel.appendChild(button);
182 button.classList.add(primary ? 'main' : 'secondary');
183 return button;
184 }
185 createButton(text) {
186 const button = document.createElement('button');
187 button.classList.add('theia-button');
188 button.textContent = text;
189 return button;
190 }
191 onAfterAttach(msg) {
192 super.onAfterAttach(msg);
193 if (this.closeButton) {
194 this.addCloseAction(this.closeButton, 'click');
195 }
196 if (this.acceptButton) {
197 this.addAcceptAction(this.acceptButton, 'click');
198 }
199 this.addCloseAction(this.closeCrossNode, 'click');
200 // TODO: use DI always to create dialog instances
201 this.toDisposeOnDetach.push(DialogOverlayService.get().push(this));
202 }
203 handleEscape(event) {
204 this.close();
205 }
206 handleEnter(event) {
207 if (event.target instanceof HTMLTextAreaElement) {
208 return false;
209 }
210 this.accept();
211 }
212 onActivateRequest(msg) {
213 super.onActivateRequest(msg);
214 if (this.acceptButton) {
215 this.acceptButton.focus();
216 }
217 }
218 open() {
219 if (this.resolve) {
220 return Promise.reject(new Error('The dialog is already opened.'));
221 }
222 this.activeElement = this.node.ownerDocument.activeElement;
223 return new Promise((resolve, reject) => {
224 this.resolve = resolve;
225 this.reject = reject;
226 this.toDisposeOnDetach.push(common_1.Disposable.create(() => {
227 this.resolve = undefined;
228 this.reject = undefined;
229 }));
230 widgets_1.Widget.attach(this, this.node.ownerDocument.body);
231 this.activate();
232 });
233 }
234 close() {
235 if (this.resolve) {
236 if (this.activeElement) {
237 this.activeElement.focus({ preventScroll: true });
238 }
239 this.resolve(undefined);
240 }
241 this.activeElement = undefined;
242 super.close();
243 }
244 onUpdateRequest(msg) {
245 super.onUpdateRequest(msg);
246 this.validate();
247 }
248 async validate() {
249 if (!this.resolve) {
250 return;
251 }
252 this.validateCancellationSource.cancel();
253 this.validateCancellationSource = new common_1.CancellationTokenSource();
254 const token = this.validateCancellationSource.token;
255 const value = this.value;
256 const error = await this.isValid(value, 'preview');
257 if (token.isCancellationRequested) {
258 return;
259 }
260 this.setErrorMessage(error);
261 }
262 async accept() {
263 if (!this.resolve) {
264 return;
265 }
266 this.acceptCancellationSource.cancel();
267 this.acceptCancellationSource = new common_1.CancellationTokenSource();
268 const token = this.acceptCancellationSource.token;
269 const value = this.value;
270 const error = await this.isValid(value, 'open');
271 if (token.isCancellationRequested) {
272 return;
273 }
274 if (!DialogError.getResult(error)) {
275 this.setErrorMessage(error);
276 }
277 else {
278 this.resolve(value);
279 widgets_1.Widget.detach(this);
280 }
281 }
282 /**
283 * Return a string of zero-length or true if valid.
284 */
285 isValid(value, mode) {
286 return '';
287 }
288 setErrorMessage(error) {
289 if (this.acceptButton) {
290 this.acceptButton.disabled = !DialogError.getResult(error);
291 }
292 this.errorMessageNode.innerText = DialogError.getMessage(error);
293 }
294 addAction(element, callback, ...additionalEventTypes) {
295 this.addKeyListener(element, keys_1.Key.ENTER, callback, ...additionalEventTypes);
296 }
297 addCloseAction(element, ...additionalEventTypes) {
298 this.addAction(element, () => this.close(), ...additionalEventTypes);
299 }
300 addAcceptAction(element, ...additionalEventTypes) {
301 this.addAction(element, () => this.accept(), ...additionalEventTypes);
302 }
303};
304AbstractDialog = __decorate([
305 (0, inversify_1.injectable)(),
306 __metadata("design:paramtypes", [DialogProps, Object])
307], AbstractDialog);
308exports.AbstractDialog = AbstractDialog;
309let MessageDialogProps = class MessageDialogProps extends DialogProps {
310};
311MessageDialogProps = __decorate([
312 (0, inversify_1.injectable)()
313], MessageDialogProps);
314exports.MessageDialogProps = MessageDialogProps;
315let ConfirmDialogProps = class ConfirmDialogProps extends MessageDialogProps {
316};
317ConfirmDialogProps = __decorate([
318 (0, inversify_1.injectable)()
319], ConfirmDialogProps);
320exports.ConfirmDialogProps = ConfirmDialogProps;
321let ConfirmDialog = class ConfirmDialog extends AbstractDialog {
322 constructor(props) {
323 super(props);
324 this.props = props;
325 this.confirmed = true;
326 this.contentNode.appendChild(this.createMessageNode(this.props.msg));
327 this.appendCloseButton(props.cancel);
328 this.appendAcceptButton(props.ok);
329 }
330 onCloseRequest(msg) {
331 super.onCloseRequest(msg);
332 this.confirmed = false;
333 this.accept();
334 }
335 get value() {
336 return this.confirmed;
337 }
338 createMessageNode(msg) {
339 if (typeof msg === 'string') {
340 const messageNode = this.node.ownerDocument.createElement('div');
341 messageNode.textContent = msg;
342 return messageNode;
343 }
344 return msg;
345 }
346};
347ConfirmDialog = __decorate([
348 __param(0, (0, inversify_1.inject)(ConfirmDialogProps)),
349 __metadata("design:paramtypes", [ConfirmDialogProps])
350], ConfirmDialog);
351exports.ConfirmDialog = ConfirmDialog;
352async function confirmExit() {
353 const safeToExit = await new ConfirmDialog({
354 title: common_1.nls.localizeByDefault('Are you sure you want to quit?'),
355 msg: common_1.nls.localize('theia/core/quitMessage', 'Any unsaved changes will not be saved.'),
356 ok: Dialog.YES,
357 cancel: Dialog.NO,
358 }).open();
359 return safeToExit === true;
360}
361exports.confirmExit = confirmExit;
362class ConfirmSaveDialogProps extends MessageDialogProps {
363}
364exports.ConfirmSaveDialogProps = ConfirmSaveDialogProps;
365// Dialog prompting the user to confirm whether they wish to save changes or not
366let ConfirmSaveDialog = class ConfirmSaveDialog extends AbstractDialog {
367 constructor(props) {
368 super(props);
369 this.props = props;
370 this.result = false;
371 // Append message and buttons to the dialog
372 this.contentNode.appendChild(this.createMessageNode(this.props.msg));
373 this.closeButton = this.appendButtonAndSetResult(props.cancel, false);
374 this.appendButtonAndSetResult(props.dontSave, false, false);
375 this.acceptButton = this.appendButtonAndSetResult(props.save, true, true);
376 }
377 get value() {
378 return this.result;
379 }
380 createMessageNode(msg) {
381 if (typeof msg === 'string') {
382 const messageNode = document.createElement('div');
383 messageNode.textContent = msg;
384 return messageNode;
385 }
386 return msg;
387 }
388 appendButtonAndSetResult(text, primary, result) {
389 const button = this.appendButton(text, primary);
390 button.addEventListener('click', () => {
391 this.result = result;
392 this.accept();
393 });
394 return button;
395 }
396};
397ConfirmSaveDialog = __decorate([
398 __param(0, (0, inversify_1.inject)(ConfirmSaveDialogProps)),
399 __metadata("design:paramtypes", [ConfirmSaveDialogProps])
400], ConfirmSaveDialog);
401exports.ConfirmSaveDialog = ConfirmSaveDialog;
402// Asks the user to confirm whether they want to exit with or without saving the changes
403async function confirmExitWithOrWithoutSaving(captionsToSave, performSave) {
404 const div = document.createElement('div');
405 div.innerText = common_1.nls.localizeByDefault("Your changes will be lost if you don't save them.");
406 if (captionsToSave.length > 0) {
407 const span = document.createElement('span');
408 span.appendChild(document.createElement('br'));
409 captionsToSave.forEach(cap => {
410 const b = document.createElement('b');
411 b.innerText = cap;
412 span.appendChild(b);
413 span.appendChild(document.createElement('br'));
414 });
415 span.appendChild(document.createElement('br'));
416 div.appendChild(span);
417 const result = await new ConfirmSaveDialog({
418 title: common_1.nls.localizeByDefault('Do you want to save the changes to the following {0} files?', captionsToSave.length),
419 msg: div,
420 dontSave: common_1.nls.localizeByDefault("Don't Save"),
421 save: common_1.nls.localizeByDefault('Save All'),
422 cancel: Dialog.CANCEL
423 }).open();
424 if (result) {
425 await performSave();
426 }
427 return result !== undefined;
428 }
429 else {
430 // fallback if not passed with an empty caption-list.
431 return confirmExit();
432 }
433}
434exports.confirmExitWithOrWithoutSaving = confirmExitWithOrWithoutSaving;
435let SingleTextInputDialogProps = class SingleTextInputDialogProps extends DialogProps {
436};
437SingleTextInputDialogProps = __decorate([
438 (0, inversify_1.injectable)()
439], SingleTextInputDialogProps);
440exports.SingleTextInputDialogProps = SingleTextInputDialogProps;
441let SingleTextInputDialog = class SingleTextInputDialog extends AbstractDialog {
442 constructor(props) {
443 super(props);
444 this.props = props;
445 this.inputField = document.createElement('input');
446 this.inputField.type = 'text';
447 this.inputField.className = 'theia-input';
448 this.inputField.spellcheck = false;
449 this.inputField.setAttribute('style', 'flex: 0;');
450 this.inputField.placeholder = props.placeholder || '';
451 this.inputField.value = props.initialValue || '';
452 if (props.initialSelectionRange) {
453 this.inputField.setSelectionRange(props.initialSelectionRange.start, props.initialSelectionRange.end, props.initialSelectionRange.direction);
454 }
455 else {
456 this.inputField.select();
457 }
458 this.contentNode.appendChild(this.inputField);
459 this.controlPanel.removeChild(this.errorMessageNode);
460 this.contentNode.appendChild(this.errorMessageNode);
461 this.appendAcceptButton(props.confirmButtonLabel);
462 }
463 get value() {
464 return this.inputField.value;
465 }
466 isValid(value, mode) {
467 if (this.props.validate) {
468 return this.props.validate(value, mode);
469 }
470 return super.isValid(value, mode);
471 }
472 onAfterAttach(msg) {
473 super.onAfterAttach(msg);
474 this.addUpdateListener(this.inputField, 'input');
475 }
476 onActivateRequest(msg) {
477 this.inputField.focus();
478 }
479 handleEnter(event) {
480 if (event.target instanceof HTMLInputElement) {
481 return super.handleEnter(event);
482 }
483 return false;
484 }
485};
486SingleTextInputDialog = __decorate([
487 __param(0, (0, inversify_1.inject)(SingleTextInputDialogProps)),
488 __metadata("design:paramtypes", [SingleTextInputDialogProps])
489], SingleTextInputDialog);
490exports.SingleTextInputDialog = SingleTextInputDialog;
491//# sourceMappingURL=dialogs.js.map
\No newline at end of file