Merge pull request #16781 from calixteman/editor_rewrite_dragging
[Editor] Refactor dragging and dropping an editor (bug 1802895, bug 1844618)
This commit is contained in:
commit
399475247f
@ -24,7 +24,6 @@
|
||||
|
||||
import { AnnotationEditorType, FeatureTest } from "../../shared/util.js";
|
||||
import { AnnotationEditor } from "./editor.js";
|
||||
import { bindEvents } from "./tools.js";
|
||||
import { FreeTextEditor } from "./freetext.js";
|
||||
import { InkEditor } from "./ink.js";
|
||||
import { setLayerDimensions } from "../display_utils.js";
|
||||
@ -345,7 +344,7 @@ class AnnotationEditorLayer {
|
||||
* being dragged and droped from a page to another.
|
||||
* @param {AnnotationEditor} editor
|
||||
*/
|
||||
#changeParent(editor) {
|
||||
changeParent(editor) {
|
||||
if (editor.parent === this) {
|
||||
return;
|
||||
}
|
||||
@ -370,7 +369,7 @@ class AnnotationEditorLayer {
|
||||
* @param {AnnotationEditor} editor
|
||||
*/
|
||||
add(editor) {
|
||||
this.#changeParent(editor);
|
||||
this.changeParent(editor);
|
||||
this.#uiManager.addEditor(editor);
|
||||
this.attach(editor);
|
||||
|
||||
@ -579,36 +578,19 @@ class AnnotationEditorLayer {
|
||||
}
|
||||
|
||||
/**
|
||||
* Drag callback.
|
||||
* @param {DragEvent} event
|
||||
*
|
||||
* @param {AnnotationEditor} editor
|
||||
* @param {number} x
|
||||
* @param {number} y
|
||||
* @returns
|
||||
*/
|
||||
drop(event) {
|
||||
const id = event.dataTransfer.getData("text/plain");
|
||||
const editor = this.#uiManager.getEditor(id);
|
||||
if (!editor) {
|
||||
return;
|
||||
findNewParent(editor, x, y) {
|
||||
const layer = this.#uiManager.findParent(x, y);
|
||||
if (layer === null || layer === this) {
|
||||
return false;
|
||||
}
|
||||
|
||||
event.preventDefault();
|
||||
event.dataTransfer.dropEffect = "move";
|
||||
|
||||
this.#changeParent(editor);
|
||||
|
||||
const rect = this.div.getBoundingClientRect();
|
||||
const endX = event.clientX - rect.x;
|
||||
const endY = event.clientY - rect.y;
|
||||
|
||||
editor.translate(endX - editor.startX, endY - editor.startY);
|
||||
this.moveEditorInDOM(editor);
|
||||
editor.div.focus();
|
||||
}
|
||||
|
||||
/**
|
||||
* Dragover callback.
|
||||
* @param {DragEvent} event
|
||||
*/
|
||||
dragover(event) {
|
||||
event.preventDefault();
|
||||
layer.changeParent(editor);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -650,7 +632,6 @@ class AnnotationEditorLayer {
|
||||
render({ viewport }) {
|
||||
this.viewport = viewport;
|
||||
setLayerDimensions(this.div, viewport);
|
||||
bindEvents(this, this.div, ["dragover", "drop"]);
|
||||
for (const editor of this.#uiManager.getEditors(this.pageIndex)) {
|
||||
this.add(editor);
|
||||
}
|
||||
|
@ -57,6 +57,8 @@ class AnnotationEditor {
|
||||
|
||||
_uiManager = null;
|
||||
|
||||
#isDraggable = false;
|
||||
|
||||
#zIndex = AnnotationEditor._zIndex++;
|
||||
|
||||
static _colorManager = new ColorManager();
|
||||
@ -148,6 +150,15 @@ class AnnotationEditor {
|
||||
return [];
|
||||
}
|
||||
|
||||
get _isDraggable() {
|
||||
return this.#isDraggable;
|
||||
}
|
||||
|
||||
set _isDraggable(value) {
|
||||
this.#isDraggable = value;
|
||||
this.div?.classList.toggle("draggable", value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add some commands into the CommandManager (undo/redo stuff).
|
||||
* @param {Object} params
|
||||
@ -237,18 +248,6 @@ class AnnotationEditor {
|
||||
this._uiManager.addToAnnotationStorage(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* We use drag-and-drop in order to move an editor on a page.
|
||||
* @param {DragEvent} event
|
||||
*/
|
||||
dragstart(event) {
|
||||
const rect = this.parent.div.getBoundingClientRect();
|
||||
this.startX = event.clientX - rect.x;
|
||||
this.startY = event.clientY - rect.y;
|
||||
event.dataTransfer.setData("text/plain", this.id);
|
||||
event.dataTransfer.effectAllowed = "move";
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the editor position within its parent.
|
||||
* @param {number} x
|
||||
@ -446,8 +445,8 @@ class AnnotationEditor {
|
||||
event.preventDefault();
|
||||
this.#resizePosition = [event.clientX, event.clientY];
|
||||
const boundResizerPointermove = this.#resizerPointermove.bind(this, name);
|
||||
const savedDraggable = this.div.draggable;
|
||||
this.div.draggable = false;
|
||||
const savedDraggable = this._isDraggable;
|
||||
this._isDraggable = false;
|
||||
const resizingClassName = `resizing${name
|
||||
.charAt(0)
|
||||
.toUpperCase()}${name.slice(1)}`;
|
||||
@ -462,7 +461,7 @@ class AnnotationEditor {
|
||||
// Stop the undo accumulation in order to have an undo action for each
|
||||
// resize session.
|
||||
this._uiManager.stopUndoAccumulation();
|
||||
this.div.draggable = savedDraggable;
|
||||
this._isDraggable = savedDraggable;
|
||||
this.parent.div.classList.remove(resizingClassName);
|
||||
window.removeEventListener("pointerup", pointerUpCallback);
|
||||
window.removeEventListener("blur", pointerUpCallback);
|
||||
@ -721,7 +720,7 @@ class AnnotationEditor {
|
||||
const [tx, ty] = this.getInitialTranslation();
|
||||
this.translate(tx, ty);
|
||||
|
||||
bindEvents(this, this.div, ["dragstart", "pointerdown"]);
|
||||
bindEvents(this, this.div, ["pointerdown"]);
|
||||
|
||||
return this.div;
|
||||
}
|
||||
@ -749,6 +748,90 @@ class AnnotationEditor {
|
||||
}
|
||||
|
||||
this.#hasBeenSelected = true;
|
||||
|
||||
this.#setUpDragSession(event);
|
||||
}
|
||||
|
||||
#setUpDragSession(event) {
|
||||
if (!this._isDraggable) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Avoid to have spurious text selection in the text layer when dragging.
|
||||
this._uiManager.disableUserSelect(true);
|
||||
|
||||
const savedParent = this.parent;
|
||||
const savedX = this.x;
|
||||
const savedY = this.y;
|
||||
|
||||
const pointerMoveOptions = { passive: true, capture: true };
|
||||
const pointerMoveCallback = e => {
|
||||
const [parentWidth, parentHeight] = this.parentDimensions;
|
||||
const [tx, ty] = this.screenToPageTranslation(e.movementX, e.movementY);
|
||||
this.x += tx / parentWidth;
|
||||
this.y += ty / parentHeight;
|
||||
if (this.x < 0 || this.x > 1 || this.y < 0 || this.y > 1) {
|
||||
// The element will be outside of its parent so change the parent.
|
||||
const { x, y } = this.div.getBoundingClientRect();
|
||||
if (this.parent.findNewParent(this, x, y)) {
|
||||
this.x -= Math.floor(this.x);
|
||||
this.y -= Math.floor(this.y);
|
||||
}
|
||||
}
|
||||
|
||||
this.div.style.left = `${(100 * this.x).toFixed(2)}%`;
|
||||
this.div.style.top = `${(100 * this.y).toFixed(2)}%`;
|
||||
this.div.scrollIntoView({ block: "nearest" });
|
||||
};
|
||||
window.addEventListener(
|
||||
"pointermove",
|
||||
pointerMoveCallback,
|
||||
pointerMoveOptions
|
||||
);
|
||||
|
||||
const pointerUpCallback = () => {
|
||||
this._uiManager.disableUserSelect(false);
|
||||
window.removeEventListener("pointerup", pointerUpCallback);
|
||||
window.removeEventListener("blur", pointerUpCallback);
|
||||
window.removeEventListener(
|
||||
"pointermove",
|
||||
pointerMoveCallback,
|
||||
pointerMoveOptions
|
||||
);
|
||||
const newParent = this.parent;
|
||||
const newX = this.x;
|
||||
const newY = this.y;
|
||||
if (newParent === savedParent && newX === savedX && newY === savedY) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.addCommands({
|
||||
cmd: () => {
|
||||
newParent.changeParent(this);
|
||||
this.x = newX;
|
||||
this.y = newY;
|
||||
this.fixAndSetPosition();
|
||||
newParent.moveEditorInDOM(this);
|
||||
},
|
||||
undo: () => {
|
||||
savedParent.changeParent(this);
|
||||
this.x = savedX;
|
||||
this.y = savedY;
|
||||
this.fixAndSetPosition();
|
||||
savedParent.moveEditorInDOM(this);
|
||||
},
|
||||
mustExec: false,
|
||||
});
|
||||
|
||||
this.fixAndSetPosition();
|
||||
this.parent.moveEditorInDOM(this);
|
||||
this.div.focus();
|
||||
};
|
||||
window.addEventListener("pointerup", pointerUpCallback);
|
||||
// If the user is using alt+tab during the dragging session, the pointerup
|
||||
// event could be not fired, but a blur event is fired so we can use it in
|
||||
// order to interrupt the dragging session.
|
||||
window.addEventListener("blur", pointerUpCallback);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -304,7 +304,7 @@ class FreeTextEditor extends AnnotationEditor {
|
||||
super.enableEditMode();
|
||||
this.overlayDiv.classList.remove("enabled");
|
||||
this.editorDiv.contentEditable = true;
|
||||
this.div.draggable = false;
|
||||
this._isDraggable = false;
|
||||
this.div.removeAttribute("aria-activedescendant");
|
||||
this.editorDiv.addEventListener("keydown", this.#boundEditorDivKeydown);
|
||||
this.editorDiv.addEventListener("focus", this.#boundEditorDivFocus);
|
||||
@ -323,7 +323,7 @@ class FreeTextEditor extends AnnotationEditor {
|
||||
this.overlayDiv.classList.add("enabled");
|
||||
this.editorDiv.contentEditable = false;
|
||||
this.div.setAttribute("aria-activedescendant", this.#editorDivId);
|
||||
this.div.draggable = true;
|
||||
this._isDraggable = true;
|
||||
this.editorDiv.removeEventListener("keydown", this.#boundEditorDivKeydown);
|
||||
this.editorDiv.removeEventListener("focus", this.#boundEditorDivFocus);
|
||||
this.editorDiv.removeEventListener("blur", this.#boundEditorDivBlur);
|
||||
@ -614,10 +614,10 @@ class FreeTextEditor extends AnnotationEditor {
|
||||
}
|
||||
|
||||
this.#setContent();
|
||||
this.div.draggable = true;
|
||||
this._isDraggable = true;
|
||||
this.editorDiv.contentEditable = false;
|
||||
} else {
|
||||
this.div.draggable = false;
|
||||
this._isDraggable = false;
|
||||
this.editorDiv.contentEditable = true;
|
||||
}
|
||||
|
||||
|
@ -294,7 +294,7 @@ class InkEditor extends AnnotationEditor {
|
||||
}
|
||||
|
||||
super.enableEditMode();
|
||||
this.div.draggable = false;
|
||||
this._isDraggable = false;
|
||||
this.canvas.addEventListener("pointerdown", this.#boundCanvasPointerdown);
|
||||
}
|
||||
|
||||
@ -305,7 +305,7 @@ class InkEditor extends AnnotationEditor {
|
||||
}
|
||||
|
||||
super.disableEditMode();
|
||||
this.div.draggable = !this.isEmpty();
|
||||
this._isDraggable = !this.isEmpty();
|
||||
this.div.classList.remove("editing");
|
||||
|
||||
this.canvas.removeEventListener(
|
||||
@ -316,7 +316,7 @@ class InkEditor extends AnnotationEditor {
|
||||
|
||||
/** @inheritdoc */
|
||||
onceAdded() {
|
||||
this.div.draggable = !this.isEmpty();
|
||||
this._isDraggable = !this.isEmpty();
|
||||
}
|
||||
|
||||
/** @inheritdoc */
|
||||
|
@ -177,7 +177,7 @@ class StampEditor extends AnnotationEditor {
|
||||
|
||||
/** @inheritdoc */
|
||||
onceAdded() {
|
||||
this.div.draggable = true;
|
||||
this._isDraggable = true;
|
||||
this.parent.addUndoableEditor(this);
|
||||
this.div.focus();
|
||||
}
|
||||
|
@ -586,6 +586,8 @@ class AnnotationEditorUIManager {
|
||||
|
||||
#container = null;
|
||||
|
||||
#viewer = null;
|
||||
|
||||
static TRANSLATE_SMALL = 1; // page units.
|
||||
|
||||
static TRANSLATE_BIG = 10; // page units.
|
||||
@ -686,8 +688,9 @@ class AnnotationEditorUIManager {
|
||||
);
|
||||
}
|
||||
|
||||
constructor(container, eventBus, pdfDocument, pageColors) {
|
||||
constructor(container, viewer, eventBus, pdfDocument, pageColors) {
|
||||
this.#container = container;
|
||||
this.#viewer = viewer;
|
||||
this.#eventBus = eventBus;
|
||||
this.#eventBus._on("editingaction", this.#boundOnEditingAction);
|
||||
this.#eventBus._on("pagechanging", this.#boundOnPageChanging);
|
||||
@ -740,6 +743,30 @@ class AnnotationEditorUIManager {
|
||||
this.#container.focus();
|
||||
}
|
||||
|
||||
findParent(x, y) {
|
||||
for (const layer of this.#allLayers.values()) {
|
||||
const {
|
||||
x: layerX,
|
||||
y: layerY,
|
||||
width,
|
||||
height,
|
||||
} = layer.div.getBoundingClientRect();
|
||||
if (
|
||||
x >= layerX &&
|
||||
x <= layerX + width &&
|
||||
y >= layerY &&
|
||||
y <= layerY + height
|
||||
) {
|
||||
return layer;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
disableUserSelect(value = false) {
|
||||
this.#viewer.classList.toggle("noUserSelect", value);
|
||||
}
|
||||
|
||||
addShouldRescale(editor) {
|
||||
this.#editorsToRescale.add(editor);
|
||||
}
|
||||
@ -961,6 +988,7 @@ class AnnotationEditorUIManager {
|
||||
this.#dispatchUpdateStates({
|
||||
isEditing: false,
|
||||
});
|
||||
this.disableUserSelect(false);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -15,6 +15,7 @@
|
||||
|
||||
const {
|
||||
closePages,
|
||||
dragAndDropAnnotation,
|
||||
getEditors,
|
||||
getEditorSelector,
|
||||
getSelectedEditors,
|
||||
@ -891,13 +892,6 @@ describe("FreeText Editor", () => {
|
||||
it("must move an annotation", async () => {
|
||||
await Promise.all(
|
||||
pages.map(async ([browserName, page]) => {
|
||||
if (browserName === "firefox") {
|
||||
pending(
|
||||
"Disabled in Firefox, because DnD isn't implemented yet (see bug 1838638)."
|
||||
);
|
||||
}
|
||||
|
||||
await page.setDragInterception(true);
|
||||
await page.click("#editorFreeText");
|
||||
|
||||
const editorIds = await getEditors(page, "freeText");
|
||||
@ -913,16 +907,12 @@ describe("FreeText Editor", () => {
|
||||
return { x, y, width, height };
|
||||
});
|
||||
|
||||
await page.mouse.dragAndDrop(
|
||||
{
|
||||
x: editorRect.x + editorRect.width / 2,
|
||||
y: editorRect.y + editorRect.height / 2,
|
||||
},
|
||||
{
|
||||
x: editorRect.x + editorRect.width / 2 + 100,
|
||||
y: editorRect.y + editorRect.height / 2 + 100,
|
||||
},
|
||||
{ delay: 100 }
|
||||
await dragAndDropAnnotation(
|
||||
page,
|
||||
editorRect.x + editorRect.width / 2,
|
||||
editorRect.y + editorRect.height / 2,
|
||||
100,
|
||||
100
|
||||
);
|
||||
|
||||
serialized = await getSerialized(page);
|
||||
|
@ -194,3 +194,11 @@ function serializeBitmapDimensions(page) {
|
||||
});
|
||||
}
|
||||
exports.serializeBitmapDimensions = serializeBitmapDimensions;
|
||||
|
||||
async function dragAndDropAnnotation(page, startX, startY, tX, tY) {
|
||||
await page.mouse.move(startX, startY);
|
||||
await page.mouse.down();
|
||||
await page.mouse.move(startX + tX, startY + tY);
|
||||
await page.mouse.up();
|
||||
}
|
||||
exports.dragAndDropAnnotation = dragAndDropAnnotation;
|
||||
|
@ -81,7 +81,7 @@
|
||||
}
|
||||
|
||||
.annotationEditorLayer
|
||||
:is(.freeTextEditor, .inkEditor, .stampEditor)[draggable="true"] {
|
||||
:is(.freeTextEditor, .inkEditor, .stampEditor).draggable {
|
||||
cursor: move;
|
||||
}
|
||||
|
||||
|
@ -87,6 +87,10 @@
|
||||
height: var(--viewer-container-height);
|
||||
}
|
||||
|
||||
.pdfViewer.noUserSelect {
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
/*#if GENERIC*/
|
||||
.pdfViewer.removePageBorders .page {
|
||||
margin: 0 auto 10px;
|
||||
|
@ -849,6 +849,7 @@ class PDFViewer {
|
||||
} else if (isValidAnnotationEditorMode(mode)) {
|
||||
this.#annotationEditorUIManager = new AnnotationEditorUIManager(
|
||||
this.container,
|
||||
this.viewer,
|
||||
this.eventBus,
|
||||
pdfDocument,
|
||||
this.pageColors
|
||||
|
Loading…
x
Reference in New Issue
Block a user