[Editor] Add a button to explicitly add an image (bug 1848108)

The main stamp button will be used to just enter in a add/edit image mode:
 - the user can add a new image in using the new button.
 - the user can edit an image in resizing, moving it.
In image mode, when the user clicks outside on the page but not on an editor,
then all the selected editors will be unselected.
This commit is contained in:
Calixte Denizet 2023-08-09 14:07:15 +02:00
parent d57e3ebbe4
commit 659fbc5020
13 changed files with 140 additions and 34 deletions

View File

@ -245,8 +245,8 @@ editor_free_text2.title=Text
editor_free_text2_label=Text
editor_ink2.title=Draw
editor_ink2_label=Draw
editor_stamp.title=Add an image
editor_stamp_label=Add an image
editor_stamp1.title=Add or edit images
editor_stamp1_label=Add or edit images
free_text2_default_content=Start typing…
@ -256,6 +256,8 @@ editor_free_text_size=Size
editor_ink_color=Color
editor_ink_thickness=Thickness
editor_ink_opacity=Opacity
editor_stamp_add_image_label=Add image
editor_stamp_add_image.title=Add image
# Editor aria
editor_free_text2_aria_label=Text Editor

View File

@ -166,7 +166,10 @@ class AnnotationEditorLayer {
}
}
const editor = this.#createAndAddNewEditor({ offsetX: 0, offsetY: 0 });
const editor = this.#createAndAddNewEditor(
{ offsetX: 0, offsetY: 0 },
/* isCentered = */ false
);
editor.setInBackground();
}
@ -481,9 +484,10 @@ class AnnotationEditorLayer {
/**
* Create and add a new editor.
* @param {PointerEvent} event
* @param {boolean} isCentered
* @returns {AnnotationEditor}
*/
#createAndAddNewEditor(event) {
#createAndAddNewEditor(event, isCentered) {
const id = this.getNextId();
const editor = this.#createNewEditor({
parent: this,
@ -491,6 +495,7 @@ class AnnotationEditorLayer {
x: event.offsetX,
y: event.offsetY,
uiManager: this.#uiManager,
isCentered,
});
if (editor) {
this.add(editor);
@ -499,6 +504,31 @@ class AnnotationEditorLayer {
return editor;
}
/**
* Create and add a new editor.
*/
addNewEditor() {
const { x, y, width, height } = this.div.getBoundingClientRect();
const tlX = Math.max(0, x);
const tlY = Math.max(0, y);
const brX = Math.min(window.innerWidth, x + width);
const brY = Math.min(window.innerHeight, y + height);
const centerX = (tlX + brX) / 2 - x;
const centerY = (tlY + brY) / 2 - y;
const [offsetX, offsetY] =
this.viewport.rotation % 180 === 0
? [centerX, centerY]
: [centerY, centerX];
this.#createAndAddNewEditor(
{
offsetX,
offsetY,
},
/* isCentered = */ true
);
}
/**
* Set the last selected editor.
* @param {AnnotationEditor} editor
@ -560,7 +590,12 @@ class AnnotationEditorLayer {
return;
}
this.#createAndAddNewEditor(event);
if (this.#uiManager.getMode() === AnnotationEditorType.STAMP) {
this.#uiManager.unselectAll();
return;
}
this.#createAndAddNewEditor(event, /* isCentered = */ false);
}
/**

View File

@ -48,6 +48,8 @@ class AnnotationEditor {
#isInEditMode = false;
_initialOptions = Object.create(null);
_uiManager = null;
_focusEventsAllowed = true;
@ -77,6 +79,7 @@ class AnnotationEditor {
this._uiManager = parameters.uiManager;
this.annotationElementId = null;
this._willKeepAspectRatio = false;
this._initialOptions.isCentered = parameters.isCentered;
const {
rotation,
@ -154,6 +157,29 @@ class AnnotationEditor {
this.div?.classList.toggle("draggable", value);
}
center() {
const [pageWidth, pageHeight] = this.pageDimensions;
switch (this.parentRotation) {
case 90:
this.x -= (this.height * pageHeight) / (pageWidth * 2);
this.y += (this.width * pageWidth) / (pageHeight * 2);
break;
case 180:
this.x += this.width / 2;
this.y += this.height / 2;
break;
case 270:
this.x += (this.height * pageHeight) / (pageWidth * 2);
this.y -= (this.width * pageWidth) / (pageHeight * 2);
break;
default:
this.x -= this.width / 2;
this.y -= this.height / 2;
break;
}
this.fixAndSetPosition();
}
/**
* Add some commands into the CommandManager (undo/redo stuff).
* @param {Object} params

View File

@ -363,6 +363,10 @@ class FreeTextEditor extends AnnotationEditor {
}
this.enableEditMode();
this.editorDiv.focus();
if (this._initialOptions?.isCentered) {
this.center();
}
this._initialOptions = null;
}
/** @inheritdoc */

View File

@ -290,7 +290,12 @@ class StampEditor extends AnnotationEditor {
this.width = width / parentWidth;
this.height = height / parentHeight;
this.setDims(width, height);
this.fixAndSetPosition();
if (this._initialOptions?.isCentered) {
this.center();
} else {
this.fixAndSetPosition();
}
this._initialOptions = null;
if (this.#resizeTimeoutId !== null) {
clearTimeout(this.#resizeTimeoutId);
}

View File

@ -18,6 +18,7 @@
/** @typedef {import("./annotation_editor_layer.js").AnnotationEditorLayer} AnnotationEditorLayer */
import {
AnnotationEditorParamsType,
AnnotationEditorPrefix,
AnnotationEditorType,
FeatureTest,
@ -1144,6 +1145,10 @@ class AnnotationEditorUIManager {
if (!this.#editorTypes) {
return;
}
if (type === AnnotationEditorParamsType.CREATE) {
this.currentLayer.addNewEditor(type);
return;
}
for (const editor of this.#selectedEditors) {
editor.updateParams(type, value);

View File

@ -78,6 +78,7 @@ const AnnotationEditorType = {
const AnnotationEditorParamsType = {
RESIZE: 1,
CREATE: 2,
FREETEXT_SIZE: 11,
FREETEXT_COLOR: 12,
FREETEXT_OPACITY: 13,

View File

@ -15,7 +15,9 @@
const {
closePages,
dragAndDropAnnotation,
getEditorDimensions,
getEditorSelector,
loadAndWait,
serializeBitmapDimensions,
waitForAnnotationEditorLayer,
@ -43,15 +45,8 @@ describe("Stamp Editor", () => {
}
await page.click("#editorStamp");
await page.click("#editorStampAddImage");
const rect = await page.$eval(".annotationEditorLayer", el => {
// With Chrome something is wrong when serializing a DomRect,
// hence we extract the values and just return them.
const { x, y } = el.getBoundingClientRect();
return { x, y };
});
await page.mouse.click(rect.x + 100, rect.y + 100);
const input = await page.$("#stampEditorFileInput");
await input.uploadFile(
`${path.join(__dirname, "../images/firefox_logo.png")}`
@ -87,14 +82,7 @@ describe("Stamp Editor", () => {
return;
}
const rect = await page.$eval(".annotationEditorLayer", el => {
// With Chrome something is wrong when serializing a DomRect,
// hence we extract the values and just return them.
const { x, y } = el.getBoundingClientRect();
return { x, y };
});
await page.mouse.click(rect.x + 100, rect.y + 100);
await page.click("#editorStampAddImage");
const input = await page.$("#stampEditorFileInput");
await input.uploadFile(
`${path.join(__dirname, "../images/firefox_logo.svg")}`
@ -146,6 +134,7 @@ describe("Stamp Editor", () => {
}
await page.click("#editorStamp");
await page.click("#editorStampAddImage");
const rect = await page.$eval(".annotationEditorLayer", el => {
// With Chrome something is wrong when serializing a DomRect,
@ -154,7 +143,13 @@ describe("Stamp Editor", () => {
return { x: right, y: bottom };
});
await page.mouse.click(rect.x - 10, rect.y - 10);
const editorRect = await page.$eval(getEditorSelector(0), el => {
// With Chrome something is wrong when serializing a DomRect,
// hence we extract the values and just return them.
const { x, y } = el.getBoundingClientRect();
return { x, y };
});
const input = await page.$("#stampEditorFileInput");
await input.uploadFile(
`${path.join(__dirname, "../images/firefox_logo.png")}`
@ -162,6 +157,15 @@ describe("Stamp Editor", () => {
await page.waitForTimeout(300);
await dragAndDropAnnotation(
page,
editorRect.x + 10,
editorRect.y + 10,
rect.x - 10,
rect.y - 10
);
await page.waitForTimeout(10);
const { left } = await getEditorDimensions(page, 0);
// The image is bigger than the page, so it has been scaled down to
@ -204,14 +208,7 @@ describe("Stamp Editor", () => {
await page.waitForTimeout(10);
}
const rect = await page.$eval(".annotationEditorLayer", el => {
// With Chrome something is wrong when serializing a DomRect,
// hence we extract the values and just return them.
const { x, y } = el.getBoundingClientRect();
return { x, y };
});
await page.mouse.click(rect.x + 10, rect.y + 10);
await page.click("#editorStampAddImage");
await page.waitForTimeout(10);
const input = await page.$("#stampEditorFileInput");
await input.uploadFile(

View File

@ -31,6 +31,7 @@ class AnnotationEditorParams {
editorInkColor,
editorInkThickness,
editorInkOpacity,
editorStampAddImage,
}) {
const dispatchEvent = (typeStr, value) => {
this.eventBus.dispatch("switchannotationeditorparams", {
@ -54,6 +55,9 @@ class AnnotationEditorParams {
editorInkOpacity.addEventListener("input", function () {
dispatchEvent("INK_OPACITY", this.valueAsNumber);
});
editorStampAddImage.addEventListener("click", () => {
dispatchEvent("CREATE");
});
this.eventBus._on("annotationeditorparamschanged", evt => {
for (const [type, value] of evt.details) {

View File

@ -218,6 +218,7 @@ class Toolbar {
editorInkButton,
editorInkParamsToolbar,
editorStampButton,
editorStampParamsToolbar,
}) {
const editorModeChanged = ({ mode }) => {
toggleCheckedBtn(
@ -230,7 +231,11 @@ class Toolbar {
mode === AnnotationEditorType.INK,
editorInkParamsToolbar
);
toggleCheckedBtn(editorStampButton, mode === AnnotationEditorType.STAMP);
toggleCheckedBtn(
editorStampButton,
mode === AnnotationEditorType.STAMP,
editorStampParamsToolbar
);
const isDisable = mode === AnnotationEditorType.DISABLE;
editorFreeTextButton.disabled = isDisable;

View File

@ -118,6 +118,7 @@
--secondaryToolbarButton-spreadOdd-icon: url(images/secondaryToolbarButton-spreadOdd.svg);
--secondaryToolbarButton-spreadEven-icon: url(images/secondaryToolbarButton-spreadEven.svg);
--secondaryToolbarButton-documentProperties-icon: url(images/secondaryToolbarButton-documentProperties.svg);
--editorParams-stampAddImage-icon: url(images/toolbarButton-zoomIn.svg);
}
:root:dir(rtl) {
@ -576,6 +577,11 @@ body {
margin-bottom: -4px;
}
#editorStampParamsToolbar {
inset-inline-end: 40px;
background-color: var(--toolbar-bg-color);
}
#editorInkParamsToolbar {
inset-inline-end: 68px;
background-color: var(--toolbar-bg-color);
@ -586,6 +592,10 @@ body {
background-color: var(--toolbar-bg-color);
}
#editorStampAddImage::before {
mask-image: var(--editorParams-stampAddImage-icon);
}
.doorHanger,
.doorHangerRight {
border-radius: 2px;

View File

@ -198,6 +198,14 @@ See https://github.com/adobe-type-tools/cmap-resources
</div>
</div>
<div class="editorParamsToolbar hidden doorHangerRight" id="editorStampParamsToolbar">
<div class="editorParamsToolbarContainer">
<button id="editorStampAddImage" class="secondaryToolbarButton" title="Add image" tabindex="105" data-l10n-id="editor_stamp_add_image">
<span data-l10n-id="editor_stamp_add_image_label">Add image</span>
</button>
</div>
</div>
<div id="secondaryToolbar" class="secondaryToolbar hidden doorHangerRight">
<div id="secondaryToolbarButtonContainer">
<!--#if GENERIC-->
@ -343,8 +351,8 @@ See https://github.com/adobe-type-tools/cmap-resources
<button id="editorInk" class="toolbarButton" disabled="disabled" title="Draw" role="radio" aria-checked="false" aria-controls="editorInkParamsToolbar" tabindex="35" data-l10n-id="editor_ink2">
<span data-l10n-id="editor_ink2_label">Draw</span>
</button>
<button id="editorStamp" class="toolbarButton hidden" disabled="disabled" title="Image" role="radio" aria-checked="false" tabindex="36" data-l10n-id="editor_stamp">
<span data-l10n-id="editor_stamp_label">Image</span>
<button id="editorStamp" class="toolbarButton hidden" disabled="disabled" title="Add or edit images" role="radio" aria-checked="false" tabindex="36" data-l10n-id="editor_stamp1">
<span data-l10n-id="editor_stamp1_label">Add or edit images</span>
</button>
</div>

View File

@ -64,6 +64,9 @@ function getViewerConfiguration() {
editorInkButton: document.getElementById("editorInk"),
editorInkParamsToolbar: document.getElementById("editorInkParamsToolbar"),
editorStampButton: document.getElementById("editorStamp"),
editorStampParamsToolbar: document.getElementById(
"editorStampParamsToolbar"
),
download: document.getElementById("download"),
},
secondaryToolbar: {
@ -160,6 +163,7 @@ function getViewerConfiguration() {
editorInkColor: document.getElementById("editorInkColor"),
editorInkThickness: document.getElementById("editorInkThickness"),
editorInkOpacity: document.getElementById("editorInkOpacity"),
editorStampAddImage: document.getElementById("editorStampAddImage"),
},
printContainer: document.getElementById("printContainer"),
openFileInput: