[Editor] Move the keyboard manager at the container level
- This way, the keyboard callbacks are called even if the page has not the focus, hence the user doesn't have to guess that they have to click on the page which is a bit painful especially in Ink mode. - Add two keyboard shortcuts to commit a Freetext editor (ctrl+enter and escape).
This commit is contained in:
parent
7a4b72ed11
commit
e1f28d3504
@ -21,8 +21,8 @@
|
||||
/** @typedef {import("../../web/interfaces").IL10n} IL10n */
|
||||
|
||||
import { AnnotationEditorType, shadow } from "../../shared/util.js";
|
||||
import { bindEvents, KeyboardManager } from "./tools.js";
|
||||
import { binarySearchFirstItem } from "../display_utils.js";
|
||||
import { bindEvents } from "./tools.js";
|
||||
import { FreeTextEditor } from "./freetext.js";
|
||||
import { InkEditor } from "./ink.js";
|
||||
|
||||
@ -61,33 +61,6 @@ class AnnotationEditorLayer {
|
||||
|
||||
static _initialized = false;
|
||||
|
||||
static _keyboardManager = new KeyboardManager([
|
||||
[["ctrl+a", "mac+meta+a"], AnnotationEditorLayer.prototype.selectAll],
|
||||
[["ctrl+c", "mac+meta+c"], AnnotationEditorLayer.prototype.copy],
|
||||
[["ctrl+v", "mac+meta+v"], AnnotationEditorLayer.prototype.paste],
|
||||
[["ctrl+x", "mac+meta+x"], AnnotationEditorLayer.prototype.cut],
|
||||
[["ctrl+z", "mac+meta+z"], AnnotationEditorLayer.prototype.undo],
|
||||
[
|
||||
["ctrl+y", "ctrl+shift+Z", "mac+meta+shift+Z"],
|
||||
AnnotationEditorLayer.prototype.redo,
|
||||
],
|
||||
[
|
||||
[
|
||||
"Backspace",
|
||||
"alt+Backspace",
|
||||
"ctrl+Backspace",
|
||||
"shift+Backspace",
|
||||
"mac+Backspace",
|
||||
"mac+alt+Backspace",
|
||||
"mac+ctrl+Backspace",
|
||||
"Delete",
|
||||
"ctrl+Delete",
|
||||
"shift+Delete",
|
||||
],
|
||||
AnnotationEditorLayer.prototype.delete,
|
||||
],
|
||||
]);
|
||||
|
||||
/**
|
||||
* @param {AnnotationEditorLayerOptions} options
|
||||
*/
|
||||
@ -205,62 +178,6 @@ class AnnotationEditorLayer {
|
||||
this.#uiManager.addCommands(params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Undo the last command.
|
||||
*/
|
||||
undo() {
|
||||
this.#uiManager.undo();
|
||||
}
|
||||
|
||||
/**
|
||||
* Redo the last command.
|
||||
*/
|
||||
redo() {
|
||||
this.#uiManager.redo();
|
||||
}
|
||||
|
||||
/**
|
||||
* Suppress the selected editor or all editors.
|
||||
*/
|
||||
delete() {
|
||||
this.#uiManager.delete();
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy the selected editor.
|
||||
*/
|
||||
copy() {
|
||||
this.#uiManager.copy();
|
||||
}
|
||||
|
||||
/**
|
||||
* Cut the selected editor.
|
||||
*/
|
||||
cut() {
|
||||
this.#uiManager.cut();
|
||||
}
|
||||
|
||||
/**
|
||||
* Paste a previously copied editor.
|
||||
*/
|
||||
paste() {
|
||||
this.#uiManager.paste();
|
||||
}
|
||||
|
||||
/**
|
||||
* Select all the editors.
|
||||
*/
|
||||
selectAll() {
|
||||
this.#uiManager.selectAll();
|
||||
}
|
||||
|
||||
/**
|
||||
* Unselect all the editors.
|
||||
*/
|
||||
unselectAll() {
|
||||
this.#uiManager.unselectAll();
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable pointer events on the main div in order to enable
|
||||
* editor creation.
|
||||
@ -299,7 +216,7 @@ class AnnotationEditorLayer {
|
||||
}
|
||||
|
||||
if (editor) {
|
||||
this.unselectAll();
|
||||
this.#uiManager.unselectAll();
|
||||
}
|
||||
}
|
||||
|
||||
@ -691,16 +608,6 @@ class AnnotationEditorLayer {
|
||||
event.preventDefault();
|
||||
}
|
||||
|
||||
/**
|
||||
* Keydown callback.
|
||||
* @param {KeyboardEvent} event
|
||||
*/
|
||||
keydown(event) {
|
||||
if (!this.#uiManager.getActive()?.shouldGetKeyboardEvents()) {
|
||||
AnnotationEditorLayer._keyboardManager.exec(this, event);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Destroy the main editor.
|
||||
*/
|
||||
@ -741,7 +648,7 @@ class AnnotationEditorLayer {
|
||||
*/
|
||||
render(parameters) {
|
||||
this.viewport = parameters.viewport;
|
||||
bindEvents(this, this.div, ["dragover", "drop", "keydown"]);
|
||||
bindEvents(this, this.div, ["dragover", "drop"]);
|
||||
this.setDimensions();
|
||||
for (const editor of this.#uiManager.getEditors(this.pageIndex)) {
|
||||
this.add(editor);
|
||||
|
@ -23,13 +23,15 @@ import {
|
||||
LINE_FACTOR,
|
||||
Util,
|
||||
} from "../../shared/util.js";
|
||||
import { bindEvents, KeyboardManager } from "./tools.js";
|
||||
import { AnnotationEditor } from "./editor.js";
|
||||
import { bindEvents } from "./tools.js";
|
||||
|
||||
/**
|
||||
* Basic text editor in order to create a FreeTex annotation.
|
||||
*/
|
||||
class FreeTextEditor extends AnnotationEditor {
|
||||
#boundEditorDivKeydown = this.editorDivKeydown.bind(this);
|
||||
|
||||
#color;
|
||||
|
||||
#content = "";
|
||||
@ -50,6 +52,13 @@ class FreeTextEditor extends AnnotationEditor {
|
||||
|
||||
static _defaultFontSize = 10;
|
||||
|
||||
static _keyboardManager = new KeyboardManager([
|
||||
[
|
||||
["ctrl+Enter", "mac+meta+Enter", "Escape", "mac+Escape"],
|
||||
FreeTextEditor.prototype.commitOrRemove,
|
||||
],
|
||||
]);
|
||||
|
||||
constructor(params) {
|
||||
super({ ...params, name: "freeTextEditor" });
|
||||
this.#color =
|
||||
@ -210,6 +219,7 @@ class FreeTextEditor extends AnnotationEditor {
|
||||
this.editorDiv.contentEditable = true;
|
||||
this.div.draggable = false;
|
||||
this.div.removeAttribute("tabIndex");
|
||||
this.editorDiv.addEventListener("keydown", this.#boundEditorDivKeydown);
|
||||
}
|
||||
|
||||
/** @inheritdoc */
|
||||
@ -220,6 +230,7 @@ class FreeTextEditor extends AnnotationEditor {
|
||||
this.editorDiv.contentEditable = false;
|
||||
this.div.draggable = true;
|
||||
this.div.tabIndex = 0;
|
||||
this.editorDiv.removeEventListener("keydown", this.#boundEditorDivKeydown);
|
||||
}
|
||||
|
||||
/** @inheritdoc */
|
||||
@ -311,13 +322,17 @@ class FreeTextEditor extends AnnotationEditor {
|
||||
* onkeydown callback.
|
||||
* @param {MouseEvent} event
|
||||
*/
|
||||
keyup(event) {
|
||||
if (event.key === "Enter") {
|
||||
keydown(event) {
|
||||
if (event.target === this.div && event.key === "Enter") {
|
||||
this.enableEditMode();
|
||||
this.editorDiv.focus();
|
||||
}
|
||||
}
|
||||
|
||||
editorDivKeydown(event) {
|
||||
FreeTextEditor._keyboardManager.exec(this, event);
|
||||
}
|
||||
|
||||
/** @inheritdoc */
|
||||
disableEditing() {
|
||||
this.editorDiv.setAttribute("role", "comment");
|
||||
@ -376,7 +391,7 @@ class FreeTextEditor extends AnnotationEditor {
|
||||
// TODO: implement paste callback.
|
||||
// The goal is to sanitize and have something suitable for this
|
||||
// editor.
|
||||
bindEvents(this, this.div, ["dblclick", "keyup"]);
|
||||
bindEvents(this, this.div, ["dblclick", "keydown"]);
|
||||
|
||||
if (this.width) {
|
||||
// This editor was created in using copy (ctrl+c).
|
||||
|
@ -261,12 +261,12 @@ class KeyboardManager {
|
||||
|
||||
/**
|
||||
* Execute a callback, if any, for a given keyboard event.
|
||||
* The page is used as `this` in the callback.
|
||||
* @param {AnnotationEditorLayer} page.
|
||||
* The self is used as `this` in the callback.
|
||||
* @param {Object} self.
|
||||
* @param {KeyboardEvent} event
|
||||
* @returns
|
||||
*/
|
||||
exec(page, event) {
|
||||
exec(self, event) {
|
||||
if (!this.allKeys.has(event.key)) {
|
||||
return;
|
||||
}
|
||||
@ -274,7 +274,7 @@ class KeyboardManager {
|
||||
if (!callback) {
|
||||
return;
|
||||
}
|
||||
callback.bind(page)();
|
||||
callback.bind(self)();
|
||||
event.preventDefault();
|
||||
}
|
||||
}
|
||||
@ -422,6 +422,8 @@ class AnnotationEditorUIManager {
|
||||
|
||||
#previousActiveEditor = null;
|
||||
|
||||
#boundKeydown = this.keydown.bind(this);
|
||||
|
||||
#boundOnEditingAction = this.onEditingAction.bind(this);
|
||||
|
||||
#boundOnPageChanging = this.onPageChanging.bind(this);
|
||||
@ -437,7 +439,37 @@ class AnnotationEditorUIManager {
|
||||
hasSelectedEditor: false,
|
||||
};
|
||||
|
||||
constructor(eventBus) {
|
||||
#container = null;
|
||||
|
||||
static _keyboardManager = new KeyboardManager([
|
||||
[["ctrl+a", "mac+meta+a"], AnnotationEditorUIManager.prototype.selectAll],
|
||||
[["ctrl+c", "mac+meta+c"], AnnotationEditorUIManager.prototype.copy],
|
||||
[["ctrl+v", "mac+meta+v"], AnnotationEditorUIManager.prototype.paste],
|
||||
[["ctrl+x", "mac+meta+x"], AnnotationEditorUIManager.prototype.cut],
|
||||
[["ctrl+z", "mac+meta+z"], AnnotationEditorUIManager.prototype.undo],
|
||||
[
|
||||
["ctrl+y", "ctrl+shift+Z", "mac+meta+shift+Z"],
|
||||
AnnotationEditorUIManager.prototype.redo,
|
||||
],
|
||||
[
|
||||
[
|
||||
"Backspace",
|
||||
"alt+Backspace",
|
||||
"ctrl+Backspace",
|
||||
"shift+Backspace",
|
||||
"mac+Backspace",
|
||||
"mac+alt+Backspace",
|
||||
"mac+ctrl+Backspace",
|
||||
"Delete",
|
||||
"ctrl+Delete",
|
||||
"shift+Delete",
|
||||
],
|
||||
AnnotationEditorUIManager.prototype.delete,
|
||||
],
|
||||
]);
|
||||
|
||||
constructor(container, eventBus) {
|
||||
this.#container = container;
|
||||
this.#eventBus = eventBus;
|
||||
this.#eventBus._on("editingaction", this.#boundOnEditingAction);
|
||||
this.#eventBus._on("pagechanging", this.#boundOnPageChanging);
|
||||
@ -445,6 +477,7 @@ class AnnotationEditorUIManager {
|
||||
}
|
||||
|
||||
destroy() {
|
||||
this.#removeKeyboardManager();
|
||||
this.#eventBus._off("editingaction", this.#boundOnEditingAction);
|
||||
this.#eventBus._off("pagechanging", this.#boundOnPageChanging);
|
||||
this.#eventBus._off("textlayerrendered", this.#boundOnTextLayerRendered);
|
||||
@ -468,6 +501,26 @@ class AnnotationEditorUIManager {
|
||||
layer?.onTextLayerRendered();
|
||||
}
|
||||
|
||||
#addKeyboardManager() {
|
||||
// The keyboard events are caught at the container level in order to be able
|
||||
// to execute some callbacks even if the current page doesn't have focus.
|
||||
this.#container.addEventListener("keydown", this.#boundKeydown);
|
||||
}
|
||||
|
||||
#removeKeyboardManager() {
|
||||
this.#container.removeEventListener("keydown", this.#boundKeydown);
|
||||
}
|
||||
|
||||
/**
|
||||
* Keydown callback.
|
||||
* @param {KeyboardEvent} event
|
||||
*/
|
||||
keydown(event) {
|
||||
if (!this.getActive()?.shouldGetKeyboardEvents()) {
|
||||
AnnotationEditorUIManager._keyboardManager.exec(this, event);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute an action for a given name.
|
||||
* For example, the user can click on the "Undo" entry in the context menu
|
||||
@ -517,6 +570,7 @@ class AnnotationEditorUIManager {
|
||||
*/
|
||||
setEditingState(isEditing) {
|
||||
if (isEditing) {
|
||||
this.#addKeyboardManager();
|
||||
this.#dispatchUpdateStates({
|
||||
isEditing: this.#mode !== AnnotationEditorType.NONE,
|
||||
isEmpty: this.#isEmpty(),
|
||||
@ -526,6 +580,7 @@ class AnnotationEditorUIManager {
|
||||
hasEmptyClipboard: this.#clipboardManager.isEmpty(),
|
||||
});
|
||||
} else {
|
||||
this.#removeKeyboardManager();
|
||||
this.#dispatchUpdateStates({
|
||||
isEditing: false,
|
||||
});
|
||||
|
@ -734,6 +734,7 @@ class BaseViewer {
|
||||
});
|
||||
|
||||
this.#annotationEditorUIManager = new AnnotationEditorUIManager(
|
||||
this.container,
|
||||
this.eventBus
|
||||
);
|
||||
if (mode !== AnnotationEditorType.NONE) {
|
||||
|
Loading…
Reference in New Issue
Block a user