From 7df89e80e6b2dbaec31f3f483aceae2cc7007820 Mon Sep 17 00:00:00 2001
From: fenn-cs
Date: Wed, 19 Jul 2023 02:11:27 +0100
Subject: [PATCH] Improve sharing flow
Resolves: https://github.com/nextcloud/server/issues/26691
Signed-off-by: fenn-cs
---
.../src/components/SharingEntry.vue | 168 ++--
.../src/components/SharingEntryLink.vue | 15 +-
.../SharingEntryQuickShareSelect.vue | 108 +++
.../src/components/SharingEntrySimple.vue | 4 +-
.../src/components/SharingInput.vue | 4 +-
.../src/views/SharingDetailsTab.vue | 757 ++++++++++++++++++
apps/files_sharing/src/views/SharingList.vue | 10 +-
apps/files_sharing/src/views/SharingTab.vue | 21 +-
apps/files_trashbin/tests/js/appSpec.js | 70 ++
apps/files_trashbin/tests/js/filelistSpec.js | 397 +++++++++
.../src/components/UserList/UserRow.vue | 689 ++++++++++++++++
.../src/components/UserList/UserRowSimple.vue | 205 +++++
12 files changed, 2325 insertions(+), 123 deletions(-)
create mode 100644 apps/files_sharing/src/components/SharingEntryQuickShareSelect.vue
create mode 100644 apps/files_sharing/src/views/SharingDetailsTab.vue
create mode 100644 apps/files_trashbin/tests/js/appSpec.js
create mode 100644 apps/files_trashbin/tests/js/filelistSpec.js
create mode 100644 apps/settings/src/components/UserList/UserRow.vue
create mode 100644 apps/settings/src/components/UserList/UserRowSimple.vue
diff --git a/apps/files_sharing/src/components/SharingEntry.vue b/apps/files_sharing/src/components/SharingEntry.vue
index 46b65c695eede..e199acdc3f6df 100644
--- a/apps/files_sharing/src/components/SharingEntry.vue
+++ b/apps/files_sharing/src/components/SharingEntry.vue
@@ -29,120 +29,39 @@
:menu-position="'left'"
:url="share.shareWithAvatar" />
-
- {{ title }} ({{ share.shareWithDisplayNameUnique }})
-
- {{ share.status.icon || '' }}
- {{ share.status.message || '' }}
-
-
-
-
-
-
- {{ t('files_sharing', 'Allow editing') }}
-
-
-
-
- {{ t('files_sharing', 'Allow creating') }}
-
-
-
-
- {{ t('files_sharing', 'Allow deleting') }}
-
-
-
-
- {{ t('files_sharing', 'Allow resharing') }}
-
-
-
- {{ allowDownloadText }}
-
-
-
-
- {{ config.isDefaultInternalExpireDateEnforced
- ? t('files_sharing', 'Expiration date enforced')
- : t('files_sharing', 'Set expiration date') }}
-
-
- {{ t('files_sharing', 'Enter a date') }}
-
-
-
-
-
- {{ t('files_sharing', 'Note to recipient') }}
-
-
-
+
+
+ {{ title }} ({{
+ share.shareWithDisplayNameUnique }})
+
+ {{ share.status.icon || '' }}
+ {{ share.status.message || '' }}
+
+
+
+
+
+
+
-
-
- {{ t('files_sharing', 'Unshare') }}
-
-
+
@@ -465,21 +392,34 @@ export default {
display: flex;
align-items: center;
height: 44px;
+
&__desc {
display: flex;
flex-direction: column;
justify-content: space-between;
- padding: 8px;
+ padding-bottom: 0;
line-height: 1.2em;
+
p {
color: var(--color-text-maxcontrast);
}
+
&-unique {
color: var(--color-text-maxcontrast);
}
}
+
&__actions {
margin-left: auto;
}
+
+ &__summary {
+ padding: 8px;
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ width: 100%;
+ }
+
}
diff --git a/apps/files_sharing/src/components/SharingEntryLink.vue b/apps/files_sharing/src/components/SharingEntryLink.vue
index 06c9cb708510f..3572a2560fb5a 100644
--- a/apps/files_sharing/src/components/SharingEntryLink.vue
+++ b/apps/files_sharing/src/components/SharingEntryLink.vue
@@ -32,6 +32,7 @@
{{ subtitle }}
+
@@ -292,6 +293,8 @@ import NcActionTextEditable from '@nextcloud/vue/dist/Components/NcActionTextEdi
import NcActions from '@nextcloud/vue/dist/Components/NcActions.js'
import NcAvatar from '@nextcloud/vue/dist/Components/NcAvatar.js'
+import QuickShareSelect from './SharingEntryQuickShareSelect.vue'
+
import ExternalShareAction from './ExternalShareAction.vue'
import SharePermissionsEditor from './SharePermissionsEditor.vue'
import GeneratePassword from '../utils/GeneratePassword.js'
@@ -313,6 +316,7 @@ export default {
NcAvatar,
ExternalShareAction,
SharePermissionsEditor,
+ QuickShareSelect,
},
mixins: [SharesMixin],
@@ -330,6 +334,9 @@ export default {
data() {
return {
+ showDropdown: false,
+ selectedOption: 'Can edit',
+ options: ['Can view', 'Can edit', 'File drop'],
copySuccess: true,
copied: false,
@@ -671,7 +678,7 @@ export default {
* accordingly
*
* @param {Share} share the new share
- * @param {boolean} [update=false] do we update the current share ?
+ * @param {boolean} [update] do we update the current share ?
*/
async pushNewLinkShare(share, update) {
try {
@@ -879,6 +886,12 @@ export default {
display: flex;
align-items: center;
min-height: 44px;
+
+ // ::v-deep &__avatar {
+ // --size: 40px !important;
+ // line-height: 40px !important;
+ // }
+
&__desc {
display: flex;
flex-direction: column;
diff --git a/apps/files_sharing/src/components/SharingEntryQuickShareSelect.vue b/apps/files_sharing/src/components/SharingEntryQuickShareSelect.vue
new file mode 100644
index 0000000000000..4c1a636bc603e
--- /dev/null
+++ b/apps/files_sharing/src/components/SharingEntryQuickShareSelect.vue
@@ -0,0 +1,108 @@
+
+
+
+ {{ selectedOption }}
+
+
+
+
+
+
+
+
+
diff --git a/apps/files_sharing/src/components/SharingEntrySimple.vue b/apps/files_sharing/src/components/SharingEntrySimple.vue
index daff947fe803b..5e858de990b44 100644
--- a/apps/files_sharing/src/components/SharingEntrySimple.vue
+++ b/apps/files_sharing/src/components/SharingEntrySimple.vue
@@ -29,8 +29,8 @@
{{ subtitle }}
-
diff --git a/apps/files_sharing/src/components/SharingInput.vue b/apps/files_sharing/src/components/SharingInput.vue
index 8b740c1bac3b4..ee95f0ad746f2 100644
--- a/apps/files_sharing/src/components/SharingInput.vue
+++ b/apps/files_sharing/src/components/SharingInput.vue
@@ -24,6 +24,7 @@
{{ t('files_sharing', 'Search for share recipients') }}
@@ -176,7 +176,7 @@ export default {
* Get suggestions
*
* @param {string} search the search query
- * @param {boolean} [lookup=false] search on lookup server
+ * @param {boolean} [lookup] search on lookup server
*/
async getSuggestions(search, lookup = false) {
this.loading = true
diff --git a/apps/files_sharing/src/views/SharingDetailsTab.vue b/apps/files_sharing/src/views/SharingDetailsTab.vue
new file mode 100644
index 0000000000000..75176c0410749
--- /dev/null
+++ b/apps/files_sharing/src/views/SharingDetailsTab.vue
@@ -0,0 +1,757 @@
+
+
+
+
+
+
+ {{ t('files_sharing', 'View only (default)') }}
+
+
+
+
+
+ {{ t('files_sharing', 'Edit') }}
+
+
+
+
+
+ {{ t('files_sharing', 'File drop') }}
+ {{ t('files_sharing', 'Upload only') }}
+
+
+
+
+
+ {{ t('files_sharing', 'Custom permissions') }}
+ {{ t('files_sharing', 'Create, read & update') }}
+
+
+
+
+
+
+
+
+ Advanced settings
+
+
+
+
+
+
+
+
+ {{ t('file_sharing', 'Note to recipient') }}
+
+
+
+ {{ t('file_sharing', 'Set password') }}
+
+
+
+
+ {{ t('file_sharing', 'Set expiration date') }}
+
+
+
+ {{ t('file_sharing', 'Hide download') }}
+
+
+
+
+
+ {{ t('file_sharing', 'Custom permisions') }}
+
+
+
+ {{ t('file_sharing', 'Create') }}
+
+
+ {{ t('file_sharing', 'Read') }}
+
+
+ {{ t('file_sharing', 'Update') }}
+
+
+ {{ t('file_sharing', 'Share') }}
+
+
+ {{ t('file_sharing', 'Delete') }}
+
+
+
+
+
+
+ Old stuff to be transferred up
+
+
+
+
+
+
+ {{ t('files_sharing', 'Allow editing') }}
+
+
+
+
+ {{ t('files_sharing', 'Allow creating') }}
+
+
+
+
+ {{ t('files_sharing', 'Allow deleting') }}
+
+
+
+
+ {{ t('files_sharing', 'Allow resharing') }}
+
+
+
+ {{ allowDownloadText }}
+
+
+
+
+ {{ config.isDefaultInternalExpireDateEnforced
+ ? t('files_sharing', 'Expiration date enforced')
+ : t('files_sharing', 'Set expiration date') }}
+
+
+ {{ t('files_sharing', 'Enter a date') }}
+
+
+
+
+
+ {{ t('files_sharing', 'Note to recipient') }}
+
+
+
+
+
+ {{ t('files_sharing', 'Unshare') }}
+
+
+
+
+
+
+
+
+
+
+
diff --git a/apps/files_sharing/src/views/SharingList.vue b/apps/files_sharing/src/views/SharingList.vue
index 05dc87d9b0760..f832d7ed3e081 100644
--- a/apps/files_sharing/src/views/SharingList.vue
+++ b/apps/files_sharing/src/views/SharingList.vue
@@ -27,7 +27,8 @@
:file-info="fileInfo"
:share="share"
:is-unique="isUnique(share)"
- @remove:share="removeShare" />
+ @remove:share="removeShare"
+ @open-sharing-details="onOpenSharingDetails(share)" />
@@ -83,6 +84,13 @@ export default {
// eslint-disable-next-line vue/no-mutating-props
this.shares.splice(index, 1)
},
+ onOpenSharingDetails(share) {
+ const openShareDetailsEventData = {
+ fileInfo: this.fileInfo,
+ share: share
+ };
+ this.$emit('open-sharing-details', openShareDetailsEventData);
+ },
},
}
diff --git a/apps/files_sharing/src/views/SharingTab.vue b/apps/files_sharing/src/views/SharingTab.vue
index bfaf8a766eed4..abf23c65ae3f7 100644
--- a/apps/files_sharing/src/views/SharingTab.vue
+++ b/apps/files_sharing/src/views/SharingTab.vue
@@ -29,7 +29,7 @@
-
+
@@ -59,7 +59,8 @@
+ :file-info="fileInfo"
+ @open-sharing-details="toggleShareDetailsView" />
@@ -74,6 +75,11 @@
:name="fileInfo.name" />
+
+
+
+
+
diff --git a/apps/files_trashbin/tests/js/appSpec.js b/apps/files_trashbin/tests/js/appSpec.js
new file mode 100644
index 0000000000000..281e7bbc2ba4a
--- /dev/null
+++ b/apps/files_trashbin/tests/js/appSpec.js
@@ -0,0 +1,70 @@
+/**
+* @copyright 2014 Vincent Petry
+ *
+ * @author Vincent Petry
+ *
+ * @license AGPL-3.0-or-later
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ *
+ */
+
+describe('OCA.Trashbin.App tests', function() {
+ var App = OCA.Trashbin.App;
+
+ beforeEach(function() {
+ $('#testArea').append(
+ '' +
+ '' +
+ '
' +
+ '
' +
+ '
' +
+ '
' +
+ '
' +
+ ' '
+ );
+ App.initialize($('#app-content-trashbin'));
+ });
+ afterEach(function() {
+ App._initialized = false;
+ App.fileList = null;
+ });
+
+ describe('initialization', function() {
+ it('creates a custom filelist instance', function() {
+ App.initialize();
+ expect(App.fileList).toBeDefined();
+ expect(App.fileList.$el.is('#app-content-trashbin')).toEqual(true);
+ });
+
+ it('registers custom file actions', function() {
+ var fileActions;
+ App.initialize();
+
+ fileActions = App.fileList.fileActions;
+
+ expect(fileActions.actions.all).toBeDefined();
+ expect(fileActions.actions.all.Restore).toBeDefined();
+ expect(fileActions.actions.all.Delete).toBeDefined();
+
+ expect(fileActions.actions.all.Rename).not.toBeDefined();
+ expect(fileActions.actions.all.Download).not.toBeDefined();
+
+ expect(fileActions.defaults.dir).toEqual('Open');
+ });
+ });
+});
diff --git a/apps/files_trashbin/tests/js/filelistSpec.js b/apps/files_trashbin/tests/js/filelistSpec.js
new file mode 100644
index 0000000000000..9e27188efb8b6
--- /dev/null
+++ b/apps/files_trashbin/tests/js/filelistSpec.js
@@ -0,0 +1,397 @@
+/**
+ * @copyright 2014 Vincent Petry
+ *
+ * @author Abijeet
+ * @author Christoph Wurst
+ * @author Jan C. Borchardt
+ * @author Jan-Christoph Borchardt
+ * @author John Molakvoæ
+ * @author Robin Appelman
+ * @author Vincent Petry
+ *
+ * @license AGPL-3.0-or-later
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ *
+ */
+
+describe('OCA.Trashbin.FileList tests', function () {
+ var testFiles, alertStub, notificationStub, fileList, client;
+
+ beforeEach(function () {
+ alertStub = sinon.stub(OC.dialogs, 'alert');
+ notificationStub = sinon.stub(OC.Notification, 'show');
+
+ client = new OC.Files.Client({
+ host: 'localhost',
+ port: 80,
+ root: '/remote.php/dav/trashbin/user',
+ useHTTPS: OC.getProtocol() === 'https'
+ });
+
+ // init parameters and test table elements
+ $('#testArea').append(
+ '' +
+ // set this but it shouldn't be used (could be the one from the
+ // files app)
+ '
' +
+ // dummy controls
+ '
' +
+ // dummy table
+ // TODO: at some point this will be rendered by the fileList class itself!
+ '
' +
+ '
Empty content message
' +
+ '
'
+ );
+
+ testFiles = [{
+ id: 1,
+ type: 'file',
+ name: 'One.txt.d11111',
+ displayName: 'One.txt',
+ mtime: 11111000,
+ mimetype: 'text/plain',
+ etag: 'abc'
+ }, {
+ id: 2,
+ type: 'file',
+ name: 'Two.jpg.d22222',
+ displayName: 'Two.jpg',
+ mtime: 22222000,
+ mimetype: 'image/jpeg',
+ etag: 'def',
+ }, {
+ id: 3,
+ type: 'file',
+ name: 'Three.pdf.d33333',
+ displayName: 'Three.pdf',
+ mtime: 33333000,
+ mimetype: 'application/pdf',
+ etag: '123',
+ }, {
+ id: 4,
+ type: 'dir',
+ mtime: 99999000,
+ name: 'somedir.d99999',
+ displayName: 'somedir',
+ mimetype: 'httpd/unix-directory',
+ etag: '456'
+ }];
+
+ // register file actions like the trashbin App does
+ var fileActions = OCA.Trashbin.App._createFileActions(fileList);
+ fileList = new OCA.Trashbin.FileList(
+ $('#app-content'), {
+ fileActions: fileActions,
+ multiSelectMenu: [{
+ name: 'restore',
+ displayName: t('files', 'Restore'),
+ iconClass: 'icon-history',
+ },
+ {
+ name: 'delete',
+ displayName: t('files', 'Delete'),
+ iconClass: 'icon-delete',
+ }
+ ],
+ client: client
+ }
+ );
+ });
+ afterEach(function () {
+ testFiles = undefined;
+ fileList.destroy();
+ fileList = undefined;
+
+ notificationStub.restore();
+ alertStub.restore();
+ });
+ describe('Initialization', function () {
+ it('Sorts by mtime by default', function () {
+ expect(fileList._sort).toEqual('mtime');
+ expect(fileList._sortDirection).toEqual('desc');
+ });
+ it('Always returns read and delete permission', function () {
+ expect(fileList.getDirectoryPermissions()).toEqual(OC.PERMISSION_READ | OC.PERMISSION_DELETE);
+ });
+ });
+ describe('Breadcrumbs', function () {
+ beforeEach(function () {
+ var data = {
+ status: 'success',
+ data: {
+ files: testFiles,
+ permissions: 1
+ }
+ };
+ fakeServer.respondWith(/\/index\.php\/apps\/files_trashbin\/ajax\/list.php\?dir=%2Fsubdir/, [
+ 200, {
+ "Content-Type": "application/json"
+ },
+ JSON.stringify(data)
+ ]);
+ });
+ it('links the breadcrumb to the trashbin view', function () {
+ fileList.changeDirectory('/subdir', false, true);
+ fakeServer.respond();
+ var $crumbs = fileList.$el.find('.files-controls .crumb');
+ expect($crumbs.length).toEqual(3);
+ expect($crumbs.eq(1).find('a').text()).toEqual('Home');
+ expect($crumbs.eq(1).find('a').attr('href'))
+ .toEqual(OC.getRootPath() + '/index.php/apps/files?view=trashbin&dir=/');
+ expect($crumbs.eq(2).find('a').text()).toEqual('subdir');
+ expect($crumbs.eq(2).find('a').attr('href'))
+ .toEqual(OC.getRootPath() + '/index.php/apps/files?view=trashbin&dir=/subdir');
+ });
+ });
+ describe('Rendering rows', function () {
+ it('renders rows with the correct data when in root', function () {
+ // dir listing is false when in root
+ fileList.setFiles(testFiles);
+ var $rows = fileList.$el.find('tbody tr');
+ var $tr = $rows.eq(0);
+ expect($rows.length).toEqual(4);
+ expect($tr.attr('data-id')).toEqual('1');
+ expect($tr.attr('data-type')).toEqual('file');
+ expect($tr.attr('data-file')).toEqual('One.txt.d11111');
+ expect($tr.attr('data-size')).not.toBeDefined();
+ expect($tr.attr('data-etag')).toEqual('abc');
+ expect($tr.attr('data-permissions')).toEqual('9'); // read and delete
+ expect($tr.attr('data-mime')).toEqual('text/plain');
+ expect($tr.attr('data-mtime')).toEqual('11111000');
+ expect($tr.find('a.name').attr('href')).toEqual('#');
+
+ expect($tr.find('.nametext').text().trim()).toEqual('One.txt');
+
+ expect(fileList.findFileEl('One.txt.d11111')[0]).toEqual($tr[0]);
+ });
+ it('renders rows with the correct data when in root after calling setFiles with the same data set', function () {
+ // dir listing is false when in root
+ fileList.setFiles(testFiles);
+ fileList.setFiles(fileList.files);
+ var $rows = fileList.$el.find('tbody tr');
+ var $tr = $rows.eq(0);
+ expect($rows.length).toEqual(4);
+ expect($tr.attr('data-id')).toEqual('1');
+ expect($tr.attr('data-type')).toEqual('file');
+ expect($tr.attr('data-file')).toEqual('One.txt.d11111');
+ expect($tr.attr('data-size')).not.toBeDefined();
+ expect($tr.attr('data-etag')).toEqual('abc');
+ expect($tr.attr('data-permissions')).toEqual('9'); // read and delete
+ expect($tr.attr('data-mime')).toEqual('text/plain');
+ expect($tr.attr('data-mtime')).toEqual('11111000');
+ expect($tr.find('a.name').attr('href')).toEqual('#');
+
+ expect($tr.find('.nametext').text().trim()).toEqual('One.txt');
+
+ expect(fileList.findFileEl('One.txt.d11111')[0]).toEqual($tr[0]);
+ });
+ it('renders rows with the correct data when in subdirectory', function () {
+ fileList.setFiles(testFiles.map(function (file) {
+ file.name = file.displayName;
+ return file;
+ }));
+ var $rows = fileList.$el.find('tbody tr');
+ var $tr = $rows.eq(0);
+ expect($rows.length).toEqual(4);
+ expect($tr.attr('data-id')).toEqual('1');
+ expect($tr.attr('data-type')).toEqual('file');
+ expect($tr.attr('data-file')).toEqual('One.txt');
+ expect($tr.attr('data-size')).not.toBeDefined();
+ expect($tr.attr('data-etag')).toEqual('abc');
+ expect($tr.attr('data-permissions')).toEqual('9'); // read and delete
+ expect($tr.attr('data-mime')).toEqual('text/plain');
+ expect($tr.attr('data-mtime')).toEqual('11111000');
+ expect($tr.find('a.name').attr('href')).toEqual('#');
+
+ expect($tr.find('.nametext').text().trim()).toEqual('One.txt');
+
+ expect(fileList.findFileEl('One.txt')[0]).toEqual($tr[0]);
+ });
+ it('does not render a size column', function () {
+ expect(fileList.$el.find('tbody tr .filesize').length).toEqual(0);
+ });
+ });
+ describe('File actions', function () {
+ describe('Deleting single files', function () {
+ // TODO: checks ajax call
+ // TODO: checks spinner
+ // TODO: remove item after delete
+ // TODO: bring back item if delete failed
+ });
+ describe('Restoring single files', function () {
+ // TODO: checks ajax call
+ // TODO: checks spinner
+ // TODO: remove item after restore
+ // TODO: bring back item if restore failed
+ });
+ });
+ describe('file previews', function () {
+ // TODO: check that preview URL is going through files_trashbin
+ });
+ describe('loading file list', function () {
+ // TODO: check that ajax URL is going through files_trashbin
+ });
+ describe('breadcrumbs', function () {
+ // TODO: test label + URL
+ });
+ describe('elementToFile', function () {
+ var $tr;
+
+ beforeEach(function () {
+ fileList.setFiles(testFiles);
+ $tr = fileList.findFileEl('One.txt.d11111');
+ });
+
+ it('converts data attributes to file info structure', function () {
+ var fileInfo = fileList.elementToFile($tr);
+ expect(fileInfo.id).toEqual(1);
+ expect(fileInfo.name).toEqual('One.txt.d11111');
+ expect(fileInfo.displayName).toEqual('One.txt');
+ expect(fileInfo.mtime).toEqual(11111000);
+ expect(fileInfo.etag).toEqual('abc');
+ expect(fileInfo.permissions).toEqual(OC.PERMISSION_READ | OC.PERMISSION_DELETE);
+ expect(fileInfo.mimetype).toEqual('text/plain');
+ expect(fileInfo.type).toEqual('file');
+ });
+ });
+ describe('Global Actions', function () {
+ beforeEach(function () {
+ fileList.setFiles(testFiles);
+ fileList.findFileEl('One.txt.d11111').find('input:checkbox').click();
+ fileList.findFileEl('Three.pdf.d33333').find('input:checkbox').click();
+ fileList.findFileEl('somedir.d99999').find('input:checkbox').click();
+ fileList.$el.find('.actions-selected').click();
+ });
+
+ afterEach(function () {
+ fileList.$el.find('.actions-selected').click();
+ });
+
+ describe('Delete', function () {
+ it('Shows trashbin actions', function () {
+ // visible because a few files were selected
+ expect($('.selectedActions').is(':visible')).toEqual(true);
+ expect($('.selectedActions .item-delete').is(':visible')).toEqual(true);
+ expect($('.selectedActions .item-restore').is(':visible')).toEqual(true);
+
+ // check
+ fileList.$el.find('.select-all').click();
+
+ // stays visible
+ expect($('.selectedActions').is(':visible')).toEqual(true);
+ expect($('.selectedActions .item-delete').is(':visible')).toEqual(true);
+ expect($('.selectedActions .item-restore').is(':visible')).toEqual(true);
+
+ // uncheck
+ fileList.$el.find('.select-all').click();
+
+ // becomes hidden now
+ expect($('.selectedActions').is(':visible')).toEqual(false);
+ expect($('.selectedActions .item-delete').is(':visible')).toEqual(false);
+ expect($('.selectedActions .item-restore').is(':visible')).toEqual(false);
+ });
+ it('Deletes selected files when "Delete" clicked', function (done) {
+ var request;
+ var promise = fileList._onClickDeleteSelected({
+ preventDefault: function () {
+ }
+ });
+ var files = ["One.txt.d11111", "Three.pdf.d33333", "somedir.d99999"];
+ expect(fakeServer.requests.length).toEqual(files.length);
+ for (var i = 0; i < files.length; i++) {
+ request = fakeServer.requests[i];
+ expect(request.url).toEqual(OC.getRootPath() + '/remote.php/dav/trashbin/user/trash/' + files[i]);
+ request.respond(200);
+ }
+ return promise.then(function () {
+ expect(fileList.findFileEl('One.txt.d11111').length).toEqual(0);
+ expect(fileList.findFileEl('Three.pdf.d33333').length).toEqual(0);
+ expect(fileList.findFileEl('somedir.d99999').length).toEqual(0);
+ expect(fileList.findFileEl('Two.jpg.d22222').length).toEqual(1);
+ }).then(done, done);
+ });
+ it('Deletes all files when all selected when "Delete" clicked', function (done) {
+ var request;
+ $('.select-all').click();
+ var promise = fileList._onClickDeleteSelected({
+ preventDefault: function () {
+ }
+ });
+ expect(fakeServer.requests.length).toEqual(1);
+ request = fakeServer.requests[0];
+ expect(request.url).toEqual(OC.getRootPath() + '/remote.php/dav/trashbin/user/trash');
+ request.respond(200);
+ return promise.then(function () {
+ expect(fileList.isEmpty).toEqual(true);
+ }).then(done, done);
+ });
+ });
+ describe('Restore', function () {
+ it('Restores selected files when "Restore" clicked', function (done) {
+ var request;
+ var promise = fileList._onClickRestoreSelected({
+ preventDefault: function () {
+ }
+ });
+ var files = ["One.txt.d11111", "Three.pdf.d33333", "somedir.d99999"];
+ expect(fakeServer.requests.length).toEqual(files.length);
+ for (var i = 0; i < files.length; i++) {
+ request = fakeServer.requests[i];
+ expect(request.url).toEqual(OC.getRootPath() + '/remote.php/dav/trashbin/user/trash/' + files[i]);
+ expect(request.requestHeaders.Destination).toEqual(OC.getRootPath() + '/remote.php/dav/trashbin/user/restore/' + files[i]);
+ request.respond(200);
+ }
+ return promise.then(function() {
+ expect(fileList.findFileEl('One.txt.d11111').length).toEqual(0);
+ expect(fileList.findFileEl('Three.pdf.d33333').length).toEqual(0);
+ expect(fileList.findFileEl('somedir.d99999').length).toEqual(0);
+ expect(fileList.findFileEl('Two.jpg.d22222').length).toEqual(1);
+ }).then(done, done);
+ });
+ it('Restores all files when all selected when "Restore" clicked', function (done) {
+ var request;
+ $('.select-all').click();
+ var promise = fileList._onClickRestoreSelected({
+ preventDefault: function () {
+ }
+ });
+ var files = ["One.txt.d11111", "Two.jpg.d22222", "Three.pdf.d33333", "somedir.d99999"];
+ expect(fakeServer.requests.length).toEqual(files.length);
+ for (var i = 0; i < files.length; i++) {
+ request = fakeServer.requests[i];
+ expect(request.url).toEqual(OC.getRootPath() + '/remote.php/dav/trashbin/user/trash/' + files[i]);
+ expect(request.requestHeaders.Destination).toEqual(OC.getRootPath() + '/remote.php/dav/trashbin/user/restore/' + files[i]);
+ request.respond(200);
+ }
+ return promise.then(function() {
+ expect(fileList.isEmpty).toEqual(true);
+ }).then(done, done);
+ });
+ });
+ });
+});
diff --git a/apps/settings/src/components/UserList/UserRow.vue b/apps/settings/src/components/UserList/UserRow.vue
new file mode 100644
index 0000000000000..797156f18822a
--- /dev/null
+++ b/apps/settings/src/components/UserList/UserRow.vue
@@ -0,0 +1,689 @@
+
+
+
+
+
+
+
+
+
+ {{ user.id }}
+
+
+ {{ t('settings','You do not have permissions to see the details of this user') }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ user.id }}
+
+
+ {{ user.displayname }}
+
+
+
+
+
+
+
+
+ {{ t('settings', 'No results') }}
+
+
+
+
+ {{ t('settings', 'No results') }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ feedbackMessage }}
+
+
+
+
+
+
+
diff --git a/apps/settings/src/components/UserList/UserRowSimple.vue b/apps/settings/src/components/UserList/UserRowSimple.vue
new file mode 100644
index 0000000000000..eddc16a64a5cd
--- /dev/null
+++ b/apps/settings/src/components/UserList/UserRowSimple.vue
@@ -0,0 +1,205 @@
+
+
+
+
+
+
+
+
+
+
+ {{ user.displayname }}
+
+
+
+ {{ user.id }}
+
+
+
+
+ {{ userGroupsLabels }}
+
+
+ {{ userSubAdminsGroupsLabels }}
+
+
+
+ {{ userQuota }} ({{ usedSpace }})
+
+
+
+
+ {{ userLanguage.name }}
+
+
+
+ {{ user.backend }}
+
+
+ {{ user.storageLocation }}
+
+
+
+ {{ userLastLogin }}
+
+
+
+
+
+
+
+
+
+
+
+ {{ feedbackMessage }}
+
+
+
+
+
+
+
+