diff --git a/public/css/app.css b/public/css/app.css
index dadd176..d188b62 100644
--- a/public/css/app.css
+++ b/public/css/app.css
@@ -74,6 +74,10 @@
opacity: 1;
}
+#toolbox i {
+ cursor: pointer;
+}
+
.canvas-container .btn-drag, .canvas-container .btn-rotate, .canvas-container .btn-delete, .canvas-container .btn-select, .canvas-container .btn-download, .canvas-container .btn-restore, .canvas-container .btn-drag-here, .canvas-container .btn-drag-here_mobile, .canvas-container .btn-cancel {
font-size: 30px;
cursor: move;
@@ -113,6 +117,10 @@
user-select: none;
}
+.input-metadata > label {
+ width: 110%;
+}
+
.input-metadata:hover > .delete-metadata {
display: block;
}
diff --git a/public/js/metadata.js b/public/js/metadata.js
index 1c9079f..2f1740b 100644
--- a/public/js/metadata.js
+++ b/public/js/metadata.js
@@ -32,6 +32,7 @@ async function loadPDF(pdfBlob) {
const loadingTask = pdfjsLib.getDocument(url);
const pdf = await loadingTask.promise;
const metadata = await pdf.getMetadata()
+ const attachments = await pdf.getAttachments();
for(fieldKey in defaultFields) {
addMetadata(fieldKey, null, defaultFields[fieldKey]['type'], false);
@@ -52,6 +53,40 @@ async function loadPDF(pdfBlob) {
addMetadata(metaKey, metadata.info.Custom[metaKey], "text", false);
}
+ if (attachments) {
+ Object.entries(attachments).forEach(([_key, value]) => {
+ if (value.filename.startsWith('factur-x') === false) {
+ return
+ }
+ const decodedAttachment = new TextDecoder().decode(value.content)
+ const parser = new DOMParser();
+ const xml = parser.parseFromString(decodedAttachment, "application/xml")
+ const error = xml.querySelector('parseerror')
+ if (error) {
+ console.log(error)
+ return
+ }
+
+ const walker = xml.createTreeWalker(xml.firstChild, NodeFilter.SHOW_TEXT)
+ while(walker.nextNode()) {
+ const node = walker.currentNode
+
+ const treeKey = []
+ treeKey.push(node.parentNode.localName)
+
+ let root = node.parentNode
+ while (! (root.parentNode instanceof XMLDocument)) {
+ root = root.parentNode
+ treeKey.push(root.localName) // nodeName si on veut le namespace
+ }
+
+ const newInput = addMetadata(treeKey.join(' « '), node.textContent.trim(), "text", false, true)
+ newInput.dataset.fromAttachment = value.filename
+ newInput.disabled = true
+ }
+ })
+ }
+
for(let pageNumber = 1; pageNumber <= pdf.numPages; pageNumber++ ) {
pdf.getPage(pageNumber).then(function(page) {
let pageIndex = (page.pageNumber - 1);
@@ -130,17 +165,19 @@ async function pageRender(pageIndex) {
})
}
-function addMetadata(key, value, type, focus) {
- let input = document.querySelector('.input-metadata input[name="'+key+'"]');
+function addMetadata(key, value, type, focus, forceCreation = false) {
+ if (! forceCreation) {
+ let input = document.querySelector('.input-metadata input[name="'+key+'"]');
- if(input && !input.value) {
- input.value = value;
- }
- if(input && focus) {
- input.focus();
- }
- if(input) {
- return;
+ if(input && !input.value) {
+ input.value = value;
+ }
+ if(input && focus) {
+ input.focus();
+ }
+ if(input) {
+ return input;
+ }
}
let div = document.createElement('div');
@@ -168,6 +205,8 @@ function addMetadata(key, value, type, focus) {
if(focus) {
input.focus();
}
+
+ return input
}
function deleteMetadata(el) {
@@ -204,6 +243,10 @@ async function save() {
const label = el.querySelector('label').innerText
const input = el.querySelector('input').value
+ if ('fromAttachment' in el.querySelector('input').dataset) {
+ return;
+ }
+
pdf.getInfoDict().set(PDFName.of(label), PDFHexString.fromText(input));
});
diff --git a/public/js/organization.js b/public/js/organization.js
index 9ad4bac..585ea2a 100644
--- a/public/js/organization.js
+++ b/public/js/organization.js
@@ -23,6 +23,7 @@ if(is_mobile()) {
let nbPDF = 0;
let pages = [];
+let formats = [];
let pdfRenderTasks = [];
async function loadPDF(pdfBlob, filename, pdfIndex) {
@@ -47,7 +48,11 @@ async function loadPDF(pdfBlob, filename, pdfIndex) {
pdf.getPage(pageNumber).then(function(page) {
let pageIndex = pdfLetter + "_" + (page.pageNumber - 1);
pages[pageIndex] = page;
-
+ const viewportFormat = page.getViewport({ scale: 1 });
+ const widthFormat = Math.round(viewportFormat.width * 25.4 / 72 * 10) / 10;
+ const heightFormat = Math.round(viewportFormat.height * 25.4 / 72 * 10) / 10;
+ formats[pageIndex] = widthFormat + " x " + heightFormat + " mm";
+ let pageTitle = trad['Page'] + ' ' + page.pageNumber + ' - '+formats[pageIndex]+' - ' + filename;
let pageHTML = '
';
pageHTML += '
';
pageHTML += '
';
@@ -57,7 +62,7 @@ async function loadPDF(pdfBlob, filename, pdfIndex) {
pageHTML += '
';
pageHTML += '
';
pageHTML += '
';
- pageHTML += '
' + trad['Page'] + ' ' + page.pageNumber + ' - ' + filename + '
';
+ pageHTML += '
' + pageTitle + '
';
pageHTML += '
';
pageHTML += '
';
pageHTML += '
';
@@ -68,6 +73,7 @@ async function loadPDF(pdfBlob, filename, pdfIndex) {
document.getElementById('container-pages').insertAdjacentHTML('beforeend', pageHTML);
let canvasContainer = document.getElementById('canvas-container-' + pageIndex);
+ canvasContainer.title = pageTitle;
canvasContainer.addEventListener('click', function(e) {
canvasContainer.querySelector('.btn-select').click();
});
@@ -214,6 +220,7 @@ async function pageRender(pageIndex) {
scrollWidth = -4;
}
let page = pages[pageIndex];
+
let rotation = parseInt(document.querySelector('#input_rotate_'+pageIndex).value);
let viewport = page.getViewport({scale: 1, rotation: rotation});
let sizeWidth = Math.floor((document.getElementById('container-pages').offsetWidth - (8*(nbPagePerLine+1)) - scrollWidth) / nbPagePerLine);
diff --git a/public/js/signature.js b/public/js/signature.js
index 505fdcc..2205c7e 100644
--- a/public/js/signature.js
+++ b/public/js/signature.js
@@ -170,6 +170,20 @@ async function loadPDF(pdfBlob) {
toolBox.init(event.selected[0])
});
+ canvasEdition.on("selection:updated", function(event) {
+ toolBox.reset()
+ if (event.selected.length > 1 || event.selected.length === 0) {
+ return;
+ }
+
+ toolBox.init(event.selected[0])
+ });
+ canvasEdition.on("object:modified", function(event) {
+ toolBox.init(event.target)
+ });
+ canvasEdition.on("selection:cleared", function(event) {
+ toolBox.reset()
+ });
canvasEditions.push(canvasEdition);
});
}
@@ -1248,30 +1262,40 @@ function storePenColor(color) {
}
const toolBox = (function () {
- const _coloricon = document.createElement('img')
- _coloricon.src = 'data:image/svg+xml,
'
+ const _coloricon = document.createElement('i')
+ _coloricon.classList.add('bi', 'bi-droplet-fill', 'mx-1')
- function _renderIcon(icon) {
- return function renderIcon(ctx, left, top, styleOverride, fabricObject) {
- const size = this.cornerSize;
- ctx.save();
- ctx.translate(left, top);
- ctx.rotate(fabric.util.degreesToRadians(fabricObject.angle));
- ctx.drawImage(icon, -size/2, -size/2, size, size);
- ctx.restore();
- }
- }
+ const _trashicon = document.createElement('i')
+ _trashicon.classList.add('bi', 'bi-trash3', 'float-end', 'border-start', 'border-2', 'mx-1', 'ps-1')
- function _changeColor(eventData, transform) {
- const target = transform.target;
+ const _copyicon = document.createElement('i')
+ _copyicon.classList.add('bi', 'bi-copy', 'mx-1')
+
+ let _elSelected
+
+ const _elToolbox = document.createElement('div')
+ _elToolbox.id = 'toolbox'
+ _elToolbox.classList.add('position-absolute', 'border', 'p-1', 'bg-body-secondary', 'shadow-sm', 'ms-3', 'mt-3', 'd-none', 'd-md-block')
+ _elToolbox.style['z-index'] = 1030
+ _elToolbox.style.width = 'max-content'
+
+ _elToolbox.appendChild(_coloricon)
+ _elToolbox.appendChild(_trashicon)
+ _elToolbox.appendChild(_copyicon)
+
+ _coloricon.addEventListener('click', _changeColor)
+ _trashicon.addEventListener('click', _delete)
+ _copyicon.addEventListener('click', _copy)
+
+ function _changeColor() {
const _colorpicker = document.createElement('input')
_colorpicker.setAttribute('type', 'color')
_colorpicker.value = penColor
_colorpicker.addEventListener('input', function (e) {
- target.set({ fill: e.target.value })
- target.canvas.requestRenderAll()
- if(target.type != "rect") {
+ _elSelected.set({ fill: e.target.value })
+ _elSelected.canvas.requestRenderAll()
+ if(_elSelected.type != "rect") {
storePenColor(e.target.value)
}
})
@@ -1280,22 +1304,53 @@ const toolBox = (function () {
_colorpicker.remove()
}
- function init(el) {
- colorControl = new fabric.Control({
- x: 0.5,
- y: -0.5,
- offsetY: -16,
- offsetX: 16,
- cursorStyle: 'pointer',
- mouseUpHandler: _changeColor,
- render: _renderIcon(_coloricon),
- cornerSize: 24
+ function _copy() {
+ canvasEditions.forEach(function (canvas) {
+ if (_elSelected.canvas === canvas) {
+ return
+ }
+
+ _elSelected.clone(function (clonedItem) {
+ addObjectInCanvas(canvas, clonedItem)
+ })
})
- el.controls.color = colorControl
+ }
+
+ function _delete() {
+ deleteActiveObject()
+ reset()
+ }
+
+ function init(el) {
+ if (_elToolbox) {
+ reset()
+ }
+
+ _elSelected = el
+ const container = document.getElementById('container-pages')
+ container.appendChild(_elToolbox)
+
+ _elToolbox.style.left = (
+ _elSelected.getBoundingRect().left
+ + _elSelected.getScaledWidth() / 2
+ - _elToolbox.offsetWidth / 2
+ + +window.getComputedStyle(_elToolbox).getPropertyValue("margin-left").replace('px', '')
+ ) + 'px'
+ _elToolbox.style.top = (
+ Math.max(..._elSelected.getCoords().map((c) => c.y)) // on sélectionne le coin le plus bas
+ + _elSelected.canvas._offset.top // hauteur du canvas dans le viewport
+ + container.scrollTop // haut du container
+ ) + 'px'
+ }
+
+ function reset() {
+ _elSelected = null
+ _elToolbox.remove()
}
return {
- init: init
+ init: init,
+ reset: reset
}
})()
diff --git a/templates/signature.html.php b/templates/signature.html.php
index d41a8f2..be4e253 100644
--- a/templates/signature.html.php
+++ b/templates/signature.html.php
@@ -41,7 +41,7 @@