diff --git a/src/main/resources/static/css/general.css b/src/main/resources/static/css/general.css index 124a35e4..90d08d12 100644 --- a/src/main/resources/static/css/general.css +++ b/src/main/resources/static/css/general.css @@ -16,11 +16,14 @@ html[lang-direction=ltr] * { direction: ltr; } - html[lang-direction=rtl] * { direction: rtl; text-align: right; } +.ignore-rtl { + direction: ltr !important; + text-align: left !important; +} .align-top { position: absolute; @@ -47,3 +50,11 @@ html[lang-direction=rtl] * { border-top-left-radius: 0.25rem !important; border-bottom-left-radius: 0.25rem !important; } + +.margin-auto-parent { + width: 100%; + display: flex; +} +.margin-center { + margin: 0 auto; +} \ No newline at end of file diff --git a/src/main/resources/static/css/tab-container.css b/src/main/resources/static/css/tab-container.css new file mode 100644 index 00000000..d8bcf5ff --- /dev/null +++ b/src/main/resources/static/css/tab-container.css @@ -0,0 +1,31 @@ + +.tab-group { + +} + +.tab-container { + display: none; +} +.tab-container.active { + display: block; + border: 1px solid #dee2e6; + padding: 15px; +} +/* +.tab-buttons { + border-bottom: 1px solid #dee2e6; +} +*/ +.tab-buttons > button { + margin-bottom: -1px; + background: 0 0; + border: 1px solid transparent; + + border-top-left-radius: 0.25rem; + border-top-right-radius: 0.25rem; +} +.tab-buttons > button.active { + color: #495057; + background-color: #fff; + border-color: #dee2e6 #dee2e6 #fff; +} \ No newline at end of file diff --git a/src/main/resources/static/js/draggable-utils.js b/src/main/resources/static/js/draggable-utils.js new file mode 100644 index 00000000..77f35d0b --- /dev/null +++ b/src/main/resources/static/js/draggable-utils.js @@ -0,0 +1,211 @@ +const DraggableUtils = { + + boxDragContainer: document.getElementById('box-drag-container'), + pdfCanvas: document.getElementById('pdf-canvas'), + nextId: 0, + pdfDoc: null, + pageIndex: 0, + + init() { + interact('.draggable-canvas') + .draggable({ + listeners: { + move: (event) => { + const target = event.target; + const x = (parseFloat(target.getAttribute('data-x')) || 0) + event.dx; + const y = (parseFloat(target.getAttribute('data-y')) || 0) + event.dy; + + target.style.transform = `translate(${x}px, ${y}px)`; + target.setAttribute('data-x', x); + target.setAttribute('data-y', y); + + this.onInteraction(target); + }, + }, + }) + .resizable({ + edges: { left: true, right: true, bottom: true, top: true }, + listeners: { + move: (event) => { + var target = event.target + var x = (parseFloat(target.getAttribute('data-x')) || 0) + var y = (parseFloat(target.getAttribute('data-y')) || 0) + + // update the element's style + target.style.width = event.rect.width + 'px' + target.style.height = event.rect.height + 'px' + + // translate when resizing from top or left edges + x += event.deltaRect.left + y += event.deltaRect.top + + target.style.transform = 'translate(' + x + 'px,' + y + 'px)' + + target.setAttribute('data-x', x) + target.setAttribute('data-y', y) + target.textContent = Math.round(event.rect.width) + '\u00D7' + Math.round(event.rect.height) + + this.onInteraction(target); + }, + }, + modifiers: [ + interact.modifiers.restrictSize({ + min: { width: 50, height: 50 }, + }), + ], + inertia: true, + }); + }, + onInteraction(target) { + this.boxDragContainer.appendChild(target); + }, + + createDraggableCanvas() { + const createdCanvas = document.createElement('canvas'); + createdCanvas.id = `draggable-canvas-${this.nextId++}`; + createdCanvas.classList.add("draggable-canvas"); + + const x = 0; + const y = 0; + createdCanvas.style.transform = `translate(${x}px, ${y}px)`; + createdCanvas.setAttribute('data-x', x); + createdCanvas.setAttribute('data-y', y); + + createdCanvas.onclick = e => this.onInteraction(e.target); + + this.boxDragContainer.appendChild(createdCanvas); + return createdCanvas; + }, + createDraggableCanvasFromUrl(dataUrl) { + return new Promise((resolve) => { + var myImage = new Image(); + myImage.src = dataUrl; + myImage.onload = () => { + var createdCanvas = this.createDraggableCanvas(); + + createdCanvas.width = myImage.width; + createdCanvas.height = myImage.height; + + const imgAspect = myImage.width / myImage.height; + const pdfAspect = this.boxDragContainer.offsetWidth / this.boxDragContainer.offsetHeight; + + var scaleMultiplier; + if (imgAspect > pdfAspect) { + scaleMultiplier = this.boxDragContainer.offsetWidth / myImage.width; + } else { + scaleMultiplier = this.boxDragContainer.offsetHeight / myImage.height; + } + + var newWidth = createdCanvas.width; + var newHeight = createdCanvas.height; + if (scaleMultiplier < 1) { + newWidth = newWidth * scaleMultiplier; + newHeight = newHeight * scaleMultiplier; + } + + createdCanvas.style.width = newWidth+"px"; + createdCanvas.style.height = newHeight+"px"; + + var myContext = createdCanvas.getContext("2d"); + myContext.drawImage(myImage,0,0); + resolve(createdCanvas); + } + }) + }, + deleteDraggableCanvas(element) { + element.remove(); + }, + deleteDraggableCanvasById(id) { + this.deleteDraggableCanvas(document.getElementById(id)); + }, + getLastInteracted() { + return this.boxDragContainer.querySelector(".draggable-canvas:last-of-type"); + }, + + async renderPage(pdfDocument, pageIdx) { + this.pdfDoc = pdfDocument ? pdfDocument : this.pdfDoc; + this.pageIndex = pageIdx; + const page = await this.pdfDoc.getPage(this.pageIndex+1); + + // set the canvas size to the size of the page + if (page.rotate == 90 || page.rotate == 270) { + this.pdfCanvas.width = page.view[3]; + this.pdfCanvas.height = page.view[2]; + } else { + this.pdfCanvas.width = page.view[2]; + this.pdfCanvas.height = page.view[3]; + } + + // render the page onto the canvas + var renderContext = { + canvasContext: this.pdfCanvas.getContext("2d"), + viewport: page.getViewport({ scale: 1 }) + }; + await page.render(renderContext).promise; + + //return pdfCanvas.toDataURL(); + }, + async incrementPage() { + if (this.pageIndex < this.pdfDoc.numPages-1) { + return await this.renderPage(this.pdfDoc, this.pageIndex+1) + } + }, + async decrementPage() { + if (this.pageIndex > 0) { + return await this.renderPage(this.pdfDoc, this.pageIndex-1) + } + }, + + parseTransform(element) { + const tansform = element.style.transform.replace(/[^.,-\d]/g, ''); + const transformComponents = tansform.split(","); + return { + x: parseFloat(transformComponents[0]), + y: parseFloat(transformComponents[1]), + width: element.offsetWidth, + height: element.offsetHeight, + } + }, + async getOverlayedPdfDocument() { + const pdfBytes = await this.pdfDoc.getData(); + const pdfDocModified = await PDFLib.PDFDocument.load(pdfBytes); + + const draggables = this.boxDragContainer.querySelectorAll(".draggable-canvas"); + for (const draggable of draggables) { + // embed the draggable canvas + const dataURL = draggable.toDataURL(); + const response = await fetch(dataURL); + const draggableImgBytes = await response.arrayBuffer(); + const pdfImageObject = await pdfDocModified.embedPng(draggableImgBytes); + + const page = pdfDocModified.getPage(this.pageIndex); + + const draggablePositionPixels = this.parseTransform(draggable); + const draggablePositionRelative = { + x: draggablePositionPixels.x / this.pdfCanvas.offsetWidth, + y: draggablePositionPixels.y / this.pdfCanvas.offsetHeight, + width: draggablePositionPixels.width / this.pdfCanvas.offsetWidth, + height: draggablePositionPixels.height / this.pdfCanvas.offsetHeight, + } + const draggablePositionPdf = { + x: draggablePositionRelative.x * page.getWidth(), + y: draggablePositionRelative.y * page.getHeight(), + width: draggablePositionRelative.width * page.getWidth(), + height: draggablePositionRelative.height * page.getHeight(), + } + + page.drawImage(pdfImageObject, { + x: draggablePositionPdf.x, + y: page.getHeight() - draggablePositionPdf.y - draggablePositionPdf.height, + width: draggablePositionPdf.width, + height: draggablePositionPdf.height, + }); + } + + return pdfDocModified; + }, +} + +document.addEventListener("DOMContentLoaded", () => { + DraggableUtils.init(); +}); diff --git a/src/main/resources/static/js/tab-container.js b/src/main/resources/static/js/tab-container.js new file mode 100644 index 00000000..6ae7162c --- /dev/null +++ b/src/main/resources/static/js/tab-container.js @@ -0,0 +1,39 @@ + +TabContainer = { + initTabGroups() { + const groups = document.querySelectorAll(".tab-group"); + const unloadedGroups = [...groups].filter(g => !g.initialised); + unloadedGroups.forEach(group => { + const containers = group.querySelectorAll(".tab-container"); + const tabTitles = [...containers].map(c => c.getAttribute("title")); + + const tabList = document.createElement("div"); + tabList.classList.add("tab-buttons"); + tabTitles.forEach(title => { + const tabButton = document.createElement("button"); + tabButton.innerHTML = title; + tabButton.onclick = e => { + this.setActiveTab(e.target); + } + tabList.appendChild(tabButton); + }); + group.prepend(tabList); + + this.setActiveTab(tabList.firstChild); + + group.initialised = true; + }); + }, + setActiveTab(tabButton) { + const group = tabButton.closest(".tab-group") + + group.querySelectorAll(".active").forEach(el => el.classList.remove("active")); + + tabButton.classList.add("active"); + group.querySelector(`[title="${tabButton.innerHTML}"]`).classList.add("active"); + }, +} + +document.addEventListener("DOMContentLoaded", () => { + TabContainer.initTabGroups(); +}) \ No newline at end of file diff --git a/src/main/resources/templates/fragments/common.html b/src/main/resources/templates/fragments/common.html index 6ae7fdef..8b056ade 100644 --- a/src/main/resources/templates/fragments/common.html +++ b/src/main/resources/templates/fragments/common.html @@ -29,6 +29,10 @@ + + + + + +
+
+
+ +
+
+ +
+ + + + +
+
+ + + + +
+ +
+ + +
-
- - - - - -
- -
+ +
- + +
+ + + +
+
- + +
+ +
+
- \ No newline at end of file