[NEW] Webdav File Picker (#14879)

pull/15021/head
Utkarsh Barsaiyan 7 years ago committed by Guilherme Gazzo
parent 7db998274b
commit 37527c05c4
  1. 158
      app/theme/client/imports/components/contextual-bar.css
  2. 1
      app/webdav/client/index.js
  3. 3
      app/webdav/client/startup/messageBoxActions.js
  4. 258
      app/webdav/client/webdavFilePicker.css
  5. 133
      app/webdav/client/webdavFilePicker.html
  6. 133
      app/webdav/client/webdavFilePicker.js
  7. 6
      app/webdav/server/methods/getFileFromWebdav.js
  8. 6
      app/webdav/server/methods/getWebdavFileList.js

@ -163,123 +163,125 @@
}
}
.attachments {
&__item {
position: relative;
.flex-tab__result {
.attachments {
&__item {
position: relative;
margin-bottom: 10px;
margin-bottom: 10px;
transition: background-color 300ms linear;
transition: background-color 300ms linear;
&.active,
&:hover {
cursor: pointer;
&.active,
&:hover {
cursor: pointer;
background-color: #f7f8fa;
background-color: #f7f8fa;
.attachments-menu {
display: inline-block;
.attachments-menu {
display: inline-block;
}
}
}
&-link {
display: flex;
flex-direction: row;
&-link {
display: flex;
flex-direction: row;
padding: 8px 0;
align-items: center;
padding: 8px 0;
align-items: center;
}
}
}
&__file,
&__thumb {
display: inline-block;
display: flex;
flex-direction: column;
flex: 0 0 auto;
&__file,
&__thumb {
display: inline-block;
display: flex;
flex-direction: column;
flex: 0 0 auto;
width: 50px;
width: 50px;
height: 50px;
margin: 0 8px;
height: 50px;
margin: 0 8px;
border-radius: 2px;
border-radius: 2px;
background: radial-gradient(ellipse at center, rgba(155, 169, 186, 1) 0%, rgba(131, 143, 158, 1) 100%);
background-size: cover;
background: radial-gradient(ellipse at center, rgba(155, 169, 186, 1) 0%, rgba(131, 143, 158, 1) 100%);
background-size: cover;
font-size: 24px;
font-size: 24px;
align-items: center;
justify-content: center;
}
&__file {
&--pdf {
background: radial-gradient(ellipse at center, rgba(250, 97, 97, 1) 0%, rgba(251, 19, 19, 1) 100%);
align-items: center;
justify-content: center;
}
&--sheets {
background: radial-gradient(ellipse at center, rgba(0, 163, 82, 1) 0%, rgba(2, 114, 59, 1) 100%);
}
&__file {
&--pdf {
background: radial-gradient(ellipse at center, rgba(250, 97, 97, 1) 0%, rgba(251, 19, 19, 1) 100%);
}
&--ppt {
background: radial-gradient(ellipse at center, rgba(250, 109, 77, 1) 0%, rgba(208, 71, 40, 1) 100%);
&--sheets {
background: radial-gradient(ellipse at center, rgba(0, 163, 82, 1) 0%, rgba(2, 114, 59, 1) 100%);
}
&--ppt {
background: radial-gradient(ellipse at center, rgba(250, 109, 77, 1) 0%, rgba(208, 71, 40, 1) 100%);
}
}
}
&__type {
overflow: hidden;
flex: 0 0 auto;
&__type {
overflow: hidden;
flex: 0 0 auto;
max-width: 100%;
padding: 0 10px;
max-width: 100%;
padding: 0 10px;
text-overflow: ellipsis;
text-overflow: ellipsis;
color: white;
color: white;
font-size: 10px;
}
font-size: 10px;
}
&__name {
&__name {
overflow: hidden;
overflow: hidden;
min-width: 0;
margin: 0 8px;
min-width: 0;
margin: 0 8px;
white-space: nowrap;
text-overflow: ellipsis;
white-space: nowrap;
text-overflow: ellipsis;
color: #2f343d;
color: #2f343d;
font-size: 14px;
line-height: 1.5;
}
font-size: 14px;
line-height: 1.5;
}
&__details {
margin: 0 8px 2px;
&__details {
margin: 0 8px 2px;
white-space: nowrap;
text-overflow: ellipsis;
white-space: nowrap;
text-overflow: ellipsis;
font-size: 12px;
}
font-size: 12px;
}
&__bold {
font-weight: 600;
}
&__bold {
font-weight: 600;
}
&__content {
display: flex;
&__content {
display: flex;
overflow: hidden;
flex-direction: column;
overflow: hidden;
flex-direction: column;
flex: 1 1 100%;
flex: 1 1 100%;
color: #9ea2a8;
color: #9ea2a8;
}
}
}

@ -2,6 +2,7 @@ import './actionButton';
import './addWebdavAccount.html';
import './addWebdavAccount';
import './webdavFilePicker.html';
import './webdavFilePicker.css';
import './webdavFilePicker';
import './selectWebdavAccount.html';
import './selectWebdavAccount';

@ -49,8 +49,9 @@ Meteor.startup(function() {
accountId: account._id,
},
title,
modifier: 'modal',
content: 'webdavFilePicker',
showCancelButton: true,
showCancelButton: false,
showFooter: false,
showConfirmButton: false,
closeOnCancel: true,

@ -0,0 +1,258 @@
.webdav {
display: flex;
flex-direction: column;
height: 60vh;
padding: 0;
& &__file-icon,
& &__file-name,
& &__file-date {
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
& &__file-name,
& &__file-size,
& &__file-date {
min-width: 80px;
}
& &__file-icon {
width: 36px;
}
.file-picker-loading .loading-animation > .bounce {
background-color: #444444;
}
.current-folder-path {
display: flex;
flex-direction: row;
height: 25px;
.webdav_parent_folder {
padding: 0 5px;
cursor: pointer;
border-radius: 5px;
background-color: #e0e0e0;
}
}
.webdav-table-header {
margin-bottom: 12px;
font-size: 16px;
i {
color: #5b5b5b;
}
.webdav-path-breadcrumb {
float: left;
overflow: hidden;
list-style: none;
.webdav-breadcrumb-item {
display: block;
float: left;
padding: 3px 0;
.webdav-breadcrumb-folder {
padding: 3px;
cursor: pointer;
&:hover {
border-radius: 5px;
background: #f1f3f4;
}
}
}
}
.listOrGridMode {
float: right;
margin-right: 5px;
padding: 3px;
cursor: pointer;
&:hover {
border-radius: 5px;
background: #f1f3f4;
}
}
}
.rc-table-content {
display: flex;
overflow-x: auto;
flex-direction: column;
flex: 1 1 100%;
border-top: 1px solid rgba(216, 216, 216, 0.4);
& tr {
color: #444444;
}
& .js-sort {
cursor: pointer;
&.is-sorting .table-fake-th .rc-icon {
opacity: 1;
}
}
& .table-fake-th {
color: #444444;
&:hover .rc-icon {
opacity: 1;
}
& .rc-icon {
transition: opacity 0.3s;
opacity: 0;
font-size: 1rem;
}
& #webdav-go-back .rc-icon {
cursor: pointer;
opacity: 1;
}
}
& .table-tr-dummy {
height: 10px;
}
& .center-cell {
text-align: center;
}
.webdav-grid-header {
padding: 0.5rem 0;
color: #444444;
border-bottom: 1px solid rgba(216, 216, 216, 0.4);
font-size: 0.75rem;
font-weight: 500;
line-height: 1rem;
#webdav-go-back {
float: left;
.rc-icon {
cursor: pointer;
opacity: 1;
font-size: 1rem;
}
}
.select-sort {
float: right;
height: auto;
border: none;
.webdav-sort-direction {
cursor: pointer;
}
.rc-select__element {
width: 120px;
padding: 0 5px;
cursor: pointer;
color: #444444;
font-family: inherit;
font-size: 0.75rem;
font-weight: 500;
line-height: 1rem;
}
}
}
.webdav-grid {
display: grid;
margin: 15px 0;
text-align: center;
grid-gap: 15px 0;
grid-template-columns: 190px 190px 190px;
.grid-file-avatar {
color: #5d5d5d;
font-size: 64px;
}
.grid-item {
overflow: hidden;
padding: 0.5rem 0;
cursor: pointer;
white-space: nowrap;
text-overflow: ellipsis;
color: #444444;
font-size: 0.9rem;
font-weight: 500;
line-height: 1rem;
&:hover {
border-radius: 5px;
background: #f1f3f4;
}
}
.grid-empty {
color: #444444;
font-size: 0.9rem;
font-weight: 500;
line-height: 1rem;
grid-column: span 3;
}
}
}
@media (width <= 980px) {
.webdav {
height: 60vh;
.rc-table-content {
& th:not(:first-child),
& td:not(:first-child) {
display: none;
}
}
}
}
}

@ -1,35 +1,122 @@
<template name="webdavFilePicker">
<div class="webdav-container">
<ul class="attachments">
{{#if $neq webdavCurrentFolder '/'}}
<li class="attachments__item" id="webdav-go-back">
<div class="attachments__thumb attachments__file">
{{>icon icon='back'}}
</div>
<div class="webdav">
<div class="webdav-table-header">
<ul class="webdav-path-breadcrumb">
<li class="webdav-breadcrumb-item">
<span class="webdav-breadcrumb-folder" data-index="-1">
<i class="icon-home"></i>
</span>
<i class="icon-angle-right"></i>
</li>
{{/if}}
{{#each parentFolder in parentFolders}}
<li class="webdav-breadcrumb-item">
<span class="webdav-breadcrumb-folder" data-index="{{@index}}">
{{ parentFolder }}
</span>
<i class="icon-angle-right"></i>
</li>
{{/each}}
</ul>
<div class="listOrGridMode">
<i class="icon-{{#if listMode}}th{{else}}list{{/if}}"></i>
</div>
</div>
<div class="rc-table-content">
{{#if isLoading}}
<div class="file-picker-loading">
{{> loading}}
</div>
{{else}}
{{#each webdavNodes}}
<li class="attachments__item webdav_{{this.type}}">
{{#with iconType}}
<div class="attachments__thumb attachments__file--{{type}}">
{{>icon icon=icon}}
<div class="attachments__type">{{extension}}</div>
{{#if listMode}}
{{#table onSort=onTableSort}}
<thead>
<tr>
<th class="webdav__file-icon">
<div class="table-fake-th">
<span id="webdav-go-back">
{{#if $neq webdavCurrentFolder '/'}}
{{>icon icon='back'}}
{{/if}}
</span>
</div>
</th>
<th class="webdav__file-name js-sort {{#if sortBy 'name'}}is-sorting{{/if}}" data-sort="name">
<div class="table-fake-th"><span>Name</span>{{> icon icon=(sortIcon 'name')}}</div>
</th>
<th class="webdav__file-size js-sort {{#if sortBy 'size'}}is-sorting{{/if}}" data-sort="size">
<div class="table-fake-th"><span>Size</span>{{> icon icon=(sortIcon 'size')}}</div>
</th>
<th class="webdav__file-date js-sort {{#if sortBy 'date'}}is-sorting{{/if}}" data-sort="date">
<div class="table-fake-th"><span>Date Modified</span>{{> icon icon=(sortIcon 'date')}}</div>
</th>
</tr>
</thead>
<tbody>
{{#each webdavNodes}}
<tr class="webdav_{{this.type}}" data-name="{{this.basename}}">
<td>
<div class="table-file-avatar-wrapper">
{{#with iconType}}
<div class="table-file-avatar">
{{>icon icon=icon}}
</div>
{{/with}}
</div>
</td>
<td>{{this.basename}}</td>
<td>{{getSize}}</td>
<td class="table-column-date">{{getDate}}</td>
</tr>
{{else}}
<tr class="table-no-click">
<td colspan="4" class="center-cell">
There is nothing here!
</td>
</tr>
{{/each}}
</tbody>
{{/table}}
{{else}}
<div class="webdav-grid-header">
<div id="webdav-go-back">
{{#if $neq webdavCurrentFolder '/'}}
{{>icon icon='back'}}
{{/if}}
</div>
<div class="rc-select select-sort">
<span class="webdav-sort-direction">
{{> icon icon=(sortIcon getSortBy)}}
</span>
<select id="webdav-select-sort" class="required rc-select__element">
<option value="name" selected="{{sortBy 'name'}}" dir="auto">Name</option>
<option value="size" selected="{{sortBy 'size'}}" dir="auto">Size</option>
<option value="date" selected="{{sortBy 'date'}}" dir="auto">Date Modified</option>
</select>
{{> icon block="rc-select__arrow" icon="arrow-down"}}
</div>
</div>
<div class="webdav-grid">
{{#each webdavNodes}}
<div class="webdav_{{this.type}} grid-item" data-name="{{this.basename}}">
<div>
<div class="grid-file-avatar-wrapper">
{{#with iconType}}
<div class="grid-file-avatar">
{{>icon icon=icon}}
</div>
{{/with}}
</div>
</div>
<div>{{this.basename}}</div>
</div>
{{/with}}
<a title="{{this.basename}}" target="_blank" class="attachments__item">
<div class="attachments__content">
<div class="attachments__name">{{this.basename}}</div>
<div class="attachments__details">{{this.lastmod}}</div>
{{else}}
<div class="grid-empty">
There is nothing here!
</div>
</a>
</li>
{{/each}}
{{/each}}
</div>
{{/if}}
{{/if}}
</ul>
</div>
</div>
</template>

@ -6,6 +6,7 @@ import { Session } from 'meteor/session';
import { Handlebars } from 'meteor/ui';
import { ReactiveVar } from 'meteor/reactive-var';
import { timeAgo } from '../../ui/client/views/app/helpers';
import { modal, call } from '../../ui-utils';
import { t } from '../../utils';
import { fileUploadHandler } from '../../file-upload';
@ -26,6 +27,36 @@ Template.webdavFilePicker.destroyed = function() {
Session.set('webdavNodes', []);
};
function sortTable(data, sortBy, sortDirection) {
if (sortDirection === 'desc') {
if (sortBy === 'name') { data.sort((a, b) => b.basename.localeCompare(a.basename)); }
if (sortBy === 'size') { data.sort((a, b) => b.size - a.size); }
if (sortBy === 'date') { data.sort((a, b) => new Date(b.lastmod) - new Date(a.lastmod)); }
} else {
if (sortBy === 'name') { data.sort((a, b) => a.basename.localeCompare(b.basename)); }
if (sortBy === 'size') { data.sort((a, b) => a.size - b.size); }
if (sortBy === 'date') { data.sort((a, b) => new Date(a.lastmod) - new Date(b.lastmod)); }
}
return data;
}
async function showWebdavFileList(directory) {
const instance = Template.instance();
const { sortDirection, sortBy } = instance;
const { accountId } = instance.data;
instance.isLoading.set(true);
Session.set('webdavCurrentFolder', directory);
Session.set('webdavNodes', []);
const response = await call('getWebdavFileList', accountId, directory);
instance.isLoading.set(false);
if (!response.success) {
modal.close();
return toastr.error(t(response.message));
}
const data = sortTable(response.data, sortBy.get(), sortDirection.get());
Session.set('webdavNodes', data);
}
Template.webdavFilePicker.helpers({
iconType() {
// add icon for different types
@ -42,7 +73,7 @@ Template.webdavFilePicker.helpers({
type = 'directory';
} else if (this.mime.match(/application\/pdf/)) {
icon = 'file-pdf';
type = 'ppt';
type = 'pdf';
} else if (['application/vnd.oasis.opendocument.text', 'application/vnd.oasis.opendocument.presentation'].includes(this.mime)) {
icon = 'file-document';
type = 'document';
@ -59,6 +90,50 @@ Template.webdavFilePicker.helpers({
isLoading() {
return Template.instance().isLoading.get();
},
listMode() {
return Template.instance().isListMode.get();
},
sortBy(key) {
return Template.instance().sortBy.get() === key;
},
getSortBy() {
return Template.instance().sortBy.get();
},
getSize() {
if (this.type === 'directory') { return ''; }
const bytes = this.size;
if (bytes === 0) { return '0 B'; }
const k = 1024;
const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return `${ parseFloat((bytes / Math.pow(k, i)).toFixed(2)) } ${ sizes[i] }`;
},
getDate() {
return timeAgo(new Date(this.lastmod), t);
},
sortIcon(key) {
const { sortDirection, sortBy } = Template.instance();
return key === sortBy.get() && sortDirection.get() === 'asc'
? 'sort-up'
: 'sort-down';
},
onTableSort() {
const { sortDirection, sortBy } = Template.instance();
return function(type) {
if (sortBy.get() === type) {
sortDirection.set(sortDirection.get() === 'asc' ? 'desc' : 'asc');
} else {
sortBy.set(type);
sortDirection.set('asc');
}
const data = sortTable(Session.get('webdavNodes'), sortBy.get(), sortDirection.get());
Session.set('webdavNodes', data);
};
},
parentFolders() {
const currentFolder = Session.get('webdavCurrentFolder');
return currentFolder ? currentFolder.split('/').filter((s) => s) : [];
},
webdavNodes() {
return Session.get('webdavNodes');
},
@ -68,12 +143,25 @@ Template.webdavFilePicker.helpers({
});
Template.webdavFilePicker.events({
async 'click #webdav-go-back'() {
'click .listOrGridMode'() {
const instance = Template.instance();
const { accountId } = instance.data;
instance.isLoading.set(true);
instance.isListMode.set(!instance.isListMode.get());
},
'click .webdav-sort-direction'() {
const { sortDirection, sortBy } = Template.instance();
sortDirection.set(sortDirection.get() === 'asc' ? 'desc' : 'asc');
const data = sortTable(Session.get('webdavNodes'), sortBy.get(), sortDirection.get());
Session.set('webdavNodes', data);
},
'change #webdav-select-sort'() {
const { sortDirection, sortBy } = Template.instance();
const newSortBy = $('#webdav-select-sort').val();
sortBy.set(newSortBy);
const data = sortTable(Session.get('webdavNodes'), sortBy.get(), sortDirection.get());
Session.set('webdavNodes', data);
},
async 'click #webdav-go-back'() {
let currentFolder = Session.get('webdavCurrentFolder');
// determine parent directory to go back
let parentFolder = '/';
if (currentFolder && currentFolder !== '/') {
@ -82,28 +170,22 @@ Template.webdavFilePicker.events({
}
parentFolder = currentFolder.substr(0, currentFolder.lastIndexOf('/') + 1);
}
Session.set('webdavCurrentFolder', parentFolder);
Session.set('webdavNodes', []);
const response = await call('getWebdavFileList', accountId, parentFolder);
instance.isLoading.set(false);
if (!response.success) {
return toastr.error(t(response.message));
}
Session.set('webdavNodes', response.data);
showWebdavFileList(parentFolder);
},
async 'click .webdav_directory'() {
const instance = Template.instance();
const { accountId } = instance.data;
instance.isLoading.set(true);
Session.set('webdavCurrentFolder', this.filename);
Session.set('webdavNodes', []);
const response = await call('getWebdavFileList', accountId, this.filename);
instance.isLoading.set(false);
if (!response.success) {
modal.close();
return toastr.error(t(response.message));
showWebdavFileList(this.filename);
},
async 'click .webdav-breadcrumb-folder'(event) {
const index = $(event.target).data('index');
const currentFolder = Session.get('webdavCurrentFolder');
const parentFolders = currentFolder.split('/').filter((s) => s);
// determine parent directory to go to
let targetFolder = '/';
for (let i = 0; i <= index; i++) {
targetFolder += parentFolders[i];
targetFolder += '/';
}
Session.set('webdavNodes', response.data);
showWebdavFileList(targetFolder);
},
async 'click .webdav_file'() {
const roomId = Session.get('openedRoom');
@ -218,4 +300,7 @@ Template.webdavFilePicker.events({
Template.webdavFilePicker.onCreated(function() {
this.isLoading = new ReactiveVar(true);
this.isListMode = new ReactiveVar(true);
this.sortBy = new ReactiveVar('name');
this.sortDirection = new ReactiveVar('asc');
});

@ -7,15 +7,15 @@ import { WebdavAccounts } from '../../../models';
Meteor.methods({
async getFileFromWebdav(accountId, file) {
if (!Meteor.userId()) {
throw new Meteor.Error('error-invalid-user', 'Invalid User', { method: 'addNewWebdavAccount' });
throw new Meteor.Error('error-invalid-user', 'Invalid User', { method: 'getFileFromWebdav' });
}
if (!settings.get('Webdav_Integration_Enabled')) {
throw new Meteor.Error('error-not-allowed', 'WebDAV Integration Not Allowed', { method: 'addNewWebdavAccount' });
throw new Meteor.Error('error-not-allowed', 'WebDAV Integration Not Allowed', { method: 'getFileFromWebdav' });
}
const account = WebdavAccounts.findOne({ _id: accountId, user_id: Meteor.userId() });
if (!account) {
throw new Meteor.Error('error-invalid-account', 'Invalid WebDAV Account', { method: 'addNewWebdavAccount' });
throw new Meteor.Error('error-invalid-account', 'Invalid WebDAV Account', { method: 'getFileFromWebdav' });
}
const client = createClient(
account.server_url,

@ -7,16 +7,16 @@ import { WebdavAccounts } from '../../../models';
Meteor.methods({
async getWebdavFileList(accountId, path) {
if (!Meteor.userId()) {
throw new Meteor.Error('error-invalid-user', 'Invalid User', { method: 'addNewWebdavAccount' });
throw new Meteor.Error('error-invalid-user', 'Invalid User', { method: 'getWebdavFileList' });
}
if (!settings.get('Webdav_Integration_Enabled')) {
throw new Meteor.Error('error-not-allowed', 'WebDAV Integration Not Allowed', { method: 'addNewWebdavAccount' });
throw new Meteor.Error('error-not-allowed', 'WebDAV Integration Not Allowed', { method: 'getWebdavFileList' });
}
const account = WebdavAccounts.findOne({ _id: accountId, user_id: Meteor.userId() });
if (!account) {
throw new Meteor.Error('error-invalid-account', 'Invalid WebDAV Account', { method: 'addNewWebdavAccount' });
throw new Meteor.Error('error-invalid-account', 'Invalid WebDAV Account', { method: 'getWebdavFileList' });
}
const client = createClient(

Loading…
Cancel
Save