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 * as commonUtils from "@dbp-toolkit/common/utils"; import {Icon, MiniSpinner} from '@dbp-toolkit/common'; import * as commonStyles from '@dbp-toolkit/common/styles'; import {NextcloudFilePicker} from "./dbp-nextcloud-file-picker"; import {classMap} from 'lit-html/directives/class-map.js'; import FileSaver from 'file-saver'; import MicroModal from "./micromodal.es"; import * as fileHandlingStyles from './styles'; import { send } from '@dbp-toolkit/common/notification'; import {humanFileSize} from '@dbp-toolkit/common/i18next'; /** * FileSink web component */ export class FileSink extends ScopedElementsMixin(DBPLitElement) { constructor() { super(); this.context = ''; this.lang = 'de'; this.nextcloudAuthUrl = ''; this.nextcloudWebDavUrl = ''; this.nextcloudName ='Nextcloud'; this.nextcloudPath = ''; this.nextcloudFileURL = ''; this.text = ''; this.buttonLabel = ''; this.filename = "files.zip"; this.files = []; this.activeTarget = 'local'; this.isDialogOpen = false; this.enabledTargets = 'local'; this.firstOpen = true; this.initialFileHandlingState = {target: '', path: ''}; this.clipBoardFiles = {files: ''}; } static get scopedElements() { return { 'dbp-icon': Icon, 'dbp-mini-spinner': MiniSpinner, 'dbp-nextcloud-file-picker': NextcloudFilePicker, }; } /** * See: https://lit-element.polymer-project.org/guide/properties#initialize */ static get properties() { return { ...super.properties, context: {type: String, attribute: 'context'}, lang: {type: String}, filename: {type: String}, files: {type: Array, attribute: false}, enabledTargets: {type: String, attribute: 'enabled-targets'}, 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'}, text: {type: String}, buttonLabel: {type: String, attribute: 'button-label'}, isDialogOpen: {type: Boolean, attribute: false}, activeTarget: {type: String, attribute: 'active-target'}, firstOpen: {type: Boolean, attribute: false}, nextcloudPath: {type: String, attribute: false}, initialFileHandlingState: {type: Object, attribute: 'initial-file-handling-state'}, clipBoardFiles: {type: Object, attribute: 'clipboard-files'}, }; } connectedCallback() { super.connectedCallback(); this.updateComplete.then(() => { console.log("initialFileHandlingState", this.initialFileHandlingState); }); } async downloadCompressedFiles() { // see: https://stuk.github.io/jszip/ let JSZip = (await import('jszip/dist/jszip.js')).default; let zip = new JSZip(); let fileNames = []; // download one file not compressed! if (this.files.length === 1) { FileSaver.saveAs(this.files[0], this.files[0].filename); this.closeDialog(); return; } // download all files compressed this.files.forEach((file) => { let fileName = file.name; // add pseudo-random string on duplicate file name if (fileNames.indexOf(fileName) !== -1) { fileName = commonUtils.getBaseName(fileName) + "-" + Math.random().toString(36).substring(7) + "." + commonUtils.getFileExtension(fileName); } fileNames.push(fileName); zip.file(fileName, file); }); let content = await zip.generateAsync({type:"blob"}); // see: https://github.com/eligrey/FileSaver.js#readme FileSaver.saveAs(content, this.filename || "files.zip"); this.closeDialog(); } update(changedProperties) { changedProperties.forEach((oldValue, propName) => { switch (propName) { case "lang": i18n.changeLanguage(this.lang); break; case "enabledTargets": if (!this.hasEnabledDestination(this.activeTargets)) { this.activeTargets = this.enabledTargets.split(",")[0]; } break; case "files": if (this.files.length !== 0 && !this.isDialogOpen) { this.openDialog(); } break; case "initialFileHandlingState": //check if default destination is set if (this.firstOpen) { this.nextcloudPath = this.initialFileHandlingState.path; } break; } }); super.update(changedProperties); } hasEnabledDestination(source) { return this.enabledTargets.split(',').includes(source); } async uploadToNextcloud(directory) { let that = this; const element = that._('#nextcloud-file-picker'); await element.uploadFiles(that.files, directory); } finishedFileUpload(event) { this.sendDestination(); MicroModal.close(this._('#modal-picker')); if (event.detail > 0) { send({ "summary": i18n.t('file-sink.upload-success-title'), "body": i18n.t('file-sink.upload-success-body', {name: this.nextcloudName, count: event.detail}), "type": "success", "timeout": 5, }); } } sendDestination() { let data = {}; if (this.activeTarget === 'nextcloud') { data = {"target": this.activeTarget, "path": this._("#nextcloud-file-picker").directoryPath}; } else { data = {"target": this.activeTarget}; } this.sendSetPropertyEvent('initial-file-handling-state', data); } preventDefaults (e) { e.preventDefault(); e.stopPropagation(); } loadWebdavDirectory() { if (this._('#nextcloud-file-picker').webDavClient !== null) { this._('#nextcloud-file-picker').loadDirectory(this._('#nextcloud-file-picker').directoryPath); } } openDialog() { this.loadWebdavDirectory(); MicroModal.show(this._('#modal-picker'), { disableScroll: true, onClose: modal => { this.isDialogOpen = false; }, }); console.log("initialFileHandlingState", this.initialFileHandlingState); //check if default destination is set if (this.initialFileHandlingState.target !== '' && typeof this.initialFileHandlingState.target !== 'undefined' && this.firstOpen) { this.activeTarget = this.initialFileHandlingState.target; this.nextcloudPath = this.initialFileHandlingState.path; if (this._('#nextcloud-file-picker').webDavClient !== null) { this._('#nextcloud-file-picker').loadDirectory(this.initialFileHandlingState.path); console.log("load default nextcloud sink", this.initialFileHandlingState.path); } this.firstOpen = false; } } saveFilesToClipboard() { //save it let data = {}; if (this.files.length !== 0) { data = {"files": this.files}; this.sendSetPropertyEvent('clipboard-files', data); this.closeDialog(); send({ "summary": i18n.t('file-sink.save-to-clipboard-title'), "body": i18n.t('file-sink.save-to-clipboard-body', {count: this.files.length}), "type": "success", "timeout": 5, }); console.log("--------------", this.clipBoardFiles); } } getClipboardFiles() { let files = []; for(let i = 0; i < this.clipBoardFiles.files.length; i ++) { files[i] = html`<div class="clipboard-list"><strong>${this.clipBoardFiles.files[i].name}</strong> ${humanFileSize(this.clipBoardFiles.files[i].size)}</div>`; } return files; } closeDialog(e) { this.sendDestination(); MicroModal.close(this._('#modal-picker')); } static get styles() { // language=css return css` ${commonStyles.getThemeCSS()} ${commonStyles.getGeneralCSS()} ${commonStyles.getButtonCSS()} ${commonStyles.getModalDialogCSS()} ${fileHandlingStyles.getFileHandlingCss()} #zip-download-block { height: 100%; width: 100%; display: flex; flex-direction: column; justify-content: center; align-items: center; } .block { margin-bottom: 10px; } .warning-icon{ font-size: 2rem; padding: 0 1rem; } .clipboard-btn{ margin-top: 1.5rem; margin-bottom: 1.5rem; } .warning-container{ display: flex; max-width: 400px; } .clipboard-data h4{ margin-top: 2rem; } .clipboard-data p{ margin-bottom: 1rem; } .clipboard-list{ padding: 1rem 0; border-top: 1px solid #eee; } @media only screen and (orientation: portrait) and (max-device-width: 765px) { .clipboard-container p, .clipboard-container h3{ text-align: center; } .warning-container{ flex-direction: column; align-items: center; } .warning-icon{ margin-bottom: 1rem; } } `; } render() { return html` <vpu-notification lang="de" client-id="my-client-id"></vpu-notification> <div class="modal micromodal-slide" id="modal-picker" aria-hidden="true"> <div class="modal-overlay" tabindex="-1"> <div class="modal-container" role="dialog" aria-modal="true" aria-labelledby="modal-picker-title"> <nav class="modal-nav"> <div title="${i18n.t('file-sink.nav-local')}" @click="${() => { this.activeTarget = "local"; }}" class="${classMap({"active": this.activeTarget === "local", hidden: !this.hasEnabledDestination("local")})}"> <dbp-icon class="nav-icon" name="laptop"></dbp-icon> <p>${i18n.t('file-source.nav-local')}</p> </div> <div title="${this.nextcloudName}" @click="${() => { this.activeTarget = "nextcloud"; this.loadWebdavDirectory();}}" class="${classMap({"active": this.activeTarget === "nextcloud", hidden: !this.hasEnabledDestination("nextcloud") || this.nextcloudWebDavUrl === "" || this.nextcloudAuthUrl === ""})}"> <dbp-icon class="nav-icon" name="cloud"></dbp-icon> <p> ${this.nextcloudName} </p> </div> <div title="Clipboard" @click="${() => { this.activeTarget = "clipboard"; }}" class="${classMap({"active": this.activeTarget === "clipboard", hidden: !this.hasEnabledDestination("clipboard") })}"> <dbp-icon class="nav-icon" name="clipboard"></dbp-icon> <p>Clipboard</p> </div> </nav> <div class="modal-header"> <button title="${i18n.t('file-sink.modal-close')}" class="modal-close" aria-label="Close modal" @click="${() => { this.closeDialog();}}"> <dbp-icon title="${i18n.t('file-sink.modal-close')}" name="close" class="close-icon"></dbp-icon> </button> <p class="modal-context"> ${this.context}</p> </div> <main class="modal-content" id="modal-picker-content"> <div class="source-main ${classMap({"hidden": this.activeTarget !== "local"})}"> <div id="zip-download-block"> <div class="block"> ${this.text || i18n.t('file-sink.local-intro', {'count': this.files.length})} </div> <button class="button is-primary" ?disabled="${this.disabled}" @click="${() => { this.downloadCompressedFiles(); }}"> ${this.buttonLabel || i18n.t('file-sink.local-button', {'count': this.files.length})} </button> </div> </div> <div class="source-main ${classMap({"hidden": this.activeTarget !== "nextcloud" || this.nextcloudWebDavUrl === "" || this.nextcloudAuthUrl === ""})}"> <dbp-nextcloud-file-picker id="nextcloud-file-picker" class="${classMap({hidden: this.nextcloudWebDavUrl === "" || this.nextcloudAuthUrl === ""})}" directories-only max-selected-items="1" select-button-text="${i18n.t('file-sink.select-directory')}" ?disabled="${this.disabled}" lang="${this.lang}" auth-url="${this.nextcloudAuthUrl}" web-dav-url="${this.nextcloudWebDavUrl}" nextcloud-name="${this.nextcloudName}" directory-path="${this.nextcloudPath}" nextcloud-file-url="${this.nextcloudFileURL}" @dbp-nextcloud-file-picker-file-uploaded="${(event) => { this.uploadToNextcloud(event.detail); }}" @dbp-nextcloud-file-picker-file-uploaded-finished="${(event) => { this.finishedFileUpload(event); }}"></dbp-nextcloud-file-picker> </div> <div class="source-main ${classMap({"hidden": this.activeTarget !== "clipboard"})}"> <div class="block clipboard-container"> <h3>${i18n.t('file-sink.save-to-clipboard-title')}</h3> <p>${i18n.t('file-sink.save-to-clipboard-text')}</p> <button class="button is-primary clipboard-btn" ?disabled="${this.disabled}" @click="${() => { this.saveFilesToClipboard(); }}"> ${this.buttonLabel || i18n.t('file-sink.save-to-clipboard-btn', {count:this.files.length})} </button> <div class="warning-container"> <dbp-icon name="warning" class="warning-icon"></dbp-icon> <p>${i18n.t('file-sink.save-to-clipboard-warning')}</p> </div> <div class="clipboard-data ${classMap({"hidden": this.clipBoardFiles.files.length === 0})}"> <h4>Aktuell vorhandene Dateien im Clipboard</h4> <p>Folgende Dateien werden überschrieben:</p> ${this.getClipboardFiles()} </div> </div> </div> </main> </div> </div> </div> `; } }