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 {
|
class AnnotationEditorLayer {
|
||||||
#boundClick;
|
#boundClick;
|
||||||
|
|
||||||
|
#boundMouseover;
|
||||||
|
|
||||||
#editors = new Map();
|
#editors = new Map();
|
||||||
|
|
||||||
#uiManager;
|
#uiManager;
|
||||||
@ -83,6 +85,7 @@ class AnnotationEditorLayer {
|
|||||||
this.pageIndex = options.pageIndex;
|
this.pageIndex = options.pageIndex;
|
||||||
this.div = options.div;
|
this.div = options.div;
|
||||||
this.#boundClick = this.click.bind(this);
|
this.#boundClick = this.click.bind(this);
|
||||||
|
this.#boundMouseover = this.mouseover.bind(this);
|
||||||
|
|
||||||
for (const editor of this.#uiManager.getEditors(options.pageIndex)) {
|
for (const editor of this.#uiManager.getEditors(options.pageIndex)) {
|
||||||
this.add(editor);
|
this.add(editor);
|
||||||
@ -91,13 +94,45 @@ class AnnotationEditorLayer {
|
|||||||
this.#uiManager.addLayer(this);
|
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).
|
* Add some commands into the CommandManager (undo/redo stuff).
|
||||||
* @param {function} cmd
|
* @param {function} cmd
|
||||||
* @param {function} undo
|
* @param {function} undo
|
||||||
|
* @param {boolean} mustExec - If true the command is executed after having
|
||||||
|
* been added.
|
||||||
*/
|
*/
|
||||||
addCommands(cmd, undo) {
|
addCommands(cmd, undo, mustExec) {
|
||||||
this.#uiManager.addCommands(cmd, undo);
|
this.#uiManager.addCommands(cmd, undo, mustExec);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -178,6 +213,17 @@ class AnnotationEditorLayer {
|
|||||||
* @param {AnnotationEditor} editor
|
* @param {AnnotationEditor} editor
|
||||||
*/
|
*/
|
||||||
setActiveEditor(editor) {
|
setActiveEditor(editor) {
|
||||||
|
const currentActive = this.#uiManager.getActive();
|
||||||
|
if (currentActive === editor) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.#uiManager.setActiveEditor(editor);
|
||||||
|
|
||||||
|
if (currentActive && currentActive !== editor) {
|
||||||
|
currentActive.commitOrRemove();
|
||||||
|
}
|
||||||
|
|
||||||
if (editor) {
|
if (editor) {
|
||||||
this.unselectAll();
|
this.unselectAll();
|
||||||
this.div.removeEventListener("click", this.#boundClick);
|
this.div.removeEventListener("click", this.#boundClick);
|
||||||
@ -185,7 +231,6 @@ class AnnotationEditorLayer {
|
|||||||
this.#uiManager.allowClick = false;
|
this.#uiManager.allowClick = false;
|
||||||
this.div.addEventListener("click", this.#boundClick);
|
this.div.addEventListener("click", this.#boundClick);
|
||||||
}
|
}
|
||||||
this.#uiManager.setActiveEditor(editor);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
attach(editor) {
|
attach(editor) {
|
||||||
@ -212,7 +257,6 @@ class AnnotationEditorLayer {
|
|||||||
if (this.#uiManager.isActive(editor) || this.#editors.size === 0) {
|
if (this.#uiManager.isActive(editor) || this.#editors.size === 0) {
|
||||||
this.setActiveEditor(null);
|
this.setActiveEditor(null);
|
||||||
this.#uiManager.allowClick = true;
|
this.#uiManager.allowClick = true;
|
||||||
this.div.focus();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -279,7 +323,22 @@ class AnnotationEditorLayer {
|
|||||||
editor.remove();
|
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;
|
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.
|
* Mouseclick callback.
|
||||||
* @param {MouseEvent} event
|
* @param {MouseEvent} event
|
||||||
@ -316,16 +395,7 @@ class AnnotationEditorLayer {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const id = this.getNextId();
|
this.#createAndAddNewEditor(event);
|
||||||
const editor = this.#createNewEditor({
|
|
||||||
parent: this,
|
|
||||||
id,
|
|
||||||
x: event.offsetX,
|
|
||||||
y: event.offsetY,
|
|
||||||
});
|
|
||||||
if (editor) {
|
|
||||||
this.addANewEditor(editor);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -53,6 +53,20 @@ class AnnotationEditor {
|
|||||||
this.isAttachedToDOM = false;
|
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.
|
* onfocus callback.
|
||||||
*/
|
*/
|
||||||
@ -81,12 +95,16 @@ class AnnotationEditor {
|
|||||||
|
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
|
||||||
|
this.commitOrRemove();
|
||||||
|
this.parent.setActiveEditor(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
commitOrRemove() {
|
||||||
if (this.isEmpty()) {
|
if (this.isEmpty()) {
|
||||||
this.remove();
|
this.remove();
|
||||||
} else {
|
} else {
|
||||||
this.commit();
|
this.commit();
|
||||||
}
|
}
|
||||||
this.parent.setActiveEditor(null);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -156,7 +174,6 @@ class AnnotationEditor {
|
|||||||
this.div = document.createElement("div");
|
this.div = document.createElement("div");
|
||||||
this.div.className = this.name;
|
this.div.className = this.name;
|
||||||
this.div.setAttribute("id", this.id);
|
this.div.setAttribute("id", this.id);
|
||||||
this.div.draggable = true;
|
|
||||||
this.div.tabIndex = 100;
|
this.div.tabIndex = 100;
|
||||||
|
|
||||||
const [tx, ty] = this.getInitialTranslation();
|
const [tx, ty] = this.getInitialTranslation();
|
||||||
|
@ -33,6 +33,8 @@ class FreeTextEditor extends AnnotationEditor {
|
|||||||
|
|
||||||
#contentHTML = "";
|
#contentHTML = "";
|
||||||
|
|
||||||
|
#hasAlreadyBeenCommitted = false;
|
||||||
|
|
||||||
#fontSize;
|
#fontSize;
|
||||||
|
|
||||||
static _freeTextDefaultContent = "";
|
static _freeTextDefaultContent = "";
|
||||||
@ -168,6 +170,13 @@ class FreeTextEditor extends AnnotationEditor {
|
|||||||
* @returns {undefined}
|
* @returns {undefined}
|
||||||
*/
|
*/
|
||||||
commit() {
|
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.disableEditMode();
|
||||||
this.#contentHTML = this.editorDiv.innerHTML;
|
this.#contentHTML = this.editorDiv.innerHTML;
|
||||||
this.#content = this.#extractText().trimEnd();
|
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.
|
* Basic draw editor in order to generate an Ink annotation.
|
||||||
*/
|
*/
|
||||||
class InkEditor extends AnnotationEditor {
|
class InkEditor extends AnnotationEditor {
|
||||||
#aspectRatio;
|
#aspectRatio = 0;
|
||||||
|
|
||||||
#baseHeight;
|
#baseHeight = 0;
|
||||||
|
|
||||||
#baseWidth;
|
#baseWidth = 0;
|
||||||
|
|
||||||
#boundCanvasMousemove;
|
#boundCanvasMousemove;
|
||||||
|
|
||||||
@ -35,9 +35,9 @@ class InkEditor extends AnnotationEditor {
|
|||||||
|
|
||||||
#boundCanvasMousedown;
|
#boundCanvasMousedown;
|
||||||
|
|
||||||
#disableEditing;
|
#disableEditing = false;
|
||||||
|
|
||||||
#observer;
|
#observer = null;
|
||||||
|
|
||||||
constructor(params) {
|
constructor(params) {
|
||||||
super({ ...params, name: "inkEditor" });
|
super({ ...params, name: "inkEditor" });
|
||||||
@ -48,10 +48,6 @@ class InkEditor extends AnnotationEditor {
|
|||||||
this.currentPath = [];
|
this.currentPath = [];
|
||||||
this.scaleFactor = 1;
|
this.scaleFactor = 1;
|
||||||
this.translationX = this.translationY = 0;
|
this.translationX = this.translationY = 0;
|
||||||
this.#baseWidth = this.#baseHeight = 0;
|
|
||||||
this.#aspectRatio = 0;
|
|
||||||
this.#disableEditing = false;
|
|
||||||
this.#observer = null;
|
|
||||||
this.x = 0;
|
this.x = 0;
|
||||||
this.y = 0;
|
this.y = 0;
|
||||||
|
|
||||||
@ -113,8 +109,6 @@ class InkEditor extends AnnotationEditor {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
super.remove();
|
|
||||||
|
|
||||||
// Destroy the canvas.
|
// Destroy the canvas.
|
||||||
this.canvas.width = this.canvas.heigth = 0;
|
this.canvas.width = this.canvas.heigth = 0;
|
||||||
this.canvas.remove();
|
this.canvas.remove();
|
||||||
@ -122,11 +116,13 @@ class InkEditor extends AnnotationEditor {
|
|||||||
|
|
||||||
this.#observer.disconnect();
|
this.#observer.disconnect();
|
||||||
this.#observer = null;
|
this.#observer = null;
|
||||||
|
|
||||||
|
super.remove();
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @inheritdoc */
|
/** @inheritdoc */
|
||||||
enableEditMode() {
|
enableEditMode() {
|
||||||
if (this.#disableEditing) {
|
if (this.#disableEditing || this.canvas === null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -145,7 +141,7 @@ class InkEditor extends AnnotationEditor {
|
|||||||
|
|
||||||
super.disableEditMode();
|
super.disableEditMode();
|
||||||
this.canvas.style.cursor = "auto";
|
this.canvas.style.cursor = "auto";
|
||||||
this.div.draggable = true;
|
this.div.draggable = !this.isEmpty();
|
||||||
this.div.classList.remove("editing");
|
this.div.classList.remove("editing");
|
||||||
|
|
||||||
this.canvas.removeEventListener("mousedown", this.#boundCanvasMousedown);
|
this.canvas.removeEventListener("mousedown", this.#boundCanvasMousedown);
|
||||||
@ -154,6 +150,7 @@ class InkEditor extends AnnotationEditor {
|
|||||||
|
|
||||||
/** @inheritdoc */
|
/** @inheritdoc */
|
||||||
onceAdded() {
|
onceAdded() {
|
||||||
|
this.div.draggable = !this.isEmpty();
|
||||||
this.div.focus();
|
this.div.focus();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -238,11 +235,15 @@ class InkEditor extends AnnotationEditor {
|
|||||||
if (this.paths.length === 0) {
|
if (this.paths.length === 0) {
|
||||||
this.remove();
|
this.remove();
|
||||||
} else {
|
} else {
|
||||||
|
if (!this.canvas) {
|
||||||
|
this.#createCanvas();
|
||||||
|
this.#createObserver();
|
||||||
|
}
|
||||||
this.#fitToContent();
|
this.#fitToContent();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
this.parent.addCommands(cmd, undo);
|
this.parent.addCommands(cmd, undo, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -273,8 +274,12 @@ class InkEditor extends AnnotationEditor {
|
|||||||
if (this.#disableEditing) {
|
if (this.#disableEditing) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.disableEditMode();
|
this.disableEditMode();
|
||||||
|
|
||||||
|
// This editor must be on top of the main ink editor.
|
||||||
|
this.setInForeground();
|
||||||
|
|
||||||
this.#disableEditing = true;
|
this.#disableEditing = true;
|
||||||
this.div.classList.add("disabled");
|
this.div.classList.add("disabled");
|
||||||
|
|
||||||
@ -297,6 +302,10 @@ class InkEditor extends AnnotationEditor {
|
|||||||
return;
|
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();
|
event.stopPropagation();
|
||||||
|
|
||||||
this.canvas.addEventListener("mouseleave", this.#boundCanvasMouseleave);
|
this.canvas.addEventListener("mouseleave", this.#boundCanvasMouseleave);
|
||||||
@ -324,6 +333,10 @@ class InkEditor extends AnnotationEditor {
|
|||||||
if (this.isInEditMode() && this.currentPath.length !== 0) {
|
if (this.isInEditMode() && this.currentPath.length !== 0) {
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
this.#endDrawing(event);
|
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) {
|
canvasMouseleave(event) {
|
||||||
this.#endDrawing(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.
|
* Add a new couple of commands to be used in case of redo/undo.
|
||||||
* @param {function} cmd
|
* @param {function} cmd
|
||||||
* @param {function} undo
|
* @param {function} undo
|
||||||
|
* @param {boolean} mustExec
|
||||||
*/
|
*/
|
||||||
add(cmd, undo) {
|
add(cmd, undo, mustExec) {
|
||||||
const save = [cmd, undo];
|
const save = [cmd, undo];
|
||||||
const next = (this.#position + 1) % this.#maxSize;
|
const next = (this.#position + 1) % this.#maxSize;
|
||||||
if (next !== this.#start) {
|
if (next !== this.#start) {
|
||||||
@ -79,7 +80,10 @@ class CommandManager {
|
|||||||
this.#position = this.#commands.length - 1;
|
this.#position = this.#commands.length - 1;
|
||||||
}
|
}
|
||||||
this.#setCommands(save);
|
this.#setCommands(save);
|
||||||
cmd();
|
|
||||||
|
if (mustExec) {
|
||||||
|
cmd();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -316,6 +320,9 @@ class AnnotationEditorUIManager {
|
|||||||
this.#disableAll();
|
this.#disableAll();
|
||||||
} else {
|
} else {
|
||||||
this.#enableAll();
|
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.
|
* Add a command to execute (cmd) and another one to undo it.
|
||||||
* @param {function} cmd
|
* @param {function} cmd
|
||||||
* @param {function} undo
|
* @param {function} undo
|
||||||
|
* @param {boolean} mustExec
|
||||||
*/
|
*/
|
||||||
addCommands(cmd, undo) {
|
addCommands(cmd, undo, mustExec) {
|
||||||
this.#commandManager.add(cmd, undo);
|
this.#commandManager.add(cmd, undo, mustExec);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -460,7 +468,7 @@ class AnnotationEditorUIManager {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
this.addCommands(cmd, undo);
|
this.addCommands(cmd, undo, true);
|
||||||
} else {
|
} else {
|
||||||
if (!this.#activeEditor) {
|
if (!this.#activeEditor) {
|
||||||
return;
|
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);
|
layer.addOrRebuild(editor);
|
||||||
};
|
};
|
||||||
|
|
||||||
this.addCommands(cmd, undo);
|
this.addCommands(cmd, undo, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -522,7 +530,7 @@ class AnnotationEditorUIManager {
|
|||||||
editor.remove();
|
editor.remove();
|
||||||
};
|
};
|
||||||
|
|
||||||
this.addCommands(cmd, undo);
|
this.addCommands(cmd, undo, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -98,6 +98,11 @@
|
|||||||
overflow: auto;
|
overflow: auto;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.annotationEditorLayer .background {
|
||||||
|
z-index: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.annotationEditorLayer .inkEditor:focus {
|
.annotationEditorLayer .inkEditor:focus {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user