Skip to content

Commit

Permalink
Merge pull request #15200 from calixteman/multiple_selection
Browse files Browse the repository at this point in the history
[Editor] Add the ability to make multiple selections (bug 1779582)
  • Loading branch information
calixteman authored Jul 21, 2022
2 parents 9fe4a66 + d6b9ca4 commit 6138e16
Show file tree
Hide file tree
Showing 7 changed files with 486 additions and 146 deletions.
69 changes: 50 additions & 19 deletions src/display/editor/annotation_editor_layer.js
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ class AnnotationEditorLayer {
} else {
this.enableClick();
}
this.setActiveEditor(null);
this.#uiManager.unselectAll();
}

addInkEditorIfNeeded(isCommitting) {
Expand Down Expand Up @@ -210,14 +210,6 @@ class AnnotationEditorLayer {
}

this.#uiManager.setActiveEditor(editor);

if (currentActive && currentActive !== editor) {
currentActive.commitOrRemove();
}

if (editor) {
this.#uiManager.unselectAll();
}
}

enableClick() {
Expand Down Expand Up @@ -250,11 +242,19 @@ class AnnotationEditorLayer {
this.#uiManager.removeEditor(editor);
this.detach(editor);
this.annotationStorage.removeKey(editor.id);
editor.div.remove();
editor.isAttachedToDOM = false;
if (this.#uiManager.isActive(editor) || this.#editors.size === 0) {
this.setActiveEditor(null);
}
editor.div.style.display = "none";
setTimeout(() => {
// When the div is removed from DOM the focus can move on the
// document.body, so we just slightly postpone the removal in
// order to let an element potentially grab the focus before
// the body.
editor.div.style.display = "";
editor.div.remove();
editor.isAttachedToDOM = false;
if (document.activeElement === document.body) {
this.#uiManager.focusMainContainer();
}
}, 0);

if (!this.#isCleaningUp) {
this.addInkEditorIfNeeded(/* isCommitting = */ false);
Expand All @@ -271,10 +271,6 @@ class AnnotationEditorLayer {
return;
}

if (this.#uiManager.isActive(editor)) {
editor.parent?.setActiveEditor(null);
}

this.attach(editor);
editor.pageIndex = this.pageIndex;
editor.parent?.detach(editor);
Expand Down Expand Up @@ -546,6 +542,42 @@ class AnnotationEditorLayer {
return editor;
}

/**
* Set the last selected editor.
* @param {AnnotationEditor} editor
*/
setSelected(editor) {
this.#uiManager.setSelected(editor);
}

/**
* Check if the editor is selected.
* @param {AnnotationEditor} editor
*/
isSelected(editor) {
return this.#uiManager.isSelected(editor);
}

/**
* Unselect an editor.
* @param {AnnotationEditor} editor
*/
unselect(editor) {
this.#uiManager.unselect(editor);
}

get isMultipleSelection() {
return this.#uiManager.isMultipleSelection;
}

/**
* An editor just got a mousedown with ctrl key pressed.
* @param {boolean}} isMultiple
*/
set isMultipleSelection(isMultiple) {
this.#uiManager.isMultipleSelection = isMultiple;
}

/**
* Mouseclick callback.
* @param {MouseEvent} event
Expand Down Expand Up @@ -662,7 +694,6 @@ class AnnotationEditorLayer {
* @param {Object} parameters
*/
update(parameters) {
this.setActiveEditor(null);
this.viewport = parameters.viewport;
this.setDimensions();
this.updateMode();
Expand Down
120 changes: 89 additions & 31 deletions src/display/editor/editor.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,8 @@
// eslint-disable-next-line max-len
/** @typedef {import("./annotation_editor_layer.js").AnnotationEditorLayer} AnnotationEditorLayer */

import {
AnnotationEditorPrefix,
shadow,
unreachable,
} from "../../shared/util.js";
import { bindEvents, ColorManager } from "./tools.js";
import { shadow, unreachable } from "../../shared/util.js";

/**
* @typedef {Object} AnnotationEditorParameters
Expand All @@ -35,8 +31,20 @@ import { bindEvents, ColorManager } from "./tools.js";
* Base class for editors.
*/
class AnnotationEditor {
#boundFocusin = this.focusin.bind(this);

#boundFocusout = this.focusout.bind(this);

#isEditing = false;

#isFocused = false;

#isInEditMode = false;

#wasSelected = false;

#wasFocused = false;

#zIndex = AnnotationEditor._zIndex++;

static _colorManager = new ColorManager();
Expand Down Expand Up @@ -88,17 +96,32 @@ class AnnotationEditor {
this.div.style.zIndex = this.#zIndex;
}

#select() {
if (this.#wasSelected) {
this.parent.unselect(this);
this.unselect();
this.#wasSelected = true;
} else {
this.parent.setSelected(this);
this.select();
}
}

/**
* onfocus callback.
*/
focusin(/* event */) {
this.parent.setActiveEditor(this);
focusin(event) {
this.#isFocused =
event.target === this.div ||
!!event.relatedTarget?.closest(`#${this.id}`);
if (event.target === this.div) {
this.#select();
}
}

/**
* onblur callback.
* @param {FocusEvent} event
* @returns {undefined}
*/
focusout(event) {
if (!this.isAttachedToDOM) {
Expand All @@ -116,10 +139,14 @@ class AnnotationEditor {

event.preventDefault();

this.commitOrRemove();

if (!target?.id?.startsWith(AnnotationEditorPrefix)) {
this.parent.setActiveEditor(null);
this.#isFocused = false;
if (!this.parent.isMultipleSelection) {
this.commitOrRemove();
if (target?.closest(".annotationEditorLayer")) {
// We only unselect the element when another editor (or its parent)
// is grabbing the focus.
this.parent.unselect(this);
}
}
}

Expand Down Expand Up @@ -228,15 +255,13 @@ class AnnotationEditor {

this.setInForeground();

this.div.addEventListener("focusin", this.#boundFocusin);
this.div.addEventListener("focusout", this.#boundFocusout);

const [tx, ty] = this.getInitialTranslation();
this.translate(tx, ty);

bindEvents(this, this.div, [
"dragstart",
"focusin",
"focusout",
"mousedown",
]);
bindEvents(this, this.div, ["dragstart", "mousedown", "mouseup"]);

return this.div;
}
Expand All @@ -250,6 +275,23 @@ class AnnotationEditor {
// Avoid to focus this editor because of a non-left click.
event.preventDefault();
}

const isMultipleSelection = (this.parent.isMultipleSelection =
event.ctrlKey || event.shiftKey);
this.#wasSelected = isMultipleSelection && this.parent.isSelected(this);
this.#wasFocused = this.#isFocused;
}

/**
* Onmouseup callback.
* @param {MouseEvent} event
*/
mouseup(event) {
if (this.#wasFocused) {
this.#select();
}
this.parent.isMultipleSelection = false;
this.#wasFocused = false;
}

getRect(tx, ty) {
Expand Down Expand Up @@ -331,15 +373,13 @@ class AnnotationEditor {

/**
* Enable edit mode.
* @returns {undefined}
*/
enableEditMode() {
this.#isInEditMode = true;
}

/**
* Disable edit mode.
* @returns {undefined}
*/
disableEditMode() {
this.#isInEditMode = false;
Expand Down Expand Up @@ -374,10 +414,9 @@ class AnnotationEditor {
* Rebuild the editor in case it has been removed on undo.
*
* To implement in subclasses.
* @returns {undefined}
*/
rebuild() {
unreachable("An editor must be rebuildable");
this.div?.addEventListener("focusin", this.#boundFocusin);
}

/**
Expand All @@ -386,7 +425,6 @@ class AnnotationEditor {
* new annotation to add to the pdf document.
*
* To implement in subclasses.
* @returns {undefined}
*/
serialize() {
unreachable("An editor must be serializable");
Expand Down Expand Up @@ -423,10 +461,11 @@ class AnnotationEditor {
/**
* Remove this editor.
* It's used on ctrl+backspace action.
*
* @returns {undefined}
*/
remove() {
this.div.removeEventListener("focusin", this.#boundFocusin);
this.div.removeEventListener("focusout", this.#boundFocusout);

if (!this.isEmpty()) {
// The editor is removed but it can be back at some point thanks to
// undo/redo so we must commit it before.
Expand All @@ -439,18 +478,14 @@ class AnnotationEditor {
* Select this editor.
*/
select() {
if (this.div) {
this.div.classList.add("selectedEditor");
}
this.div?.classList.add("selectedEditor");
}

/**
* Unselect this editor.
*/
unselect() {
if (this.div) {
this.div.classList.remove("selectedEditor");
}
this.div?.classList.remove("selectedEditor");
}

/**
Expand Down Expand Up @@ -494,6 +529,29 @@ class AnnotationEditor {
get contentDiv() {
return this.div;
}

/**
* If true then the editor is currently edited.
* @type {boolean}
*/
get isEditing() {
return this.#isEditing;
}

/**
* When set to true, it means that this editor is currently edited.
* @param {boolean} value
*/
set isEditing(value) {
this.#isEditing = value;
if (value) {
this.select();
this.parent.setSelected(this);
this.parent.setActiveEditor(this);
} else {
this.parent.setActiveEditor(null);
}
}
}

export { AnnotationEditor };
Loading

0 comments on commit 6138e16

Please sign in to comment.