Merge pull request #15050 from calixteman/make_ink_better
[Editor] - Add the ability to directly draw after selecting ink tool
This commit is contained in:
commit
b8688128e3
@ -43,6 +43,8 @@ import { PixelsPerInch } from "../display_utils.js";
|
||||
class AnnotationEditorLayer {
|
||||
#boundClick;
|
||||
|
||||
#boundMouseover;
|
||||
|
||||
#editors = new Map();
|
||||
|
||||
#uiManager;
|
||||
@ -83,6 +85,7 @@ class AnnotationEditorLayer {
|
||||
this.pageIndex = options.pageIndex;
|
||||
this.div = options.div;
|
||||
this.#boundClick = this.click.bind(this);
|
||||
this.#boundMouseover = this.mouseover.bind(this);
|
||||
|
||||
for (const editor of this.#uiManager.getEditors(options.pageIndex)) {
|
||||
this.add(editor);
|
||||
@ -91,13 +94,45 @@ class AnnotationEditorLayer {
|
||||
this.#uiManager.addLayer(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* The mode has changed: it must be updated.
|
||||
* @param {number} mode
|
||||
*/
|
||||
updateMode(mode) {
|
||||
if (mode === AnnotationEditorType.INK) {
|
||||
// We want to have the ink editor covering all of the page without having
|
||||
// to click to create it: it must be here when we start to draw.
|
||||
this.div.addEventListener("mouseover", this.#boundMouseover);
|
||||
this.div.removeEventListener("click", this.#boundClick);
|
||||
} else {
|
||||
this.div.removeEventListener("mouseover", this.#boundMouseover);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Mouseover callback.
|
||||
* @param {MouseEvent} event
|
||||
*/
|
||||
mouseover(event) {
|
||||
if (event.target === this.div && event.buttons === 0) {
|
||||
// The div is the target so there is no ink editor, hence we can
|
||||
// create a new one.
|
||||
// event.buttons === 0 is here to avoid adding a new ink editor
|
||||
// when we drop an editor.
|
||||
const editor = this.#createAndAddNewEditor(event);
|
||||
editor.setInBackground();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add some commands into the CommandManager (undo/redo stuff).
|
||||
* @param {function} cmd
|
||||
* @param {function} undo
|
||||
* @param {boolean} mustExec - If true the command is executed after having
|
||||
* been added.
|
||||
*/
|
||||
addCommands(cmd, undo) {
|
||||
this.#uiManager.addCommands(cmd, undo);
|
||||
addCommands(cmd, undo, mustExec) {
|
||||
this.#uiManager.addCommands(cmd, undo, mustExec);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -178,6 +213,17 @@ class AnnotationEditorLayer {
|
||||
* @param {AnnotationEditor} editor
|
||||
*/
|
||||
setActiveEditor(editor) {
|
||||
const currentActive = this.#uiManager.getActive();
|
||||
if (currentActive === editor) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.#uiManager.setActiveEditor(editor);
|
||||
|
||||
if (currentActive && currentActive !== editor) {
|
||||
currentActive.commitOrRemove();
|
||||
}
|
||||
|
||||
if (editor) {
|
||||
this.unselectAll();
|
||||
this.div.removeEventListener("click", this.#boundClick);
|
||||
@ -185,7 +231,6 @@ class AnnotationEditorLayer {
|
||||
this.#uiManager.allowClick = false;
|
||||
this.div.addEventListener("click", this.#boundClick);
|
||||
}
|
||||
this.#uiManager.setActiveEditor(editor);
|
||||
}
|
||||
|
||||
attach(editor) {
|
||||
@ -212,7 +257,6 @@ class AnnotationEditorLayer {
|
||||
if (this.#uiManager.isActive(editor) || this.#editors.size === 0) {
|
||||
this.setActiveEditor(null);
|
||||
this.#uiManager.allowClick = true;
|
||||
this.div.focus();
|
||||
}
|
||||
}
|
||||
|
||||
@ -279,7 +323,22 @@ class AnnotationEditorLayer {
|
||||
editor.remove();
|
||||
};
|
||||
|
||||
this.addCommands(cmd, undo);
|
||||
this.addCommands(cmd, undo, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a new editor and make this addition undoable.
|
||||
* @param {AnnotationEditor} editor
|
||||
*/
|
||||
addUndoableEditor(editor) {
|
||||
const cmd = () => {
|
||||
this.addOrRebuild(editor);
|
||||
};
|
||||
const undo = () => {
|
||||
editor.remove();
|
||||
};
|
||||
|
||||
this.addCommands(cmd, undo, false);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -305,6 +364,26 @@ class AnnotationEditorLayer {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create and add a new editor.
|
||||
* @param {MouseEvent} event
|
||||
* @returns {AnnotationEditor}
|
||||
*/
|
||||
#createAndAddNewEditor(event) {
|
||||
const id = this.getNextId();
|
||||
const editor = this.#createNewEditor({
|
||||
parent: this,
|
||||
id,
|
||||
x: event.offsetX,
|
||||
y: event.offsetY,
|
||||
});
|
||||
if (editor) {
|
||||
this.add(editor);
|
||||
}
|
||||
|
||||
return editor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Mouseclick callback.
|
||||
* @param {MouseEvent} event
|
||||
@ -316,16 +395,7 @@ class AnnotationEditorLayer {
|
||||
return;
|
||||
}
|
||||
|
||||
const id = this.getNextId();
|
||||
const editor = this.#createNewEditor({
|
||||
parent: this,
|
||||
id,
|
||||
x: event.offsetX,
|
||||
y: event.offsetY,
|
||||
});
|
||||
if (editor) {
|
||||
this.addANewEditor(editor);
|
||||
}
|
||||
this.#createAndAddNewEditor(event);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -53,6 +53,20 @@ class AnnotationEditor {
|
||||
this.isAttachedToDOM = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* This editor will be behind the others.
|
||||
*/
|
||||
setInBackground() {
|
||||
this.div.classList.add("background");
|
||||
}
|
||||
|
||||
/**
|
||||
* This editor will be in the foreground.
|
||||
*/
|
||||
setInForeground() {
|
||||
this.div.classList.remove("background");
|
||||
}
|
||||
|
||||
/**
|
||||
* onfocus callback.
|
||||
*/
|
||||
@ -81,12 +95,16 @@ class AnnotationEditor {
|
||||
|
||||
event.preventDefault();
|
||||
|
||||
this.commitOrRemove();
|
||||
this.parent.setActiveEditor(null);
|
||||
}
|
||||
|
||||
commitOrRemove() {
|
||||
if (this.isEmpty()) {
|
||||
this.remove();
|
||||
} else {
|
||||
this.commit();
|
||||
}
|
||||
this.parent.setActiveEditor(null);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -156,7 +174,6 @@ class AnnotationEditor {
|
||||
this.div = document.createElement("div");
|
||||
this.div.className = this.name;
|
||||
this.div.setAttribute("id", this.id);
|
||||
this.div.draggable = true;
|
||||
this.div.tabIndex = 100;
|
||||
|
||||
const [tx, ty] = this.getInitialTranslation();
|
||||
|
@ -33,6 +33,8 @@ class FreeTextEditor extends AnnotationEditor {
|
||||
|
||||
#contentHTML = "";
|
||||
|
||||
#hasAlreadyBeenCommitted = false;
|
||||
|
||||
#fontSize;
|
||||
|
||||
static _freeTextDefaultContent = "";
|
||||
@ -168,6 +170,13 @@ class FreeTextEditor extends AnnotationEditor {
|
||||
* @returns {undefined}
|
||||
*/
|
||||
commit() {
|
||||
if (!this.#hasAlreadyBeenCommitted) {
|
||||
// This editor has something and it's the first time
|
||||
// it's commited so we can it in the undo/redo stack.
|
||||
this.#hasAlreadyBeenCommitted = true;
|
||||
this.parent.addUndoableEditor(this);
|
||||
}
|
||||
|
||||
this.disableEditMode();
|
||||
this.#contentHTML = this.editorDiv.innerHTML;
|
||||
this.#content = this.#extractText().trimEnd();
|
||||
|
@ -21,11 +21,11 @@ import { fitCurve } from "./fit_curve/fit_curve.js";
|
||||
* Basic draw editor in order to generate an Ink annotation.
|
||||
*/
|
||||
class InkEditor extends AnnotationEditor {
|
||||
#aspectRatio;
|
||||
#aspectRatio = 0;
|
||||
|
||||
#baseHeight;
|
||||
#baseHeight = 0;
|
||||
|
||||
#baseWidth;
|
||||
#baseWidth = 0;
|
||||
|
||||
#boundCanvasMousemove;
|
||||
|
||||
@ -35,9 +35,9 @@ class InkEditor extends AnnotationEditor {
|
||||
|
||||
#boundCanvasMousedown;
|
||||
|
||||
#disableEditing;
|
||||
#disableEditing = false;
|
||||
|
||||
#observer;
|
||||
#observer = null;
|
||||
|
||||
constructor(params) {
|
||||
super({ ...params, name: "inkEditor" });
|
||||
@ -48,10 +48,6 @@ class InkEditor extends AnnotationEditor {
|
||||
this.currentPath = [];
|
||||
this.scaleFactor = 1;
|
||||
this.translationX = this.translationY = 0;
|
||||
this.#baseWidth = this.#baseHeight = 0;
|
||||
this.#aspectRatio = 0;
|
||||
this.#disableEditing = false;
|
||||
this.#observer = null;
|
||||
this.x = 0;
|
||||
this.y = 0;
|
||||
|
||||
@ -113,8 +109,6 @@ class InkEditor extends AnnotationEditor {
|
||||
return;
|
||||
}
|
||||
|
||||
super.remove();
|
||||
|
||||
// Destroy the canvas.
|
||||
this.canvas.width = this.canvas.heigth = 0;
|
||||
this.canvas.remove();
|
||||
@ -122,11 +116,13 @@ class InkEditor extends AnnotationEditor {
|
||||
|
||||
this.#observer.disconnect();
|
||||
this.#observer = null;
|
||||
|
||||
super.remove();
|
||||
}
|
||||
|
||||
/** @inheritdoc */
|
||||
enableEditMode() {
|
||||
if (this.#disableEditing) {
|
||||
if (this.#disableEditing || this.canvas === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -145,7 +141,7 @@ class InkEditor extends AnnotationEditor {
|
||||
|
||||
super.disableEditMode();
|
||||
this.canvas.style.cursor = "auto";
|
||||
this.div.draggable = true;
|
||||
this.div.draggable = !this.isEmpty();
|
||||
this.div.classList.remove("editing");
|
||||
|
||||
this.canvas.removeEventListener("mousedown", this.#boundCanvasMousedown);
|
||||
@ -154,6 +150,7 @@ class InkEditor extends AnnotationEditor {
|
||||
|
||||
/** @inheritdoc */
|
||||
onceAdded() {
|
||||
this.div.draggable = !this.isEmpty();
|
||||
this.div.focus();
|
||||
}
|
||||
|
||||
@ -238,11 +235,15 @@ class InkEditor extends AnnotationEditor {
|
||||
if (this.paths.length === 0) {
|
||||
this.remove();
|
||||
} else {
|
||||
if (!this.canvas) {
|
||||
this.#createCanvas();
|
||||
this.#createObserver();
|
||||
}
|
||||
this.#fitToContent();
|
||||
}
|
||||
};
|
||||
|
||||
this.parent.addCommands(cmd, undo);
|
||||
this.parent.addCommands(cmd, undo, true);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -273,8 +274,12 @@ class InkEditor extends AnnotationEditor {
|
||||
if (this.#disableEditing) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.disableEditMode();
|
||||
|
||||
// This editor must be on top of the main ink editor.
|
||||
this.setInForeground();
|
||||
|
||||
this.#disableEditing = true;
|
||||
this.div.classList.add("disabled");
|
||||
|
||||
@ -297,6 +302,10 @@ class InkEditor extends AnnotationEditor {
|
||||
return;
|
||||
}
|
||||
|
||||
// We want to draw on top of any other editors.
|
||||
// Since it's the last child, there's no need to give it a higher z-index.
|
||||
this.setInForeground();
|
||||
|
||||
event.stopPropagation();
|
||||
|
||||
this.canvas.addEventListener("mouseleave", this.#boundCanvasMouseleave);
|
||||
@ -324,6 +333,10 @@ class InkEditor extends AnnotationEditor {
|
||||
if (this.isInEditMode() && this.currentPath.length !== 0) {
|
||||
event.stopPropagation();
|
||||
this.#endDrawing(event);
|
||||
|
||||
// Since the ink editor covers all of the page and we want to be able
|
||||
// to select another editor, we just put this one in the background.
|
||||
this.setInBackground();
|
||||
}
|
||||
}
|
||||
|
||||
@ -334,6 +347,7 @@ class InkEditor extends AnnotationEditor {
|
||||
*/
|
||||
canvasMouseleave(event) {
|
||||
this.#endDrawing(event);
|
||||
this.setInBackground();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -63,8 +63,9 @@ class CommandManager {
|
||||
* Add a new couple of commands to be used in case of redo/undo.
|
||||
* @param {function} cmd
|
||||
* @param {function} undo
|
||||
* @param {boolean} mustExec
|
||||
*/
|
||||
add(cmd, undo) {
|
||||
add(cmd, undo, mustExec) {
|
||||
const save = [cmd, undo];
|
||||
const next = (this.#position + 1) % this.#maxSize;
|
||||
if (next !== this.#start) {
|
||||
@ -79,7 +80,10 @@ class CommandManager {
|
||||
this.#position = this.#commands.length - 1;
|
||||
}
|
||||
this.#setCommands(save);
|
||||
cmd();
|
||||
|
||||
if (mustExec) {
|
||||
cmd();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -316,6 +320,9 @@ class AnnotationEditorUIManager {
|
||||
this.#disableAll();
|
||||
} else {
|
||||
this.#enableAll();
|
||||
for (const layer of this.#allLayers) {
|
||||
layer.updateMode(mode);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -409,9 +416,10 @@ class AnnotationEditorUIManager {
|
||||
* Add a command to execute (cmd) and another one to undo it.
|
||||
* @param {function} cmd
|
||||
* @param {function} undo
|
||||
* @param {boolean} mustExec
|
||||
*/
|
||||
addCommands(cmd, undo) {
|
||||
this.#commandManager.add(cmd, undo);
|
||||
addCommands(cmd, undo, mustExec) {
|
||||
this.#commandManager.add(cmd, undo, mustExec);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -460,7 +468,7 @@ class AnnotationEditorUIManager {
|
||||
}
|
||||
};
|
||||
|
||||
this.addCommands(cmd, undo);
|
||||
this.addCommands(cmd, undo, true);
|
||||
} else {
|
||||
if (!this.#activeEditor) {
|
||||
return;
|
||||
@ -474,7 +482,7 @@ class AnnotationEditorUIManager {
|
||||
};
|
||||
}
|
||||
|
||||
this.addCommands(cmd, undo);
|
||||
this.addCommands(cmd, undo, true);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -501,7 +509,7 @@ class AnnotationEditorUIManager {
|
||||
layer.addOrRebuild(editor);
|
||||
};
|
||||
|
||||
this.addCommands(cmd, undo);
|
||||
this.addCommands(cmd, undo, true);
|
||||
}
|
||||
}
|
||||
|
||||
@ -522,7 +530,7 @@ class AnnotationEditorUIManager {
|
||||
editor.remove();
|
||||
};
|
||||
|
||||
this.addCommands(cmd, undo);
|
||||
this.addCommands(cmd, undo, true);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -98,6 +98,11 @@
|
||||
overflow: auto;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.annotationEditorLayer .background {
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
.annotationEditorLayer .inkEditor:focus {
|
||||
|
Loading…
x
Reference in New Issue
Block a user