From abed2f0d15ed8fd4214fda29be0291de0bcd915e Mon Sep 17 00:00:00 2001
From: Tamara Steinwender <tamara.steinwender@tugraz.at>
Date: Mon, 31 May 2021 16:37:03 +0200
Subject: [PATCH] Change clipboard filehandling

---
 packages/file-handling/rollup.config.js       |   2 +-
 packages/file-handling/src/clipboard.js       | 698 ++++++++++++++++++
 packages/file-handling/src/dbp-clipboard.js   |   4 +
 packages/file-handling/src/file-sink.js       |   5 +-
 packages/file-handling/src/file-source.js     |   6 +-
 .../src/i18n/de/translation.json              |  37 +-
 .../src/i18n/en/translation.json              |  37 +-
 7 files changed, 781 insertions(+), 8 deletions(-)
 create mode 100644 packages/file-handling/src/clipboard.js
 create mode 100644 packages/file-handling/src/dbp-clipboard.js

diff --git a/packages/file-handling/rollup.config.js b/packages/file-handling/rollup.config.js
index 63b5926c..a7b1bd00 100644
--- a/packages/file-handling/rollup.config.js
+++ b/packages/file-handling/rollup.config.js
@@ -17,7 +17,7 @@ let nextcloudFileURL = nextcloudBaseURL + '/apps/files/?dir=';
 
 export default (async () => {
     return {
-        input: (build !== 'test') ? ['src/demo.js', 'src/dbp-file-source.js', 'src/dbp-file-sink.js'] : glob.sync('test/**/*.js'),
+        input: (build !== 'test') ? ['src/demo.js', 'src/dbp-file-source.js', 'src/dbp-file-sink.js', 'src/dbp-clipboard.js'] : glob.sync('test/**/*.js'),
         output: {
             dir: 'dist',
             entryFileNames: '[name].js',
diff --git a/packages/file-handling/src/clipboard.js b/packages/file-handling/src/clipboard.js
new file mode 100644
index 00000000..7235702d
--- /dev/null
+++ b/packages/file-handling/src/clipboard.js
@@ -0,0 +1,698 @@
+import {i18n} from './i18n';
+import {css, html} from 'lit-element';
+import {ScopedElementsMixin} from '@open-wc/scoped-elements';
+import * as commonUtils from '@dbp-toolkit/common/utils';
+import * as commonStyles from '@dbp-toolkit/common/styles';
+import * as fileHandlingStyles from '@dbp-toolkit/file-handling/src/styles';
+import {Icon} from '@dbp-toolkit/common';
+import Tabulator from "tabulator-tables";
+import {humanFileSize} from "@dbp-toolkit/common/i18next";
+import {FileSink} from "@dbp-toolkit/file-handling/src/file-sink";
+import {FileSource} from "@dbp-toolkit/file-handling/src/file-source";
+import {name as pkgName} from "@dbp-toolkit/file-handling/package.json";
+import {send} from "@dbp-toolkit/common/notification";
+import {AdapterLitElement} from "@dbp-toolkit/provider/src/adapter-lit-element";
+
+
+export class Clipboard extends ScopedElementsMixin(AdapterLitElement) {
+    constructor() {
+        super();
+        this.lang = 'de';
+        this.allowedMimeTypes = '*/*';
+        this.clipboardFiles = {files: ''};
+        this.clipboardSelectBtnDisabled = true;
+        this.tabulatorTable = null;
+        this._onReceiveBeforeUnload = this.onReceiveBeforeUnload.bind(this);
+        this.filesToSave = [];
+        this.numberOfSelectedFiles = 0;
+        this.showAdditionalButtons = false;
+        this.enabledTargets = 'local';
+
+        this.nextcloudWebAppPasswordURL = "";
+        this.nextcloudWebDavURL = "";
+        this.nextcloudName = "";
+        this.nextcloudFileURL = "";
+
+        this.isFileSource = false;
+        this.isFileSink = false;
+    }
+
+    static get scopedElements() {
+        return {
+            'dbp-icon': Icon,
+            'dbp-file-sink': FileSink,
+            'dbp-file-source': FileSource,
+        };
+    }
+
+    static get properties() {
+        return {
+            ...super.properties,
+            lang: { type: String },
+            allowedMimeTypes: { type: String, attribute: 'allowed-mime-types' },
+            clipboardSelectBtnDisabled: { type: Boolean, attribute: true },
+            clipboardFiles: {type: Object, attribute: 'clipboard-files'},
+            filesToSave: {type: Array, attribute: 'files-to-save'},
+            numberOfSelectedFiles: {type: Number, attribute: false },
+            showAdditionalButtons: {type: Boolean, attribute: 'show-additional-buttons' },
+            enabledTargets: {type: String, attribute: 'enabled-targets'},
+
+            nextcloudWebAppPasswordURL: { type: String, attribute: 'nextcloud-auth-url' },
+            nextcloudWebDavURL: { type: String, attribute: 'nextcloud-web-dav-url' },
+            nextcloudName: { type: String, attribute: 'nextcloud-name' },
+            nextcloudFileURL: { type: String, attribute: 'nextcloud-file-url' },
+
+            isFileSource: {type: Boolean, attribute: 'file-sink' },
+            isFileSink: {type: Boolean, attribute: 'file-source' },
+        };
+    }
+
+    _(selector) {
+        return this.shadowRoot === null ? this.querySelector(selector) : this.shadowRoot.querySelector(selector);
+    }
+
+    update(changedProperties) {
+        changedProperties.forEach((oldValue, propName) => {
+            switch (propName) {
+                case "lang":
+                    i18n.changeLanguage(this.lang);
+                    break;
+                case "clipboardFiles":
+                    this.generateClipboardTable();
+                    break;
+            }
+        });
+
+        super.update(changedProperties);
+    }
+
+    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: true,
+                selectableRangeMode: "drag",
+                responsiveLayout: true,
+                resizableColumns: false,
+                placeholder: i18n.t("clipboard.no-data"),
+                columns: [
+                    {
+                        title: "",
+                        field: "type",
+                        align: "center",
+                        headerSort: false,
+                        width: 50,
+                        responsive: 1,
+                        formatter: (cell, formatterParams, onRendered) => {
+                            const icon_tag = that.getScopedTagName("dbp-icon");
+                            let icon = `<${icon_tag} name="empty-file" class="nextcloud-picker-icon"></${icon_tag}>`;
+                            return icon;
+                        }
+                    },
+                    {
+                        title: i18n.t("clipboard.file-name"),
+                        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("clipboard.file-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("clipboard.file-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("clipboard.file-mod"),
+                        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) => {
+                    this.numberOfSelectedFiles = this.tabulatorTable !== null ? this.tabulatorTable.getSelectedRows().length : 0;
+                    if (this.tabulatorTable !== null
+                        && this.tabulatorTable.getSelectedRows().length === this.tabulatorTable.getRows().filter(row => this.checkFileType(row.getData())).length) {
+                        this._("#select_all").checked = true;
+
+                    } else {
+                        this._("#select_all").checked = false;
+
+                    }
+                },
+                rowSelectionChanged: (data, rows) => {
+                    if (this.tabulatorTable && this.tabulatorTable.getSelectedRows().length > 0) {
+                        this.clipboardSelectBtnDisabled = false;
+                    } else {
+                        this.clipboardSelectBtnDisabled = true;
+                    }
+                }
+            });
+            that.generateClipboardTable();
+
+        });
+        if(!window.clipboardWarning)  {
+            window.addEventListener('beforeunload', this._onReceiveBeforeUnload, false);
+            window.clipboardWarning = true;
+        }
+
+    }
+
+    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();
+    }
+
+    /**
+     * Select or deselect all files from tabulator table
+     *
+     */
+    selectAllFiles() {
+        let maxSelected = this.tabulatorTable.getRows().filter(row => row.getData().type != 'directory' && this.checkFileType(row.getData(), this.allowedMimeTypes)).length;
+        let selected = this.tabulatorTable.getSelectedRows().length;
+
+        if (selected === maxSelected) {
+            this.tabulatorTable.getSelectedRows().forEach(row => row.deselect());
+            this.numberOfSelectedFiles = 0;
+        } else {
+            this.tabulatorTable.selectRow(this.tabulatorTable.getRows().filter(row => row.getData().type != 'directory' && this.checkFileType(row.getData(), this.allowedMimeTypes)));
+            this.numberOfSelectedFiles = maxSelected;
+        }
+    }
+
+
+    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() {
+        if (this.clipboardFiles.files) {
+            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();
+
+    }
+
+    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;
+        }
+
+        // we need to handle custom events ourselves
+        if(event.target && event.target.activeElement && event.target.activeElement.nodeName) {
+
+            send({
+                "summary": i18n.t('clipboard.file-warning'),
+                "body": i18n.t('clipboard.file-warning-body', {count: this.clipboardFiles.files.length}),
+                "type": "warning",
+                "timeout": 5,
+            });
+            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("##carefulsaveialuge");
+                // 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(ev)
+    {
+        //save it
+        let data = {};
+        let files = [];
+        if (this.clipboardFiles && this.clipboardFiles.files.length !== 0) {
+            files = files.concat(this.clipboardFiles.files);
+            files = files.concat(ev.detail.file);
+        } else {
+            files = files.concat(ev.detail.file);
+        }
+        this.filesToSave = files;
+        if (files && files.length !== 0) {
+            data = {"files": files};
+            this.sendSetPropertyEvent('clipboard-files', data);
+            const event = new CustomEvent("dbp-clipboard-file-picker-file-uploaded",
+                {  bubbles: true, composed: true });
+            this.dispatchEvent(event);
+        }
+    }
+
+    finishedSaveFilesToClipboard(ev) {
+        send({
+            "summary": i18n.t('clipboard.saved-files-title', {count: ev.detail.count}),
+            "body": i18n.t('clipboard.saved-files-body', {count: ev.detail.count}),
+            "type": "success",
+            "timeout": 5,
+        });
+    }
+
+    saveFilesFromClipboard() {
+        const fileSink = this._("#file-sink-clipboard");
+        if ( fileSink ) {
+            this._("#file-sink-clipboard").files = Object.create(this.tabulatorTable.getSelectedData().length > 0 ? this.tabulatorTable.getSelectedData() : this.clipboardFiles.files);
+            this._("#file-sink-clipboard").openDialog();
+        }
+    }
+
+    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() {
+        const fileSink = this._("#file-sink-clipboard");
+        if (fileSink) {
+            this._("#file-sink-clipboard").files = Object.create(this.clipboardFiles.files);
+            this._("#file-sink-clipboard").openDialog();
+        }
+    }
+
+    clearClipboard() {
+        if(this.tabulatorTable && this.tabulatorTable.getSelectedData().length > 0) {
+            let data = {"files": this.clipboardFiles.files};
+            this.tabulatorTable.getSelectedData().forEach(toRemove =>
+                data.files = data.files.filter(file => toRemove.name !== file.name)
+            );
+            this.sendSetPropertyEvent('clipboard-files', data);
+            const event = new CustomEvent("dbp-clipboard-file-picker-file-uploaded",
+                {  bubbles: true, composed: true });
+            this.dispatchEvent(event);
+            send({
+                "summary": i18n.t('clipboard.clear-count-clipboard-title', {count: this.tabulatorTable.getSelectedData().length}),
+                "body": i18n.t('clipboard.clear-count-clipboard-body', {count: this.tabulatorTable.getSelectedData().length}),
+                "type": "success",
+                "timeout": 5,
+            });
+            this.numberOfSelectedFiles = 0;
+
+        } else {
+            let data = {"files": []};
+            this.sendSetPropertyEvent('clipboard-files', data);
+            const event = new CustomEvent("dbp-clipboard-file-picker-file-uploaded",
+                {  bubbles: true, composed: true });
+            this.dispatchEvent(event);
+            send({
+                "summary": i18n.t('clipboard.clear-clipboard-title'),
+                "body": i18n.t('clipboard.clear-clipboard-body'),
+                "type": "success",
+                "timeout": 5,
+            });
+        }
+    }
+
+    openFilesink() {
+        const fileSink = this._("#file-source");
+        if (fileSink) {
+            this._("#file-source").setAttribute("dialog-open", "");
+        }
+    }
+
+    getAdditionalButtons() {
+
+        return html`
+            <div class="flex-container">
+                       
+                        <div class="btn-flex-container-mobile">
+                            <button @click="${() => { this.openFilesink(); }}"
+                                    class="button" title="${i18n.t('clipboard.add-files')}">
+                                <dbp-icon class="nav-icon" name="clipboard"></dbp-icon> ${i18n.t('clipboard.add-files-btn')}
+                            </button>
+                            <button @click="${() => { this.clearClipboard(); }}"
+                                    class="button" title="${(this.numberOfSelectedFiles > 0) ? i18n.t('clipboard.remove-count', {count: this.numberOfSelectedFiles}) : i18n.t('clipboard.remove-all')}"
+                                    ?disabled="${this.clipboardFiles.files.length === 0}">
+                                ${(this.numberOfSelectedFiles > 0) ? i18n.t('clipboard.remove-count-btn', {count: this.numberOfSelectedFiles}) : i18n.t('clipboard.remove-all-btn')}
+                            </button>
+                        </div>
+                        <div class="btn-flex-container-mobile">
+                            <button @click="${() => { this.saveFilesFromClipboard(); }}"
+                                    ?disabled="${this.clipboardFiles.files.length === 0}"
+                                    class="button" title="${(this.numberOfSelectedFiles > 0) ? i18n.t('clipboard.save-count', {count: this.numberOfSelectedFiles}) : i18n.t('clipboard.save-all')}">
+                                ${(this.numberOfSelectedFiles > 0) ? i18n.t('clipboard.save-count-btn', {count: this.numberOfSelectedFiles}) : i18n.t('clipboard.save-all-btn')}
+                            </button>
+                        </div>
+                        
+                    </div>
+                    <dbp-file-source
+                                id="file-source"
+                                context="${i18n.t('clipboard.add-files')}"
+                                allowed-mime-types="${this.allowedMimeTypes}"
+                                nextcloud-auth-url="${this.nextcloudWebAppPasswordURL}"
+                                nextcloud-web-dav-url="${this.nextcloudWebDavURL}"
+                                nextcloud-name="${this.nextcloudName}"
+                                nextcloud-file-url="${this.nextcloudFileURL}"
+                                enabled-targets="${this.enabledTargets}"
+                                decompress-zip
+                                lang="${this.lang}"
+                                text="${i18n.t('clipboard.upload-area-text')}"
+                                button-label="${i18n.t('clipboard.upload-button-label')}"
+                                show-clipboard
+                                @dbp-file-source-file-selected="${this.saveFilesToClipboard}"
+                                @dbp-nextcloud-file-picker-number-files="${this.finishedSaveFilesToClipboard}"
+                                @dbp-file-source-file-upload-finished="${this.finishedSaveFilesToClipboard}"
+                        ></dbp-file-source>
+                    <dbp-file-sink id="file-sink-clipboard"
+                                   context="${(this.numberOfSelectedFiles > 0) ? i18n.t('clipboard.save-count', {count: this.numberOfSelectedFiles}) : i18n.t('clipboard.save-all')}"
+                                   filename="clipboard-documents.zip"
+                                   allowed-mime-types="${this.allowedMimeTypes}"
+                                   enabled-targets="${this.enabledTargets}"
+                                   show-clipboard
+                                   nextcloud-auth-url="${this.nextcloudWebAppPasswordURL}"
+                                   nextcloud-web-dav-url="${this.nextcloudWebDavURL}"
+                                   nextcloud-name="${this.nextcloudName}"
+                                   nextcloud-file-url="${this.nextcloudFileURL}"
+                                   lang="${this.lang}"
+                                   subscribe="nextcloud-file-url:nextcloud-file-url"
+                    ></dbp-file-sink>
+        `;
+    }
+
+    getClipboardSink() {
+        return html`
+            //todo header + table
+            <!-- Clipboard Footer -->
+            <div class="clipboard-footer">
+                <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>
+        `;
+    }
+
+    getClipboardSource() {
+        return html`
+            //todo header + table
+            <!-- Clipboard Footer -->
+            <div class="clipboard-footer">
+                <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>
+        `;
+    }
+
+    static get styles() {
+        // language=css
+        return css`
+            ${commonStyles.getThemeCSS()}
+            ${commonStyles.getGeneralCSS(false)}
+            ${commonStyles.getButtonCSS()}
+            ${commonStyles.getTextUtilities()}
+            ${commonStyles.getModalDialogCSS()}
+            ${commonStyles.getRadioAndCheckboxCss()}
+            ${fileHandlingStyles.getFileHandlingCss()}
+
+            a {
+                border-bottom: 1px solid rgba(0,0,0,0.3);
+                padding: 0;
+            }
+
+            a:hover {
+                color: #fff;
+                background-color: #000;
+            }
+
+            h2:first-child {
+                margin-top: 0;
+                margin-bottom: 0px;
+            }
+
+            .subheadline{
+                font-style: italic;
+                padding-left: 2em;
+                margin-top: -1px;
+                /*line-height: 1.8;*/
+                margin-bottom: 1.2em;
+            }
+            
+            .warning-container{
+                display: flex;
+                flex-direction: inherit;
+                align-items: center;
+                margin-bottom: 1.5rem;
+            }
+            
+            .warning-icon{
+                margin-right: 20px;
+                font-size: 1.5rem;
+            }
+            
+            .container{
+                margin-top: 2rem;
+            }
+            
+            .flex-container{
+                margin-bottom: 5px;
+            }
+            
+            .select-btn-wrapper{
+                float: right;
+            }
+            
+            .init{
+                margin: 0px;
+            }
+            
+            .flex-container{
+                display: flex;
+                justify-content: space-between;
+            }
+
+            .tabulator .tabulator-tableHolder .tabulator-placeholder span{
+                margin: initial;
+            }
+            
+            .checkmark{
+                height: 20px;
+                width:20px;
+            }
+            
+            .button-container .checkmark::after{
+                left: 8px;
+                top: 3px;
+                width: 4px;
+                height: 11px;
+            }
+            
+            .table-wrapper{
+                position: relative;
+            }
+            
+            .select-all-icon{
+                position: absolute;
+                top: 17px;
+                left: 10px;
+                z-index: 100;
+                height: 40px;
+            }
+
+            .clipboard-footer {
+                align-self: end;
+            }
+
+            @media only screen
+            and (orientation: portrait)
+            and (max-device-width: 765px) {
+                .flex-container{
+                    justify-content: space-between;
+                    display: flex;
+                }
+                
+                .btn-flex-container-mobile{
+                    width: 100%;
+                    display: flex;
+                    justify-content: space-between;
+                    margin-bottom: 5px;
+                }
+                
+                .select-btn-wrapper{
+                    width: 100%;
+                    display: flex;
+                    justify-content: end;
+                    float: none;
+                }
+                
+                .flex-container{
+                    display: block;
+                }
+
+                .checkmark{
+                    height: 30px;
+                    width:30px;
+                }
+
+                .button-container .checkmark::after{
+                    left: 11px;
+                    top: 4px;
+                    width: 8px;
+                    height: 15px;
+                }
+                
+
+                .select-all-icon{
+                    top: 10px;
+                    left: 10px;
+                }
+                
+                .btn-flex-container-mobile{
+                    flex-direction: column;
+                }
+                
+                .btn-flex-container-mobile button:nth-child(2){
+                    margin-top: 5px;
+                }
+
+                .clipboard-footer {
+                    padding-top: 10px;
+                    align-self: center;
+                }
+            }
+
+        `;
+    }
+
+    render() {
+        const tabulatorCss = commonUtils.getAssetURL(pkgName, 'tabulator-tables/css/tabulator.min.css');
+
+        let additionalButtons = this.showAdditionalButtons ? this.getAdditionalButtons() : "";
+
+        if (this.isFileSink)
+            return this.getClipboardSink();
+        if (this.isFileSource)
+            return this.getClipboardSource()
+
+        return html`
+            <div class="container">
+                
+                ${additionalButtons}
+                
+                <link rel="stylesheet" href="${tabulatorCss}">
+               
+                <div class="table-wrapper">
+                    <label class="button-container select-all-icon">
+                        <input type="checkbox" id="select_all" name="select_all" value="select_all" @click="${() => {this.selectAllFiles();}}">
+                        <span class="checkmark"></span>
+                    </label>
+                    <table id="clipboard-content-table" class="force-no-select"></table>
+                </div>
+            </div>
+        `;
+    }
+}
\ No newline at end of file
diff --git a/packages/file-handling/src/dbp-clipboard.js b/packages/file-handling/src/dbp-clipboard.js
new file mode 100644
index 00000000..cc8fc382
--- /dev/null
+++ b/packages/file-handling/src/dbp-clipboard.js
@@ -0,0 +1,4 @@
+import * as commonUtils from "@dbp-toolkit/common/utils";
+import {Clipboard} from './clipboard';
+
+commonUtils.defineCustomElement('dbp-clipboard', Clipboard);
diff --git a/packages/file-handling/src/file-sink.js b/packages/file-handling/src/file-sink.js
index 9326d6d9..1577aeb5 100644
--- a/packages/file-handling/src/file-sink.js
+++ b/packages/file-handling/src/file-sink.js
@@ -11,7 +11,7 @@ import FileSaver from 'file-saver';
 import MicroModal from "./micromodal.es";
 import * as fileHandlingStyles from './styles';
 import { send } from '@dbp-toolkit/common/notification';
-import {FileHandlingClipboard} from "./dbp-file-handling-clipboard";
+import {Clipboard} from "@dbp-toolkit/file-handling/src/clipboard";
 
 
 /**
@@ -46,7 +46,7 @@ export class FileSink extends ScopedElementsMixin(DBPLitElement) {
             'dbp-icon': Icon,
             'dbp-mini-spinner': MiniSpinner,
             'dbp-nextcloud-file-picker': NextcloudFilePicker,
-            'dbp-clipboard': FileHandlingClipboard,
+            'dbp-clipboard': Clipboard,
         };
     }
 
@@ -239,6 +239,7 @@ export class FileSink extends ScopedElementsMixin(DBPLitElement) {
                 <dbp-clipboard 
                    id="clipboard-file-sink"
                    subscribe="clipboard-files:clipboard-files"
+                   file-sink
                    lang="${this.lang}"
                    auth-url="${this.nextcloudAuthUrl}"
                    allowed-mime-types="${this.allowedMimeTypes}"
diff --git a/packages/file-handling/src/file-source.js b/packages/file-handling/src/file-source.js
index 9101c4fd..34005ad4 100644
--- a/packages/file-handling/src/file-source.js
+++ b/packages/file-handling/src/file-source.js
@@ -10,7 +10,7 @@ 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 {FileHandlingClipboard} from "./dbp-file-handling-clipboard";
+import {Clipboard} from "@dbp-toolkit/file-handling/src/clipboard";
 
 function mimeTypesToAccept(mimeTypes) {
     // Some operating systems can't handle mime types and
@@ -64,7 +64,7 @@ export class FileSource extends ScopedElementsMixin(DBPLitElement) {
             'dbp-icon': Icon,
             'dbp-mini-spinner': MiniSpinner,
             'dbp-nextcloud-file-picker': NextcloudFilePicker,
-            'dbp-clipboard': FileHandlingClipboard,
+            'dbp-clipboard': Clipboard,
         };
     }
 
@@ -428,7 +428,7 @@ export class FileSource extends ScopedElementsMixin(DBPLitElement) {
             return html`
                 <dbp-clipboard 
                    id="clipboard-file-picker"
-                   clipboard-source
+                   file-source
                    subscribe="clipboard-files:clipboard-files"
                    lang="${this.lang}"
                    auth-url="${this.nextcloudAuthUrl}"
diff --git a/packages/file-handling/src/i18n/de/translation.json b/packages/file-handling/src/i18n/de/translation.json
index b06a6151..cd595fd7 100644
--- a/packages/file-handling/src/i18n/de/translation.json
+++ b/packages/file-handling/src/i18n/de/translation.json
@@ -106,6 +106,41 @@
     "save-files-from-clipboard_plurafile-sinkl": "{{count}} Dateien aus der Zwischenablage speichern",
     "save-from-clipboard": "Aktuellen Inhalt aus der Zwischenablage speichern:",
     "save-from-clipboard-btn": "Zwischenablage sichern",
-    "save-to-clipboard-title": "Dateien in der Zwischenablage ablegen"
+    "save-to-clipboard-title": "Dateien in der Zwischenablage ablegen",
+    "add-files": "Dateien der Zwischenablage hinzufügen",
+    "add-files-btn": "Dateien hinzufügen",
+    "remove-all": "Alle Dateien aus der Zwischenablage entfernen.",
+    "remove-all-btn": "Alle entfernen",
+    "remove-count": "Eine Datei aus der Zwischenablage entfernen.",
+    "remove-count_plural": "{{count}} Dateien aus der Zwischenablage entfernen.",
+    "remove-count-btn": "Datei entfernen",
+    "remove-count-btn_plural": "{{count}} Dateien entfernen",
+    "save-all": "Alle Dateien aus der Zwischenablage speichern",
+    "save-all-btn": "Alle sichern",
+    "upload-area-text": "Sie können in diesem Bereich Dokumente mit einer Maximalgröße von bis zu 32MB pro Dokument per Drag & Drop oder per Direktauswahl hochladen.",
+    "upload-button-label": "Dateien auswählen",
+    "clear-clipboard-title": "Zwischenablage geleert",
+    "clear-clipboard-body": "Die Zwischenablage wurde erfolgreich geleert.",
+    "clear-count-clipboard-title": "Datei entfernt",
+    "clear-count-clipboard-title_plural": "Dateien entfernt",
+    "clear-count-clipboard-body": "Eine Datei wurde erfolgreich aus der Zwischenablage entfernt.",
+    "clear-count-clipboard-body_plural": "{{count}} Dateien wurde erfolgreich aus der Zwischenablage entfernt.",
+    "saved-files-title": "Datei erfolgreich abgelegt",
+    "saved-files-title_plural": "Dateien erfolgreich abgelegt",
+    "saved-files-body": "Eine Datei wurde erfolgreich temporär in der Zwischenablage abgelegt und kann jetzt innerhalb dieser Applikation verwendet werden.",
+    "saved-files-body_plural": "{{count}} Dateien wurde erfolgreich temporär in der Zwischenablage abgelegt und können jetzt innerhalb dieser Applikation verwendet werden.",
+    "save-count-btn": "Datei speichern",
+    "save-count-btn_plural": "{{count}} Dateien speichern",
+    "save-count": "Eine Datei aus der Zwischenablage speichern",
+    "save-count_plural": "{{count}} Dateien aus der Zwischenablage speichern",
+    "file-name": "Name",
+    "file-size": "Größe",
+    "file-type": "Art",
+    "file-mod": "Geändert",
+    "select-all": "Alle auswählen",
+    "select-all-title": "Alle verfügbaren Dateien in diesem Ordner auswählen",
+    "select-nothing": "Nichts auswählen",
+    "select-nothing-title": "Alle gewählten Dateien nicht mehr selektieren",
+    "no-data": "Keine Dateien vorhanden"
   }
 }
diff --git a/packages/file-handling/src/i18n/en/translation.json b/packages/file-handling/src/i18n/en/translation.json
index 17694556..236906f5 100644
--- a/packages/file-handling/src/i18n/en/translation.json
+++ b/packages/file-handling/src/i18n/en/translation.json
@@ -106,6 +106,41 @@
     "save-files-from-clipboard_plural": "Save {{count}} files from the clipboard",
     "save-from-clipboard": "Save the current content from the clipboard:",
     "save-from-clipboard-btn": "Save the clipboard",
-    "save-to-clipboard-title": "Cached files in the clipboard "
+    "save-to-clipboard-title": "Cached files in the clipboard ",
+    "add-files": "Add files to clipboard",
+    "add-files-btn": "Add files",
+    "remove-all": "Remove all files from the clipboard.",
+    "remove-all-btn": "Remove all",
+    "remove-count": "Remove file from the clipboard.",
+    "remove-count_plural": "Remove {{count}} files from the clipboard.",
+    "remove-count-btn": "Remove file",
+    "remove-count-btn_plural": "Remove {{count}} files",
+    "save-all": "Save all files from the clipboard",
+    "save-all-btn": "Save all",
+    "upload-area-text": "In this area you can upload documents up to a size of 32MB via Drag & Drop or by selecting them directly. ",
+    "upload-button-label": "Load to clipboard",
+    "clear-clipboard-title": "Clipboard cleared",
+    "clear-clipboard-body": "The clipboard was successfully cleared.",
+    "clear-count-clipboard-title": "File removed",
+    "clear-count-clipboard-title_plural": "Files removed",
+    "clear-count-clipboard-body": "One file was successfully removed from clipboard.",
+    "clear-count-clipboard-body_plural": "{{count}} files were successfully removed from clipboard.",
+    "saved-files-title": "File successfully filed",
+    "saved-files-title_plural": "Files successfully filed",
+    "saved-files-body": "A file was successfully stored temporarily in the clipboard and can now be used within this application.",
+    "saved-files-body_plural": "{{count}} files were successfully stored temporarily in the clipboard and can now be used within this application.",
+    "save-count-btn": "Save file",
+    "save-count-btn_plural": "Save {{count}} files",
+    "save-count": "Save one file from the clipboard",
+    "save-count_plural": "Save {{count}} files from the clipboard",
+    "file-name": "Name",
+    "file-size": "Size",
+    "file-type": "Type",
+    "file-mod": "Last modified",
+    "select-all": "Alle auswählen",
+    "select-all-title": "Alle verfügbaren Dateien in diesem Ordner auswählen",
+    "select-nothing": "Nichts auswählen",
+    "select-nothing-title": "Alle gewählten Dateien nicht mehr selektieren",
+    "no-data": "There are no files"
   }
 }
-- 
GitLab