Skip to content

Commit

Permalink
[Editor] Use the global clipboard for the copy/paste/cut operations
Browse files Browse the repository at this point in the history
It slightly helps to reduce the code size and its complexity.
But the cool thing is that it allows to copy/paste some anntations from a pdf
to an other.
  • Loading branch information
calixteman committed Aug 31, 2022
1 parent 54d3b64 commit 24583db
Showing 1 changed file with 110 additions and 119 deletions.
229 changes: 110 additions & 119 deletions src/display/editor/tools.js
Original file line number Diff line number Diff line change
Expand Up @@ -281,53 +281,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]],
Expand Down Expand Up @@ -404,8 +357,6 @@ class AnnotationEditorUIManager {

#allLayers = new Map();

#clipboardManager = new ClipboardManager();

#commandManager = new CommandManager();

#currentPageIndex = 0;
Expand All @@ -422,6 +373,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);
Expand All @@ -431,7 +388,6 @@ class AnnotationEditorUIManager {
#previousStates = {
isEditing: false,
isEmpty: true,
hasEmptyClipboard: true,
hasSomethingToUndo: false,
hasSomethingToRedo: false,
hasSelectedEditor: false,
Expand All @@ -441,9 +397,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"],
Expand Down Expand Up @@ -485,7 +438,6 @@ class AnnotationEditorUIManager {
this.#allEditors.clear();
this.#activeEditor = null;
this.#selectedEditors.clear();
this.#clipboardManager.destroy();
this.#commandManager.destroy();
}

Expand All @@ -507,6 +459,106 @@ 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 {
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 {}
}

/**
* Keydown callback.
* @param {KeyboardEvent} event
Expand Down Expand Up @@ -534,8 +586,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) {
Expand Down Expand Up @@ -567,16 +619,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,
});
Expand Down Expand Up @@ -909,68 +962,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
Expand Down

0 comments on commit 24583db

Please sign in to comment.