UNPKG

@digital-blueprint/formalize-app

Version:

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

480 lines (436 loc) • 18.4 kB
// @ts-nocheck import {css, html} from 'lit'; import {classMap} from 'lit/directives/class-map.js'; import {ScopedElementsMixin, Button, Icon, sendNotification} from '@dbp-toolkit/common'; import {FileSink} from '@dbp-toolkit/file-handling'; import {PdfViewer} from '@dbp-toolkit/pdf-viewer'; import DBPLitElement from '@dbp-toolkit/common/dbp-lit-element'; import {Modal} from '@dbp-toolkit/common/src/modal.js'; import {createInstance} from './i18n.js'; import {MANAGE_FORMS_COMPONENT_STYLES} from './manage-forms-component-styles.js'; export class ManageSubmissionModal extends ScopedElementsMixin(DBPLitElement) { constructor() { super(); this._i18n = createInstance(); this.lang = this._i18n.language; this.state = ''; this.hiddenColumns = false; this.isPrevEnabled = false; this.isNextEnabled = false; this.currentBeautyId = 0; this.totalItems = 0; this.auth = {}; this.isPdfPreviewOpen = false; this.contentItems = []; this.boundHandleKeydown = this.handleKeydown.bind(this); this.boundHandlePdfModalClosed = this.handlePdfModalClosed.bind(this); this.boundHandleModalClosed = this.handleModalClosed.bind(this); } static get scopedElements() { return { 'dbp-button': Button, 'dbp-icon': Icon, 'dbp-file-sink': FileSink, 'dbp-modal': Modal, 'dbp-pdf-viewer': PdfViewer, }; } static get properties() { return { ...super.properties, lang: {type: String}, state: {type: String}, hiddenColumns: {type: Boolean, attribute: false}, isPrevEnabled: {type: Boolean, attribute: false}, isNextEnabled: {type: Boolean, attribute: false}, currentBeautyId: {type: Number, attribute: false}, totalItems: {type: Number, attribute: false}, auth: {type: Object, attribute: false}, isPdfPreviewOpen: {type: Boolean, attribute: false}, contentItems: {type: Array, attribute: false}, }; } update(changedProperties) { changedProperties.forEach((oldValue, propName) => { if (propName === 'lang') { void this._i18n.changeLanguage(this.lang); } }); super.update(changedProperties); } static get styles() { return [ MANAGE_FORMS_COMPONENT_STYLES, css` .submitted-files .file-block + .file-block { margin-top: 0.75rem; } .submitted-files .file-action-buttons { margin-top: 0.5rem; } dbp-modal.detailed-submission-modal { --dbp-modal-width: 65vw; --dbp-modal-max-width: 1280px; --dbp-modal-min-width: 768px; --dbp-modal-min-height: 0; --dbp-modal-content-overflow-y: hidden; } .detailed-submission-footer .modal-footer-btn { padding: 16px 0 0; } @media only screen and (max-width: 900px) { dbp-modal.detailed-submission-modal { --dbp-modal-width: calc(100vw - 40px); --dbp-modal-max-width: calc(100vw - 40px); --dbp-modal-min-width: 0; } .detailed-submission-footer .modal-footer-btn { padding: 16px 12px 6px; flex-direction: column; gap: 10px; } } `, ]; } getModalElement() { return this.renderRoot?.querySelector('#detailed-submission-modal') ?? null; } show() { const modal = this.getModalElement(); const pdfModal = this.renderRoot?.querySelector('#pdf-view-modal'); if (!modal) { return; } pdfModal?.addEventListener('dbp-modal-closed', this.boundHandlePdfModalClosed); modal.addEventListener('dbp-modal-closed', this.boundHandleModalClosed); document.addEventListener('keydown', this.boundHandleKeydown, true); modal.open(); } close() { const modal = this.getModalElement(); if (modal) { modal.close(); } } handleModalClosed() { const modal = this.getModalElement(); const pdfModal = this.renderRoot?.querySelector('#pdf-view-modal'); document.removeEventListener('keydown', this.boundHandleKeydown, true); modal?.removeEventListener('dbp-modal-closed', this.boundHandleModalClosed); pdfModal?.removeEventListener('dbp-modal-closed', this.boundHandlePdfModalClosed); this.isPdfPreviewOpen = false; this.dispatchEvent( new CustomEvent('detail-modal-close', { detail: {state: this.state}, bubbles: true, composed: true, }), ); } handleKeydown(event) { if (this.isPdfPreviewOpen && event.key === 'Escape') { event.stopPropagation(); return; } if (event.keyCode === 37 && this.isPrevEnabled) { this.dispatchEvent( new CustomEvent('detail-modal-previous', { detail: {state: this.state}, bubbles: true, composed: true, }), ); } if (event.keyCode === 39 && this.isNextEnabled) { this.dispatchEvent( new CustomEvent('detail-modal-next', { detail: {state: this.state}, bubbles: true, composed: true, }), ); } } handlePdfModalClosed() { this.isPdfPreviewOpen = false; } formatFileSize(fileSize) { const normalizedFileSize = Number(fileSize); return Number.isFinite(normalizedFileSize) ? `${(normalizedFileSize / 1024).toFixed(2)} KB` : ''; } isPdfFile(file) { const mimeType = (file?.mimeType || '').toLowerCase(); const fileName = (file?.fileName || '').toLowerCase(); return mimeType === 'application/pdf' || fileName.endsWith('.pdf'); } async fetchDownloadableFile(file) { if (!file?.downloadUrl) { return null; } const headers = {}; if (this.auth?.token) { headers.Authorization = 'Bearer ' + this.auth.token; } const response = await fetch(file.downloadUrl, { method: 'GET', headers, }); if (!response.ok) { throw new Error(`Failed to fetch file: ${response.status}`); } const fileBlob = await response.blob(); const downloadableFile = new File([fileBlob], file.fileName, { type: file.mimeType || fileBlob.type || 'application/octet-stream', }); // FileSink expects this property for non-streamed single-file downloads. downloadableFile.filename = file.fileName; return downloadableFile; } async downloadFile(file) { const fileSink = this.renderRoot?.querySelector('#file-sink'); if (!fileSink) { return; } try { const downloadableFile = await this.fetchDownloadableFile(file); if (!downloadableFile) { return; } fileSink.files = [downloadableFile]; } catch (error) { console.error(error); sendNotification({ summary: this._i18n.t('errors.other-title'), body: this._i18n.t('errors.other-body'), type: 'danger', timeout: 0, }); } } async previewPdfFile(file) { const pdfModal = this.renderRoot?.querySelector('#pdf-view-modal'); const pdfViewer = this.renderRoot?.querySelector('#dbp-pdf-viewer'); if (!pdfModal || !pdfViewer || !file?.downloadUrl) { return; } try { const pdfFile = await this.fetchDownloadableFile(file); if (!pdfFile) { return; } this.isPdfPreviewOpen = true; pdfModal.open(); await pdfViewer.showPDF(pdfFile); } catch (error) { console.error(error); sendNotification({ summary: this._i18n.t('errors.other-title'), body: this._i18n.t('errors.other-body'), type: 'danger', timeout: 0, }); } } renderFileItem(file) { const fileSize = this.formatFileSize(file.fileSize); return html` <div class="file-block"> <span class="file-info"> <strong class="file-name">${file.fileName}</strong> <span class="additional-data"> ${ file.mimeType ? html` <span class="file-type">(${file.mimeType})</span> ` : '' } ${ fileSize ? html` <span class="file-size">${fileSize}</span> ` : '' } </span> </span> <div class="file-action-buttons"> ${ this.isPdfFile(file) ? html` <button class="view-file-button button is-secondary" title="${this._i18n.t( 'render-form.download-widget.view-attachment-title', { filename: file.fileName, }, )}" aria-label="${this._i18n.t( 'render-form.download-widget.view-attachment-title', { filename: file.fileName, }, )}" @click=${(event) => { event.preventDefault(); void this.previewPdfFile(file); }}> <dbp-icon name="eye" aria-hidden="true"></dbp-icon> ${this._i18n.t('render-form.download-widget.view-attachment')} </button> ` : '' } <button class="download-file-button button is-secondary" title="${this._i18n.t( 'render-form.download-widget.download-attachment-title', { filename: file.fileName, }, )}" aria-label="${this._i18n.t( 'render-form.download-widget.download-attachment-title', { filename: file.fileName, }, )}" @click=${(event) => { event.preventDefault(); void this.downloadFile(file); }}> <dbp-icon name="download" aria-hidden="true"></dbp-icon> ${this._i18n.t('render-form.download-widget.download-attachment')} </button> </div> </div> `; } renderContentItem(item) { if (item.type === 'files' && item.files?.length > 0) { return html` <div class="fileblock-container submitted-files"> ${item.files.map((file) => this.renderFileItem(file))} </div> `; } return item.value; } render() { const i18n = this._i18n; return html` <dbp-modal id="detailed-submission-modal" class="detailed-submission-modal" modal-id="detailed-submission-modal-${this.state}" title="${i18n.t('manage-forms.detailed-submission-dialog-title')}" sticky-footer subscribe="lang"> <div slot="content" class="detailed-submission-modal-content" id="detailed-submission-modal-content-${this.state}"> <div class="content-wrapper"> ${this.contentItems.map( (item, index) => html` <div class="element-left ${classMap({first: index === 0})}"> ${item.label}: </div> <div class="element-right ${classMap({first: index === 0})}"> ${this.renderContentItem(item)} </div> `, )} </div> </div> <div slot="footer" class="detailed-submission-footer"> <div class="modal-footer-btn"> <label class="button-container ${classMap({ hidden: !this.hiddenColumns, })}"> ${i18n.t('manage-forms.apply-col-settings')} <input type="checkbox" id="apply-col-settings-${this.state}" class="apply-col-settings" name="apply-col-settings" checked /> <span class="checkmark"></span> </label> <div class="btn-row-left"> <dbp-button class="back-btn" no-spinner-on-click title="${i18n.t('manage-forms.previous-entry-btn-title')}" @click="${() => { this.dispatchEvent( new CustomEvent('detail-modal-previous', { detail: {state: this.state}, bubbles: true, composed: true, }), ); }}" ?disabled=${!this.isPrevEnabled}> <dbp-icon name="chevron-left" aria-hidden="true"></dbp-icon> <span class="button-label"> ${i18n.t('manage-forms.previous-entry-btn-title')} </span> </dbp-button> <div class="page-numbering"> ${i18n.t('manage-forms.detailed-submission-dialog-id', { id: this.currentBeautyId, nItems: this.totalItems, })} </div> <dbp-button class="next-btn" no-spinner-on-click title="${i18n.t('manage-forms.next-entry-btn-title')}" @click="${() => { this.dispatchEvent( new CustomEvent('detail-modal-next', { detail: {state: this.state}, bubbles: true, composed: true, }), ); }}" ?disabled=${!this.isNextEnabled}> <span class="button-label"> ${i18n.t('manage-forms.next-entry-btn-title')} </span> <dbp-icon name="chevron-right" aria-hidden="true"></dbp-icon> </dbp-button> </div> </div> </div> </dbp-modal> <dbp-file-sink id="file-sink" class="file-sink" lang="${this.lang}" allowed-mime-types="*/*" decompress-zip enabled-targets="local,clipboard,nextcloud" subscribe="auth,nextcloud-auth-url,nextcloud-web-dav-url,nextcloud-name,nextcloud-file-url"></dbp-file-sink> <dbp-modal id="pdf-view-modal" class="pdf-view-modal" modal-id="pdf-viewer-modal-${this.state}" subscribe="lang"> <div slot="content"> <dbp-pdf-viewer id="dbp-pdf-viewer" lang="${this.lang}" auto-resize="cover"></dbp-pdf-viewer> </div> </dbp-modal> `; } }