UNPKG

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