Skip to content
Snippets Groups Projects
clipboard.js 38.36 KiB
import {createInstance} from './i18n';
import {css, html} from 'lit';
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, getShadowRootDocument} from '@dbp-toolkit/common';
import {TabulatorFull as Tabulator} from 'tabulator-tables';
import {humanFileSize} from '@dbp-toolkit/common/i18next';
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';
import {classMap} from 'lit/directives/class-map.js';

const MODE_TABLE_ONLY = 'table-only';
const MODE_FILE_SINK = 'file-sink';
const MODE_FILE_SOURCE = 'file-source';

export class Clipboard extends ScopedElementsMixin(AdapterLitElement) {
    constructor() {
        super();
        this._i18n = createInstance();
        this.lang = this._i18n.language;
        this.allowedMimeTypes = '';
        this.clipboardFiles = {files: ''};
        this.clipboardSelectBtnDisabled = true;
        this.tabulatorTable = null;
        this._onReceiveBeforeUnload = this.onReceiveBeforeUnload.bind(this);
        this.boundSelectHandler = this.selectAllFiles.bind(this);
        this.filesToSave = [];
        this.numberOfSelectedFiles = 0;
        this.enabledTargets = 'local';
        this.countUploadFiles = 0;
        this.buttonsDisabled = false;

        this.nextcloudWebAppPasswordURL = '';
        this.nextcloudWebDavURL = '';
        this.nextcloudName = '';
        this.nextcloudFileURL = '';
        this.nextcloudAuthInfo = '';
        this.nextcloudStoreSession = false;
        this.authInfo = '';

        this.allowNesting = false;

        // To avoid a cyclic dependency
        import('./file-sink').then(({FileSink}) =>
            this.defineScopedElement('dbp-file-sink', FileSink)
        );
        import('./file-source').then(({FileSource}) =>
            this.defineScopedElement('dbp-file-source', FileSource)
        );

        this.mode = MODE_TABLE_ONLY;
    }

    static get scopedElements() {
        return {
            'dbp-icon': Icon,
        };
    }

    static get properties() {
        return {
            ...super.properties,
            lang: {type: String},
            allowedMimeTypes: {type: String, attribute: 'allowed-mime-types'},
            clipboardSelectBtnDisabled: {type: Boolean},
            clipboardFiles: {type: Object, attribute: 'clipboard-files'},
            filesToSave: {type: Array, attribute: 'files-to-save'},
            numberOfSelectedFiles: {type: Number, attribute: false},
            enabledTargets: {type: String, attribute: 'enabled-targets'},
            buttonsDisabled: {type: Boolean},

            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'},
            nextcloudAuthInfo: {type: String, attribute: 'nextcloud-auth-info'},
            nextcloudStoreSession: {type: Boolean, attribute: 'nextcloud-store-session'},

            mode: {type: String, attribute: 'mode'},

            allowNesting: {type: Boolean, attribute: 'allow-nesting'},
        };
    }

    _(selector) {
        return this.shadowRoot === null
            ? this.querySelector(selector)
            : this.shadowRoot.querySelector(selector);
    }

    _a(selector) {
        return this.shadowRoot === null
            ? this.querySelectorAll(selector)
            : this.shadowRoot.querySelectorAll(selector);
    }

    update(changedProperties) {
        changedProperties.forEach((oldValue, propName) => {
            switch (propName) {
                case 'lang':
                    this._i18n.changeLanguage(this.lang);
                    break;
                case 'clipboardFiles':
                    if (this.tabulatorTable)
                        this.generateClipboardTable();
                    break;
            }
        });
        super.update(changedProperties);
    }

    toggleCollapse(e) {
        const table = this.tabulatorTable;
        // give a chance to draw the table
        // this is for getting more hight in tabulator table, when toggle is called
        setTimeout(function () {
            table.redraw();
        }, 0);
    }

    connectedCallback() {
        const i18n = this._i18n;
        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,
                placeholder: i18n.t('clipboard.no-data'),
                responsiveLayout: 'collapse',
                responsiveLayoutCollapseStartOpen: false,
                columnDefaults: {
                    vertAlign: 'middle',
                    hozAlign: 'left',
                    resizable: false,
                },
                columns: [
                    {
                        minWidth: 40,
                        headerSort: false,
                        formatter: 'responsiveCollapse',
                    },
                    {
                        title:
                            '<label class="button-container select-all-icon">' +
                            '<input type="checkbox" id="select_all" name="select_all" value="select_all">' +
                            '<span class="checkmark" id="select_all_checkmark"></span>' +
                            '</label>',
                        field: 'type',
                        hozAlign: 'center',
                        width: 50,
                        headerSort: false,
                        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}>`;
                            let div = getShadowRootDocument(this).createElement('div');
                            div.innerHTML = icon;
                            return div;

                        },
                    },
                    {
                        title: i18n.t('clipboard.file-name'),
                        responsive: 0,
                        widthGrow: 5,
                        minWidth: 150,
                        field: 'name',
                        sorter: 'alphanum',
                        formatter: (cell) => {
                            var data = cell.getValue();
                            let div = getShadowRootDocument(this).createElement('div');
                            div.classList.add('filename');
                            div.innerHTML = cell.getValue();
                            return div;
                        },
                    },
                    {
                        title: i18n.t('clipboard.file-size'),
                        responsive: 4,
                        widthGrow: 1,
                        minWidth: 84,
                        field: 'size',
                        formatter: (cell, formatterParams, onRendered) => {
                            return humanFileSize(cell.getValue());
                        },
                    },
                    {
                        title: i18n.t('clipboard.file-type'),
                        responsive: 2,
                        widthGrow: 1,
                        minWidth: 58,
                        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'},
                ],
            });

            this.tabulatorTable.on("tableBuilt", this.tableBuiltFunction.bind(this));
            this.tabulatorTable.on("rowClick", this.rowClickFunction.bind(this));
            this.tabulatorTable.on("rowSelectionChanged", this.rowSelectionChangedFunction.bind(this));
            this.tabulatorTable.on("dataLoaded", this.dataLoadedFunction.bind(this));
        });

        //Register only one beforeunload Event for the clipboard warning
        if (!window.clipboardWarning) {
            window.addEventListener('beforeunload', this._onReceiveBeforeUnload, false);
            window.clipboardWarning = true;
        }
    }

    tableBuiltFunction() {
        this.tabulatorTable.addRow([{}]).then(function (row) {
            row.delete();
        });

        this.generateClipboardTable();
        if (this._('#select_all')) {
            this._('#select_all').addEventListener('click', this.boundSelectHandler);
        }
    }

    rowClickFunction(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;
        }
    }

    rowSelectionChangedFunction(data, rows) {
        const i18n = this._i18n;

        if (this.tabulatorTable && this.tabulatorTable.getSelectedRows().length > 0) {
            this.clipboardSelectBtnDisabled = false;
        } else {
            this.clipboardSelectBtnDisabled = true;
        }

        if (this._('#select_all_checkmark')) {
            this._('#select_all_checkmark').title = this.checkAllSelected()
                ? i18n.t('clipboard.select-nothing')
                : i18n.t('clipboard.select-all');
        }
    }

    dataLoadedFunction() {
        if (this.tabulatorTable !== null) {

            const that = this;
            setTimeout(function () {
                if (that._('.tabulator-responsive-collapse-toggle-open')) {
                    that._a('.tabulator-responsive-collapse-toggle-open').forEach(
                        (element) =>
                            element.addEventListener(
                                'click',
                                that.toggleCollapse.bind(that)
                            )
                    );
                }

                if (that._('.tabulator-responsive-collapse-toggle-close')) {
                    that._a('.tabulator-responsive-collapse-toggle-close').forEach(
                        (element) =>
                            element.addEventListener(
                                'click',
                                that.toggleCollapse.bind(that)
                            )
                    );
                }
            }, 0);

        }
    }

    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();
        this.tabulatorTable.off("tableBuilt");
        this.tabulatorTable.off("rowClick");
        this.tabulatorTable.off("rowSelectionChanged");
        this.tabulatorTable.off("dataLoaded");
    }

    /**
     * Select or deselect all files from tabulator table
     *
     */
    selectAllFiles() {
        let allSelected = this.checkAllSelected();
        if (allSelected) {
            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 = this.tabulatorTable.getSelectedRows().length;
        }
    }

    /**
     * Checks if all files are already selected
     * Returns true if all files are selected
     *
     * @returns {boolean}
     */
    checkAllSelected() {
        if (this.tabulatorTable) {
            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) {
                return true;
            }
        }
        return false;
    }

    /**
     * Check mime type of a file, returns true if this.allowedMimeTypes contains the mime type of the file
     *
     * @param file
     * @returns {boolean}
     */
    checkFileType(file) {
        if (this.allowedMimeTypes === '') return true;
        // 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;
    }

    /**
     * If clipboard files and the tabulator table exists, then clear the table and sets the new data
     *
     */
    generateClipboardTable() {
        this.numberOfSelectedFiles = 0;
        if (this.clipboardFiles.files && this.tabulatorTable) {
            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],
                };
            }

            this.tabulatorTable.clearData();
            this.tabulatorTable.setData(data);

        }
        if (this._('#select_all')) {
            this._('#select_all').checked = false;
        }
    }

    /**
     * Sends the files to a provider and throws a notification
     *
     * @param files
     */
    async sendClipboardFiles(files) {
        const i18n = this._i18n;
        for (let i = 0; i < files.length; i++) {
            await this.sendFileEvent(files[i].file);
        }
        this.tabulatorTable.deselectRow();
        send({
            summary: i18n.t('clipboard.saved-files-title', {count: files.length}),
            body: i18n.t('clipboard.saved-files-body', {count: files.length}),
            type: 'success',
            timeout: 5,
        });
    }

    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) {
        const i18n = this._i18n;

        // 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 = '';
        }
    }

    /**
     * Saves files from an event to the clipboard
     *
     * @param event
     */
    saveFilesToClipboardEvent(event) {
        //save it
        let data = {};
        let files = [];
        if (this.clipboardFiles && this.clipboardFiles.files.length !== 0) {
            files = files.concat(this.clipboardFiles.files);
            files = files.concat(event.detail.file);
        } else {
            files = files.concat(event.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);
        }

        this.countUploadFiles += 1;
        if (this.countUploadFiles === event.detail.maxUpload) {
            this.buttonsDisabled = false;
            this.countUploadFiles = 0;
        } else {
            this.buttonsDisabled = true;
        }
    }

    /**
     * Saves all files from this.filesToSave in clipboard and throws a notification
     *
     */
    saveFilesToClipboard() {
        const i18n = this._i18n;

        //save it
        let data = {};
        let files = [];
        if (this.clipboardFiles && this.clipboardFiles.files.length !== 0) {
            files = files.concat(this.clipboardFiles.files);
            files = files.concat(this.filesToSave);
        } else {
            files = files.concat(this.filesToSave);
        }
        if (this.filesToSave && this.filesToSave.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);
            send({
                summary: i18n.t('clipboard.saved-files-title', {count: this.filesToSave.length}),
                body: i18n.t('clipboard.saved-files-body', {count: this.filesToSave.length}),
                type: 'success',
                timeout: 5,
            });
        }
    }

    /**
     * Throws a finish notification with the count from the event.detail
     *
     * @param event
     */
    finishedSaveFilesToClipboard(event) {
        const i18n = this._i18n;
        send({
            summary: i18n.t('clipboard.saved-files-title', {count: event.detail.count}),
            body: i18n.t('clipboard.saved-files-body', {count: event.detail.count}),
            type: 'success',
            timeout: 5,
        });
    }

    /**
     * Open the file sink with clipboardfiles
     *
     */
    openFileSink() {
        const fileSink = this._('#file-sink-clipboard');
        if (fileSink) {
            let files = Array();
            if (this.tabulatorTable.getSelectedData().length > 0) {
                this.tabulatorTable.getSelectedData().forEach((fileObject) => {
                    files.push(fileObject.file);
                });
            } else {
                files = this.clipboardFiles.files;
            }
            this._('#file-sink-clipboard').files = Object.create(files);
            this._('#file-sink-clipboard').openDialog();
        }
    }

    /**
     * Open the file source with clipboardfiles
     *
     */
    openFileSource() {
        const fileSource = this._('#file-source-clipboard');
        if (fileSource) {
            this._('#file-source-clipboard').openDialog();
        }
    }

    /**
     * Delete all or only selected files from clipboard and throws a notification
     *
     */
    clearClipboard() {
        const i18n = this._i18n;

        if (this.tabulatorTable && this.tabulatorTable.getSelectedData().length > 0) {
            let count = this.tabulatorTable.getSelectedData().length;
            this.tabulatorTable.deleteRow(this.tabulatorTable.getSelectedRows());
            let data = {files: []};
            this.tabulatorTable.getRows().forEach((row) => data.files.push(row.getData().file));
            this.sendSetPropertyEvent('clipboard-files', data);
            send({
                summary: i18n.t('clipboard.clear-count-clipboard-title', {count: count}),
                body: i18n.t('clipboard.clear-count-clipboard-body', {count: count}),
                type: 'success',
                timeout: 5,
            });
            this.numberOfSelectedFiles = 0;
        } else {
            let data = {files: []};
            this.sendSetPropertyEvent('clipboard-files', data);
            send({
                summary: i18n.t('clipboard.clear-clipboard-title'),
                body: i18n.t('clipboard.clear-clipboard-body'),
                type: 'success',
                timeout: 5,
            });
        }
    }

    /**
     * Get the additional clipboard buttons
     * If this.mode === MODE_FILE_SINK or MODE_FILE_SOURCE then there are only delete and save files buttons available
     * Else there are the add, delete and save files buttons available
     *
     * @returns {html}
     */
    getAdditionalButtons() {
        const i18n = this._i18n;
        let buttonsAreDisabled =
            this.clipboardFiles.files.length === 0 ? true : this.clipboardSelectBtnDisabled;
        buttonsAreDisabled = this.buttonsDisabled ? true : buttonsAreDisabled;
        return html`
            <div class="flex-container additional-button-container">
                <div class="btn-flex-container-mobile">
                    <button
                            id="clipboard-add-files-button"
                            @click="${() => {
                                this.openFileSource();
                            }}"
                            class="button ${classMap({
                                hidden: this.mode === MODE_FILE_SINK || this.mode === MODE_FILE_SOURCE,
                            })}"
                            title="${i18n.t('clipboard.add-files')}"
                            ?disabled="${this.buttonsDisabled}">
                        <dbp-icon class="nav-icon" name="clipboard"></dbp-icon>
                        ${i18n.t('clipboard.add-files-btn')}
                    </button>
                    <button
                            id="clipboard-remove-files-button"
                            @click="${() => {
                                this.clearClipboard();
                            }}"
                            class="button"
                            title="${this.numberOfSelectedFiles > 0
                                    ? i18n.t('clipboard.remove-count', {count: this.numberOfSelectedFiles})
                                    : i18n.t('clipboard.remove-all')}"
                            ?disabled="${buttonsAreDisabled}">
                        ${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
                            id="clipboard-save-files-button"
                            @click="${() => {
                                this.openFileSink();
                            }}"
                            ?disabled="${buttonsAreDisabled}"
                            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-clipboard"
                    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}"
                    nexcloud-auth-info="${this.nextcloudAuthInfo}"
                    ?nextcloud-store-session="${this.nextcloudStoreSession}"
                    enabled-targets="${this.allowNesting
                            ? this.enabledTargets
                            : this.enabledTargets.replace('clipboard', '')}"
                    decompress-zip
                    lang="${this.lang}"
                    text="${i18n.t('clipboard.upload-area-text')}"
                    button-label="${i18n.t('clipboard.upload-button-label')}"
                    @dbp-file-source-file-selected="${this.saveFilesToClipboardEvent}"
                    @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.allowNesting
                            ? this.enabledTargets
                            : this.enabledTargets.replace('clipboard', '')}"
                    nextcloud-auth-url="${this.nextcloudWebAppPasswordURL}"
                    nextcloud-web-dav-url="${this.nextcloudWebDavURL}"
                    nextcloud-name="${this.nextcloudName}"
                    nextcloud-file-url="${this.nextcloudFileURL}"
                    nexcloud-auth-info="${this.nextcloudAuthInfo}"
                    ?nextcloud-store-session="${this.nextcloudStoreSession}"
                    lang="${this.lang}"></dbp-file-sink>
        `;
    }

    /**
     * Get the clipboard sink html
     *
     * @returns {html}
     */
    getClipboardSink() {
        const i18n = this._i18n;
        const tabulatorCss = commonUtils.getAssetURL(
            pkgName,
            'tabulator-tables/css/tabulator.min.css'
        );
        return html`
            <div class="wrapper">
                <div class="content">
                    <h3>${i18n.t('clipboard.sink-title')}</h3>
                    <div class="warning-container">
                        <dbp-icon name="warning-high" class="warning-icon"></dbp-icon>
                        <p>${i18n.t('clipboard.warning')}</p>
                    </div>
                    <div>
                        ${this.getAdditionalButtons()}
                        <link rel="stylesheet" href="${tabulatorCss}"/>
                        <div class="table-wrapper">
                            <table id="clipboard-content-table" class="force-no-select"></table>
                        </div>
                    </div>
                </div>
                <div class="clipboard-footer">
                    <button
                            class="button select-button is-primary"
                            title="${i18n.t('clipboard.sink-btn', {count: this.filesToSave.length})}"
                            @click="${() => {
                                this.saveFilesToClipboard();
                            }}">
                        <dbp-icon class="nav-icon" name="clipboard"></dbp-icon>
                        ${i18n.t('clipboard.sink-btn', {count: this.filesToSave.length})}
                    </button>
                </div>
            </div>
        `;
    }

    /**
     * Get the clipboard source html
     *
     * @returns {html}
     */
    getClipboardSource() {
        const tabulatorCss = commonUtils.getAssetURL(
            pkgName,
            'tabulator-tables/css/tabulator.min.css'
        );
        const i18n = this._i18n;
        return html`
            <div class="wrapper">
                <div class="content">
                    <h3>${i18n.t('clipboard.source-title')}</h3>
                    <div class="warning-container">
                        <dbp-icon name="warning-high" class="warning-icon"></dbp-icon>
                        <p>${i18n.t('clipboard.warning')}</p>
                    </div>
                    <div>
                        ${this.getAdditionalButtons()}
                        <link rel="stylesheet" href="${tabulatorCss}"/>
                        <div class="table-wrapper">
                            <table id="clipboard-content-table" class="force-no-select"></table>
                        </div>
                    </div>
                </div>
                <div class="clipboard-footer">
                    <button
                            class="button select-button is-primary"
                            ?disabled="${this.clipboardSelectBtnDisabled}"
                            @click="${() => {
                                this.sendClipboardFiles(this.tabulatorTable.getSelectedData());
                            }}">
                        ${this.tabulatorTable && this.tabulatorTable.getSelectedRows().length > 0
                                ? i18n.t('clipboard.source-btn', {
                                    count: this.tabulatorTable
                                            ? this.tabulatorTable.getSelectedRows().length
                                            : 0,
                                })
                                : i18n.t('clipboard.source-btn-none')}
                    </button>
                </div>
            </div>
        `;
    }

    static get styles() {
        // language=css
        return css`
            ${commonStyles.getThemeCSS()}
            ${commonStyles.getGeneralCSS(false)}
            ${commonStyles.getButtonCSS()}
            ${commonStyles.getTextUtilities()}
            ${commonStyles.getModalDialogCSS()}
            ${commonStyles.getRadioAndCheckboxCss()}
            ${commonStyles.getTabulatorStyles()}
            ${fileHandlingStyles.getFileHandlingCss()}
            a {
                border-bottom: var(--dbp-border);
                padding: 0;
            }

            a:hover {
                color: var(--dbp-hover-color, var(--dbp-content));
                background-color: var(--dbp-hover-background-color);
            }

            h2:first-child {
                margin-top: 0;
                margin-bottom: 0px;
            }

            .subheadline {
                font-style: italic;
                padding-left: 2em;
                margin-top: -1px;
                margin-bottom: 1.2em;
            }

            .warning-container {
                display: flex;
                flex-direction: inherit;
                align-items: center;
            }

            .warning-icon {
                margin-right: 10px;
                font-size: 1.5rem;
                margin-top: -23px;
            }

            .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;
                left: 11px;
                top: 4px;
            }

            .button-container .checkmark::after {
                left: 8px;
                top: 3px;
                width: 4px;
                height: 11px;
            }

            .table-wrapper {
                position: relative;
            }

            .select-all-icon {
                height: 30px;
            }

            .clipboard-footer {
                background-color: var(--dbp-base);
                width: 100%;
                padding-top: 10px;
                display: flex;
                align-items: end;
                flex-direction: column;
            }

            .wrapper {
                width: 100%;
                height: 100%;
                display: flex;
                flex-direction: column;
                justify-content: center;
                /* position: relative; */
            }

            .content {
                width: 100%;
                height: 100%;
                overflow-y: auto;
                -webkit-overflow-scrolling: touch;
            }

            .additional-button-container {
                margin-top: 0.5rem;
            }

            .warning-container p {
                margin-top: 0px;
            }

            @media only screen and (orientation: portrait) and (max-width: 768px) {
                .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: 25px;
                    width: 25px;
                    left: 9px;
                    top: 2px;
                }

                .button-container .checkmark::after {
                    left: 8px;
                    top: 2px;
                    width: 8px;
                    height: 15px;
                }

                .select-all-icon {
                    height: 32px;
                }

                .btn-flex-container-mobile {
                    flex-direction: column;
                }

                .btn-flex-container-mobile button:nth-child(2) {
                    margin-top: 5px;
                }

                .warning-icon {
                    margin-right: 10px;
                    font-size: 85px;
                    margin-top: -43px;
                }
            }
        `;
    }

    render() {
        const tabulatorCss = commonUtils.getAssetURL(
            pkgName,
            'tabulator-tables/css/tabulator.min.css'
        );

        if (this.mode === MODE_FILE_SINK) {
            return this.getClipboardSink();
        } else if (this.mode === MODE_FILE_SOURCE) {
            return this.getClipboardSource();
        } else {
            return html`
                <div>
                    ${this.getAdditionalButtons()}
                    <link rel="stylesheet" href="${tabulatorCss}"/>
                    <div class="table-wrapper">
                        <table id="clipboard-content-table" class="force-no-select"></table>
                    </div>
                </div>
            `;
        }
    }
}