UNPKG

@digital-blueprint/esign-app

Version:

[GitHub Repository](https://github.com/digital-blueprint/esign-app) | [npmjs package](https://www.npmjs.com/package/@digital-blueprint/esign-app) | [Unpkg CDN](https://unpkg.com/browse/@digital-blueprint/esign-app/) | [Esign Bundle](https://gitlab.tugraz.

936 lines (864 loc) 47.2 kB
import {createInstance} from './i18n.js'; import {humanFileSize} from '@dbp-toolkit/common/i18next.js'; import {css, html} from 'lit'; import {ScopedElementsMixin} from '@dbp-toolkit/common'; import DBPSignatureLitElement from './dbp-signature-lit-element'; import {PdfPreview} from './dbp-pdf-preview'; import * as commonUtils from '@dbp-toolkit/common/utils'; import * as utils from './utils'; import {Button, Icon, IconButton, LoadingButton, MiniSpinner, combineURLs, Modal} from '@dbp-toolkit/common'; import * as commonStyles from '@dbp-toolkit/common/styles'; import {TooltipElement} from '@dbp-toolkit/tooltip'; import {classMap} from 'lit/directives/class-map.js'; import {FileSource} from '@dbp-toolkit/file-handling'; import {FileSink} from '@dbp-toolkit/file-handling'; import {name as pkgName} from './../package.json'; import {send as notify} from '@dbp-toolkit/common/notification'; import metadata from './dbp-qualified-signature-pdf-upload.metadata.json'; import {Activity} from './activity.js'; import {PdfAnnotationView} from './dbp-pdf-annotation-view'; import {ExternalSignIFrame} from './ext-sign-iframe.js'; import * as SignatureStyles from './styles'; import {TabulatorTable} from '@dbp-toolkit/tabulator-table'; class QualifiedSignaturePdfUpload extends ScopedElementsMixin(DBPSignatureLitElement) { constructor() { super(); this._i18n = createInstance(); this.lang = this._i18n.language; this.entryPointUrl = ''; this.nextcloudWebAppPasswordURL = ''; this.nextcloudWebDavURL = ''; this.nextcloudName = ''; this.nextcloudFileURL = ''; this.nextcloudAuthInfo = ''; this.activity = new Activity(metadata); this.fileHandlingEnabledTargets = 'local'; // Bind all event handlers this._onReceiveBeforeUnload = this.onReceiveBeforeUnload.bind(this); this._setQueuedFilesTabulatorTable = this.setQueuedFilesTabulatorTable.bind(this); this._tabulatorTableHandleCollapse = this.tabulatorTableHandleCollapse.bind(this); this._handleTableSelection = this.handleTableSelection.bind(this); this._tabulatorTableHandleRenderCompleted = this.tabulatorTableHandleRenderCompleted.bind(this); this._handleModalClosed = this.handleModalClosed.bind(this); this._handlePdfModalClosing = this.handlePdfModalClosing.bind(this); this._handleAnnotationModalClosing = this.handleAnnotationModalClosing.bind(this); } static get scopedElements() { return { 'dbp-icon': Icon, 'dbp-file-source': FileSource, 'dbp-file-sink': FileSink, 'dbp-pdf-preview': PdfPreview, 'dbp-mini-spinner': MiniSpinner, 'dbp-button': Button, 'dbp-icon-button': IconButton, 'dbp-loading-button': LoadingButton, 'dbp-pdf-annotation-view': PdfAnnotationView, 'external-sign-iframe': ExternalSignIFrame, 'dbp-tabulator-table': TabulatorTable, 'dbp-tooltip': TooltipElement, 'dbp-modal': Modal, }; } static get properties() { return { ...super.properties, externalAuthInProgress: {type: Boolean, attribute: false}, }; } connectedCallback() { super.connectedCallback(); // needs to be called in a function to get the variable scope of "this" setInterval(() => { this.handleQueuedFiles(); }, 1000); // Add event listeners using bound methods window.addEventListener('beforeunload', this._onReceiveBeforeUnload); window.addEventListener('dbp-pdf-preview-accept', this._setQueuedFilesTabulatorTable); window.addEventListener('dbp-pdf-annotations-save', this._setQueuedFilesTabulatorTable); window.addEventListener('dbp-pdf-annotations-cancel', this._setQueuedFilesTabulatorTable); window.addEventListener('dbp-tabulator-table-collapsible-event', this._tabulatorTableHandleCollapse); window.addEventListener('dbp-tabulator-table-row-selection-changed-event', this._handleTableSelection); window.addEventListener('dbp-tabulator-table-render-complete-event', this._tabulatorTableHandleRenderCompleted); window.addEventListener('dbp-modal-closed', this._handleModalClosed); window.addEventListener('dbp-pdf-preview-accept', this._handlePdfModalClosing); window.addEventListener('dbp-pdf-preview-cancel', this._handlePdfModalClosing); window.addEventListener('dbp-pdf-annotations-cancel', this._handleAnnotationModalClosing); window.addEventListener('dbp-pdf-annotations-save', this._handleAnnotationModalClosing); } disconnectedCallback() { // Remove event listeners using bound methods window.removeEventListener('beforeunload', this._onReceiveBeforeUnload); window.removeEventListener('dbp-pdf-preview-accept', this._setQueuedFilesTabulatorTable); window.removeEventListener('dbp-pdf-annotations-save', this._setQueuedFilesTabulatorTable); window.removeEventListener('dbp-pdf-annotations-cancel', this._setQueuedFilesTabulatorTable); window.removeEventListener('dbp-tabulator-table-collapsible-event', this._tabulatorTableHandleCollapse); window.removeEventListener('dbp-tabulator-table-render-complete-event', this._tabulatorTableHandleRenderCompleted); window.removeEventListener('dbp-tabulator-table-row-selection-changed-event', this._handleTableSelection); window.removeEventListener('dbp-modal-closed', this._handleModalClosed); window.removeEventListener('dbp-pdf-preview-accept', this._handlePdfModalClosing); window.removeEventListener('dbp-pdf-preview-cancel', this._handlePdfModalClosing); window.removeEventListener('dbp-pdf-annotations-cancel', this._handleAnnotationModalClosing); window.removeEventListener('dbp-pdf-annotations-save', this._handleAnnotationModalClosing); this.stopPositionButtonObserver(); super.disconnectedCallback(); } firstUpdated(changedProperties) { super.firstUpdated(changedProperties); this.tableQueuedFilesTable = /** @type {TabulatorTable} */ (this._('#table-queued-files')); this.tableSignedFilesTable = /** @type {TabulatorTable} */ (this._('#table-signed-files')); this.tableFailedFilesTable = /** @type {TabulatorTable} */ (this._('#table-failed-files')); } async queueFile(file) { let id = await super.queueFile(file); await this._updateNeedsPlacementStatus(id); this.setQueuedFilesTabulatorTable(); this.requestUpdate(); return id; } /** * Processes queued files */ async handleQueuedFiles() { const i18n = this._i18n; this.endSigningProcessIfQueueEmpty(); if (this.queuedFilesCount === 0) { // reset signingProcessEnabled button this.signingProcessEnabled = false; return; } if ( !this.signingProcessEnabled || this.externalAuthInProgress || this.uploadInProgress || this.addAnnotationInProgress ) { return; } this.signaturePlacementInProgress = false; // Validate that all PDFs with a signature have manual placement let errorInPositioning = false; for (const key of Object.keys(this.queuedFiles)) { if (errorInPositioning === true) continue; const isManual = this.queuedFilesPlacementModes[key] === 'manual'; if (this.queuedFilesNeedsPlacement.get(key) && !isManual && (this.selectedFiles.length === 0 || this.fileIsSelectedFile(key))) { // Some have a signature but are not "manual", stop everything notify({ summary: i18n.t('error-manual-positioning-missing-title'), body: i18n.t('error-manual-positioning-missing'), type: 'danger', timeout: 5 }); errorInPositioning = true; } } if (errorInPositioning) { this.signingProcessEnabled = false; this.signingProcessActive = false; await this.stopSigningProcess(); return; } let key = null; if (this.selectedFiles.length > 0) { // If we have selected files in the table use the selected file const selectedFile = this.selectedFiles.shift(); key = Object.keys(this.queuedFiles).find( (index) => { return this.queuedFiles[index].file.name.trim() === selectedFile.filename.trim(); } ); this.selectedFilesProcessing = true; } else { // Process all queued files key = Object.keys(this.queuedFiles)[0]; } this._('#external-auth').open(); const entry = this.takeFileFromQueue(key); const file = entry.file; this.currentFile = file; this.currentKey = key; // set placement mode and parameters to restore them when canceled this.currentFilePlacementMode = this.queuedFilesPlacementModes[key]; this.currentFileSignaturePlacement = this.queuedFilesSignaturePlacements[key]; this.uploadInProgress = true; let params = {}; // prepare parameters to tell PDF-AS where and how the signature should be placed if (this.queuedFilesPlacementModes[key] === 'manual') { const data = this.queuedFilesSignaturePlacements[key]; if (data !== undefined) { params = utils.fabricjs2pdfasPosition(data); } } params['profile'] = 'default'; this.uploadStatusText = i18n.t('qualified-pdf-upload.upload-status-file-text', { fileName: file.name, fileSize: humanFileSize(file.size, false), }); const annotationsEnabled = this.isAnnotationsEnabledForKey(key); const annotations = this.takeAnnotationsFromQueue(key); await this.uploadFile(file, params, annotationsEnabled ? annotations : []); this.uploadInProgress = false; // Stop processing if no more selected file exists if (this.selectedFilesProcessing && this.selectedFiles.length === 0) { this.signingProcessEnabled = false; this.signingProcessActive = false; await this.stopSigningProcess(); } } /** * Decides if the "beforeunload" event needs to be canceled * * @param event */ onReceiveBeforeUnload(event) { const i18n = this._i18n; // we don't need to stop if there are no signed files if (this.signedFilesCount === 0) { return; } // we need to handle custom events ourselves if (!event.isTrusted) { // note that this only works with custom event since calls of "confirm" are ignored // in the non-custom event, see https://developer.mozilla.org/en-US/docs/Web/API/Window/beforeunload_event const result = confirm(i18n.t('qualified-pdf-upload.confirm-page-leave')); // don't stop the page leave if the user wants to leave if (result) { return; } } // Cancel the event as stated by the standard event.preventDefault(); // Chrome requires returnValue to be set event.returnValue = ''; } /** * Parse error message for user friendly output * * @param error */ parseError(error) { const i18n = this._i18n; let errorParsed = error; // Common Error Messages fpr pdf-as: https://www.buergerkarte.at/konzept/securitylayer/spezifikation/20140114/errorcodes/errorcodes.html // SecurityLayer Error: [6000] Unklassifizierter Abbruch durch den Bürger. if (error.includes('SecurityLayer Error: [6001]')) { errorParsed = i18n.t('error-cancel-message'); } // SecurityLayer Error: [6001] Abbruch durch den Bürger über die Benutzerschnittstelle. else if (error.includes('SecurityLayer Error: [6000]')) { errorParsed = i18n.t('error-cancel-message'); } // SecurityLayer Error: [6002] Abbruch auf Grund mangelnder Rechte zur Befehlsausführung. else if (error.includes('SecurityLayer Error: [6002]')) { errorParsed = i18n.t('error-rights-message'); } return errorParsed; } _onIFrameDone(event) { const sessionId = event.detail.id; // check if sessionId is valid if (typeof sessionId !== 'string' || sessionId.length < 15) { return; } const that = this; // get correct file name const fileName = this.currentFileName === '' ? 'mydoc.pdf' : this.currentFileName; let apiUrlBase = combineURLs(this.entryPointUrl, '/esign/qualifiedly-signed-documents'); const apiUrl = apiUrlBase + '/' + encodeURIComponent(sessionId) + '?fileName=' + encodeURIComponent(fileName); fetch(apiUrl, { headers: { 'Content-Type': 'application/ld+json', Authorization: 'Bearer ' + that.auth.token, }, }) .then((result) => { // hide iframe that.externalAuthInProgress = false; this._('#iframe').reset(); this.endSigningProcessIfQueueEmpty(); if (!result.ok) throw result; return result.json(); }) .then((document) => { // this doesn't seem to trigger an update() execution that.signedFiles.push(document); // this triggers the correct update() execution that.signedFilesCount++; that.signedFilesCountToReport++; this.sendSetPropertyEvent('analytics-event', { category: 'QualifiedlySigning', action: 'DocumentSigned', name: document.contentSize, }); }) .catch((error) => { let file = this.currentFile; // let's override the json to inject an error message file.json = {'hydra:description': 'Download failed!'}; this.addToErrorFiles(file); }) .finally(() => { // Close the external auth modal that._('#external-auth').close(); this.sendReportNotification(); }); } _onIFrameError(event) { let error = event.detail.message; let file = this.currentFile; file.json = {'hydra:description': this.parseError(error)}; this.addToErrorFiles(file); this._('#iframe').reset(); this.externalAuthInProgress = false; this.endSigningProcessIfQueueEmpty(); // Close the external auth modal this._('#external-auth').close(); this.sendReportNotification(); } addToErrorFiles(file) { this.endSigningProcessIfQueueEmpty(); // this doesn't seem to trigger an update() execution this.errorFiles[Math.floor(Math.random() * 1000000)] = file; // this triggers the correct update() execution this.errorFilesCount++; this.errorFilesCountToReport++; this.sendSetPropertyEvent('analytics-event', { category: 'QualifiedlySigning', action: 'SigningFailed', name: file.json['hydra:description'], }); } /** * @param data */ onFileUploadFinished(data) { if (data.status !== 201) { this.addToErrorFiles(data); this.sendReportNotification(); this._('#external-auth').close(); } else if (data.json['@type'] === 'http://schema.org/EntryPoint') { // after the "real" upload we immediately start with the 2FA process // show the iframe and lock processing this.externalAuthInProgress = true; const entryPoint = data.json; this.currentFileName = entryPoint.name; // we need the full file to upload it again in case the download of the signed file fails this.currentFile = data; // we want to load the redirect url in the iframe let iframe = this._('#iframe'); iframe.setUrl(entryPoint.url); } } update(changedProperties) { changedProperties.forEach((oldValue, propName) => { switch (propName) { case 'lang': this._i18n.changeLanguage(this.lang); this.setQueuedFilesTabulatorTable(); break; case 'entryPointUrl': if (this.entryPointUrl) { this.fileSourceUrl = combineURLs(this.entryPointUrl, '/esign/qualified-signing-requests'); } break; case 'queuedFilesCount': this.setQueuedFilesTabulatorTable(); break; case 'signedFilesCount': this.setSignedFilesTabulatorTable(); break; case 'errorFilesCount': this.setFailedFilesTabulatorTable(); break; } }); super.update(changedProperties); } clearQueuedFiles() { // Delete selected files from the queues if (this.selectedFiles.length) { let filesToRemove = []; for (const selectedFile of this.selectedFiles) { this.queuedFilesSignaturePlacements.forEach((placement, index) => { if (index == selectedFile.key) { delete this.queuedFilesSignaturePlacements[index]; } }); this.queuedFilesPlacementModes.forEach((placementMode, index) => { if (index == selectedFile.key) { delete this.queuedFilesPlacementModes[index]; } }); this.queuedFilesNeedsPlacement.delete(selectedFile.key); filesToRemove.push(selectedFile.key); } super.clearQueuedFiles(filesToRemove); } } static get styles() { // language=css return css` ${commonStyles.getThemeCSS()} ${commonStyles.getGeneralCSS(false)} ${commonStyles.getButtonCSS()} ${commonStyles.getNotificationCSS()} ${SignatureStyles.getSignatureCss()} #external-auth #iframe { margin-top: 0.5em; } #external-auth .button.is-cancel { color: var(--dbp-danger); } #iframe { width: 100%; height: 350px; /* keeps the A-Trust webpage aligned left */ max-width: 575px; } `; } hasSignaturePermissions() { return this._hasSignaturePermissions('ROLE_SCOPE_QUALIFIED-SIGNATURE'); } async stopSigningProcess() { if (!this.externalAuthInProgress) { return; } this._('#iframe').reset(); this.signingProcessEnabled = false; this.externalAuthInProgress = false; this.signingProcessActive = false; if (this.currentFile.file !== undefined) { // Re-queue file with the same key const key = await this.reQueueFile(this.currentFile.file); // Set placement mode and parameters, so they are restore when canceled this.queuedFilesPlacementModes[key] = this.currentFilePlacementMode; this.queuedFilesSignaturePlacements[key] = this.currentFileSignaturePlacement; this.setQueuedFilesTabulatorTable(); } } _onLoginClicked(e) { this.sendSetPropertyEvent('requested-login-status', "logged-in"); e.preventDefault(); } render() { const placeholderUrl = commonUtils.getAssetURL( pkgName, 'qualified-signature-placeholder.png' ); const i18n = this._i18n; return html` <div class="${classMap({ hidden: !this.isLoggedIn() || !this.hasSignaturePermissions() || this.isLoading(), })}"> <div class="field"> <h2>${this.activity.getName(this.lang)}</h2> <p class="subheadline">${this.activity.getDescription(this.lang)}</p> <div class="control"> <p>${i18n.t('qualified-pdf-upload.upload-text')}</p> <button @click="${() => { this._('#file-source').setAttribute('dialog-open', ''); }}" ?disabled="${this.signingProcessActive}" class="button is-primary" id="upload-pdf-button"> ${i18n.t('qualified-pdf-upload.upload-button-label')} </button> <dbp-file-source id="file-source" subscribe="nextcloud-store-session:nextcloud-store-session" context="${i18n.t('qualified-pdf-upload.file-picker-context')}" allowed-mime-types="application/pdf" enabled-targets="${this.fileHandlingEnabledTargets}" nextcloud-auth-url="${this.nextcloudWebAppPasswordURL}" nextcloud-web-dav-url="${this.nextcloudWebDavURL}" nextcloud-name="${this.nextcloudName}" nextcloud-auth-info="${this.nextcloudAuthInfo}" nextcloud-file-url="${this.nextcloudFileURL}" decompress-zip max-file-size="32000" lang="${this.lang}" ?disabled="${this.signingProcessActive}" text="${i18n.t('qualified-pdf-upload.upload-area-text')}" button-label="${i18n.t('qualified-pdf-upload.upload-button-label')}" @dbp-file-source-file-selected="${this.onFileSelected}" @dbp-file-source-switched="${this.onFileSourceSwitch}"></dbp-file-source> </div> </div> <div id="grid-container"> <div class="table-container"> <div class="files-block queued-files field ${classMap({ hidden: !this.queueBlockEnabled, })}"> <!-- Queued files headline and queueing spinner --> <h3 class="section-title"> ${i18n.t('qualified-pdf-upload.queued-files-label')} </h3> <div class="control field tabulator-actions"> <div class="table-actions"> <dbp-loading-button id="expand-all-btn-queued-files" class="${classMap({ hidden: this.queuedFilesTableExpanded })}" ?disabled="${this.queuedFilesCount === 0 || this.queuedFilesTableCollapsible === false}" value="${i18n.t('qualified-pdf-upload.expand-all')}" @click="${() => { this.tableQueuedFilesTable.expandAll(); this.queuedFilesTableExpanded = true; }}" title="${i18n.t('qualified-pdf-upload.expand-all')}" >${i18n.t('qualified-pdf-upload.expand-all')}</dbp-loading-button> <dbp-loading-button id="collapse-all-btn-queued-files" class="${classMap({ hidden: !this.queuedFilesTableExpanded })}" ?disabled="${this.queuedFilesCount === 0 || this.queuedFilesTableCollapsible === false}" value="${i18n.t('qualified-pdf-upload.collapse-all')}" @click="${() => { this.tableQueuedFilesTable.collapseAll(); this.queuedFilesTableExpanded = false; }}" title="${i18n.t('qualified-pdf-upload.collapse-all')}" >${i18n.t('qualified-pdf-upload.collapse-all')}</dbp-loading-button> <dbp-loading-button id="select-all-btn-queued-files" class="${classMap({ hidden: this.queuedFilesTableAllSelected })}" ?disabled="${this.queuedFilesCount === 0}" value="${i18n.t('qualified-pdf-upload.select-all')}" @click="${() => { this.queuedFilesTableAllSelected = true; this.tableQueuedFilesTable.selectAllRows(); }}" title="${i18n.t('qualified-pdf-upload.select-all')}" >${i18n.t('qualified-pdf-upload.select-all')}</dbp-loading-button> <dbp-loading-button id="deselect-all-btn-queued-files" class="${classMap({ hidden: !this.queuedFilesTableAllSelected })}" ?disabled="${this.queuedFilesCount === 0}" value="${i18n.t('qualified-pdf-upload.deselect-all')}" @click="${() => { this.queuedFilesTableAllSelected = false; this.tableQueuedFilesTable.deselectAllRows(); }}" title="${i18n.t('qualified-pdf-upload.deselect-all')}" >${i18n.t('qualified-pdf-upload.deselect-all')}</dbp-loading-button> </div> <div class="sign-actions"> <!-- Buttons to start/stop signing process and clear queue --> <button id="clear-queue-button-queued-files" @click="${this.clearQueuedFiles}" ?disabled="${this.queuedFilesCount === 0 || this.signingProcessActive || this.selectedFiles.length < 1}" class="button"> ${i18n.t('qualified-pdf-upload.clear-all')} </button> <button id="start-signing-button" @click="${() => { this.signingProcessEnabled = true; this.signingProcessActive = true; this.initialQueuedFilesCount = this.queuedFilesCount; }}" ?disabled="${this.queuedFilesCount === 0}" class="button is-primary"> ${i18n.t('qualified-pdf-upload.start-signing-process-button')} </button> </div> </div> <!-- List of queued files --> <div class="control file-list"> <dbp-tabulator-table id="table-queued-files" identifier="table-queued-files" class="table-queued-files" lang="${this.lang}" select-rows-enabled .options="${this.queuedFilesOptions}"> </dbp-tabulator-table> </div> <!-- Text "queue empty" --> <div class="empty-queue control ${classMap({ hidden: this.queuedFilesCount !== 0 })}"> ${i18n.t('qualified-pdf-upload.queued-files-empty1')} <br /> ${i18n.t('qualified-pdf-upload.queued-files-empty2')} </div> </div> <!-- List of signed PDFs --> <div class="files-block signed-files field ${classMap({ hidden: this.signedFilesCount === 0 })}"> <h3 class="section-title ">${i18n.t('qualified-pdf-upload.signed-files-label')}</h3> <!-- Button to download all signed PDFs --> <div class="field ${classMap({hidden: this.signedFilesCount === 0})}"> <div class="control tabulator-actions"> <div class="table-actions"> <dbp-loading-button id="expand-all-btn-signed-files" class="${classMap({ hidden: this.signedFilesTableExpanded })}" ?disabled="${this.signedFilesCount === 0 || this.signedFilesTableCollapsible === false}" value="${i18n.t('qualified-pdf-upload.expand-all')}" @click="${() => { this.tableSignedFilesTable.expandAll(); this.signedFilesTableExpanded = true; }}" title="${i18n.t('qualified-pdf-upload.expand-all')}" >${i18n.t('qualified-pdf-upload.expand-all')}</dbp-loading-button> <dbp-loading-button id="collapse-all-btn-signed-files" class="${classMap({ hidden: !this.signedFilesTableExpanded })}" ?disabled="${this.signedFilesCount === 0 || this.signedFilesTableCollapsible === false}" value="${i18n.t('qualified-pdf-upload.collapse-all')}" @click="${() => { this.tableSignedFilesTable.collapseAll(); this.signedFilesTableExpanded = false; }}" title="${i18n.t('qualified-pdf-upload.collapse-all')}" >${i18n.t('qualified-pdf-upload.collapse-all')}</dbp-loading-button> </div> <div class="signed-actions"> <button id="clear-signed-files-btn" class="clear-signed-files button" @click="${this.clearSignedFiles}" class="button"> ${i18n.t('qualified-pdf-upload.clear-all')} </button> <dbp-loading-button id="zip-download-button" value="${i18n.t( 'qualified-pdf-upload.download-zip-button' )}" title="${i18n.t( 'qualified-pdf-upload.download-zip-button-tooltip' )}" class="zip-download-button" @click="${() => { this.zipDownloadClickHandler(); let id = 0; for (const file of this.signedFiles) { this.tableSignedFilesTable.tabulatorTable.updateData([ { index: id, fileName: `<span id="file-download-${id}">${file.name}</span>` } ]); id++; } }}" type="is-primary"></dbp-loading-button> </div> </div> </div> <dbp-tabulator-table id="table-signed-files" identifier="table-signed-files" class="table-signed-files" lang="${this.lang}" .options="${this.signedFilesOptions}"></dbp-tabulator-table> </div> <!-- List of errored files --> <div class="files-block error-files field ${classMap({ hidden: this.errorFilesCount === 0 })}"> <h3 class="section-title">${i18n.t('qualified-pdf-upload.error-files-label')}</h3> <!-- Button to upload errored files again --> <div class="field ${classMap({hidden: this.errorFilesCount === 0})}"> <div class="control tabulator-actions"> <div class="table-actions"> <dbp-loading-button id="expand-all-btn-failed-files" class="${classMap({ hidden: this.failedFilesTableExpanded })}" ?disabled="${this.errorFilesCount === 0 || this.failedFilesTableCollapsible === false}" value="${i18n.t('qualified-pdf-upload.expand-all')}" @click="${() => { this.tableFailedFilesTable.expandAll(); this.failedFilesTableExpanded = true; }}" title="${i18n.t('qualified-pdf-upload.expand-all')}" >${i18n.t('qualified-pdf-upload.expand-all')}</dbp-loading-button> <dbp-loading-button id="collapse-all-btn-failed-files" class="${classMap({ hidden: !this.failedFilesTableExpanded })}" ?disabled="${this.errorFilesCount === 0 || this.failedFilesTableCollapsible === false}" value="${i18n.t('qualified-pdf-upload.collapse-all')}" @click="${() => { this.tableFailedFilesTable.collapseAll(); this.failedFilesTableExpanded = false; }}" title="${i18n.t('qualified-pdf-upload.collapse-all')}" >${i18n.t('qualified-pdf-upload.collapse-all')}</dbp-loading-button> </div> <div class="failed-actions"> <button id="clear-error-files-btn" @click="${this.clearErrorFiles}" class="clear-signed-files button"> ${i18n.t('qualified-pdf-upload.clear-all')} </button> <dbp-loading-button id="re-upload-all-button" ?disabled="${this.uploadInProgress}" value="${i18n.t( 'qualified-pdf-upload.re-upload-all-button' )}" title="${i18n.t( 'qualified-pdf-upload.re-upload-all-button-title' )}" class="is-right" @click="${this.reUploadAllClickHandler}" type="is-primary"></dbp-loading-button> </div> </div> </div> <dbp-tabulator-table id="table-failed-files" identifier="table-failed-files" class="table-failed-files" lang="${this.lang}" .options="${this.failedFilesOptions}"></dbp-tabulator-table> </div> </div> <div class="modal-container"> <!-- PDF preview --> <dbp-modal id="pdf-preview" modal-id="pdf-preview-modal" class="modal--pdf-preview" title="${this.withSigBlock ? i18n.t('official-pdf-upload.signature-placement-label') : i18n.t('official-pdf-upload.preview-label')}"> <div slot="header" class="header"> <div class="filename"> <strong>${this.currentFile.name}</strong> (${humanFileSize( this.currentFile !== undefined ? this.currentFile.size : 0 )}) </div> </div> <div slot="content"> <dbp-pdf-preview lang="${this.lang}" allow-signature-rotation signature-placeholder-image-src="${placeholderUrl}" signature-width="80" signature-height="29" @dbp-pdf-preview-accept="${this.storePDFData}" @dbp-pdf-preview-cancel="${this.hidePDF}"></dbp-pdf-preview> </div> </dbp-modal> <!-- Annotation view --> <dbp-modal id="annotation-view" modal-id="annotation-view-modal" class="modal--annotation-view ${classMap({ hidden: !this.isAnnotationViewVisible, })}" title="${i18n.t('qualified-pdf-upload.annotation-view-label')}"> <div slot="header" class="header"> <div class="modal-notification"> <dbp-notification id="dbp-modal-notification-annotation" inline lang="${this.lang}"></dbp-notification> </div> <div class="filename"> <strong>${this.currentFile.file !== undefined ? this.currentFile.file.name : ''}</strong> (${humanFileSize( this.currentFile.file !== undefined ? this.currentFile.file.size : 0 )}) </div> </div> <div slot="content"> <dbp-pdf-annotation-view lang="${this.lang}" @dbp-pdf-annotations-save="${this.processAnnotationEvent}" @dbp-pdf-annotations-cancel="${this.processAnnotationCancelEvent}"> </dbp-pdf-annotation-view> </div> </dbp-modal> <!-- File upload progress --> <div id="upload-progress" class="field notification is-info ${classMap({ hidden: !this.uploadInProgress, })}"> <dbp-mini-spinner></dbp-mini-spinner> <strong>${this.uploadStatusFileName}</strong> ${this.uploadStatusText} </div> <!-- External auth --> <dbp-modal id="external-auth" modal-id="external-auth-modal" class="modal--external-auth ${classMap({ hidden: !this.externalAuthInProgress, })}" title="${i18n.t('qualified-pdf-upload.current-signing-process-label')}"> <div slot="header" class="header"> <div class="filename"> <strong>${this.currentFileName}</strong> (${humanFileSize( this.currentFile.file !== undefined ? this.currentFile.file.size : 0 )}) </div> </div> <div slot="content"> <external-sign-iframe id="iframe" @signature-error="${this._onIFrameError}" @signature-done="${this._onIFrameDone}"> </external-sign-iframe> </div> </dbp-modal> </div> </div> </div> <div class="notification is-warning ${classMap({ hidden: this.isLoggedIn() || this.isLoading(), })}"> ${i18n.t('error-login-message')} <a href="#" @click="${this._onLoginClicked}">${i18n.t('error-login-link')}</a> </div> <div class="notification is-danger ${classMap({ hidden: this.hasSignaturePermissions() || !this.isLoggedIn() || this.isLoading(), })}"> ${i18n.t('error-permission-message')} </div> <div class="${classMap({hidden: !this.isLoading()})}"> <dbp-mini-spinner></dbp-mini-spinner> </div> <dbp-file-sink id="file-sink" context="${i18n.t('qualified-pdf-upload.save-field-label', { count: this.signedFilesToDownload, })}" filename="signed-documents.zip" subscribe="initial-file-handling-state:initial-file-handling-state,nextcloud-store-session:nextcloud-store-session" enabled-targets="${this.fileHandlingEnabledTargets}" nextcloud-auth-url="${this.nextcloudWebAppPasswordURL}" nextcloud-web-dav-url="${this.nextcloudWebDavURL}" nextcloud-name="${this.nextcloudName}" nextcloud-file-url="${this.nextcloudFileURL}" lang="${this.lang}"></dbp-file-sink> `; } } commonUtils.defineCustomElement('dbp-qualified-signature-pdf-upload', QualifiedSignaturePdfUpload);