import {i18n} from './i18n'; import {css, html} from 'lit-element'; import {ScopedElementsMixin} from '@open-wc/scoped-elements'; import VPULitElement from 'vpu-common/vpu-lit-element'; import {MiniSpinner} from 'vpu-common'; import * as commonUtils from 'vpu-common/utils'; import * as commonStyles from 'vpu-common/styles'; // `import {createClient} from 'webdav/web';` didn't seem to work if fileupload demo page is built // `import createClient from 'webdav/web';` didn't seem to work if Signature project is built import * as webDavWeb from 'webdav/web'; import {classMap} from 'lit-html/directives/class-map.js'; import {humanFileSize} from 'vpu-common/i18next'; import Tabulator from 'tabulator-tables'; /** * NextcloudFilePicker web component */ export class NextcloudFilePicker extends ScopedElementsMixin(VPULitElement) { constructor() { super(); this.lang = 'de'; this.authUrl = ''; this.webDavUrl = ''; this.loginWindow = null; this.isPickerActive = false; this.statusText = ""; this.lastDirectoryPath = "/"; this.directoryPath = "/"; this.webDavClient = null; this.tabulatorTable = null; this._onReceiveWindowMessage = this.onReceiveWindowMessage.bind(this); } static get scopedElements() { return { 'vpu-mini-spinner': MiniSpinner, }; } /** * See: https://lit-element.polymer-project.org/guide/properties#initialize */ static get properties() { return { lang: { type: String }, authUrl: { type: String, attribute: "auth-url" }, webDavUrl: { type: String, attribute: "web-dav-url" }, isPickerActive: { type: Boolean, attribute: false }, statusText: { type: String, attribute: false }, directoryPath: { type: String, attribute: false }, }; } update(changedProperties) { changedProperties.forEach((oldValue, propName) => { switch (propName) { case "lang": i18n.changeLanguage(this.lang); break; } }); super.update(changedProperties); } disconnectedCallback() { window.removeEventListener('message', this._onReceiveWindowMessage); super.disconnectedCallback(); } connectedCallback() { super.connectedCallback(); this.updateComplete.then(() => { // see: https://developer.mozilla.org/en-US/docs/Web/API/Window/postMessage window.addEventListener('message', this._onReceiveWindowMessage); // http://tabulator.info/docs/4.7 // TODO: format size and lastmod // TODO: translation of column headers // TODO: mime type icon this.tabulatorTable = new Tabulator(this._("#directory-content-table"), { layout: "fitDataStretch", selectable: true, columns: [ {title: "Filename", field: "basename"}, {title: "Size", field: "size", formatter: (cell, formatterParams, onRendered) => { return cell.getRow().getData().type === "directory" ? "" : humanFileSize(cell.getValue());}}, {title: "Type", field: "type"}, {title: "Mime", field: "mime"}, {title: "Last modified", field: "lastmod", sorter: "date"}, ], rowClick: (e, row) => { const data = row.getData(); switch(data.type) { case "directory": this.directoryClicked(e, data); break; case "file": console.log("file selected", data); break; } }, }); }); } openFilePicker() { // TODO: translation this.statusText = "Auth in progress"; this.loginWindow = window.open(this.authUrl, "Nextcloud Login", "width=400,height=400,menubar=no,scrollbars=no,status=no,titlebar=no,toolbar=no"); } onReceiveWindowMessage(event) { const data = event.data; console.log("data", data); if (data.type === "webapppassword") { this.loginWindow.close(); // alert("Login name: " + data.loginName + "\nApp password: " + data.token); const apiUrl = this.webDavUrl + "/" + data.loginName; // https://github.com/perry-mitchell/webdav-client/blob/master/API.md#module_WebDAV.createClient this.webDavClient = webDavWeb.createClient( apiUrl, { username: data.loginName, password: data.token } ); this.loadDirectory("/"); } } /** * Loads the directory from WebDAV * * @param path */ loadDirectory(path) { // TODO: translation this.statusText = "Loading directory from Nextcloud: " + path; this.lastDirectoryPath = this.directoryPath; this.directoryPath = path; // https://github.com/perry-mitchell/webdav-client#getdirectorycontents this.webDavClient .getDirectoryContents(path, {details: true}) .then(contents => { console.log("contents", contents); this.statusText = ""; this.tabulatorTable.setData(contents.data); this.isPickerActive = true; }).catch(error => { console.error(error.message); this.statusText = error.message; this.isPickerActive = false; }); } directoryClicked(event, file) { this.loadDirectory(file.filename); event.preventDefault(); } downloadFiles(files) { files.forEach((fileData) => this.downloadFile(fileData)); } downloadFile(fileData) { this.statusText = "Loading " + fileData.filename + "..."; // https://github.com/perry-mitchell/webdav-client#getfilecontents this.webDavClient .getFileContents(fileData.filename) .then(contents => { // create file to send via event const file = new File([contents], fileData.basename, { type: fileData.mime }); console.log("binaryFile", file); // send event const data = {"file": file, "data": fileData}; const event = new CustomEvent("vpu-nextcloud-file-picker-file-downloaded", { "detail": data, bubbles: true, composed: true }); this.dispatchEvent(event); this.statusText = ""; }).catch(error => { console.error(error.message); this.statusText = error.message; }); } /** * Returns the parent directory path * * @returns {string} parent directory path */ getParentDirectoryPath() { let path = this.directoryPath.replace(/\/$/, ""); path = path.replace(path.split("/").pop(), "").replace(/\/$/, ""); return (path === "") ? "/" : path; } static get styles() { // language=css return css` ${commonStyles.getGeneralCSS()} ${commonStyles.getButtonCSS()} .block { margin-bottom: 10px; } `; } render() { commonUtils.initAssetBaseURL('vpu-tabulator-table'); const tabulatorCss = commonUtils.getAssetURL('local/vpu-fileupload/tabulator-tables/css/tabulator.min.css'); console.log("tabulatorCss", tabulatorCss); return html` <link rel="stylesheet" href="${tabulatorCss}"> <div class="block"> <button class="button" title="${i18n.t('nextcloud-file-picker.open-nextcloud-file-picker')}" @click="${async () => { this.openFilePicker(); } }">${i18n.t('nextcloud-file-picker.open')}</button> </div> <div class="block ${classMap({hidden: this.statusText === ""})}"> <vpu-mini-spinner style="font-size: 0.7em"></vpu-mini-spinner> ${this.statusText} </div> <div class="block ${classMap({hidden: !this.isPickerActive})}"> <h2>${this.directoryPath}</h2> <button class="button is-small" title="${i18n.t('nextcloud-file-picker.folder-last')}" @click="${() => { this.loadDirectory(this.lastDirectoryPath); }}">⇦</button> <button class="button is-small" title="${i18n.t('nextcloud-file-picker.folder-up')}" @click="${() => { this.loadDirectory(this.getParentDirectoryPath()); }}">⇧</button> <table id="directory-content-table"></table> <button class="button" title="${i18n.t('nextcloud-file-picker.folder-up')}" @click="${() => { this.downloadFiles(this.tabulatorTable.getSelectedData()); }}">${i18n.t('nextcloud-file-picker.select-files')}</button> </div> `; } }