diff --git a/src/i18n/de/translation.json b/src/i18n/de/translation.json index 071444e6d7beb1ab8f6df1a47a359e36e2882881..eca9504d819a41a522eae7cb4d2cec3462c77ec8 100644 --- a/src/i18n/de/translation.json +++ b/src/i18n/de/translation.json @@ -5,9 +5,12 @@ "word3": "Leidenschaft" }, "pdf-upload": { - "field-label": "PDF Dateien zum Signieren hochladen", + "upload-field-label": "PDF Dateien zum Signieren hochladen", "upload-area-text": "Sie können in diesem Bereich PDF Dateien per Drag & Drop oder per Direktauswahl hochladen", - "upload-button-label": "PDF Dateien auswählen" + "signed-files-label": "Signierte Dateien", + "download-zip-button": "Als ZIP Datei herunterladen", + "upload-button-label": "PDF Dateien auswählen", + "download-file-button-title": "Signiertes PDF herunterladen" }, "welcome": { "headline": "Willkommen beim Amtssignaturservice der TU Graz", diff --git a/src/i18n/en/translation.json b/src/i18n/en/translation.json index c5c1957441b8dbe109bcaafbf5b3286de3805bb9..0d4378a0de535d14ca4e22adf0f840d06c21fc72 100644 --- a/src/i18n/en/translation.json +++ b/src/i18n/en/translation.json @@ -5,9 +5,12 @@ "word3": "Technology" }, "pdf-upload": { - "field-label": "Upload PDF files to sign", + "upload-field-label": "Upload PDF files to sign", "upload-area-text": "In this area you can upload PDF files via Drag & Drop or by selecting them directly", - "upload-button-label": "Select PDF files" + "signed-files-label": "Signed files", + "download-zip-button": "Download ZIP", + "upload-button-label": "Select PDF files", + "download-file-button-title": "Download signed PDF" }, "welcome": { "headline": "Welcome to the official signature service of the TU Graz", diff --git a/src/utils.js b/src/utils.js index 979060c887dfeb331b41099a9e108902b1794185..f6ecedb659cc50a622200e64bd69f5ae217e59dc 100644 --- a/src/utils.js +++ b/src/utils.js @@ -18,3 +18,29 @@ export const findObjectInApiResults = (identifier, results, identifierAttribute } } }; + +export const getPDFFileBase64Content = (file) => { + return file.file.replace("data:application/pdf;base64,", ""); +}; + +export const convertDataURIToBinary = (dataURI) => { + const BASE64_MARKER = ';base64,'; + const base64Index = dataURI.indexOf(BASE64_MARKER) + BASE64_MARKER.length; + const base64 = dataURI.substring(base64Index); + const raw = window.atob(base64); + const rawLength = raw.length; + let array = new Uint8Array(rawLength); + + for(let i = 0; i < rawLength; i++) { + array[i] = raw.charCodeAt(i); + } + + return array; +}; + +export const getDataURIContentType = (dataURI) => { + const BASE64_MARKER = ';base64,'; + const base64Index = dataURI.indexOf(BASE64_MARKER); + + return dataURI.substring(5, base64Index); +}; diff --git a/src/vpu-signature-lit-element.js b/src/vpu-signature-lit-element.js index b254e8d792ab64f019095d80817a0f72512a5a9d..bbff7c1b39b32ef9a3d3afaf3d0df6f8c6400dde 100644 --- a/src/vpu-signature-lit-element.js +++ b/src/vpu-signature-lit-element.js @@ -8,7 +8,7 @@ export default class VPUSignatureLitElement extends LitElement { } hasSignaturePermissions() { - // TODO: Roles need to be discussed + // TODO: Roles need to be discussed, e.g. implement role scopes // return (window.VPUPerson && Array.isArray(window.VPUPerson.roles) && window.VPUPerson.roles.indexOf('ROLE_F_BIB_F') !== -1); return window.VPUPerson && Array.isArray(window.VPUPerson.roles); } diff --git a/src/vpu-signature-pdf-upload.js b/src/vpu-signature-pdf-upload.js index 5755df05b60c74b3e97f13da552dd0f84072209f..14da48e0b2c8d9ba4e422193d2304a7c4d58fc34 100644 --- a/src/vpu-signature-pdf-upload.js +++ b/src/vpu-signature-pdf-upload.js @@ -1,7 +1,9 @@ import {createI18nInstance} from './i18n.js'; +import {humanFileSize} from 'vpu-common/i18next.js'; import {css, html} from 'lit-element'; import VPUSignatureLitElement from "./vpu-signature-lit-element"; import * as commonUtils from 'vpu-common/utils'; +import * as utils from './utils'; import JSZip from 'jszip/dist/jszip.js'; import 'file-saver'; import * as commonStyles from 'vpu-common/styles'; @@ -17,12 +19,15 @@ class SignaturePdfUpload extends VPUSignatureLitElement { this.lang = i18n.language; this.entryPointUrl = commonUtils.getAPiUrl(); this.files = []; + this.filesCount = 0; } static get properties() { return { lang: { type: String }, entryPointUrl: { type: String, attribute: 'entry-point-url' }, + files: { type: Array, attribute: false }, + filesCount: { type: Number, attribute: false }, }; } @@ -32,27 +37,24 @@ class SignaturePdfUpload extends VPUSignatureLitElement { this.updateComplete.then(()=>{ this.shadowRoot.querySelectorAll('vpu-fileupload') .forEach(element => { - element.addEventListener('vpu-fileupload-finished', this.addLogEntry.bind(this)); + element.addEventListener('vpu-fileupload-finished', this.onUploadFinished.bind(this)); }); }); } /** - * TODO: Implement real box - * * @param ev */ - addLogEntry(ev) { - const ul = this.shadowRoot.querySelector('#log'); - const li = document.createElement('li'); - li.innerHTML = `<b>${ev.detail.status}</b> <tt>${ev.detail.filename}</tt>`; - console.log(ev.detail); + onUploadFinished(ev) { + // TODO: check ev.detail.status to show if an error occurred + // TODO: on ev.detail.status == 503 we need to upload the file again if (ev.detail.json) { + // this doesn't seem to cause an update() execution this.files.push(ev.detail.json); + // this causes the correct update() execution + this.filesCount++; } - - ul.appendChild(li); } update(changedProperties) { @@ -60,6 +62,8 @@ class SignaturePdfUpload extends VPUSignatureLitElement { if (propName === "lang") { i18n.changeLanguage(this.lang); } + + console.log(propName, oldValue); }); super.update(changedProperties); @@ -74,64 +78,98 @@ class SignaturePdfUpload extends VPUSignatureLitElement { return css` ${commonStyles.getThemeCSS()} ${commonStyles.getGeneralCSS()} + ${commonStyles.getButtonCSS()} ${commonStyles.getNotificationCSS()} .hidden { display: none; } - #location-identifier-block { display: none; } + #files-download .file { + margin: 10px; + } - #location-identifier-block input { - width: 100%; - border-radius: var(--vpu-border-radius); + #files-download .file button { + margin-right: 5px; } `; } /** - * Download signed pdf files + * Download signed pdf files as zip */ zipDownloadClickHandler() { + // see: https://stuk.github.io/jszip/ let zip = new JSZip(); const that = this; // add all signed pdf files this.files.forEach((file) => { console.log(file); - zip.file(file.fileName, file.file.replace("data:application/pdf;base64,", ""), {base64: true}); + // TODO: check for duplicate file names + zip.file(file.fileName, utils.getPDFFileBase64Content(file), {base64: true}); }); zip.generateAsync({type:"blob"}) .then(function(content) { - console.log(zip); - // save with FileSaver.js + // see: https://github.com/eligrey/FileSaver.js saveAs(content, "signed-documents.zip"); that._("#zip-download-button").stop(); + that.files = []; + that.filesCount = 0; }); } + /** + * Download one signed pdf file + * + * @param file + */ + fileDownloadClickHandler(file) { + const arr = utils.convertDataURIToBinary(file.file); + const blob = new Blob([arr], { type: utils.getDataURIContentType(file.file) }); + + // see: https://github.com/eligrey/FileSaver.js + saveAs(blob, file.fileName); + } + + getSignedFilesHtml() { + return this.files.map(file => html` + <div class="file"> + <button class="button is-small" + title="${i18n.t('pdf-upload.download-file-button-title')}" + @click="${() => {this.fileDownloadClickHandler(file);}}"><vpu-icon name="download"></vpu-icon></button> + <strong>${file.fileName}</strong> (${humanFileSize(file.fileSize)}) + </div> + `); + } + render() { const suggestionsCSS = commonUtils.getAssetURL(suggestionsCSSPath); return html` <link rel="stylesheet" href="${suggestionsCSS}"> - <form class="${classMap({hidden: !this.isLoggedIn() || !this.hasSignaturePermissions()})}"> + <div class="${classMap({hidden: !this.isLoggedIn() || !this.hasSignaturePermissions()})}"> <div class="field"> - <label class="label">${i18n.t('pdf-upload.field-label')}</label> + <label class="label">${i18n.t('pdf-upload.upload-field-label')}</label> <div class="control"> <vpu-fileupload lang="${this.lang}" url="${this.entryPointUrl}/pdf_official_signing_actions" accept="application/pdf" text="${i18n.t('pdf-upload.upload-area-text')}" button-label="${i18n.t('pdf-upload.upload-button-label')}"></vpu-fileupload> </div> </div> - </form> - <div id="log"></div> - <div class="field"> - <div class="control"> - <vpu-button id="zip-download-button" value="Download ZIP" @click="${this.zipDownloadClickHandler}" type="is-primary"></vpu-button> + <div id="files-download" class="field ${classMap({hidden: this.filesCount === 0})}"> + <label class="label">${i18n.t('pdf-upload.signed-files-label')}</label> + <div class="control"> + ${this.getSignedFilesHtml()} + </div> + </div> + <div class="field ${classMap({hidden: this.filesCount === 0})}"> + <div class="control"> + <vpu-button id="zip-download-button" value="${i18n.t('pdf-upload.download-zip-button')}" @click="${this.zipDownloadClickHandler}" type="is-primary"></vpu-button> + </div> </div> </div> <div class="notification is-warning ${classMap({hidden: this.isLoggedIn()})}"> diff --git a/vendor/common b/vendor/common index 2c74b7aabd5c51b650004f0ba874809709faa8fe..564b17bf0b128d1ba7b4bbddf9b3c1123a2450c9 160000 --- a/vendor/common +++ b/vendor/common @@ -1 +1 @@ -Subproject commit 2c74b7aabd5c51b650004f0ba874809709faa8fe +Subproject commit 564b17bf0b128d1ba7b4bbddf9b3c1123a2450c9 diff --git a/vendor/file-upload b/vendor/file-upload index ca511205f4ba5f399a3fcf9401a7b3749b8f72cd..b02716f49afd999a90c9d194d706a04fab861cc1 160000 --- a/vendor/file-upload +++ b/vendor/file-upload @@ -1 +1 @@ -Subproject commit ca511205f4ba5f399a3fcf9401a7b3749b8f72cd +Subproject commit b02716f49afd999a90c9d194d706a04fab861cc1