Skip to content
Snippets Groups Projects
dbp-file-handling-clipboard.js 24.1 KiB
Newer Older
import {i18n} from './i18n';
import {css, html} from 'lit-element';
import {ScopedElementsMixin} from '@open-wc/scoped-elements';
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";

 */
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);
        this.filesToSave = null;

        this.nextcloudAuthUrl = '';
        this.nextcloudWebDavUrl = '';
        this.nextcloudName ='Nextcloud';
        this.nextcloudPath = '';
        this.nextcloudFileURL = '';
        // To avoid a cyclic dependency
        import('./file-sink').then(({ FileSink }) => this.defineScopedElement('dbp-file-sink', FileSink));
    }

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

    /**
     * See: https://lit-element.polymer-project.org/guide/properties#initialize
     */
    static get properties() {
        return {
            ...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'},
            filesToSave: {type: Object, attribute: 'files-to-save'},
            nextcloudAuthUrl: {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'},
        };

    }

    update(changedProperties) {
        changedProperties.forEach((oldValue, propName) => {
            switch (propName) {
                case "lang":
                    i18n.changeLanguage(this.lang);
                    break;
                case "clipboardFiles":
                    this.generateClipboardTable();
                    break;
            }
        });

        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(() => {
            if (this.clipboardSource) {
                // 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.getScopedTagName("dbp-icon");
                                return `<${icon_tag} name="empty-file" class="nextcloud-picker-icon"></${icon_tag}>`;
                        },
                        {
                            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) => {
                        this.showSelectAllButton = !(this.tabulatorTable !== null
                            && this.tabulatorTable.getSelectedRows().length === this.tabulatorTable.getRows().filter(row => this.checkFileType(row.getData())).length);
                    rowSelectionChanged: (data, rows) => {
                        this.clipboardSelectBtnDisabled = !(this.tabulatorTable && this.tabulatorTable.getSelectedRows().length > 0);
        if (!this.clipboardSource) {
            if (!window.clipboardWarning)  {
                window.addEventListener('beforeunload', this._onReceiveBeforeUnload, false);
                window.clipboardWarning = true;
            }
    }

    /**
     * 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;
        }
    }

    /**
     * Deselect files from tabulator table
     *
     */
    deselectAll() {
        this.tabulatorTable.deselectRow();
        this.showSelectAllButton = true;
    }

    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);
            }
        for (let i = 0; i < files.length; i ++) {
        //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;
        }

        // we need to handle custom events ourselves
        if (event.target && event.target.activeElement && event.target.activeElement.nodeName) {
Steinwender, Tamara's avatar
Steinwender, Tamara committed
            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(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
        if (this.filesToSave && this.filesToSave.length !== 0) {
            data = {"files": this.filesToSave};
            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('file-sink.save-to-clipboard-title'),
                "body": i18n.t('file-sink.save-to-clipboard-body', {count: this.filesToSave.length}),
    getClipboardFileList() {
        let files = [];
        for(let i = 0; i < this.clipboardFiles.files.length; i ++)
        {
Steinwender, Tamara's avatar
Steinwender, Tamara committed
            files[i] =  html`
                <div class="clipboard-list">
                    <div class="clipboard-list-name"><strong>${this.clipboardFiles.files[i].name}</strong></div>
                    <div class="clipboard-list-size">${humanFileSize(this.clipboardFiles.files[i].size)}</div>
                </div>`;
        }
        return files;
    }

    /**
     * Open Filesink for multiple files
     */
    async openClipboardFileSink() {
        this._("#file-sink-clipboard").files = Object.create(this.clipboardFiles.files);
        console.log("------ this.clipboardFiles.files;", this.clipboardFiles.files);
        this._("#file-sink-clipboard").openDialog();
    static get styles() {
        // language=css
        return css`
            ${commonStyles.getGeneralCSS()}
            ${commonStyles.getButtonCSS()}
            ${commonStyles.getTextUtilities()}
            ${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 {
            .clipboard-container .wrapper .inner {
            .clipboard-footer {
            #select-all-wrapper {
            .clipboard-container {
                display: flex;
                flex-direction: column;
                justify-content: center;
            }

            .clipboard-container.table {
            .clipboard-container .inner {
Steinwender, Tamara's avatar
Steinwender, Tamara committed
                overflow-y: scroll;
Steinwender, Tamara's avatar
Steinwender, Tamara committed
                margin-bottom: 2rem;
            .warning-container {
Steinwender, Tamara's avatar
Steinwender, Tamara committed
                margin-top: 2rem;
                margin-bottom: 2rem;
Steinwender, Tamara's avatar
Steinwender, Tamara committed
                text-align: left;
            }
Steinwender, Tamara's avatar
Steinwender, Tamara committed
                margin-bottom: 1rem;
                margin-top: 1rem;
Steinwender, Tamara's avatar
Steinwender, Tamara committed
                border-top: 1px solid #eee;
                display: flex;
                justify-content: space-between;
Steinwender, Tamara's avatar
Steinwender, Tamara committed
                display: flex;
                justify-content: space-between;
            }

            .border {
                border-top: solid 1px #888;
Steinwender, Tamara's avatar
Steinwender, Tamara committed
                width: 80%;
                overflow: hidden;
                white-space: nowrap;
                text-overflow: ellipsis;
            }

            .clipboard-list-size {

            @media only screen
            and (orientation: portrait)
            and (max-device-width: 765px) {
                .clipboard-container p, .clipboard-container h3 {
Steinwender, Tamara's avatar
Steinwender, Tamara committed
                    padding-top: 10px;
                    align-self: center;
                }
Steinwender, Tamara's avatar
Steinwender, Tamara committed
                    flex-direction: column;
                .clipboard-container .inner {
Steinwender, Tamara's avatar
Steinwender, Tamara committed
                    overflow-y: auto;
                }
        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">
Steinwender, Tamara's avatar
Steinwender, Tamara committed
                        <h3>${i18n.t('clipboard.save-to-clipboard-title')}</h3>
                        <p>${i18n.t('file-sink.save-to-clipboard-text')}</p>
Steinwender, Tamara's avatar
Steinwender, Tamara committed
                         <div class="warning-container">
                            <dbp-icon name="warning" class="warning-icon"></dbp-icon>
                            <p>${i18n.t('file-sink.save-to-clipboard-warning')}</p>
                        </div>
Steinwender, Tamara's avatar
Steinwender, Tamara committed
                        <div class="${classMap({"button-wrapper": this.clipboardFiles.files.length !== 0})}">
                            <button id="clipboard-download-button"
                                    class="button is-right clipboard-btn ${classMap({"hidden": this.clipboardFiles.files.length === 0})}"
                                    @click="${this.openClipboardFileSink}">
                                ${i18n.t('clipboard.save-from-clipboard-btn')}
                            </button>
                            <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.filesToSave ? this.filesToSave.length : 0})}
                            </button>
                        </div>
                        <div class="border ${classMap({"hidden": this.clipboardFiles.files.length === 0})}">
Steinwender, Tamara's avatar
Steinwender, Tamara committed
                        <div class="clipboard-data ${classMap({"hidden": this.clipboardFiles.files.length === 0})}">
Steinwender, Tamara's avatar
Steinwender, Tamara committed
                            <h4>${i18n.t('file-sink.clipboard-files', {count: this.clipboardFiles.files.length})}</h4>
Steinwender, Tamara's avatar
Steinwender, Tamara committed
                            <dbp-file-sink id="file-sink-clipboard"
                            context="${i18n.t('clipboard.save-files-from-clipboard', {count: this.clipboardFiles ? this.clipboardFiles.files.length : 0})}"
                            filename="clipboard-documents.zip"
                            enabled-targets="local,nextcloud"
                            nextcloud-auth-url="${this.nextcloudAuthUrl}"
                            nextcloud-web-dav-url="${this.nextcloudWebDavUrl}"
                            nextcloud-name="${this.nextcloudName}"
                            nextcloud-file-url="${this.nextcloudFileURL}"
                            fullsize-modal="true"
                            lang="${this.lang}"
                            ></dbp-file-sink>
Steinwender, Tamara's avatar
Steinwender, Tamara committed
                            <details>   
                                <summary>Dateien anzeigen</summary>
                                <p>${i18n.t('file-sink.clipboard-files-overwrite')}</p>
                                ${this.getClipboardFileList()}
                            </details>