@theia/core
Version:
Theia is a cloud & desktop IDE framework implemented in TypeScript.
478 lines • 19 kB
JavaScript
"use strict";
// *****************************************************************************
// Copyright (C) 2017 TypeFox and others.
//
// This program and the accompanying materials are made available under the
// terms of the Eclipse Public License v. 2.0 which is available at
// http://www.eclipse.org/legal/epl-2.0.
//
// This Source Code may also be made available under the following Secondary
// Licenses when the conditions for such availability set forth in the Eclipse
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
// with the GNU Classpath Exception which is available at
// https://www.gnu.org/software/classpath/license.html.
//
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
// *****************************************************************************
var DialogOverlayService_1;
Object.defineProperty(exports, "__esModule", { value: true });
exports.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;
const tslib_1 = require("tslib");
const inversify_1 = require("inversify");
const common_1 = require("../common");
const keys_1 = require("./keyboard/keys");
const widget_1 = require("./widgets/widget");
let DialogProps = class DialogProps {
};
exports.DialogProps = DialogProps;
exports.DialogProps = DialogProps = tslib_1.__decorate([
(0, inversify_1.injectable)()
], DialogProps);
var DialogError;
(function (DialogError) {
function getResult(error) {
if (typeof error === 'string') {
return !error.length;
}
if (typeof error === 'boolean') {
return error;
}
return error.result;
}
DialogError.getResult = getResult;
function getMessage(error) {
if (typeof error === 'string') {
return error;
}
if (typeof error === 'boolean') {
return '';
}
return error.message;
}
DialogError.getMessage = getMessage;
})(DialogError || (exports.DialogError = DialogError = {}));
var Dialog;
(function (Dialog) {
Dialog.YES = common_1.nls.localizeByDefault('Yes');
Dialog.NO = common_1.nls.localizeByDefault('No');
Dialog.OK = common_1.nls.localizeByDefault('OK');
Dialog.CANCEL = common_1.nls.localizeByDefault('Cancel');
})(Dialog || (exports.Dialog = Dialog = {}));
let DialogOverlayService = DialogOverlayService_1 = class DialogOverlayService {
static get() {
return DialogOverlayService_1.INSTANCE;
}
constructor() {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
this.dialogs = [];
this.documents = [];
}
initialize() {
DialogOverlayService_1.INSTANCE = this;
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
get currentDialog() {
return this.dialogs[0];
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
push(dialog) {
if (this.documents.findIndex(document => document === dialog.node.ownerDocument) < 0) {
(0, widget_1.addKeyListener)(dialog.node.ownerDocument.body, keys_1.Key.ENTER, e => this.handleEnter(e));
(0, widget_1.addKeyListener)(dialog.node.ownerDocument.body, keys_1.Key.ESCAPE, e => this.handleEscape(e));
this.documents.push(dialog.node.ownerDocument);
}
this.dialogs.unshift(dialog);
return common_1.Disposable.create(() => {
const index = this.dialogs.indexOf(dialog);
if (index > -1) {
this.dialogs.splice(index, 1);
}
});
}
handleEscape(event) {
const dialog = this.currentDialog;
if (dialog) {
return dialog['handleEscape'](event);
}
return false;
}
handleEnter(event) {
const dialog = this.currentDialog;
if (dialog) {
return dialog['handleEnter'](event);
}
return false;
}
};
exports.DialogOverlayService = DialogOverlayService;
exports.DialogOverlayService = DialogOverlayService = DialogOverlayService_1 = tslib_1.__decorate([
(0, inversify_1.injectable)(),
tslib_1.__metadata("design:paramtypes", [])
], DialogOverlayService);
let AbstractDialog = class AbstractDialog extends widget_1.BaseWidget {
constructor(props, options) {
super(options);
this.props = props;
this.validateCancellationSource = new common_1.CancellationTokenSource();
this.acceptCancellationSource = new common_1.CancellationTokenSource();
this.id = 'theia-dialog-shell';
this.addClass('dialogOverlay');
this.toDispose.push(common_1.Disposable.create(() => {
if (this.reject) {
widget_1.Widget.detach(this);
}
}));
const container = this.node.ownerDocument.createElement('div');
container.classList.add('dialogBlock');
if (props.maxWidth === undefined) {
container.setAttribute('style', 'max-width: none');
}
else if (props.maxWidth < 400) {
container.setAttribute('style', `max-width: ${props.maxWidth}px; min-width: 0px`);
}
else {
container.setAttribute('style', `max-width: ${props.maxWidth}px`);
}
this.node.appendChild(container);
const titleContentNode = this.node.ownerDocument.createElement('div');
titleContentNode.classList.add('dialogTitle');
container.appendChild(titleContentNode);
this.titleNode = this.node.ownerDocument.createElement('div');
this.titleNode.textContent = props.title;
titleContentNode.appendChild(this.titleNode);
this.closeCrossNode = this.node.ownerDocument.createElement('i');
this.closeCrossNode.classList.add(...(0, widget_1.codiconArray)('close', true));
this.closeCrossNode.classList.add('closeButton');
titleContentNode.appendChild(this.closeCrossNode);
this.contentNode = this.node.ownerDocument.createElement('div');
this.contentNode.classList.add('dialogContent');
if (props.wordWrap !== undefined) {
this.contentNode.setAttribute('style', `word-wrap: ${props.wordWrap}`);
}
container.appendChild(this.contentNode);
this.controlPanel = this.node.ownerDocument.createElement('div');
this.controlPanel.classList.add('dialogControl');
container.appendChild(this.controlPanel);
this.errorMessageNode = this.node.ownerDocument.createElement('div');
this.errorMessageNode.classList.add('error');
this.errorMessageNode.setAttribute('style', 'flex: 2');
this.controlPanel.appendChild(this.errorMessageNode);
this.update();
}
appendCloseButton(text = Dialog.CANCEL) {
return this.closeButton = this.appendButton(text, false);
}
appendAcceptButton(text = Dialog.OK) {
return this.acceptButton = this.appendButton(text, true);
}
appendButton(text, primary) {
const button = this.createButton(text);
this.controlPanel.appendChild(button);
button.classList.add(primary ? 'main' : 'secondary');
return button;
}
createButton(text) {
const button = document.createElement('button');
button.classList.add('theia-button');
button.textContent = text;
return button;
}
onAfterAttach(msg) {
super.onAfterAttach(msg);
if (this.closeButton) {
this.addCloseAction(this.closeButton, 'click');
}
if (this.acceptButton) {
this.addAcceptAction(this.acceptButton, 'click');
}
this.addCloseAction(this.closeCrossNode, 'click');
this.toDisposeOnDetach.push(this.preventTabbingOutsideDialog());
// TODO: use DI always to create dialog instances
this.toDisposeOnDetach.push(DialogOverlayService.get().push(this));
}
/**
* This prevents tabbing outside the dialog by marking elements as inert, i.e., non-clickable and non-focussable.
*
* @param elements the elements for which we disable tabbing. By default all elements within the body element are considered.
* Please note that this may also include other popups such as the suggestion overlay, the notification center or quick picks.
* @returns a disposable that will restore the previous tabbing behavior
*/
preventTabbingOutsideDialog(elements = Array.from(this.node.ownerDocument.body.children)) {
const nonInertElements = elements.filter(child => child !== this.node && !(child.hasAttribute('inert')));
nonInertElements.forEach(child => child.setAttribute('inert', ''));
return common_1.Disposable.create(() => nonInertElements.forEach(child => child.removeAttribute('inert')));
}
handleEscape(event) {
this.close();
}
handleEnter(event) {
if (event.target instanceof HTMLTextAreaElement) {
return false;
}
this.accept();
}
onActivateRequest(msg) {
super.onActivateRequest(msg);
if (this.acceptButton) {
this.acceptButton.focus();
}
}
open(disposeOnResolve = true) {
if (this.resolve) {
return Promise.reject(new Error('The dialog is already opened.'));
}
this.activeElement = this.node.ownerDocument.activeElement;
return new Promise((resolve, reject) => {
this.resolve = value => {
resolve(value);
};
this.reject = reject;
this.toDisposeOnDetach.push(common_1.Disposable.create(() => {
this.resolve = undefined;
this.reject = undefined;
}));
widget_1.Widget.attach(this, this.node.ownerDocument.body);
this.activate();
}).finally(() => {
if (disposeOnResolve) {
this.dispose();
}
});
}
onCloseRequest(msg) {
// super.onCloseRequest() would automatically dispose the dialog, which we don't want because we're reusing it
if (this.parent) {
// eslint-disable-next-line no-null/no-null
this.parent = null;
}
else if (this.isAttached) {
widget_1.Widget.detach(this);
}
}
close() {
if (this.resolve) {
if (this.activeElement) {
this.activeElement.focus({ preventScroll: true });
}
this.resolve(undefined);
}
this.activeElement = undefined;
super.close();
}
onUpdateRequest(msg) {
super.onUpdateRequest(msg);
this.validate();
}
async validate() {
if (!this.resolve) {
return;
}
this.validateCancellationSource.cancel();
this.validateCancellationSource = new common_1.CancellationTokenSource();
const token = this.validateCancellationSource.token;
const value = this.value;
const error = await this.isValid(value, 'preview');
if (token.isCancellationRequested) {
return;
}
this.setErrorMessage(error);
}
async accept() {
if (!this.resolve) {
return;
}
this.acceptCancellationSource.cancel();
this.acceptCancellationSource = new common_1.CancellationTokenSource();
const token = this.acceptCancellationSource.token;
const value = this.value;
const error = await this.isValid(value, 'open');
if (token.isCancellationRequested) {
return;
}
if (!DialogError.getResult(error)) {
this.setErrorMessage(error);
}
else {
this.resolve(value);
widget_1.Widget.detach(this);
}
}
/**
* Return a string of zero-length or true if valid.
*/
isValid(value, mode) {
return '';
}
setErrorMessage(error) {
if (this.acceptButton) {
this.acceptButton.disabled = !DialogError.getResult(error);
}
this.errorMessageNode.innerText = DialogError.getMessage(error);
}
addAction(element, callback, ...additionalEventTypes) {
this.addKeyListener(element, keys_1.Key.ENTER, callback, ...additionalEventTypes);
}
addCloseAction(element, ...additionalEventTypes) {
this.addAction(element, () => this.close(), ...additionalEventTypes);
}
addAcceptAction(element, ...additionalEventTypes) {
this.addAction(element, () => this.accept(), ...additionalEventTypes);
}
};
exports.AbstractDialog = AbstractDialog;
exports.AbstractDialog = AbstractDialog = tslib_1.__decorate([
(0, inversify_1.injectable)(),
tslib_1.__param(0, (0, inversify_1.unmanaged)()),
tslib_1.__param(1, (0, inversify_1.unmanaged)()),
tslib_1.__metadata("design:paramtypes", [DialogProps, Object])
], AbstractDialog);
let MessageDialogProps = class MessageDialogProps extends DialogProps {
};
exports.MessageDialogProps = MessageDialogProps;
exports.MessageDialogProps = MessageDialogProps = tslib_1.__decorate([
(0, inversify_1.injectable)()
], MessageDialogProps);
let ConfirmDialogProps = class ConfirmDialogProps extends MessageDialogProps {
};
exports.ConfirmDialogProps = ConfirmDialogProps;
exports.ConfirmDialogProps = ConfirmDialogProps = tslib_1.__decorate([
(0, inversify_1.injectable)()
], ConfirmDialogProps);
let ConfirmDialog = class ConfirmDialog extends AbstractDialog {
constructor(props) {
super(props);
this.props = props;
this.confirmed = true;
this.contentNode.appendChild(this.createMessageNode(this.props.msg));
this.appendCloseButton(props.cancel);
this.appendAcceptButton(props.ok);
}
onCloseRequest(msg) {
super.onCloseRequest(msg);
this.confirmed = false;
this.accept();
}
get value() {
return this.confirmed;
}
createMessageNode(msg) {
if (typeof msg === 'string') {
const messageNode = this.node.ownerDocument.createElement('div');
messageNode.textContent = msg;
return messageNode;
}
return msg;
}
};
exports.ConfirmDialog = ConfirmDialog;
exports.ConfirmDialog = ConfirmDialog = tslib_1.__decorate([
tslib_1.__param(0, (0, inversify_1.inject)(ConfirmDialogProps)),
tslib_1.__metadata("design:paramtypes", [ConfirmDialogProps])
], ConfirmDialog);
async function confirmExit() {
const safeToExit = await new ConfirmDialog({
title: common_1.nls.localizeByDefault('Are you sure you want to quit?'),
msg: common_1.nls.localize('theia/core/quitMessage', 'Any unsaved changes will not be saved.'),
ok: Dialog.YES,
cancel: Dialog.NO,
}).open();
return safeToExit === true;
}
exports.confirmExit = confirmExit;
class ConfirmSaveDialogProps extends MessageDialogProps {
}
exports.ConfirmSaveDialogProps = ConfirmSaveDialogProps;
// Dialog prompting the user to confirm whether they wish to save changes or not
let ConfirmSaveDialog = class ConfirmSaveDialog extends AbstractDialog {
constructor(props) {
super(props);
this.props = props;
this.result = false;
// Append message and buttons to the dialog
this.contentNode.appendChild(this.createMessageNode(this.props.msg));
this.closeButton = this.appendButtonAndSetResult(props.cancel, false);
this.appendButtonAndSetResult(props.dontSave, false, false);
this.acceptButton = this.appendButtonAndSetResult(props.save, true, true);
}
get value() {
return this.result;
}
createMessageNode(msg) {
if (typeof msg === 'string') {
const messageNode = document.createElement('div');
messageNode.textContent = msg;
return messageNode;
}
return msg;
}
appendButtonAndSetResult(text, primary, result) {
const button = this.appendButton(text, primary);
button.addEventListener('click', () => {
this.result = result;
this.accept();
});
return button;
}
};
exports.ConfirmSaveDialog = ConfirmSaveDialog;
exports.ConfirmSaveDialog = ConfirmSaveDialog = tslib_1.__decorate([
tslib_1.__param(0, (0, inversify_1.inject)(ConfirmSaveDialogProps)),
tslib_1.__metadata("design:paramtypes", [ConfirmSaveDialogProps])
], ConfirmSaveDialog);
let SingleTextInputDialogProps = class SingleTextInputDialogProps extends DialogProps {
};
exports.SingleTextInputDialogProps = SingleTextInputDialogProps;
exports.SingleTextInputDialogProps = SingleTextInputDialogProps = tslib_1.__decorate([
(0, inversify_1.injectable)()
], SingleTextInputDialogProps);
let SingleTextInputDialog = class SingleTextInputDialog extends AbstractDialog {
constructor(props) {
super(props);
this.props = props;
this.inputField = document.createElement('input');
this.inputField.type = 'text';
this.inputField.className = 'theia-input';
this.inputField.spellcheck = false;
this.inputField.setAttribute('style', 'flex: 0;');
this.inputField.placeholder = props.placeholder || '';
this.inputField.value = props.initialValue || '';
if (props.initialSelectionRange) {
this.inputField.setSelectionRange(props.initialSelectionRange.start, props.initialSelectionRange.end, props.initialSelectionRange.direction);
}
else {
this.inputField.select();
}
this.contentNode.appendChild(this.inputField);
this.controlPanel.removeChild(this.errorMessageNode);
this.contentNode.appendChild(this.errorMessageNode);
this.appendAcceptButton(props.confirmButtonLabel);
}
get value() {
return this.inputField.value;
}
isValid(value, mode) {
if (this.props.validate) {
return this.props.validate(value, mode);
}
return super.isValid(value, mode);
}
onAfterAttach(msg) {
super.onAfterAttach(msg);
this.addUpdateListener(this.inputField, 'input');
}
onActivateRequest(msg) {
this.inputField.focus();
}
handleEnter(event) {
if (event.target instanceof HTMLInputElement) {
return super.handleEnter(event);
}
return false;
}
};
exports.SingleTextInputDialog = SingleTextInputDialog;
exports.SingleTextInputDialog = SingleTextInputDialog = tslib_1.__decorate([
tslib_1.__param(0, (0, inversify_1.inject)(SingleTextInputDialogProps)),
tslib_1.__metadata("design:paramtypes", [SingleTextInputDialogProps])
], SingleTextInputDialog);
//# sourceMappingURL=dialogs.js.map