Merge pull request #15373 from calixteman/copy_paste
[Editor] Use the global clipboard for the copy/paste/cut operations
This commit is contained in:
commit
e9bdbe4574
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user