Merge pull request #15373 from calixteman/copy_paste

[Editor] Use the global clipboard for the copy/paste/cut operations
This commit is contained in:
calixteman 2022-08-31 16:53:16 +02:00 committed by GitHub
commit e9bdbe4574
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

View File

@ -22,6 +22,7 @@ import {
AnnotationEditorType,
shadow,
Util,
warn,
} from "../../shared/util.js";
import { getColorValues, getRGB } from "../display_utils.js";
@ -281,53 +282,6 @@ class KeyboardManager {
}
}
/**
* Basic clipboard to copy/paste some editors.
* It has to be used as a singleton.
*/
class ClipboardManager {
#elements = null;
/**
* Copy an element.
* @param {AnnotationEditor|Array<AnnotationEditor>} element
*/
copy(element) {
if (!element) {
return;
}
if (Array.isArray(element)) {
this.#elements = element.map(el => el.serialize());
} else {
this.#elements = [element.serialize()];
}
this.#elements = this.#elements.filter(el => !!el);
if (this.#elements.length === 0) {
this.#elements = null;
}
}
/**
* Create a new element.
* @returns {AnnotationEditor|null}
*/
paste() {
return this.#elements;
}
/**
* Check if the clipboard is empty.
* @returns {boolean}
*/
isEmpty() {
return this.#elements === null;
}
destroy() {
this.#elements = null;
}
}
class ColorManager {
static _colorsMapping = new Map([
["CanvasText", [0, 0, 0]],
@ -404,8 +358,6 @@ class AnnotationEditorUIManager {
#allLayers = new Map();
#clipboardManager = new ClipboardManager();
#commandManager = new CommandManager();
#currentPageIndex = 0;
@ -422,6 +374,12 @@ class AnnotationEditorUIManager {
#selectedEditors = new Set();
#boundCopy = this.copy.bind(this);
#boundCut = this.cut.bind(this);
#boundPaste = this.paste.bind(this);
#boundKeydown = this.keydown.bind(this);
#boundOnEditingAction = this.onEditingAction.bind(this);
@ -431,7 +389,6 @@ class AnnotationEditorUIManager {
#previousStates = {
isEditing: false,
isEmpty: true,
hasEmptyClipboard: true,
hasSomethingToUndo: false,
hasSomethingToRedo: false,
hasSelectedEditor: false,
@ -441,9 +398,6 @@ class AnnotationEditorUIManager {
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"],
@ -485,7 +439,6 @@ class AnnotationEditorUIManager {
this.#allEditors.clear();
this.#activeEditor = null;
this.#selectedEditors.clear();
this.#clipboardManager.destroy();
this.#commandManager.destroy();
}
@ -507,6 +460,109 @@ class AnnotationEditorUIManager {
this.#container.removeEventListener("keydown", this.#boundKeydown);
}
#addCopyPasteListeners() {
document.addEventListener("copy", this.#boundCopy);
document.addEventListener("cut", this.#boundCut);
document.addEventListener("paste", this.#boundPaste);
}
#removeCopyPasteListeners() {
document.removeEventListener("copy", this.#boundCopy);
document.removeEventListener("cut", this.#boundCut);
document.removeEventListener("paste", this.#boundPaste);
}
/**
* Copy callback.
* @param {ClipboardEvent} event
*/
copy(event) {
event.preventDefault();
if (this.#activeEditor) {
// An editor is being edited so just commit it.
this.#activeEditor.commitOrRemove();
}
if (!this.hasSelection) {
return;
}
const editors = [];
for (const editor of this.#selectedEditors) {
if (!editor.isEmpty()) {
editors.push(editor.serialize());
}
}
if (editors.length === 0) {
return;
}
event.clipboardData.setData("application/pdfjs", JSON.stringify(editors));
}
/**
* Cut callback.
* @param {ClipboardEvent} event
*/
cut(event) {
this.copy(event);
this.delete();
}
/**
* Paste callback.
* @param {ClipboardEvent} event
*/
paste(event) {
event.preventDefault();
let data = event.clipboardData.getData("application/pdfjs");
if (!data) {
return;
}
try {
data = JSON.parse(data);
} catch (ex) {
warn(`paste: "${ex.message}".`);
return;
}
if (!Array.isArray(data)) {
return;
}
this.unselectAll();
const layer = this.#allLayers.get(this.#currentPageIndex);
try {
const newEditors = [];
for (const editor of data) {
const deserializedEditor = layer.deserialize(editor);
if (!deserializedEditor) {
return;
}
newEditors.push(deserializedEditor);
}
const cmd = () => {
for (const editor of newEditors) {
this.#addEditorToLayer(editor);
}
this.#selectEditors(newEditors);
};
const undo = () => {
for (const editor of newEditors) {
editor.remove();
}
};
this.addCommands({ cmd, undo, mustExec: true });
} catch (ex) {
warn(`paste: "${ex.message}".`);
}
}
/**
* Keydown callback.
* @param {KeyboardEvent} event
@ -534,8 +590,8 @@ class AnnotationEditorUIManager {
}
/**
* Update the different possible states of this manager, e.g. is the clipboard
* empty or is there something to undo, ...
* Update the different possible states of this manager, e.g. is there
* something to undo, redo, ...
* @param {Object} details
*/
#dispatchUpdateStates(details) {
@ -567,16 +623,17 @@ class AnnotationEditorUIManager {
setEditingState(isEditing) {
if (isEditing) {
this.#addKeyboardManager();
this.#addCopyPasteListeners();
this.#dispatchUpdateStates({
isEditing: this.#mode !== AnnotationEditorType.NONE,
isEmpty: this.#isEmpty(),
hasSomethingToUndo: this.#commandManager.hasSomethingToUndo(),
hasSomethingToRedo: this.#commandManager.hasSomethingToRedo(),
hasSelectedEditor: false,
hasEmptyClipboard: this.#clipboardManager.isEmpty(),
});
} else {
this.#removeKeyboardManager();
this.#removeCopyPasteListeners();
this.#dispatchUpdateStates({
isEditing: false,
});
@ -909,68 +966,6 @@ class AnnotationEditorUIManager {
this.addCommands({ cmd, undo, mustExec: true });
}
/**
* Copy the selected editor.
*/
copy() {
if (this.#activeEditor) {
// An editor is being edited so just commit it.
this.#activeEditor.commitOrRemove();
}
if (this.hasSelection) {
const editors = [];
for (const editor of this.#selectedEditors) {
if (!editor.isEmpty()) {
editors.push(editor);
}
}
if (editors.length === 0) {
return;
}
this.#clipboardManager.copy(editors);
this.#dispatchUpdateStates({ hasEmptyClipboard: false });
}
}
/**
* Cut the selected editor.
*/
cut() {
this.copy();
this.delete();
}
/**
* Paste a previously copied editor.
* @returns {undefined}
*/
paste() {
if (this.#clipboardManager.isEmpty()) {
return;
}
this.unselectAll();
const layer = this.#allLayers.get(this.#currentPageIndex);
const newEditors = this.#clipboardManager
.paste()
.map(data => layer.deserialize(data));
const cmd = () => {
for (const editor of newEditors) {
this.#addEditorToLayer(editor);
}
this.#selectEditors(newEditors);
};
const undo = () => {
for (const editor of newEditors) {
editor.remove();
}
};
this.addCommands({ cmd, undo, mustExec: true });
}
/**
* Select the editors.
* @param {Array<AnnotationEditor>} editors