sprotty
Version:
A next-gen framework for graphical views
275 lines • 12.5 kB
JavaScript
"use strict";
/********************************************************************************
* Copyright (c) 2019-2022 EclipseSource 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 WITH Classpath-exception-2.0
********************************************************************************/
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
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;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __metadata = (this && this.__metadata) || function (k, v) {
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
var EditLabelUI_1;
Object.defineProperty(exports, "__esModule", { value: true });
exports.EditLabelUI = exports.EditLabelActionHandler = void 0;
const inversify_1 = require("inversify");
const actions_1 = require("sprotty-protocol/lib/actions");
const types_1 = require("../../base/types");
const ui_extension_1 = require("../../base/ui-extensions/ui-extension");
const ui_extension_registry_1 = require("../../base/ui-extensions/ui-extension-registry");
const dom_helper_1 = require("../../base/views/dom-helper");
const commit_model_1 = require("../../model-source/commit-model");
const keyboard_1 = require("../../utils/keyboard");
const model_1 = require("../bounds/model");
const zoom_1 = require("../viewport/zoom");
const edit_label_1 = require("./edit-label");
const model_2 = require("./model");
/** Shows a UI extension for editing a label on emitted `EditLabelAction`s. */
let EditLabelActionHandler = class EditLabelActionHandler {
handle(action) {
if ((0, edit_label_1.isEditLabelAction)(action)) {
return ui_extension_registry_1.SetUIExtensionVisibilityAction.create({ extensionId: EditLabelUI.ID, visible: true, contextElementsId: [action.labelId] });
}
}
};
exports.EditLabelActionHandler = EditLabelActionHandler;
exports.EditLabelActionHandler = EditLabelActionHandler = __decorate([
(0, inversify_1.injectable)()
], EditLabelActionHandler);
let EditLabelUI = EditLabelUI_1 = class EditLabelUI extends ui_extension_1.AbstractUIExtension {
constructor() {
super(...arguments);
this.validationTimeout = undefined;
this.isActive = false;
this.blockApplyEditOnInvalidInput = true;
this.isCurrentLabelValid = true;
}
id() { return EditLabelUI_1.ID; }
containerClass() { return 'label-edit'; }
get labelId() { return this.label ? this.label.id : 'unknown'; }
initializeContents(containerElement) {
containerElement.style.position = 'absolute';
this.inputElement = document.createElement('input');
this.textAreaElement = document.createElement('textarea');
[this.inputElement, this.textAreaElement].forEach((element) => {
element.onkeydown = event => this.applyLabelEditOnEvent(event, 'Enter');
this.configureAndAdd(element, containerElement);
});
}
configureAndAdd(element, containerElement) {
element.style.visibility = 'hidden';
element.style.position = 'absolute';
element.style.top = '0px';
element.style.left = '0px';
element.addEventListener('keydown', (event) => this.hideIfEscapeEvent(event));
element.addEventListener('keyup', (event) => this.validateLabelIfContentChange(event, element.value));
element.addEventListener('blur', () => window.setTimeout(() => this.applyLabelEdit(), 200));
containerElement.appendChild(element);
}
get editControl() {
if (this.label && this.label.isMultiLine) {
return this.textAreaElement;
}
return this.inputElement;
}
hideIfEscapeEvent(event) {
if ((0, keyboard_1.matchesKeystroke)(event, 'Escape')) {
this.hide();
}
}
applyLabelEditOnEvent(event, code, ...modifiers) {
if ((0, keyboard_1.matchesKeystroke)(event, code ? code : 'Enter', ...modifiers)) {
event.preventDefault();
this.applyLabelEdit();
}
}
validateLabelIfContentChange(event, value) {
if (this.previousLabelContent === undefined || this.previousLabelContent !== value) {
this.previousLabelContent = value;
this.performLabelValidation(event, this.editControl.value);
}
}
async applyLabelEdit() {
var _a;
if (!this.isActive) {
return;
}
if (((_a = this.label) === null || _a === void 0 ? void 0 : _a.text) === this.editControl.value) {
// no action necessary
this.hide();
return;
}
if (this.blockApplyEditOnInvalidInput) {
const result = await this.validateLabel(this.editControl.value);
if ('error' === result.severity) {
this.editControl.focus();
return;
}
}
this.actionDispatcherProvider()
.then((actionDispatcher) => actionDispatcher.dispatchAll([actions_1.ApplyLabelEditAction.create(this.labelId, this.editControl.value), commit_model_1.CommitModelAction.create()]))
.catch((reason) => this.logger.error(this, 'No action dispatcher available to execute apply label edit action', reason));
this.hide();
}
performLabelValidation(event, value) {
if (this.validationTimeout) {
window.clearTimeout(this.validationTimeout);
}
this.validationTimeout = window.setTimeout(() => this.validateLabel(value), 200);
}
async validateLabel(value) {
if (this.labelValidator && this.label) {
try {
const result = await this.labelValidator.validate(value, this.label);
this.isCurrentLabelValid = 'error' !== result.severity;
this.showValidationResult(result);
return result;
}
catch (reason) {
this.logger.error(this, 'Error validating edited label', reason);
}
}
this.isCurrentLabelValid = true;
return { severity: 'ok', message: undefined };
}
showValidationResult(result) {
this.clearValidationResult();
if (this.validationDecorator) {
this.validationDecorator.decorate(this.editControl, result);
}
}
clearValidationResult() {
if (this.validationDecorator) {
this.validationDecorator.dispose(this.editControl);
}
}
show(root, ...contextElementIds) {
if (!hasEditableLabel(contextElementIds, root) || this.isActive) {
return;
}
super.show(root, ...contextElementIds);
this.isActive = true;
}
hide() {
this.editControl.style.visibility = 'hidden';
super.hide();
this.clearValidationResult();
this.isActive = false;
this.isCurrentLabelValid = true;
this.previousLabelContent = undefined;
if (this.labelElement) {
this.labelElement.style.visibility = 'visible';
}
}
onBeforeShow(containerElement, root, ...contextElementIds) {
this.label = getEditableLabels(contextElementIds, root)[0];
this.previousLabelContent = this.label.text;
this.setPosition(containerElement);
this.applyTextContents();
this.applyFontStyling();
this.editControl.style.visibility = 'visible';
this.editControl.focus();
}
setPosition(containerElement) {
let x = 0;
let y = 0;
let width = 100;
let height = 20;
if (this.label) {
const zoom = (0, zoom_1.getZoom)(this.label);
const bounds = (0, model_1.getAbsoluteClientBounds)(this.label, this.domHelper, this.viewerOptions);
x = bounds.x + (this.label.editControlPositionCorrection ? this.label.editControlPositionCorrection.x : 0) * zoom;
y = bounds.y + (this.label.editControlPositionCorrection ? this.label.editControlPositionCorrection.y : 0) * zoom;
height = (this.label.editControlDimension ? this.label.editControlDimension.height : height) * zoom;
width = (this.label.editControlDimension ? this.label.editControlDimension.width : width) * zoom;
}
containerElement.style.left = `${x}px`;
containerElement.style.top = `${y}px`;
containerElement.style.width = `${width}px`;
this.editControl.style.width = `${width}px`;
containerElement.style.height = `${height}px`;
this.editControl.style.height = `${height}px`;
}
applyTextContents() {
if (this.label) {
this.editControl.value = this.label.text;
if (this.editControl instanceof HTMLTextAreaElement) {
this.editControl.selectionStart = 0;
this.editControl.selectionEnd = 0;
this.editControl.scrollTop = 0;
this.editControl.scrollLeft = 0;
}
else {
this.editControl.setSelectionRange(0, this.editControl.value.length);
}
}
}
applyFontStyling() {
if (this.label) {
this.labelElement = document.getElementById(this.domHelper.createUniqueDOMElementId(this.label));
if (this.labelElement) {
this.labelElement.style.visibility = 'hidden';
const style = window.getComputedStyle(this.labelElement);
this.editControl.style.font = style.font;
this.editControl.style.fontStyle = style.fontStyle;
this.editControl.style.fontFamily = style.fontFamily;
this.editControl.style.fontSize = scaledFont(style.fontSize, (0, zoom_1.getZoom)(this.label));
this.editControl.style.fontWeight = style.fontWeight;
this.editControl.style.lineHeight = style.lineHeight;
}
}
}
};
exports.EditLabelUI = EditLabelUI;
EditLabelUI.ID = 'editLabelUi';
__decorate([
(0, inversify_1.inject)(types_1.TYPES.IActionDispatcherProvider),
__metadata("design:type", Function)
], EditLabelUI.prototype, "actionDispatcherProvider", void 0);
__decorate([
(0, inversify_1.inject)(types_1.TYPES.ViewerOptions),
__metadata("design:type", Object)
], EditLabelUI.prototype, "viewerOptions", void 0);
__decorate([
(0, inversify_1.inject)(types_1.TYPES.DOMHelper),
__metadata("design:type", dom_helper_1.DOMHelper)
], EditLabelUI.prototype, "domHelper", void 0);
__decorate([
(0, inversify_1.inject)(types_1.TYPES.IEditLabelValidator),
(0, inversify_1.optional)(),
__metadata("design:type", Object)
], EditLabelUI.prototype, "labelValidator", void 0);
__decorate([
(0, inversify_1.inject)(types_1.TYPES.IEditLabelValidationDecorator),
(0, inversify_1.optional)(),
__metadata("design:type", Object)
], EditLabelUI.prototype, "validationDecorator", void 0);
exports.EditLabelUI = EditLabelUI = EditLabelUI_1 = __decorate([
(0, inversify_1.injectable)()
], EditLabelUI);
function hasEditableLabel(contextElementIds, root) {
return getEditableLabels(contextElementIds, root).length === 1;
}
function getEditableLabels(contextElementIds, root) {
return contextElementIds.map(id => root.index.getById(id)).filter(model_2.isEditableLabel);
}
function scaledFont(font, zoom) {
return font.replace(/\d+(\.\d+)?/, (match) => {
return String(Number.parseInt(match, 10) * zoom);
});
}
//# sourceMappingURL=edit-label-ui.js.map