Skip to content
Snippets Groups Projects
Commit 7417d7e3 authored by Bekerle, Patrizio's avatar Bekerle, Patrizio :fire: Committed by Reiter, Christoph
Browse files

Integrate Nextcloud Filepicker (#4)

parent 755d7404
No related branches found
No related tags found
No related merge requests found
......@@ -27,10 +27,16 @@ Files will be uploaded sequentially (not parallel) to prevent overburdening the
- example `<vpu-fileupload allowed-mime-types='image/png,text/plain'></vpu-fileupload>` ... PNGs or TXTs only
- example `<vpu-fileupload allowed-mime-types='*/*'></vpu-fileupload>` ... all file types (default)
- `disabled` (optional): disable input control
- example `<vpu-fileupload disabled>`
- example `<vpu-fileupload disabled></vpu-fileupload>`
- `decompress-zip` (optional): decompress zip file and queue the contained files (including files in folders)
- example `<vpu-fileupload decompress-zip>`
- example `<vpu-fileupload decompress-zip></vpu-fileupload>`
- mime types of `allowed-mime-types` will also be checked for the files in the zip file
- `nextcloud-auth-url` (optional): Nextcloud Auth Url to use with the Nextcloud file picker
- example `<vpu-fileupload nextcloud-auth-url="http://localhost:8081/index.php/apps/webapppassword"></vpu-fileupload>`
- `nextcloud-web-dav-url` also needs to be set for the Nextcloud file picker to be active
- `nextcloud-web-dav-url` (optional): Nextcloud WebDav Url to use with the Nextcloud file picker
- example `<vpu-fileupload nextcloud-web-dav-url="http://localhost:8081/remote.php/dav/files"></vpu-fileupload>`
- `nextcloud-auth-url` also needs to be set for the Nextcloud file picker to be active
## Local development
......
......@@ -29,7 +29,9 @@
"i18next": "^19.4.2",
"lit-element": "^2.1.0",
"lit-html": "^1.1.1",
"material-design-icons-svg": "^3.0.0"
"material-design-icons-svg": "^3.0.0",
"tabulator-tables": "^4.7.0",
"webdav": "^3.3.0"
},
"scripts": {
"clean": "rm dist/*",
......
......@@ -44,6 +44,7 @@ export default {
{src: 'assets/index.html', dest: 'dist'},
{src: 'assets/favicon.ico', dest: 'dist'},
{src: 'node_modules/material-design-icons-svg/paths/*.json', dest: 'dist/local/vpu-common/icons'},
{src: 'node_modules/tabulator-tables/dist/css', dest: 'dist/local/fileupload/tabulator-tables'},
],
}),
(process.env.ROLLUP_WATCH === 'true') ? serve({contentBase: 'dist', host: '127.0.0.1', port: 8002}) : false
......
......@@ -6,6 +6,8 @@ import VPULitElement from 'vpu-common/vpu-lit-element';
import * as commonUtils from "vpu-common/utils";
import {Icon, MiniSpinner} from 'vpu-common';
import * as commonStyles from 'vpu-common/styles';
import {NextcloudFilePicker} from "./vpu-nextcloud-file-picker";
import {classMap} from 'lit-html/directives/class-map.js';
function mimeTypesToAccept(mimeTypes) {
......@@ -34,6 +36,8 @@ export class FileUpload extends ScopedElementsMixin(VPULitElement) {
super();
this.lang = 'de';
this.url = '';
this.nextcloudAuthUrl = '';
this.nextcloudWebDavUrl = '';
this.dropArea = null;
this.allowedMimeTypes = '*/*';
this.text = '';
......@@ -53,6 +57,7 @@ export class FileUpload extends ScopedElementsMixin(VPULitElement) {
return {
'vpu-icon': Icon,
'vpu-mini-spinner': MiniSpinner,
'vpu-nextcloud-file-picker': NextcloudFilePicker,
};
}
......@@ -64,6 +69,8 @@ export class FileUpload extends ScopedElementsMixin(VPULitElement) {
lang: { type: String },
url: { type: String },
allowedMimeTypes: { type: String, attribute: 'allowed-mime-types' },
nextcloudAuthUrl: { type: String, attribute: 'nextcloud-auth-url' },
nextcloudWebDavUrl: { type: String, attribute: 'nextcloud-web-dav-url' },
text: { type: String },
buttonLabel: { type: String, attribute: 'button-label' },
uploadInProgress: { type: Boolean, attribute: false },
......@@ -451,6 +458,14 @@ export class FileUpload extends ScopedElementsMixin(VPULitElement) {
multiple
accept="${mimeTypesToAccept(allowedMimeTypes)}"
name='file'>
<vpu-nextcloud-file-picker class="${classMap({hidden: this.nextcloudWebDavUrl === "" || this.nextcloudAuthUrl === ""})}"
?disabled="${this.uploadInProgress || this.disabled}"
lang="${this.lang}"
auth-url="${this.nextcloudAuthUrl}"
web-dav-url="${this.nextcloudWebDavUrl}"
@vpu-nextcloud-file-picker-file-downloaded="${(event) => {
this.queueFile(event.detail.file);
}}"></vpu-nextcloud-file-picker>
<label class="button is-primary" for="fileElem" ?disabled="${this.disabled}">
<vpu-icon style="display: ${this.uploadInProgress ? "inline-block" : "none"}" name="lock"></vpu-icon>
${this.buttonLabel || i18n.t('upload-label')}
......
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); }}">&#8678;</button>
<button class="button is-small"
title="${i18n.t('nextcloud-file-picker.folder-up')}"
@click="${() => { this.loadDirectory(this.getParentDirectoryPath()); }}">&#8679;</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>
`;
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment