Merge pull request #16535 from calixteman/restore_freetext
[Editor] Allow to edit FreeText annotations
This commit is contained in:
commit
5581e22cc7
@ -3553,7 +3553,7 @@ class FreeTextAnnotation extends MarkupAnnotation {
|
|||||||
constructor(params) {
|
constructor(params) {
|
||||||
super(params);
|
super(params);
|
||||||
|
|
||||||
this.data.hasOwnCanvas = this.data.noRotate;
|
this.data.hasOwnCanvas = true;
|
||||||
|
|
||||||
const { xref } = params;
|
const { xref } = params;
|
||||||
this.data.annotationType = AnnotationType.FREETEXT;
|
this.data.annotationType = AnnotationType.FREETEXT;
|
||||||
|
@ -2629,7 +2629,7 @@ class AnnotationLayer {
|
|||||||
|
|
||||||
#div = null;
|
#div = null;
|
||||||
|
|
||||||
#editableAnnotations = new Set();
|
#editableAnnotations = new Map();
|
||||||
|
|
||||||
constructor({ div, accessibilityManager, annotationCanvasMap }) {
|
constructor({ div, accessibilityManager, annotationCanvasMap }) {
|
||||||
this.#div = div;
|
this.#div = div;
|
||||||
@ -2696,7 +2696,7 @@ class AnnotationLayer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (element.annotationEditorType > 0) {
|
if (element.annotationEditorType > 0) {
|
||||||
this.#editableAnnotations.add(element);
|
this.#editableAnnotations.set(element.data.id, element);
|
||||||
}
|
}
|
||||||
|
|
||||||
const rendered = element.render();
|
const rendered = element.render();
|
||||||
@ -2767,7 +2767,11 @@ class AnnotationLayer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
getEditableAnnotations() {
|
getEditableAnnotations() {
|
||||||
return this.#editableAnnotations;
|
return Array.from(this.#editableAnnotations.values());
|
||||||
|
}
|
||||||
|
|
||||||
|
getEditableAnnotation(id) {
|
||||||
|
return this.#editableAnnotations.get(id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -13,7 +13,6 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/** @typedef {import("./editor.js").AnnotationEditor} AnnotationEditor */
|
|
||||||
// eslint-disable-next-line max-len
|
// eslint-disable-next-line max-len
|
||||||
/** @typedef {import("./tools.js").AnnotationEditorUIManager} AnnotationEditorUIManager */
|
/** @typedef {import("./tools.js").AnnotationEditorUIManager} AnnotationEditorUIManager */
|
||||||
/** @typedef {import("../display_utils.js").PageViewport} PageViewport */
|
/** @typedef {import("../display_utils.js").PageViewport} PageViewport */
|
||||||
@ -24,6 +23,7 @@
|
|||||||
/** @typedef {import("../src/display/annotation_layer.js").AnnotationLayer} AnnotationLayer */
|
/** @typedef {import("../src/display/annotation_layer.js").AnnotationLayer} AnnotationLayer */
|
||||||
|
|
||||||
import { AnnotationEditorType, FeatureTest } from "../../shared/util.js";
|
import { AnnotationEditorType, FeatureTest } from "../../shared/util.js";
|
||||||
|
import { AnnotationEditor } from "./editor.js";
|
||||||
import { bindEvents } from "./tools.js";
|
import { bindEvents } from "./tools.js";
|
||||||
import { FreeTextEditor } from "./freetext.js";
|
import { FreeTextEditor } from "./freetext.js";
|
||||||
import { InkEditor } from "./ink.js";
|
import { InkEditor } from "./ink.js";
|
||||||
@ -66,6 +66,8 @@ class AnnotationEditorLayer {
|
|||||||
|
|
||||||
#isCleaningUp = false;
|
#isCleaningUp = false;
|
||||||
|
|
||||||
|
#isDisabling = false;
|
||||||
|
|
||||||
#uiManager;
|
#uiManager;
|
||||||
|
|
||||||
static _initialized = false;
|
static _initialized = false;
|
||||||
@ -86,6 +88,7 @@ class AnnotationEditorLayer {
|
|||||||
this.div = options.div;
|
this.div = options.div;
|
||||||
this.#accessibilityManager = options.accessibilityManager;
|
this.#accessibilityManager = options.accessibilityManager;
|
||||||
this.#annotationLayer = options.annotationLayer;
|
this.#annotationLayer = options.annotationLayer;
|
||||||
|
this.viewport = options.viewport;
|
||||||
|
|
||||||
this.#uiManager.addLayer(this);
|
this.#uiManager.addLayer(this);
|
||||||
}
|
}
|
||||||
@ -175,18 +178,33 @@ class AnnotationEditorLayer {
|
|||||||
*/
|
*/
|
||||||
enable() {
|
enable() {
|
||||||
this.div.style.pointerEvents = "auto";
|
this.div.style.pointerEvents = "auto";
|
||||||
if (this.#annotationLayer) {
|
const annotationElementIds = new Set();
|
||||||
const editables = this.#annotationLayer.getEditableAnnotations();
|
for (const editor of this.#editors.values()) {
|
||||||
for (const editable of editables) {
|
editor.enableEditing();
|
||||||
const editor = this.deserialize(editable);
|
if (editor.annotationElementId) {
|
||||||
if (!editor) {
|
annotationElementIds.add(editor.annotationElementId);
|
||||||
continue;
|
|
||||||
}
|
|
||||||
editable.hide();
|
|
||||||
this.addOrRebuild(editor);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (const editor of this.#editors.values()) {
|
|
||||||
|
if (!this.#annotationLayer) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const editables = this.#annotationLayer.getEditableAnnotations();
|
||||||
|
for (const editable of editables) {
|
||||||
|
// The element must be hidden whatever its state is.
|
||||||
|
editable.hide();
|
||||||
|
if (this.#uiManager.isDeletedAnnotationElement(editable.data.id)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (annotationElementIds.has(editable.data.id)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const editor = this.deserialize(editable);
|
||||||
|
if (!editor) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
this.addOrRebuild(editor);
|
||||||
editor.enableEditing();
|
editor.enableEditing();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -195,18 +213,25 @@ class AnnotationEditorLayer {
|
|||||||
* Disable editor creation.
|
* Disable editor creation.
|
||||||
*/
|
*/
|
||||||
disable() {
|
disable() {
|
||||||
|
this.#isDisabling = true;
|
||||||
this.div.style.pointerEvents = "none";
|
this.div.style.pointerEvents = "none";
|
||||||
for (const editor of this.#editors.values()) {
|
for (const editor of this.#editors.values()) {
|
||||||
editor.disableEditing();
|
editor.disableEditing();
|
||||||
if (!editor.hasElementChanged()) {
|
if (!editor.annotationElementId || editor.serialize() !== null) {
|
||||||
editor.annotationElement.show();
|
continue;
|
||||||
editor.remove();
|
|
||||||
}
|
}
|
||||||
|
this.getEditableAnnotation(editor.annotationElementId)?.show();
|
||||||
|
editor.remove();
|
||||||
}
|
}
|
||||||
this.#cleanup();
|
this.#cleanup();
|
||||||
if (this.isEmpty) {
|
if (this.isEmpty) {
|
||||||
this.div.hidden = true;
|
this.div.hidden = true;
|
||||||
}
|
}
|
||||||
|
this.#isDisabling = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
getEditableAnnotation(id) {
|
||||||
|
return this.#annotationLayer?.getEditableAnnotation(id) || null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -234,11 +259,22 @@ class AnnotationEditorLayer {
|
|||||||
|
|
||||||
attach(editor) {
|
attach(editor) {
|
||||||
this.#editors.set(editor.id, editor);
|
this.#editors.set(editor.id, editor);
|
||||||
|
const { annotationElementId } = editor;
|
||||||
|
if (
|
||||||
|
annotationElementId &&
|
||||||
|
this.#uiManager.isDeletedAnnotationElement(annotationElementId)
|
||||||
|
) {
|
||||||
|
this.#uiManager.removeDeletedAnnotationElement(editor);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
detach(editor) {
|
detach(editor) {
|
||||||
this.#editors.delete(editor.id);
|
this.#editors.delete(editor.id);
|
||||||
this.#accessibilityManager?.removePointerInTextLayer(editor.contentDiv);
|
this.#accessibilityManager?.removePointerInTextLayer(editor.contentDiv);
|
||||||
|
|
||||||
|
if (!this.#isDisabling && editor.annotationElementId) {
|
||||||
|
this.#uiManager.addDeletedAnnotationElement(editor);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -249,8 +285,8 @@ class AnnotationEditorLayer {
|
|||||||
// Since we can undo a removal we need to keep the
|
// Since we can undo a removal we need to keep the
|
||||||
// parent property as it is, so don't null it!
|
// parent property as it is, so don't null it!
|
||||||
|
|
||||||
this.#uiManager.removeEditor(editor);
|
|
||||||
this.detach(editor);
|
this.detach(editor);
|
||||||
|
this.#uiManager.removeEditor(editor);
|
||||||
editor.div.style.display = "none";
|
editor.div.style.display = "none";
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
// When the div is removed from DOM the focus can move on the
|
// When the div is removed from DOM the focus can move on the
|
||||||
@ -280,6 +316,12 @@ class AnnotationEditorLayer {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (editor.annotationElementId) {
|
||||||
|
this.#uiManager.addDeletedAnnotationElement(editor.annotationElementId);
|
||||||
|
AnnotationEditor.deleteAnnotationElement(editor);
|
||||||
|
editor.annotationElementId = null;
|
||||||
|
}
|
||||||
|
|
||||||
this.attach(editor);
|
this.attach(editor);
|
||||||
editor.parent?.detach(editor);
|
editor.parent?.detach(editor);
|
||||||
editor.setParent(this);
|
editor.setParent(this);
|
||||||
|
@ -67,7 +67,7 @@ class AnnotationEditor {
|
|||||||
this.name = parameters.name;
|
this.name = parameters.name;
|
||||||
this.div = null;
|
this.div = null;
|
||||||
this._uiManager = parameters.uiManager;
|
this._uiManager = parameters.uiManager;
|
||||||
this.annotationElement = null;
|
this.annotationElementId = null;
|
||||||
|
|
||||||
const {
|
const {
|
||||||
rotation,
|
rotation,
|
||||||
@ -85,6 +85,7 @@ class AnnotationEditor {
|
|||||||
this.y = parameters.y / height;
|
this.y = parameters.y / height;
|
||||||
|
|
||||||
this.isAttachedToDOM = false;
|
this.isAttachedToDOM = false;
|
||||||
|
this.deleted = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
static get _defaultLineColor() {
|
static get _defaultLineColor() {
|
||||||
@ -95,6 +96,17 @@ class AnnotationEditor {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static deleteAnnotationElement(editor) {
|
||||||
|
const fakeEditor = new FakeEditor({
|
||||||
|
id: editor.parent.getNextId(),
|
||||||
|
parent: editor.parent,
|
||||||
|
uiManager: editor._uiManager,
|
||||||
|
});
|
||||||
|
fakeEditor.annotationElementId = editor.annotationElementId;
|
||||||
|
fakeEditor.deleted = true;
|
||||||
|
fakeEditor._uiManager.addToAnnotationStorage(fakeEditor);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add some commands into the CommandManager (undo/redo stuff).
|
* Add some commands into the CommandManager (undo/redo stuff).
|
||||||
* @param {Object} params
|
* @param {Object} params
|
||||||
@ -601,14 +613,22 @@ class AnnotationEditor {
|
|||||||
this.parent.setActiveEditor(null);
|
this.parent.setActiveEditor(null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
// This class is used to fake an editor which has been deleted.
|
||||||
* Check if the editor has been changed.
|
class FakeEditor extends AnnotationEditor {
|
||||||
* @param {Object} serialized
|
constructor(params) {
|
||||||
* @returns {boolean}
|
super(params);
|
||||||
*/
|
this.annotationElementId = params.annotationElementId;
|
||||||
hasElementChanged(serialized = null) {
|
this.deleted = true;
|
||||||
return false;
|
}
|
||||||
|
|
||||||
|
serialize() {
|
||||||
|
return {
|
||||||
|
id: this.annotationElementId,
|
||||||
|
deleted: true,
|
||||||
|
pageIndex: this.pageIndex,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -48,6 +48,8 @@ class FreeTextEditor extends AnnotationEditor {
|
|||||||
|
|
||||||
#fontSize;
|
#fontSize;
|
||||||
|
|
||||||
|
#initialData = null;
|
||||||
|
|
||||||
static _freeTextDefaultContent = "";
|
static _freeTextDefaultContent = "";
|
||||||
|
|
||||||
static _l10nPromise;
|
static _l10nPromise;
|
||||||
@ -285,6 +287,7 @@ class FreeTextEditor extends AnnotationEditor {
|
|||||||
/** @inheritdoc */
|
/** @inheritdoc */
|
||||||
onceAdded() {
|
onceAdded() {
|
||||||
if (this.width) {
|
if (this.width) {
|
||||||
|
this.#cheatInitialRect();
|
||||||
// The editor was created in using ctrl+c.
|
// The editor was created in using ctrl+c.
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -481,12 +484,17 @@ class FreeTextEditor extends AnnotationEditor {
|
|||||||
if (this.width) {
|
if (this.width) {
|
||||||
// This editor was created in using copy (ctrl+c).
|
// This editor was created in using copy (ctrl+c).
|
||||||
const [parentWidth, parentHeight] = this.parentDimensions;
|
const [parentWidth, parentHeight] = this.parentDimensions;
|
||||||
this.setAt(
|
if (this.annotationElementId) {
|
||||||
baseX * parentWidth,
|
const [tx] = this.getInitialTranslation();
|
||||||
baseY * parentHeight,
|
this.setAt(baseX * parentWidth, baseY * parentHeight, tx, tx);
|
||||||
this.width * parentWidth,
|
} else {
|
||||||
this.height * parentHeight
|
this.setAt(
|
||||||
);
|
baseX * parentWidth,
|
||||||
|
baseY * parentHeight,
|
||||||
|
this.width * parentWidth,
|
||||||
|
this.height * parentHeight
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
this.#setContent();
|
this.#setContent();
|
||||||
this.div.draggable = true;
|
this.div.draggable = true;
|
||||||
@ -519,14 +527,37 @@ class FreeTextEditor extends AnnotationEditor {
|
|||||||
|
|
||||||
/** @inheritdoc */
|
/** @inheritdoc */
|
||||||
static deserialize(data, parent, uiManager) {
|
static deserialize(data, parent, uiManager) {
|
||||||
|
let initialData = null;
|
||||||
if (data instanceof FreeTextAnnotationElement) {
|
if (data instanceof FreeTextAnnotationElement) {
|
||||||
return null;
|
const {
|
||||||
|
data: {
|
||||||
|
defaultAppearanceData: { fontSize, fontColor },
|
||||||
|
rect,
|
||||||
|
rotation,
|
||||||
|
id,
|
||||||
|
},
|
||||||
|
textContent,
|
||||||
|
page: { pageNumber },
|
||||||
|
} = data;
|
||||||
|
initialData = data = {
|
||||||
|
annotationType: AnnotationEditorType.FREETEXT,
|
||||||
|
color: Array.from(fontColor),
|
||||||
|
fontSize,
|
||||||
|
value: textContent.join("\n"),
|
||||||
|
pageIndex: pageNumber - 1,
|
||||||
|
rect,
|
||||||
|
rotation,
|
||||||
|
id,
|
||||||
|
deleted: false,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
const editor = super.deserialize(data, parent, uiManager);
|
const editor = super.deserialize(data, parent, uiManager);
|
||||||
|
|
||||||
editor.#fontSize = data.fontSize;
|
editor.#fontSize = data.fontSize;
|
||||||
editor.#color = Util.makeHexColor(...data.color);
|
editor.#color = Util.makeHexColor(...data.color);
|
||||||
editor.#content = data.value;
|
editor.#content = data.value;
|
||||||
|
editor.annotationElementId = data.id || null;
|
||||||
|
editor.#initialData = initialData;
|
||||||
|
|
||||||
return editor;
|
return editor;
|
||||||
}
|
}
|
||||||
@ -537,16 +568,23 @@ class FreeTextEditor extends AnnotationEditor {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this.deleted) {
|
||||||
|
return {
|
||||||
|
pageIndex: this.pageIndex,
|
||||||
|
id: this.annotationElementId,
|
||||||
|
deleted: true,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
const padding = FreeTextEditor._internalPadding * this.parentScale;
|
const padding = FreeTextEditor._internalPadding * this.parentScale;
|
||||||
const rect = this.getRect(padding, padding);
|
const rect = this.getRect(padding, padding);
|
||||||
|
|
||||||
const color = AnnotationEditor._colorManager.convert(
|
const color = AnnotationEditor._colorManager.convert(
|
||||||
this.isAttachedToDOM
|
this.isAttachedToDOM
|
||||||
? getComputedStyle(this.editorDiv).color
|
? getComputedStyle(this.editorDiv).color
|
||||||
: this.#color
|
: this.#color
|
||||||
);
|
);
|
||||||
|
|
||||||
return {
|
const serialized = {
|
||||||
annotationType: AnnotationEditorType.FREETEXT,
|
annotationType: AnnotationEditorType.FREETEXT,
|
||||||
color,
|
color,
|
||||||
fontSize: this.#fontSize,
|
fontSize: this.#fontSize,
|
||||||
@ -554,7 +592,45 @@ class FreeTextEditor extends AnnotationEditor {
|
|||||||
pageIndex: this.pageIndex,
|
pageIndex: this.pageIndex,
|
||||||
rect,
|
rect,
|
||||||
rotation: this.rotation,
|
rotation: this.rotation,
|
||||||
|
id: this.annotationElementId,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (this.annotationElementId && !this.#hasElementChanged(serialized)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return serialized;
|
||||||
|
}
|
||||||
|
|
||||||
|
#hasElementChanged(serialized) {
|
||||||
|
const { value, fontSize, color, rect, pageIndex } = this.#initialData;
|
||||||
|
|
||||||
|
return (
|
||||||
|
serialized.value !== value ||
|
||||||
|
serialized.fontSize !== fontSize ||
|
||||||
|
serialized.rect.some((x, i) => Math.abs(x - rect[i]) >= 1) ||
|
||||||
|
serialized.color.some((c, i) => c !== color[i]) ||
|
||||||
|
serialized.pageIndex !== pageIndex
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#cheatInitialRect(delayed = false) {
|
||||||
|
// The annotation has a rect but the editor has an other one.
|
||||||
|
// When we want to know if the annotation has changed (e.g. has been moved)
|
||||||
|
// we must compare the editor initial rect with the current one.
|
||||||
|
// So this method is a hack to have a way to compare the real rects.
|
||||||
|
if (!this.annotationElementId) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.#setEditorDimensions();
|
||||||
|
if (!delayed && (this.width === 0 || this.height === 0)) {
|
||||||
|
setTimeout(() => this.#cheatInitialRect(/* delayed = */ true), 0);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const padding = FreeTextEditor._internalPadding * this.parentScale;
|
||||||
|
this.#initialData.rect = this.getRect(padding, padding);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -362,6 +362,8 @@ class AnnotationEditorUIManager {
|
|||||||
|
|
||||||
#currentPageIndex = 0;
|
#currentPageIndex = 0;
|
||||||
|
|
||||||
|
#deletedAnnotationsElementIds = new Set();
|
||||||
|
|
||||||
#editorTypes = null;
|
#editorTypes = null;
|
||||||
|
|
||||||
#editorsToRescale = new Set();
|
#editorsToRescale = new Set();
|
||||||
@ -554,7 +556,11 @@ class AnnotationEditorUIManager {
|
|||||||
const editors = [];
|
const editors = [];
|
||||||
for (const editor of this.#selectedEditors) {
|
for (const editor of this.#selectedEditors) {
|
||||||
if (!editor.isEmpty()) {
|
if (!editor.isEmpty()) {
|
||||||
editors.push(editor.serialize());
|
const serialized = editor.serialize();
|
||||||
|
// Remove the id from the serialized data because it mustn't be linked
|
||||||
|
// to an existing annotation.
|
||||||
|
delete serialized.id;
|
||||||
|
editors.push(serialized);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (editors.length === 0) {
|
if (editors.length === 0) {
|
||||||
@ -862,7 +868,39 @@ class AnnotationEditorUIManager {
|
|||||||
removeEditor(editor) {
|
removeEditor(editor) {
|
||||||
this.#allEditors.delete(editor.id);
|
this.#allEditors.delete(editor.id);
|
||||||
this.unselect(editor);
|
this.unselect(editor);
|
||||||
this.#annotationStorage?.remove(editor.id);
|
if (
|
||||||
|
!editor.annotationElementId ||
|
||||||
|
!this.#deletedAnnotationsElementIds.has(editor.annotationElementId)
|
||||||
|
) {
|
||||||
|
this.#annotationStorage?.remove(editor.id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The annotation element with the given id has been deleted.
|
||||||
|
* @param {AnnotationEditor} editor
|
||||||
|
*/
|
||||||
|
addDeletedAnnotationElement(editor) {
|
||||||
|
this.#deletedAnnotationsElementIds.add(editor.annotationElementId);
|
||||||
|
editor.deleted = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if the annotation element with the given id has been deleted.
|
||||||
|
* @param {string} annotationElementId
|
||||||
|
* @returns {boolean}
|
||||||
|
*/
|
||||||
|
isDeletedAnnotationElement(annotationElementId) {
|
||||||
|
return this.#deletedAnnotationsElementIds.has(annotationElementId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The annotation element with the given id have been restored.
|
||||||
|
* @param {AnnotationEditor} editor
|
||||||
|
*/
|
||||||
|
removeDeletedAnnotationElement(editor) {
|
||||||
|
this.#deletedAnnotationsElementIds.delete(editor.annotationElementId);
|
||||||
|
editor.deleted = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -15,8 +15,10 @@
|
|||||||
|
|
||||||
const {
|
const {
|
||||||
closePages,
|
closePages,
|
||||||
|
getEditors,
|
||||||
getEditorSelector,
|
getEditorSelector,
|
||||||
getSelectedEditors,
|
getSelectedEditors,
|
||||||
|
getSerialized,
|
||||||
loadAndWait,
|
loadAndWait,
|
||||||
waitForEvent,
|
waitForEvent,
|
||||||
waitForSelectedEditor,
|
waitForSelectedEditor,
|
||||||
@ -39,7 +41,7 @@ const copyPaste = async page => {
|
|||||||
await promise;
|
await promise;
|
||||||
};
|
};
|
||||||
|
|
||||||
describe("Editor", () => {
|
describe("FreeText Editor", () => {
|
||||||
describe("FreeText", () => {
|
describe("FreeText", () => {
|
||||||
let pages;
|
let pages;
|
||||||
|
|
||||||
@ -837,4 +839,224 @@ describe("Editor", () => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("FreeText (move existing)", () => {
|
||||||
|
let pages;
|
||||||
|
|
||||||
|
beforeAll(async () => {
|
||||||
|
pages = await loadAndWait("freetexts.pdf", ".annotationEditorLayer");
|
||||||
|
});
|
||||||
|
|
||||||
|
afterAll(async () => {
|
||||||
|
await closePages(pages);
|
||||||
|
});
|
||||||
|
|
||||||
|
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");
|
||||||
|
expect(editorIds.length).withContext(`In ${browserName}`).toEqual(6);
|
||||||
|
|
||||||
|
// All the current annotations should be serialized as null objects
|
||||||
|
// because they haven't been edited yet.
|
||||||
|
let serialized = await getSerialized(page);
|
||||||
|
expect(serialized).withContext(`In ${browserName}`).toEqual([]);
|
||||||
|
|
||||||
|
const editorRect = await page.$eval(getEditorSelector(0), el => {
|
||||||
|
const { x, y, width, height } = el.getBoundingClientRect();
|
||||||
|
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 }
|
||||||
|
);
|
||||||
|
|
||||||
|
serialized = await getSerialized(page);
|
||||||
|
expect(serialized.length).withContext(`In ${browserName}`).toEqual(1);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("FreeText (update existing)", () => {
|
||||||
|
let pages;
|
||||||
|
|
||||||
|
beforeAll(async () => {
|
||||||
|
pages = await loadAndWait("freetexts.pdf", ".annotationEditorLayer");
|
||||||
|
});
|
||||||
|
|
||||||
|
afterAll(async () => {
|
||||||
|
await closePages(pages);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("must update an existing annotation", async () => {
|
||||||
|
await Promise.all(
|
||||||
|
pages.map(async ([browserName, page]) => {
|
||||||
|
await page.click("#editorFreeText");
|
||||||
|
|
||||||
|
let editorIds = await getEditors(page, "freeText");
|
||||||
|
expect(editorIds.length).withContext(`In ${browserName}`).toEqual(6);
|
||||||
|
|
||||||
|
const editorRect = await page.$eval(getEditorSelector(0), el => {
|
||||||
|
const { x, y, width, height } = el.getBoundingClientRect();
|
||||||
|
return { x, y, width, height };
|
||||||
|
});
|
||||||
|
await page.mouse.click(
|
||||||
|
editorRect.x + editorRect.width / 2,
|
||||||
|
editorRect.y + editorRect.height / 2,
|
||||||
|
{ clickCount: 2 }
|
||||||
|
);
|
||||||
|
|
||||||
|
await page.keyboard.down("Control");
|
||||||
|
await page.keyboard.press("End");
|
||||||
|
await page.keyboard.up("Control");
|
||||||
|
await page.waitForTimeout(10);
|
||||||
|
|
||||||
|
await page.type(
|
||||||
|
`${getEditorSelector(0)} .internal`,
|
||||||
|
" and edited in Firefox"
|
||||||
|
);
|
||||||
|
|
||||||
|
// Commit.
|
||||||
|
await page.mouse.click(
|
||||||
|
editorRect.x,
|
||||||
|
editorRect.y + 2 * editorRect.height
|
||||||
|
);
|
||||||
|
|
||||||
|
let serialized = await getSerialized(page);
|
||||||
|
expect(serialized.length).withContext(`In ${browserName}`).toEqual(1);
|
||||||
|
expect(serialized[0]).toEqual(
|
||||||
|
jasmine.objectContaining({
|
||||||
|
color: [107, 217, 41],
|
||||||
|
fontSize: 14,
|
||||||
|
value: "Hello World from Acrobat and edited in Firefox",
|
||||||
|
id: "26R",
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
// Disable editing mode.
|
||||||
|
await page.click("#editorFreeText");
|
||||||
|
// We want to check that the editor is displayed but not the original
|
||||||
|
// annotation.
|
||||||
|
editorIds = await getEditors(page, "freeText");
|
||||||
|
expect(editorIds.length).withContext(`In ${browserName}`).toEqual(1);
|
||||||
|
const hidden = await page.$eval(
|
||||||
|
"[data-annotation-id='26R']",
|
||||||
|
el => el.hidden
|
||||||
|
);
|
||||||
|
expect(hidden).withContext(`In ${browserName}`).toBeTrue();
|
||||||
|
|
||||||
|
// Re-enable editing mode.
|
||||||
|
await page.click("#editorFreeText");
|
||||||
|
await page.focus(".annotationEditorLayer");
|
||||||
|
|
||||||
|
// Undo.
|
||||||
|
await page.keyboard.down("Control");
|
||||||
|
await page.keyboard.press("z");
|
||||||
|
await page.keyboard.up("Control");
|
||||||
|
await page.waitForTimeout(10);
|
||||||
|
|
||||||
|
serialized = await getSerialized(page);
|
||||||
|
expect(serialized).withContext(`In ${browserName}`).toEqual([]);
|
||||||
|
|
||||||
|
editorIds = await getEditors(page, "freeText");
|
||||||
|
expect(editorIds.length).withContext(`In ${browserName}`).toEqual(6);
|
||||||
|
|
||||||
|
// Undo again.
|
||||||
|
await page.keyboard.down("Control");
|
||||||
|
await page.keyboard.press("z");
|
||||||
|
await page.keyboard.up("Control");
|
||||||
|
await page.waitForTimeout(10);
|
||||||
|
|
||||||
|
// We check that the editor hasn't been removed.
|
||||||
|
editorIds = await getEditors(page, "freeText");
|
||||||
|
expect(editorIds.length).withContext(`In ${browserName}`).toEqual(6);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("FreeText (delete existing)", () => {
|
||||||
|
let pages;
|
||||||
|
|
||||||
|
beforeAll(async () => {
|
||||||
|
pages = await loadAndWait("freetexts.pdf", ".annotationEditorLayer");
|
||||||
|
});
|
||||||
|
|
||||||
|
afterAll(async () => {
|
||||||
|
await closePages(pages);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("must delete an existing annotation", async () => {
|
||||||
|
await Promise.all(
|
||||||
|
pages.map(async ([browserName, page]) => {
|
||||||
|
await page.click("#editorFreeText");
|
||||||
|
|
||||||
|
let editorIds = await getEditors(page, "freeText");
|
||||||
|
expect(editorIds.length).withContext(`In ${browserName}`).toEqual(6);
|
||||||
|
|
||||||
|
const editorRect = await page.$eval(getEditorSelector(3), el => {
|
||||||
|
const { x, y, width, height } = el.getBoundingClientRect();
|
||||||
|
return { x, y, width, height };
|
||||||
|
});
|
||||||
|
await page.mouse.click(
|
||||||
|
editorRect.x + editorRect.width / 2,
|
||||||
|
editorRect.y + editorRect.height / 2
|
||||||
|
);
|
||||||
|
|
||||||
|
await page.keyboard.press("Backspace");
|
||||||
|
await page.waitForTimeout(10);
|
||||||
|
|
||||||
|
let serialized = await getSerialized(page);
|
||||||
|
expect(serialized).toEqual([
|
||||||
|
{
|
||||||
|
pageIndex: 0,
|
||||||
|
id: "51R",
|
||||||
|
deleted: true,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
await page.click("#editorFreeText");
|
||||||
|
// We want to check that nothing is displayed.
|
||||||
|
editorIds = await getEditors(page, "freeText");
|
||||||
|
expect(editorIds.length).withContext(`In ${browserName}`).toEqual(0);
|
||||||
|
const hidden = await page.$eval(
|
||||||
|
"[data-annotation-id='51R']",
|
||||||
|
el => el.hidden
|
||||||
|
);
|
||||||
|
expect(hidden).withContext(`In ${browserName}`).toBeTrue();
|
||||||
|
|
||||||
|
// Re-enable editing mode.
|
||||||
|
await page.click("#editorFreeText");
|
||||||
|
await page.focus(".annotationEditorLayer");
|
||||||
|
|
||||||
|
// Undo.
|
||||||
|
await page.keyboard.down("Control");
|
||||||
|
await page.keyboard.press("z");
|
||||||
|
await page.keyboard.up("Control");
|
||||||
|
await page.waitForTimeout(10);
|
||||||
|
|
||||||
|
serialized = await getSerialized(page);
|
||||||
|
expect(serialized).withContext(`In ${browserName}`).toEqual([]);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
@ -134,3 +134,21 @@ const mockClipboard = async pages => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
exports.mockClipboard = mockClipboard;
|
exports.mockClipboard = mockClipboard;
|
||||||
|
|
||||||
|
const getSerialized = page =>
|
||||||
|
page.evaluate(() => [
|
||||||
|
...window.PDFViewerApplication.pdfDocument.annotationStorage.serializable.values(),
|
||||||
|
]);
|
||||||
|
exports.getSerialized = getSerialized;
|
||||||
|
|
||||||
|
function getEditors(page, kind) {
|
||||||
|
return page.evaluate(aKind => {
|
||||||
|
const elements = document.querySelectorAll(`.${aKind}Editor`);
|
||||||
|
const results = [];
|
||||||
|
for (const { id } of elements) {
|
||||||
|
results.push(id);
|
||||||
|
}
|
||||||
|
return results;
|
||||||
|
}, kind);
|
||||||
|
}
|
||||||
|
exports.getEditors = getEditors;
|
||||||
|
Loading…
Reference in New Issue
Block a user