diff --git a/app/theme/client/imports/components/contextual-bar.css b/app/theme/client/imports/components/contextual-bar.css index 81659afb9a24..9c53099942d4 100644 --- a/app/theme/client/imports/components/contextual-bar.css +++ b/app/theme/client/imports/components/contextual-bar.css @@ -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; + } } } diff --git a/app/webdav/client/index.js b/app/webdav/client/index.js index f98661ff626f..592f18ad60ae 100644 --- a/app/webdav/client/index.js +++ b/app/webdav/client/index.js @@ -2,6 +2,7 @@ import './actionButton'; import './addWebdavAccount.html'; import './addWebdavAccount'; import './webdavFilePicker.html'; +import './webdavFilePicker.css'; import './webdavFilePicker'; import './selectWebdavAccount.html'; import './selectWebdavAccount'; diff --git a/app/webdav/client/startup/messageBoxActions.js b/app/webdav/client/startup/messageBoxActions.js index 0ccfcb1760ac..4ca3e60e5b94 100644 --- a/app/webdav/client/startup/messageBoxActions.js +++ b/app/webdav/client/startup/messageBoxActions.js @@ -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, diff --git a/app/webdav/client/webdavFilePicker.css b/app/webdav/client/webdavFilePicker.css new file mode 100644 index 000000000000..f92ffdcea9e9 --- /dev/null +++ b/app/webdav/client/webdavFilePicker.css @@ -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; + } + } + } + } +} diff --git a/app/webdav/client/webdavFilePicker.html b/app/webdav/client/webdavFilePicker.html index 79ebe7556be5..2408517ff091 100644 --- a/app/webdav/client/webdavFilePicker.html +++ b/app/webdav/client/webdavFilePicker.html @@ -1,35 +1,122 @@ diff --git a/app/webdav/client/webdavFilePicker.js b/app/webdav/client/webdavFilePicker.js index ccbd3919177c..9100a7ccde53 100644 --- a/app/webdav/client/webdavFilePicker.js +++ b/app/webdav/client/webdavFilePicker.js @@ -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'); }); diff --git a/app/webdav/server/methods/getFileFromWebdav.js b/app/webdav/server/methods/getFileFromWebdav.js index c4a7d78fadbe..9d3b3a980fdb 100644 --- a/app/webdav/server/methods/getFileFromWebdav.js +++ b/app/webdav/server/methods/getFileFromWebdav.js @@ -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, diff --git a/app/webdav/server/methods/getWebdavFileList.js b/app/webdav/server/methods/getWebdavFileList.js index 3f48d845edad..e082fe21e781 100644 --- a/app/webdav/server/methods/getWebdavFileList.js +++ b/app/webdav/server/methods/getWebdavFileList.js @@ -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(