Clean up multitext editor

This commit is contained in:
Paulo Gustavo Veiga 2022-02-16 22:24:41 -08:00
parent 9b45ec7b41
commit 2a43f3310f
4 changed files with 77 additions and 113 deletions

View File

@ -29,7 +29,7 @@ class LocalStorageManager extends PersistenceManager {
this.forceLoad = forceLoad; this.forceLoad = forceLoad;
} }
saveMapXml(mapId: string, mapDoc: Document, pref = null, events = null): void { saveMapXml(mapId: string, mapDoc: Document): void {
const mapXml = new XMLSerializer().serializeToString(mapDoc); const mapXml = new XMLSerializer().serializeToString(mapDoc);
localStorage.setItem(`${mapId}-xml`, mapXml); localStorage.setItem(`${mapId}-xml`, mapXml);
} }

View File

@ -21,17 +21,21 @@ import $ from 'jquery';
import initHotKeyPluggin from '../../../../libraries/jquery.hotkeys'; import initHotKeyPluggin from '../../../../libraries/jquery.hotkeys';
import Events from './Events'; import Events from './Events';
import ActionDispatcher from './ActionDispatcher'; import ActionDispatcher from './ActionDispatcher';
import Topic from './Topic';
initHotKeyPluggin($); initHotKeyPluggin($);
class MultilineTextEditor extends Events { class MultilineTextEditor extends Events {
private _topic: Topic;
private _containerElem: JQuery;
constructor() { constructor() {
super(); super();
this._topic = null; this._topic = null;
this._timeoutId = -1;
} }
static _buildEditor() { private static _buildEditor() {
const result = $('<div></div>') const result = $('<div></div>')
.attr('id', 'textContainer') .attr('id', 'textContainer')
.css({ .css({
@ -54,25 +58,19 @@ class MultilineTextEditor extends Events {
return result; return result;
} }
_registerEvents(containerElem) { private _registerEvents(containerElem: JQuery) {
const textareaElem = this._getTextareaElem(); const textareaElem = this._getTextareaElem();
const me = this; textareaElem.on('keydown', (event) => {
let start; const j: any = $;
let end; switch (j.hotkeys.specialKeys[event.keyCode]) {
textareaElem.on('keydown', function keydown(event) {
switch ($.hotkeys.specialKeys[event.keyCode]) {
case 'esc': case 'esc':
me.close(false); this.close(false);
break; break;
case 'enter': case 'enter': {
if (event.metaKey || event.ctrlKey) { if (event.metaKey || event.ctrlKey) {
// Add return ... // Add return ...
const text = textareaElem.val(); const text = this._getTextAreaText();
let cursorPosition = text.length; const cursorPosition = text.length;
if (textareaElem.selectionStart) {
cursorPosition = textareaElem.selectionStart;
}
const head = text.substring(0, cursorPosition); const head = text.substring(0, cursorPosition);
let tail = ''; let tail = '';
if (cursorPosition < text.length) { if (cursorPosition < text.length) {
@ -80,31 +78,12 @@ class MultilineTextEditor extends Events {
} }
textareaElem.val(`${head}\n${tail}`); textareaElem.val(`${head}\n${tail}`);
// Position cursor ...
if (textareaElem[0].setSelectionRange) {
textareaElem.focus(); textareaElem.focus();
textareaElem[0].setSelectionRange(cursorPosition + 1, cursorPosition + 1); textareaElem[0].setSelectionRange(cursorPosition + 1, cursorPosition + 1);
} else if (textareaElem.createTextRange) {
const range = textareaElem.createTextRange();
range.moveStart('character', cursorPosition + 1);
range.select();
}
} else { } else {
me.close(true); this.close(true);
} }
break; break;
case 'tab': {
event.preventDefault();
start = $(this).get(0).selectionStart;
end = $(this).get(0).selectionEnd;
// set textarea value to: text before caret + tab + text after caret
$(this).val(`${$(this).val().substring(0, start)}\t${$(this).val().substring(end)}`);
// put caret at right position again
$(this).get(0).selectionEnd = start + 1;
$(this).get(0).selectionStart = $(this).get(0).selectionEnd;
break;
} }
default: default:
// No actions... // No actions...
@ -118,9 +97,9 @@ class MultilineTextEditor extends Events {
}); });
textareaElem.on('keyup', (event) => { textareaElem.on('keyup', (event) => {
const text = me._getTextareaElem().val(); const text = this._getTextareaElem().val();
me.fireEvent('input', [event, text]); this.fireEvent('input', [event, text]);
me._adjustEditorSize(); this._adjustEditorSize();
}); });
// If the user clicks on the input, all event must be ignored ... // If the user clicks on the input, all event must be ignored ...
@ -135,14 +114,16 @@ class MultilineTextEditor extends Events {
}); });
} }
_adjustEditorSize() { private _adjustEditorSize() {
if (this.isVisible()) { if (this.isVisible()) {
const textElem = this._getTextareaElem(); const textElem = this._getTextareaElem();
const lines = textElem.val().split('\n'); const lines = this._getTextAreaText().split('\n');
let maxLineLength = 1; let maxLineLength = 1;
lines.forEach((line) => { lines.forEach((line) => {
if (maxLineLength < line.length) maxLineLength = line.length; if (maxLineLength < line.length) {
maxLineLength = line.length;
}
}); });
textElem.attr('cols', maxLineLength); textElem.attr('cols', maxLineLength);
@ -155,13 +136,13 @@ class MultilineTextEditor extends Events {
} }
} }
isVisible() { isVisible(): boolean {
return $defined(this._containerElem) && this._containerElem.css('display') === 'block'; return $defined(this._containerElem) && this._containerElem.css('display') === 'block';
} }
_updateModel() { private _updateModel() {
if (this._topic.getText() !== this._getText()) { if (this._topic.getText() !== this._getTextAreaText()) {
const text = this._getText(); const text = this._getTextAreaText();
const topicId = this._topic.getId(); const topicId = this._topic.getId();
const actionDispatcher = ActionDispatcher.getInstance(); const actionDispatcher = ActionDispatcher.getInstance();
@ -169,7 +150,7 @@ class MultilineTextEditor extends Events {
} }
} }
show(topic, text) { show(topic: Topic, text: string): void {
// Close a previous node editor if it's opened ... // Close a previous node editor if it's opened ...
if (this._topic) { if (this._topic) {
this.close(false); this.close(false);
@ -187,7 +168,7 @@ class MultilineTextEditor extends Events {
} }
} }
_showEditor(defaultText) { private _showEditor(defaultText: string) {
const topic = this._topic; const topic = this._topic;
// Hide topic text ... // Hide topic text ...
@ -199,32 +180,26 @@ class MultilineTextEditor extends Events {
fontStyle.size = nodeText.getHtmlFontSize(); fontStyle.size = nodeText.getHtmlFontSize();
fontStyle.color = nodeText.getColor(); fontStyle.color = nodeText.getColor();
this._setStyle(fontStyle); this._setStyle(fontStyle);
const me = this;
// Set editor's initial size // Set editor's initial size
const displayFunc = function displayFunc() {
// Position the editor and set the size... // Position the editor and set the size...
const textShape = topic.getTextShape(); const textShape = topic.getTextShape();
me._containerElem.css('display', 'block'); this._containerElem.css('display', 'block');
// FIXME: Im not sure if this is best way...
const shapePosition = textShape.getNativePosition(); const shapePosition = textShape.getNativePosition();
me._containerElem.offset(shapePosition); this._containerElem.offset(shapePosition);
// Set editor's initial text ... // Set editor's initial text ...
const text = $defined(defaultText) ? defaultText : topic.getText(); const text = $defined(defaultText) ? defaultText : topic.getText();
me._setText(text); this._setText(text);
// Set the element focus and select the current text ... // Set the element focus and select the current text ...
const inputElem = me._getTextareaElem(); const inputElem = this._getTextareaElem();
me._positionCursor(inputElem, !$defined(defaultText)); this._positionCursor(inputElem, !$defined(defaultText));
};
this._timeoutId = setTimeout(() => displayFunc(), 10);
} }
_setStyle(fontStyle) { private _setStyle(fontStyle) {
const inputField = this._getTextareaElem(); const inputField = this._getTextareaElem();
// allowed param reassign to avoid risks of existing code relying in this side-effect // allowed param reassign to avoid risks of existing code relying in this side-effect
/* eslint-disable no-param-reassign */ /* eslint-disable no-param-reassign */
@ -252,51 +227,33 @@ class MultilineTextEditor extends Events {
this._containerElem.css(style); this._containerElem.css(style);
} }
_setText(text) { private _setText(text: string) {
const textareaElem = this._getTextareaElem(); const textareaElem = this._getTextareaElem();
textareaElem.val(text); textareaElem.val(text);
this._adjustEditorSize(); this._adjustEditorSize();
} }
_getText() { private _getTextAreaText(): string {
return this._getTextareaElem().val(); return this._getTextareaElem().val() as string;
} }
_getTextareaElem() { private _getTextareaElem(): JQuery<HTMLTextAreaElement> {
return this._containerElem.find('textarea'); return this._containerElem.find('textarea');
} }
_positionCursor(textareaElem, selectText) { private _positionCursor(textareaElem: JQuery<HTMLTextAreaElement>, selectText: boolean) {
textareaElem.focus(); textareaElem.focus();
const lengh = textareaElem.val().length; const lengh = this._getTextAreaText().length;
if (selectText) { if (selectText) {
// Mark text as selected ... // Mark text as selected ...
if (textareaElem.createTextRange) {
const rang = textareaElem.createTextRange();
rang.select();
rang.move('character', lengh);
} else {
textareaElem[0].setSelectionRange(0, lengh); textareaElem[0].setSelectionRange(0, lengh);
}
} else if (textareaElem.createTextRange) {
const range = textareaElem.createTextRange();
range.move('character', lengh);
} else { } else {
// allowed param reassign to avoid risks of existing code relying in this side-effect
/* eslint-disable no-param-reassign */
textareaElem.selectionStart = lengh;
textareaElem.selectionEnd = lengh;
/* eslint-enable no-param-reassign */
textareaElem.focus(); textareaElem.focus();
} }
} }
close(update) { close(update: boolean): void {
if (this.isVisible() && this._topic) { if (this.isVisible() && this._topic) {
// Update changes ...
clearTimeout(this._timeoutId);
if (!$defined(update) || update) { if (!$defined(update) || update) {
this._updateModel(); this._updateModel();
} }
@ -307,7 +264,6 @@ class MultilineTextEditor extends Events {
// Remove it form the screen ... // Remove it form the screen ...
this._containerElem.remove(); this._containerElem.remove();
this._containerElem = null; this._containerElem = null;
this._timeoutId = -1;
} }
this._topic = null; this._topic = null;
} }

View File

@ -1268,7 +1268,7 @@ abstract class Topic extends NodeGraph {
} }
// If a drag node is create for it, let's hide the editor. // If a drag node is create for it, let's hide the editor.
this._getTopicEventDispatcher().close(); this._getTopicEventDispatcher().close(false);
return result; return result;
} }

View File

@ -19,6 +19,7 @@ import { $assert } from '@wisemapping/core-js';
import Events from './Events'; import Events from './Events';
import MultilineTextEditor from './MultilineTextEditor'; import MultilineTextEditor from './MultilineTextEditor';
import { TopicShape } from './model/INodeModel'; import { TopicShape } from './model/INodeModel';
import Topic from './Topic';
const TopicEvent = { const TopicEvent = {
EDIT: 'editnode', EDIT: 'editnode',
@ -26,30 +27,39 @@ const TopicEvent = {
}; };
class TopicEventDispatcher extends Events { class TopicEventDispatcher extends Events {
constructor(readOnly) { private _readOnly: boolean;
private _activeEditor: MultilineTextEditor;
private _multilineEditor: MultilineTextEditor;
// eslint-disable-next-line no-use-before-define
static _instance: TopicEventDispatcher;
constructor(readOnly: boolean) {
super(); super();
this._readOnly = readOnly; this._readOnly = readOnly;
this._activeEditor = null; this._activeEditor = null;
this._multilineEditor = new MultilineTextEditor(); this._multilineEditor = new MultilineTextEditor();
} }
close(update) { close(update: boolean): void {
if (this.isVisible()) { if (this.isVisible()) {
this._activeEditor.close(update); this._activeEditor.close(update);
this._activeEditor = null; this._activeEditor = null;
} }
} }
show(topic, options) { show(topic: Topic, options?): void {
this.process(TopicEvent.EDIT, topic, options); this.process(TopicEvent.EDIT, topic, options);
} }
process(eventType, topic, options) { process(eventType: string, topic: Topic, options?): void {
$assert(eventType, 'eventType can not be null'); $assert(eventType, 'eventType can not be null');
// Close all previous open editor .... // Close all previous open editor ....
if (this.isVisible()) { if (this.isVisible()) {
this.close(); this.close(false);
} }
// Open the new editor ... // Open the new editor ...
@ -66,20 +76,18 @@ class TopicEventDispatcher extends Events {
} }
} }
isVisible() { isVisible(): boolean {
return this._activeEditor != null && this._activeEditor.isVisible(); return this._activeEditor != null && this._activeEditor.isVisible();
} }
static configure(readOnly: boolean): void {
this._instance = new TopicEventDispatcher(readOnly);
} }
TopicEventDispatcher._instance = null; static getInstance(): TopicEventDispatcher {
TopicEventDispatcher.configure = function configure(readOnly) {
this._instance = new TopicEventDispatcher(readOnly);
};
TopicEventDispatcher.getInstance = function getInstance() {
return this._instance; return this._instance;
}; }
}
export { TopicEvent }; export { TopicEvent };
export default TopicEventDispatcher; export default TopicEventDispatcher;