From 693ae15d252abfa53226f5496aecd710327c5885 Mon Sep 17 00:00:00 2001 From: Tamara Steinwender <tamara.steinwender@tugraz.at> Date: Thu, 8 Apr 2021 09:52:44 +0200 Subject: [PATCH] Outsourcing the file-handling clipboard part to an external webcomponent --- .../src/dbp-file-handling-clipboard.js | 494 +++++++++++++++++- packages/file-handling/src/file-sink.js | 203 ++----- packages/file-handling/src/file-source.js | 288 ++-------- .../src/i18n/de/translation.json | 5 + .../src/i18n/en/translation.json | 5 + 5 files changed, 583 insertions(+), 412 deletions(-) diff --git a/packages/file-handling/src/dbp-file-handling-clipboard.js b/packages/file-handling/src/dbp-file-handling-clipboard.js index 7edac72e..db3cb055 100644 --- a/packages/file-handling/src/dbp-file-handling-clipboard.js +++ b/packages/file-handling/src/dbp-file-handling-clipboard.js @@ -5,15 +5,32 @@ import DBPLitElement from '@dbp-toolkit/common/dbp-lit-element'; import {Icon, MiniSpinner} from '@dbp-toolkit/common'; import * as commonStyles from '@dbp-toolkit/common/styles'; import * as fileHandlingStyles from './styles'; +import Tabulator from "tabulator-tables"; +import {humanFileSize} from "@dbp-toolkit/common/i18next"; +import {classMap} from 'lit-html/directives/class-map.js'; +import * as commonUtils from "@dbp-toolkit/common/utils"; +import {name as pkgName} from "../package.json"; +import {send} from "@dbp-toolkit/common/notification"; + /** - * NextcloudFilePicker web component + * Clipboard web component */ export class FileHandlingClipboard extends ScopedElementsMixin(DBPLitElement) { constructor() { super(); this.lang = 'de'; this.authUrl = ''; + this.allowedMimeTypes = '*/*'; + this.clipboardSource = false; + this.clipboardFiles = {files: ''}; + this.clipboardSelectBtnDisabled = true; + this.clipboardSelectBtnDisabled = true; + this.showSelectAllButton = true; + this.tabulatorTable = null; + this.maxSelectedItems = true; + this._onReceiveBeforeUnload = this.onReceiveBeforeUnload.bind(this); + } static get scopedElements() { @@ -31,6 +48,12 @@ export class FileHandlingClipboard extends ScopedElementsMixin(DBPLitElement) { ...super.properties, lang: { type: String }, authUrl: { type: String, attribute: 'auth-url' }, + allowedMimeTypes: { type: String, attribute: 'allowed-mime-types' }, + showSelectAllButton: { type: Boolean, attribute: true }, + clipboardSelectBtnDisabled: { type: Boolean, attribute: true }, + clipboardFiles: {type: Object, attribute: 'clipboard-files'}, + clipboardSource: {type: Boolean, attribute: 'clipboard-source'}, + }; } @@ -41,19 +64,291 @@ export class FileHandlingClipboard extends ScopedElementsMixin(DBPLitElement) { case "lang": i18n.changeLanguage(this.lang); break; + case "clipboardFiles": + this.generateClipboardTable(); + break; } + console.log("source", this.clipboardSource); }); super.update(changedProperties); } disconnectedCallback() { + + //We doesn't want to deregister this event, because we want to use this event over activities + //window.removeEventListener('beforeunload', this._onReceiveBeforeUnload); + + super.disconnectedCallback(); } connectedCallback() { + super.connectedCallback(); + const that = this; + this.updateComplete.then(() => { + + // see: http://tabulator.info/docs/4.7 + this.tabulatorTable = new Tabulator(this._("#clipboard-content-table"), { + layout: "fitColumns", + selectable: this.maxSelectedItems, + selectableRangeMode: "drag", + responsiveLayout: true, + placeholder: i18n.t('nextcloud-file-picker.no-data-type'), + resizableColumns: false, + columns: [ + { + title: "", + field: "type", + align: "center", + headerSort: false, + width: 50, + responsive: 1, + formatter: (cell, formatterParams, onRendered) => { + const icon_tag = that.constructor.getScopedTagName("dbp-icon"); + let icon = `<${icon_tag} name="empty-file" class="nextcloud-picker-icon"></${icon_tag}>`; + return icon; + } + }, + { + title: i18n.t('nextcloud-file-picker.filename'), + responsive: 0, + widthGrow: 5, + minWidth: 150, + field: "name", + sorter: "alphanum", + formatter: (cell) => { + let data = cell.getRow().getData(); + if (data.edit) { + cell.getElement().classList.add("fokus-edit"); + } + return cell.getValue(); + } + }, + { + title: i18n.t('nextcloud-file-picker.size'), + responsive: 4, + widthGrow: 1, + minWidth: 50, + field: "size", + formatter: (cell, formatterParams, onRendered) => { + return cell.getRow().getData().type === "directory" ? "" : humanFileSize(cell.getValue()); + } + }, + { + title: i18n.t('nextcloud-file-picker.mime-type'), + responsive: 2, + widthGrow: 1, + minWidth: 20, + field: "type", + formatter: (cell, formatterParams, onRendered) => { + if (typeof cell.getValue() === 'undefined') { + return ""; + } + const [, fileSubType] = cell.getValue().split('/'); + return fileSubType; + } + }, + { + title: i18n.t('nextcloud-file-picker.last-modified'), + responsive: 3, + widthGrow: 1, + minWidth: 150, + field: "lastModified", + sorter: (a, b, aRow, bRow, column, dir, sorterParams) => { + const a_timestamp = Date.parse(a); + const b_timestamp = Date.parse(b); + return a_timestamp - b_timestamp; + }, + formatter: function (cell, formatterParams, onRendered) { + const timestamp = new Date(cell.getValue()); + const year = timestamp.getFullYear(); + const month = ("0" + (timestamp.getMonth() + 1)).slice(-2); + const date = ("0" + timestamp.getDate()).slice(-2); + const hours = ("0" + timestamp.getHours()).slice(-2); + const minutes = ("0" + timestamp.getMinutes()).slice(-2); + return date + "." + month + "." + year + " " + hours + ":" + minutes; + } + }, + {title: "file", field: "file", visible: false} + ], + initialSort: [ + {column: "name", dir: "asc"}, + {column: "type", dir: "asc"}, + ], + rowClick: (e, row) => { + if (this.tabulatorTable !== null + && this.tabulatorTable.getSelectedRows().length === this.tabulatorTable.getRows().filter(row => this.checkFileType(row.getData())).length) { + this.showSelectAllButton = false; + } else { + this.showSelectAllButton = true; + } + }, + rowSelectionChanged: (data, rows) => { + if (this.tabulatorTable && this.tabulatorTable.getSelectedRows().length > 0) { + this.clipboardSelectBtnDisabled = false; + } else { + this.clipboardSelectBtnDisabled = true; + } + } + }); + }); + window.addEventListener('beforeunload', this._onReceiveBeforeUnload); + + } + + /** + * Select all files from tabulator table + * + */ + selectAll() { + this.tabulatorTable.selectRow(this.tabulatorTable.getRows().filter(row => this.checkFileType(row.getData()))); + if (this.tabulatorTable.getSelectedRows().length > 0) { + this.showSelectAllButton = false; + console.log("Show Select All Button:", this.showSelectAllButton); + } + } + + /** + * Deselect files from tabulator table + * + */ + deselectAll() { + this.tabulatorTable.deselectRow(); + this.showSelectAllButton = true; + console.log("Show Select All Button:", this.showSelectAllButton); + } + + checkFileType(file) { + // check if file is allowed + const [fileMainType, fileSubType] = file.type.split('/'); + const mimeTypes = this.allowedMimeTypes.split(','); + let deny = true; + + mimeTypes.forEach((str) => { + const [mainType, subType] = str.split('/'); + deny = deny && ((mainType !== '*' && mainType !== fileMainType) || (subType !== '*' && subType !== fileSubType)); + }); + + if (deny) { + console.log(`mime type ${file.type} of file '${file.name}' is not compatible with ${this.allowedMimeTypes}`); + return false; + } + return true; + } + + generateClipboardTable() { + let data = []; + for (let i = 0; i < this.clipboardFiles.files.length; i++){ + data[i] = { + name: this.clipboardFiles.files[i].name, + size: this.clipboardFiles.files[i].size, + type: this.clipboardFiles.files[i].type, + lastModified: this.clipboardFiles.files[i].lastModified, + file: this.clipboardFiles.files[i] + }; + } + + if (this.tabulatorTable !== null){ + this.tabulatorTable.clearData(); + this.tabulatorTable.setData(data); + } + } + + async sendClipboardFiles(files) { + + for(let i = 0; i < files.length; i ++) + { + await this.sendFileEvent(files[i].file); + } + this.tabulatorTable.deselectRow(); + this.closeDialog(); + + } + + async sendFileEvent(file) { + const data = {"file": file, "data": file}; + + const event = new CustomEvent("dbp-clipboard-file-picker-file-downloaded", + { "detail": data, bubbles: true, composed: true }); + this.dispatchEvent(event); + } + + /** + * Decides if the "beforeunload" event needs to be canceled + * + * @param event + */ + onReceiveBeforeUnload(event){ + // we don't need to stop if there are no signed files + if (this.clipboardFiles.files.length === 0) { + return; + } + + send({ + "summary": i18n.t('clipboard.file-warning'), + "body": i18n.t('clipboard.file-warning-body', {count: this.clipboardFiles.files.length}), + "type": "warning", + "timeout": 5, + }); + + // we need to handle custom events ourselves + if(event.target && event.target.activeElement && event.target.activeElement.nodeName) { + + 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('page-leaving-warn-dialogue')); + // 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 = ''; + } + } + + saveFilesToClipboard() + { + //save it + let data = {}; + if (this.files.length !== 0) { + data = {"files": this.files}; + this.sendSetPropertyEvent('clipboard-files', data); + this.closeDialog(); + send({ + "summary": i18n.t('file-sink.save-to-clipboard-title'), + "body": i18n.t('file-sink.save-to-clipboard-body', {count: this.files.length}), + "type": "success", + "timeout": 5, + }); + console.log("--------------", this.clipboardFiles); + } + } + + getClipboardFileList() { + let files = []; + for(let i = 0; i < this.clipboardFiles.files.length; i ++) + { + files[i] = html`<div class="clipboard-list"><strong>${this.clipboardFiles.files[i].name}</strong> ${humanFileSize(this.clipboardFiles.files[i].size)}</div>`; + } + return files; + } + + /** + * Open Filesink for multiple files + */ + async openClipboardFileSink() { + this._("#file-sink-clipboard").files = this.clipboardFiles.files; + this._("#file-sink-clipboard").setAttribute("dialog-open", ""); + } + static get styles() { // language=css return css` @@ -63,14 +358,201 @@ export class FileHandlingClipboard extends ScopedElementsMixin(DBPLitElement) { ${commonStyles.getModalDialogCSS()} ${commonStyles.getRadioAndCheckboxCss()} ${fileHandlingStyles.getFileHandlingCss()} - + + .clipboard-container{ + display: flex; + flex-direction: column; + justify-content: center; + padding: var(--FUPadding, 20px); + width: 100%; + height: 100%; + position: relative; + } + + .clipboard-container .wrapper{ + overflow-y: auto; + text-align: center; + width: 100%; + height: 100%; + display: flex; + flex-direction: column; + justify-content: center; + } + + .clipboard-container .wrapper.table{ + justify-content: start; + } + + .clipboard-container .wrapper .inner{ + overflow-y: auto; + text-align: center; + width: 100%; + } + + .clipboard-footer{ + align-self: end; + } + + #select-all-wrapper{ + text-align: right; + } + + .clipboard-container{ + display: flex; + flex-direction: column; + justify-content: center; + padding: var(--FUPadding, 20px); + } + + .clipboard-container.table{ + justify-content: start; + } + + .clipboard-container .inner{ + overflow-y: auto; + text-align: center; + width: 100%; + } + + .warning-icon{ + font-size: 2rem; + padding: 0 1rem; + } + + .clipboard-btn{ + margin-top: 1.5rem; + margin-bottom: 1.5rem; + } + + .warning-container{ + display: flex; + max-width: 400px; + text-align: left; + margin: auto; + } + + .clipboard-data h4{ + margin-top: 2rem; + } + + .clipboard-data p{ + margin-bottom: 1rem; + } + + .clipboard-list{ + padding: 1rem 0; + border-top: 1px solid #eee; + } + + + @media only screen + and (orientation: portrait) + and (max-device-width: 765px) { + .clipboard-container p, .clipboard-container h3{ + text-align: center; + } + .warning-container{ + flex-direction: column; + align-items: center; + } + .warning-icon{ + margin-bottom: 1rem; + } + } `; } render() { - return html` - - HALLLOOOO - `; + const tabulatorCss = commonUtils.getAssetURL(pkgName, 'tabulator-tables/css/tabulator.min.css'); + + if (this.clipboardSource) { + + return html` + <link rel="stylesheet" href="${tabulatorCss}"> + <div class="block clipboard-container"> + <div class="wrapper ${classMap({"table": this.clipboardFiles.files.length !== 0})}"> + <div class="inner"> + <h3>${i18n.t('file-source.clipboard-title')}</h3> + <p>${i18n.t('file-source.clipboard-body')}<br><br></p> + <p class="${classMap({"hidden": this.clipboardFiles.files.length !== 0})}"> + ${i18n.t('file-source.clipboard-no-files')}</p> + <div class="clipboard-table ${classMap({"hidden": this.clipboardFiles.files.length === 0})}"> + <div id="select-all-wrapper"> + <button class="button ${classMap({"hidden": !this.showSelectAllButton})}" + title="${i18n.t('nextcloud-file-picker.select-all-title')}" + @click="${() => { + this.selectAll(); + }}"> + ${i18n.t('nextcloud-file-picker.select-all')} + </button> + <button class="button ${classMap({"hidden": this.showSelectAllButton})}" + title="${i18n.t('nextcloud-file-picker.select-nothing-title')}" + @click="${() => { + this.deselectAll(); + }}"> + ${i18n.t('nextcloud-file-picker.select-nothing')} + </button> + </div> + <table id="clipboard-content-table" class="force-no-select"></table> + </div> + </div> + </div> + <div class="clipboard-footer ${classMap({"hidden": this.clipboardFiles.files.length === 0})}"> + <button class="button select-button is-primary" ?disabled="${this.clipboardSelectBtnDisabled}" + @click="${() => { + this.sendClipboardFiles(this.tabulatorTable.getSelectedData()); + }}">${i18n.t('nextcloud-file-picker.select-files')} + </button> + </div> + </div> + `; + + } else { + return html` + <div class="block clipboard-container ${classMap({"table": this.clipboardFiles && this.clipboardFiles.files.length !== 0})}"> + <div class="inner"> + <h3>${i18n.t('file-sink.save-to-clipboard-title')}</h3> + <p>${i18n.t('file-sink.save-to-clipboard-text')}</p> + <button class="button is-primary clipboard-btn" + ?disabled="${this.disabled}" + @click="${() => { this.saveFilesToClipboard(); }}"> + ${this.buttonLabel || i18n.t('file-sink.save-to-clipboard-btn', {count:this.clipboardFiles.files.length})} + </button> + <div class="warning-container"> + <dbp-icon name="warning" class="warning-icon"></dbp-icon> + <p>${i18n.t('file-sink.save-to-clipboard-warning')}</p> + </div> + + <!-- filesink for clipboard TODO übersetzen--> + + <div class="${classMap({"hidden": this.clipboardFiles.files.length === 0})}"> + <button id="clipboard-download-button" + class="button is-right clipboard-btn" + @click="${this.openClipboardFileSink}" + >Aktuellen Zwischenablageninhalt speichern</button> + </div> + + <dbp-file-sink id="file-sink-clipboard" + context="${i18n.t('qualified-pdf-upload.save-field-label', {count: this.clipboardFiles ? this.clipboardFiles.files.length : 0})}" + filename="signed-documents.zip" + subscribe="initial-file-handling-state:initial-file-handling-state" + enabled-targets="local${this.showNextcloudFilePicker ? ",nextcloud" : ""}" + 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> + + + <div class="clipboard-data ${classMap({"hidden": this.clipboardFiles.files.length === 0})}"> + <h4>${i18n.t('file-sink.clipboard-files')}</h4> + <p>${i18n.t('file-sink.clipboard-files-overwrite')}</p> + ${this.getClipboardFileList()} + </div> + </div> + </div> + `; + } } } diff --git a/packages/file-handling/src/file-sink.js b/packages/file-handling/src/file-sink.js index b19b48ed..c433d6e3 100644 --- a/packages/file-handling/src/file-sink.js +++ b/packages/file-handling/src/file-sink.js @@ -13,6 +13,7 @@ import * as fileHandlingStyles from './styles'; import { send } from '@dbp-toolkit/common/notification'; import {humanFileSize} from '@dbp-toolkit/common/i18next'; import * as utils from "../../../../../src/utils"; +import {FileHandlingClipboard} from "./dbp-file-handling-clipboard"; /** @@ -39,7 +40,6 @@ export class FileSink extends ScopedElementsMixin(DBPLitElement) { this.showClipboard = false; this.initialFileHandlingState = {target: '', path: ''}; - this.clipBoardFiles = {files: ''}; } static get scopedElements() { @@ -47,6 +47,7 @@ export class FileSink extends ScopedElementsMixin(DBPLitElement) { 'dbp-icon': Icon, 'dbp-mini-spinner': MiniSpinner, 'dbp-nextcloud-file-picker': NextcloudFilePicker, + 'dbp-clipboard': FileHandlingClipboard, }; } @@ -74,7 +75,6 @@ export class FileSink extends ScopedElementsMixin(DBPLitElement) { showClipboard: { type: Boolean, attribute: 'show-clipboard' }, initialFileHandlingState: {type: Object, attribute: 'initial-file-handling-state'}, - clipBoardFiles: {type: Object, attribute: 'clipboard-files'}, }; } @@ -220,46 +220,54 @@ export class FileSink extends ScopedElementsMixin(DBPLitElement) { } } - saveFilesToClipboard() - { - //save it - let data = {}; - if (this.files.length !== 0) { - data = {"files": this.files}; - this.sendSetPropertyEvent('clipboard-files', data); - this.closeDialog(); - send({ - "summary": i18n.t('file-sink.save-to-clipboard-title'), - "body": i18n.t('file-sink.save-to-clipboard-body', {count: this.files.length}), - "type": "success", - "timeout": 5, - }); - console.log("--------------", this.clipBoardFiles); - } - + closeDialog(e) { + this.sendDestination(); + MicroModal.close(this._('#modal-picker')); } - getClipboardFiles() { - let files = []; - for(let i = 0; i < this.clipBoardFiles.files.length; i ++) - { - files[i] = html`<div class="clipboard-list"><strong>${this.clipBoardFiles.files[i].name}</strong> ${humanFileSize(this.clipBoardFiles.files[i].size)}</div>`; + getClipboardHtml() { + if (this.enabledTargets.includes('clipboard') && this.showClipboard) { + return html` + <dbp-clipboard + id="clipboard-file-sink" + subscribe="clipboard-files:clipboard-files" + lang="${this.lang}" + auth-url="${this.nextcloudAuthUrl}" + allowed-mime-types="${this.allowedMimeTypes}" + @dbp-clipboard-file-picker-file-downloaded="${(event) => { + this.sendFileEvent(event.detail.file);}}"> + </dbp-clipboard>`; } - return files; + return html``; } - closeDialog(e) { - this.sendDestination(); - MicroModal.close(this._('#modal-picker')); + getNextcloudHtml() { + if (this.enabledTargets.includes('nextcloud') && this.nextcloudWebDavUrl !== "" && this.nextcloudAuthUrl !== "") { + return html` + <dbp-nextcloud-file-picker id="nextcloud-file-picker" + class="${classMap({hidden: this.nextcloudWebDavUrl === "" || this.nextcloudAuthUrl === ""})}" + directories-only + max-selected-items="1" + select-button-text="${i18n.t('file-sink.select-directory')}" + ?disabled="${this.disabled}" + lang="${this.lang}" + auth-url="${this.nextcloudAuthUrl}" + web-dav-url="${this.nextcloudWebDavUrl}" + nextcloud-name="${this.nextcloudName}" + directory-path="${this.nextcloudPath}" + nextcloud-file-url="${this.nextcloudFileURL}" + @dbp-nextcloud-file-picker-file-uploaded="${(event) => { + this.uploadToNextcloud(event.detail); + }}" + @dbp-nextcloud-file-picker-file-uploaded-finished="${(event) => { + this.finishedFileUpload(event); + }}"> + </dbp-nextcloud-file-picker>`; + } + return html``; } - /** - * Open Filesink for multiple files - */ - async openClipboardFileSink() { - this._("#file-sink-clipboard").files = this.clipBoardFiles.files; - this._("#file-sink-clipboard").setAttribute("dialog-open", ""); - } + static get styles() { // language=css @@ -283,67 +291,9 @@ export class FileSink extends ScopedElementsMixin(DBPLitElement) { margin-bottom: 10px; } - .clipboard-container{ - display: flex; - flex-direction: column; - justify-content: center; - padding: var(--FUPadding, 20px); - } - - .clipboard-container.table{ - justify-content: start; - } - - .clipboard-container .inner{ - overflow-y: auto; - text-align: center; + #clipboard-file-sink{ width: 100%; - } - - .warning-icon{ - font-size: 2rem; - padding: 0 1rem; - } - - .clipboard-btn{ - margin-top: 1.5rem; - margin-bottom: 1.5rem; - } - - .warning-container{ - display: flex; - max-width: 400px; - text-align: left; - margin: auto; - } - - .clipboard-data h4{ - margin-top: 2rem; - } - - .clipboard-data p{ - margin-bottom: 1rem; - } - - .clipboard-list{ - padding: 1rem 0; - border-top: 1px solid #eee; - } - - - @media only screen - and (orientation: portrait) - and (max-device-width: 765px) { - .clipboard-container p, .clipboard-container h3{ - text-align: center; - } - .warning-container{ - flex-direction: column; - align-items: center; - } - .warning-icon{ - margin-bottom: 1rem; - } + height: 100%; } `; } @@ -399,69 +349,10 @@ export class FileSink extends ScopedElementsMixin(DBPLitElement) { </div> </div> <div class="source-main ${classMap({"hidden": this.activeTarget !== "nextcloud" || this.nextcloudWebDavUrl === "" || this.nextcloudAuthUrl === ""})}"> - <dbp-nextcloud-file-picker id="nextcloud-file-picker" - class="${classMap({hidden: this.nextcloudWebDavUrl === "" || this.nextcloudAuthUrl === ""})}" - directories-only - max-selected-items="1" - select-button-text="${i18n.t('file-sink.select-directory')}" - ?disabled="${this.disabled}" - lang="${this.lang}" - auth-url="${this.nextcloudAuthUrl}" - web-dav-url="${this.nextcloudWebDavUrl}" - nextcloud-name="${this.nextcloudName}" - directory-path="${this.nextcloudPath}" - nextcloud-file-url="${this.nextcloudFileURL}" - @dbp-nextcloud-file-picker-file-uploaded="${(event) => { - this.uploadToNextcloud(event.detail); - }}" - @dbp-nextcloud-file-picker-file-uploaded-finished="${(event) => { - this.finishedFileUpload(event); - }}"></dbp-nextcloud-file-picker> + ${this.getNextcloudHtml()} </div> <div class="source-main ${classMap({"hidden": this.activeTarget !== "clipboard" || isClipboardHidden})}"> - <div class="block clipboard-container ${classMap({"table": this.clipBoardFiles && this.clipBoardFiles.files.length !== 0})}"> - <div class="inner"> - <h3>${i18n.t('file-sink.save-to-clipboard-title')}</h3> - <p>${i18n.t('file-sink.save-to-clipboard-text')}</p> - <button class="button is-primary clipboard-btn" - ?disabled="${this.disabled}" - @click="${() => { this.saveFilesToClipboard(); }}"> - ${this.buttonLabel || i18n.t('file-sink.save-to-clipboard-btn', {count:this.files.length})} - </button> - <div class="warning-container"> - <dbp-icon name="warning" class="warning-icon"></dbp-icon> - <p>${i18n.t('file-sink.save-to-clipboard-warning')}</p> - </div> - - <!-- filesink for clipboard TODO übersetzen--> - - <div clHALLLOOOOass="${classMap({"hidden": this.clipBoardFiles.files.length === 0})}"> - <button id="clipboard-download-button" - class="button is-right clipboard-btn" - @click="${this.openClipboardFileSink}" - >Aktuellen Zwischenablageninhalt speichern</button> - </div> - - <dbp-file-sink id="file-sink-clipboard" - context="${i18n.t('qualified-pdf-upload.save-field-label', {count: this.clipBoardFiles ? this.clipBoardFiles.files.length : 0})}" - filename="signed-documents.zip" - subscribe="initial-file-handling-state:initial-file-handling-state" - enabled-targets="local${this.showNextcloudFilePicker ? ",nextcloud" : ""}" - 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> - - - <div class="clipboard-data ${classMap({"hidden": this.clipBoardFiles.files.length === 0})}"> - <h4>${i18n.t('file-sink.clipboard-files')}</h4> - <p>${i18n.t('file-sink.clipboard-files-overwrite')}</p> - ${this.getClipboardFiles()} - </div> - </div> - </div> + ${this.getClipboardHtml()} </div> </main> </div> diff --git a/packages/file-handling/src/file-source.js b/packages/file-handling/src/file-source.js index 5d2fd118..04ed7eeb 100644 --- a/packages/file-handling/src/file-source.js +++ b/packages/file-handling/src/file-source.js @@ -10,9 +10,8 @@ import {NextcloudFilePicker} from "./dbp-nextcloud-file-picker"; import {classMap} from 'lit-html/directives/class-map.js'; import MicroModal from './micromodal.es'; import * as fileHandlingStyles from './styles'; -import Tabulator from "tabulator-tables"; -import {humanFileSize} from "@dbp-toolkit/common/i18next"; import {name as pkgName} from "../package.json"; +import {FileHandlingClipboard} from "./dbp-file-handling-clipboard"; function mimeTypesToAccept(mimeTypes) { // Some operating systems can't handle mime types and @@ -56,16 +55,10 @@ export class FileSource extends ScopedElementsMixin(DBPLitElement) { this.activeTarget = 'local'; this.isDialogOpen = false; this.firstOpen = true; - this.tabulatorTable = null; - this.maxSelectedItems = true; - this.showSelectAllButton = true; - this.clipboardSelectBtnDisabled = true; this.showClipboard = false; this.initialFileHandlingState = {target: '', path: ''}; - this.clipboardFiles = {files: ''}; - this._onReceiveBeforeUnload = this.onReceiveBeforeUnload.bind(this); } @@ -74,6 +67,7 @@ export class FileSource extends ScopedElementsMixin(DBPLitElement) { 'dbp-icon': Icon, 'dbp-mini-spinner': MiniSpinner, 'dbp-nextcloud-file-picker': NextcloudFilePicker, + 'dbp-clipboard': FileHandlingClipboard, }; } @@ -97,13 +91,9 @@ export class FileSource extends ScopedElementsMixin(DBPLitElement) { decompressZip: { type: Boolean, attribute: 'decompress-zip' }, activeTarget: { type: String, attribute: 'active-target' }, isDialogOpen: { type: Boolean, attribute: 'dialog-open' }, - showSelectAllButton: { type: Boolean, attribute: true }, - clipboardSelectBtnDisabled: { type: Boolean, attribute: true }, showClipboard: { type: Boolean, attribute: 'show-clipboard' }, initialFileHandlingState: {type: Object, attribute: 'initial-file-handling-state'}, - clipboardFiles: {type: Object, attribute: 'clipboard-files'}, - }; } @@ -133,10 +123,6 @@ export class FileSource extends ScopedElementsMixin(DBPLitElement) { this.nextcloudPath = this.initialFileHandlingState.path; } break; - case "clipboardFiles": - this.generateClipboardTable(); - break; - } }); super.update(changedProperties); @@ -145,7 +131,6 @@ export class FileSource extends ScopedElementsMixin(DBPLitElement) { connectedCallback() { super.connectedCallback(); - const that = this; this.updateComplete.then(() => { this.dropArea = this._('#dropArea'); ['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => { @@ -159,109 +144,16 @@ export class FileSource extends ScopedElementsMixin(DBPLitElement) { }); this.dropArea.addEventListener('drop', this.handleDrop.bind(this), false); this._('#fileElem').addEventListener('change', this.handleChange.bind(this)); - - // see: http://tabulator.info/docs/4.7 - this.tabulatorTable = new Tabulator(this._("#clipboard-content-table"), { - layout: "fitColumns", - selectable: this.maxSelectedItems, - selectableRangeMode: "drag", - responsiveLayout: true, - placeholder: i18n.t('nextcloud-file-picker.no-data-type'), - resizableColumns:false, - columns: [ - {title: "", field: "type", align:"center", headerSort:false, width:50, responsive:1, formatter: (cell, formatterParams, onRendered) => { - const icon_tag = that.constructor.getScopedTagName("dbp-icon"); - let icon = `<${icon_tag} name="empty-file" class="nextcloud-picker-icon"></${icon_tag}>`; - return icon; - }}, - {title: i18n.t('nextcloud-file-picker.filename'), responsive: 0, widthGrow:5, minWidth: 150, field: "name", sorter: "alphanum", - formatter: (cell) => { - let data = cell.getRow().getData(); - if (data.edit) { - cell.getElement().classList.add("fokus-edit"); - } - return cell.getValue(); - }}, - {title: i18n.t('nextcloud-file-picker.size'), responsive: 4, widthGrow:1, minWidth: 50, field: "size", formatter: (cell, formatterParams, onRendered) => { - return cell.getRow().getData().type === "directory" ? "" : humanFileSize(cell.getValue()); - }}, - {title: i18n.t('nextcloud-file-picker.mime-type'), responsive: 2, widthGrow:1, minWidth: 20, field: "type", formatter: (cell, formatterParams, onRendered) => { - if (typeof cell.getValue() === 'undefined') { - return ""; - } - const [, fileSubType] = cell.getValue().split('/'); - return fileSubType; - }}, - {title: i18n.t('nextcloud-file-picker.last-modified'), responsive: 3, widthGrow:1, minWidth: 150, field: "lastModified",sorter: (a, b, aRow, bRow, column, dir, sorterParams) => { - const a_timestamp = Date.parse(a); - const b_timestamp = Date.parse(b); - return a_timestamp - b_timestamp; - }, formatter:function(cell, formatterParams, onRendered) { - const timestamp = new Date(cell.getValue()); - const year = timestamp.getFullYear(); - const month = ("0" + (timestamp.getMonth() + 1)).slice(-2); - const date = ("0" + timestamp.getDate()).slice(-2); - const hours = ("0" + timestamp.getHours()).slice(-2); - const minutes = ("0" + timestamp.getMinutes()).slice(-2); - return date + "." + month + "." + year + " " + hours + ":" + minutes; - }}, - {title: "file", field: "file", visible: false} - ], - initialSort:[ - {column:"name", dir:"asc"}, - {column:"type", dir:"asc"}, - ], - rowClick: (e, row) => { - if (this.tabulatorTable !== null - && this.tabulatorTable.getSelectedRows().length === this.tabulatorTable.getRows().filter(row => this.checkFileType(row.getData())).length) { - this.showSelectAllButton = false; - } else { - this.showSelectAllButton = true; - } - }, - rowSelectionChanged: (data, rows) => { - if (this.tabulatorTable && this.tabulatorTable.getSelectedRows().length > 0) { - this.clipboardSelectBtnDisabled = false; - } else { - this.clipboardSelectBtnDisabled = true; - } - } - }); }); - window.addEventListener('beforeunload', this._onReceiveBeforeUnload); } disconnectedCallback() { - // remove event listeners - - //We doesn't want to deregister this event, because we want to use this event over activities - //window.removeEventListener('beforeunload', this._onReceiveBeforeUnload); super.disconnectedCallback(); } - /** - * Select all files from tabulator table - * - */ - selectAll() { - this.tabulatorTable.selectRow(this.tabulatorTable.getRows().filter(row => this.checkFileType(row.getData()))); - if (this.tabulatorTable.getSelectedRows().length > 0) { - this.showSelectAllButton = false; - console.log("Show Select All Button:", this.showSelectAllButton); - } - } - /** - * Deselect files from tabulator table - * - */ - deselectAll() { - this.tabulatorTable.deselectRow(); - this.showSelectAllButton = true; - console.log("Show Select All Button:", this.showSelectAllButton); - } preventDefaults (e) { e.preventDefault(); @@ -487,8 +379,8 @@ export class FileSource extends ScopedElementsMixin(DBPLitElement) { } if (this.enabledTargets.includes('clipboard')) { - this.generateClipboardTable(); - this.showSelectAllButton = true; + this._("#clipboard-file-picker").generateClipboardTable(); + this._("#clipboard-file-picker").showSelectAllButton = true; } const filePicker = this._('#modal-picker'); @@ -521,74 +413,45 @@ export class FileSource extends ScopedElementsMixin(DBPLitElement) { MicroModal.close(this._('#modal-picker')); } - - - generateClipboardTable() { - let data = []; - for (let i = 0; i < this.clipboardFiles.files.length; i++){ - data[i] = { - name: this.clipboardFiles.files[i].name, - size: this.clipboardFiles.files[i].size, - type: this.clipboardFiles.files[i].type, - lastModified: this.clipboardFiles.files[i].lastModified, - file: this.clipboardFiles.files[i] - }; - } - - if (this.tabulatorTable !== null){ - this.tabulatorTable.clearData(); - this.tabulatorTable.setData(data); + getClipboardHtml() { + if (this.enabledTargets.includes('clipboard') && this.showClipboard) { + return html` + <dbp-clipboard + id="clipboard-file-picker" + clipboard-source + subscribe="clipboard-files:clipboard-files" + lang="${this.lang}" + auth-url="${this.nextcloudAuthUrl}" + allowed-mime-types="${this.allowedMimeTypes}" + @dbp-clipboard-file-picker-file-downloaded="${(event) => { + this.sendFileEvent(event.detail.file);}}"> + </dbp-clipboard>`; } + return html``; } - - async sendClipboardFiles(files) { - - for(let i = 0; i < files.length; i ++) - { - await this.sendFileEvent(files[i].file); + getNextcloudHtml() { + if (this.enabledTargets.includes('nextcloud') && this.nextcloudWebDavUrl !== "" && this.nextcloudAuthUrl !== "") { + return html` + <dbp-nextcloud-file-picker id="nextcloud-file-picker" + class="${classMap({hidden: this.nextcloudWebDavUrl === "" || this.nextcloudAuthUrl === ""})}" + ?disabled="${this.disabled}" + lang="${this.lang}" + auth-url="${this.nextcloudAuthUrl}" + web-dav-url="${this.nextcloudWebDavUrl}" + nextcloud-name="${this.nextcloudName}" + nextcloud-file-url="${this.nextcloudFileURL}" + allowed-mime-types="${this.allowedMimeTypes}" + @dbp-nextcloud-file-picker-file-downloaded="${(event) => { + this.sendFileEvent(event.detail.file);}}"> + </dbp-nextcloud-file-picker>`; } - this.tabulatorTable.deselectRow(); - this.closeDialog(); - + return html``; } - /** - * Decides if the "beforeunload" event needs to be canceled - * - * @param event - */ - onReceiveBeforeUnload(event) { - - - // we don't need to stop if there are no signed files - if (!this.showClipboard || !this.hasEnabledSource("clipboard") || this.clipboardFiles.files.length === 0) { - return; - } - - // we need to handle custom events ourselves - if(event.target && event.target.activeElement && event.target.activeElement.nodeName) { - - 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('page-leaving-warn-dialogue')); - // 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 = ''; - } - } - static get styles() { // language=css return css` @@ -625,46 +488,12 @@ export class FileSource extends ScopedElementsMixin(DBPLitElement) { #dropArea.highlight { border-color: var(--FUBorderColorHighlight, purple); } - - .clipboard-container{ - display: flex; - flex-direction: column; - justify-content: center; - padding: var(--FUPadding, 20px); - width: 100%; - height: 100%; - position: relative; - } - - .clipboard-container .wrapper{ - overflow-y: auto; - text-align: center; - width: 100%; - height: 100%; - display: flex; - flex-direction: column; - justify-content: center; - } - - .clipboard-container .wrapper.table{ - justify-content: start; - } - .clipboard-container .wrapper .inner{ - overflow-y: auto; - text-align: center; + #clipboard-file-picker{ width: 100%; + height: 100%; } - .clipboard-footer{ - align-self: end; - } - - #select-all-wrapper{ - text-align: right; - } - - @media only screen and (orientation: portrait) and (max-device-width: 800px) { @@ -673,7 +502,6 @@ export class FileSource extends ScopedElementsMixin(DBPLitElement) { } } - `; } @@ -685,7 +513,6 @@ export class FileSource extends ScopedElementsMixin(DBPLitElement) { allowedMimeTypes += ",application/zip,application/x-zip-compressed"; } - const tabulatorCss = commonUtils.getAssetURL(pkgName, 'tabulator-tables/css/tabulator.min.css'); return html` <!-- @@ -694,7 +521,6 @@ export class FileSource extends ScopedElementsMixin(DBPLitElement) { @click="${() => { this.openDialog(); }}">${i18n.t('file-source.open-menu')}</button> --> <div class="modal micromodal-slide" id="modal-picker" aria-hidden="true"> - <link rel="stylesheet" href="${tabulatorCss}"> <div class="modal-overlay" tabindex="-1" data-micromodal-close> <div class="modal-container" role="dialog" aria-modal="true" aria-labelledby="modal-picker-title"> <nav class="modal-nav"> @@ -744,48 +570,10 @@ export class FileSource extends ScopedElementsMixin(DBPLitElement) { </div> </div> <div class="source-main ${classMap({"hidden": this.activeTarget !== "nextcloud" || this.nextcloudWebDavUrl === "" || this.nextcloudAuthUrl === ""})}"> - <dbp-nextcloud-file-picker id="nextcloud-file-picker" - class="${classMap({hidden: this.nextcloudWebDavUrl === "" || this.nextcloudAuthUrl === ""})}" - ?disabled="${this.disabled}" - lang="${this.lang}" - auth-url="${this.nextcloudAuthUrl}" - web-dav-url="${this.nextcloudWebDavUrl}" - nextcloud-name="${this.nextcloudName}" - nextcloud-file-url="${this.nextcloudFileURL}" - allowed-mime-types="${this.allowedMimeTypes}" - @dbp-nextcloud-file-picker-file-downloaded="${(event) => { - this.sendFileEvent(event.detail.file); - }}"></dbp-nextcloud-file-picker> + ${this.getNextcloudHtml()} </div> <div class="source-main ${classMap({"hidden": (this.activeTarget !== "clipboard" || isClipboardHidden)})}"> - <div class="block clipboard-container"> - <div class="wrapper ${classMap({"table": this.clipboardFiles.files.length !== 0})}"> - <div class="inner"> - <h3>${i18n.t('file-source.clipboard-title')}</h3> - <p>${i18n.t('file-source.clipboard-body')}<br><br></p> - <p class="${classMap({"hidden": this.clipboardFiles.files.length !== 0})}">${i18n.t('file-source.clipboard-no-files')}</p> - <div class="clipboard-table ${classMap({"hidden": this.clipboardFiles.files.length === 0})}"> - <div id="select-all-wrapper"> - <button class="button ${classMap({"hidden": !this.showSelectAllButton})}" - title="${i18n.t('nextcloud-file-picker.select-all-title')}" - @click="${() => { this.selectAll(); }}"> - ${i18n.t('nextcloud-file-picker.select-all')} - </button> - <button class="button ${classMap({"hidden": this.showSelectAllButton})}" - title="${i18n.t('nextcloud-file-picker.select-nothing-title')}" - @click="${() => { this.deselectAll(); }}"> - ${i18n.t('nextcloud-file-picker.select-nothing')} - </button> - </div> - <table id="clipboard-content-table" class="force-no-select"></table> - </div> - </div> - </div> - <div class="clipboard-footer ${classMap({"hidden": this.clipboardFiles.files.length === 0 })}"> - <button class="button select-button is-primary" ?disabled="${ this.clipboardSelectBtnDisabled }" - @click="${() => { this.sendClipboardFiles(this.tabulatorTable.getSelectedData()); }}">${i18n.t('nextcloud-file-picker.select-files')}</button> - </div> - </div> + ${this.getClipboardHtml()} </div> </main> </div> diff --git a/packages/file-handling/src/i18n/de/translation.json b/packages/file-handling/src/i18n/de/translation.json index b59f1ecb..f6a46d42 100644 --- a/packages/file-handling/src/i18n/de/translation.json +++ b/packages/file-handling/src/i18n/de/translation.json @@ -96,5 +96,10 @@ "select-nothing-title": "Alle gewählten Dateien nicht mehr selektieren", "abort": "Vorgang abbrechen", "abort-message": "Vorgang wurde abgebrochen." + }, + "clipboard": { + "file-warning": "Achtung!", + "file-warning-body": "Es befindet sich noch eine Datei in der Zwischenablage. Die Zwischenablage wird beim Verlassen der Seite automatisch verworfen.", + "file-warning-body_plural": "Es befinden sich noch {{count}} Dateien in der Zwischenablage. Die Zwischenablage wird beim Verlassen der Seite automatisch verworfen." } } diff --git a/packages/file-handling/src/i18n/en/translation.json b/packages/file-handling/src/i18n/en/translation.json index b2d0a2a8..881fb18d 100644 --- a/packages/file-handling/src/i18n/en/translation.json +++ b/packages/file-handling/src/i18n/en/translation.json @@ -96,5 +96,10 @@ "select-nothing-title": "Select no files", "abort": "Cancel process", "abort-message": "The process was canceled." + }, + "clipboard": { + "file-warning": "Attention!", + "file-warning-body": "There is still a file on the clipboard. The clipboard is automatically discarded when you exit the page.", + "file-warning-body_plural": "There are still {{count}} files on the clipboard. The clipboard is automatically discarded when you exit the page." } } -- GitLab