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 @@
-
+