diff options
author | John Molakvoæ (skjnldsv) <skjnldsv@protonmail.com> | 2018-09-12 17:20:39 +0200 |
---|---|---|
committer | John Molakvoæ (skjnldsv) <skjnldsv@protonmail.com> | 2018-09-28 15:37:37 +0200 |
commit | 2b41b01bf2c2c1346d5368a6686a175a4e694f15 (patch) | |
tree | d7a74acb632953e35524629036adfee31c22cd48 /settings/src | |
parent | a23d5240987392f4eb140eefbb81882ef6ae6a5a (diff) | |
download | nextcloud-server-2b41b01bf2c2c1346d5368a6686a175a4e694f15.tar.gz nextcloud-server-2b41b01bf2c2c1346d5368a6686a175a4e694f15.zip |
Add new group entry on users list + fixes
Signed-off-by: John Molakvoæ (skjnldsv) <skjnldsv@protonmail.com>
Diffstat (limited to 'settings/src')
-rw-r--r-- | settings/src/components/appNavigation.vue | 54 | ||||
-rw-r--r-- | settings/src/components/appNavigation/navigationItem.vue | 155 | ||||
-rw-r--r-- | settings/src/components/userList.vue | 47 | ||||
-rw-r--r-- | settings/src/store/users.js | 5 | ||||
-rw-r--r-- | settings/src/views/Apps.vue | 5 | ||||
-rw-r--r-- | settings/src/views/Users.vue | 97 |
6 files changed, 117 insertions, 246 deletions
diff --git a/settings/src/components/appNavigation.vue b/settings/src/components/appNavigation.vue deleted file mode 100644 index 52a70786a9a..00000000000 --- a/settings/src/components/appNavigation.vue +++ /dev/null @@ -1,54 +0,0 @@ -<!-- - - @copyright Copyright (c) 2018 John Molakvoæ <skjnldsv@protonmail.com> - - - - @author John Molakvoæ <skjnldsv@protonmail.com> - - - - @license GNU AGPL version 3 or any later version - - - - 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 <http://www.gnu.org/licenses/>. - - - --> - -<template> - <div id="app-navigation" :class="{'icon-loading': menu.loading}"> - <div class="app-navigation-new" v-if="menu.new"> - <button type="button" :id="menu.new.id" :class="menu.new.icon" @click="menu.new.action">{{menu.new.text}}</button> - </div> - <ul :id="menu.id"> - <navigation-item v-for="item in menu.items" :item="item" :key="item.key" /> - </ul> - <div id="app-settings" v-if="!!$slots['settings-content']"> - <div id="app-settings-header"> - <button class="settings-button" - data-apps-slide-toggle="#app-settings-content" - >{{t('settings', 'Settings')}}</button> - </div> - <div id="app-settings-content"> - <slot name="settings-content"></slot> - </div> - </div> - </div> -</template> - -<script> -import navigationItem from './appNavigation/navigationItem'; - -export default { - name: 'appNavigation', - props: ['menu'], - components: { - navigationItem - } -}; -</script> diff --git a/settings/src/components/appNavigation/navigationItem.vue b/settings/src/components/appNavigation/navigationItem.vue deleted file mode 100644 index 4464c0abd0a..00000000000 --- a/settings/src/components/appNavigation/navigationItem.vue +++ /dev/null @@ -1,155 +0,0 @@ -<!-- - - @copyright Copyright (c) 2018 John Molakvoæ <skjnldsv@protonmail.com> - - - - @author John Molakvoæ <skjnldsv@protonmail.com> - - - - @license GNU AGPL version 3 or any later version - - - - 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 <http://www.gnu.org/licenses/>. - - - --> -<template> - - <!-- Is this a caption ? --> - <li class="app-navigation-caption" v-if="item.caption">{{item.text}}</li> - - <!-- Navigation item --> - <nav-element v-else :id="item.id" v-bind="navElement(item)" - :class="[{'icon-loading-small': item.loading, 'open': item.opened, 'collapsible': item.collapsible&&item.children&&item.children.length>0 }, item.classes]"> - - <!-- Bullet --> - <div v-if="item.bullet" class="app-navigation-entry-bullet" :style="{ backgroundColor: item.bullet }"></div> - - <!-- Main link --> - <a :href="(item.href) ? item.href : '#' " @click="toggleCollapse" :class="item.icon"> - <img v-if="item.iconUrl" :alt="item.text" :src="item.iconUrl"> - {{item.text}} - </a> - - <!-- Popover, counter and button(s) --> - <div v-if="item.utils" class="app-navigation-entry-utils"> - <ul> - <!-- counter --> - <li v-if="Number.isInteger(item.utils.counter)" - class="app-navigation-entry-utils-counter">{{item.utils.counter}}</li> - - <!-- first action if only one action --> - <li v-if="item.utils.actions && item.utils.actions.length === 1" - class="app-navigation-entry-utils-menu-button"> - <button @click="item.utils.actions[0].action" :class="item.utils.actions[0].icon" :title="item.utils.actions[0].text"></button> - </li> - - <!-- second action only two actions and no counter --> - <li v-else-if="item.utils.actions && item.utils.actions.length === 2 && !Number.isInteger(item.utils.counter)" - v-for="action in item.utils.actions" :key="action.action" - class="app-navigation-entry-utils-menu-button"> - <button @click="action.action" :class="action.icon" :title="action.text"></button> - </li> - - <!-- menu if only at least one action and counter OR two actions and no counter--> - <li v-else-if="item.utils.actions && item.utils.actions.length > 1 && (Number.isInteger(item.utils.counter) || item.utils.actions.length > 2)" - class="app-navigation-entry-utils-menu-button"> - <button v-click-outside="hideMenu" @click="showMenu" ></button> - </li> - </ul> - </div> - - <!-- if more than 2 actions or more than 1 actions with counter --> - <div v-if="item.utils && item.utils.actions && item.utils.actions.length > 1 && (Number.isInteger(item.utils.counter) || item.utils.actions.length > 2)" - class="app-navigation-entry-menu" :class="{ 'open': openedMenu }"> - <popover-menu :menu="item.utils.actions"/> - </div> - - <!-- undo entry --> - <div class="app-navigation-entry-deleted" v-if="item.undo"> - <div class="app-navigation-entry-deleted-description">{{item.undo.text}}</div> - <button class="app-navigation-entry-deleted-button icon-history" :title="t('settings', 'Undo')"></button> - </div> - - <!-- edit entry --> - <div class="app-navigation-entry-edit" v-if="item.edit"> - <form> - <input type="text" v-model="item.text"> - <input type="submit" value="" class="icon-confirm"> - <input type="submit" value="" class="icon-close" @click.stop.prevent="cancelEdit"> - </form> - </div> - - <!-- if the item has children, inject the component with proper data --> - <ul v-if="item.children"> - <navigation-item v-for="(item, key) in item.children" :item="item" :key="key" /> - </ul> - </nav-element> -</template> - -<script> -import popoverMenu from '../popoverMenu'; -import ClickOutside from 'vue-click-outside'; -import Vue from 'vue'; - -export default { - name: 'navigationItem', - props: ['item'], - components: { - popoverMenu - }, - directives: { - ClickOutside - }, - data() { - return { - openedMenu: false - }; - }, - methods: { - showMenu() { - this.openedMenu = true; - }, - hideMenu() { - this.openedMenu = false; - }, - toggleCollapse() { - // if item.opened isn't set, Vue won't trigger view updates https://vuejs.org/v2/api/#Vue-set - // ternary is here to detect the undefined state of item.opened - Vue.set(this.item, 'opened', this.item.opened ? !this.item.opened : true); - }, - cancelEdit() { - // remove the editing class - if (Array.isArray(this.item.classes)) - this.item.classes = this.item.classes.filter( - item => item !== 'editing' - ); - }, - // This is used to decide which outter element type to use - // li or router-link - navElement(item) { - if (item.href) { - return { - is: 'li' - }; - } - return { - is: 'router-link', - tag: 'li', - to: item.router, - exact: true - }; - } - }, - mounted() { - // prevent click outside event with popupItem. - this.popupItem = this.$el; - } -}; -</script> diff --git a/settings/src/components/userList.vue b/settings/src/components/userList.vue index 69f459b3a6c..521ad7cc392 100644 --- a/settings/src/components/userList.vue +++ b/settings/src/components/userList.vue @@ -44,9 +44,9 @@ </div> <form class="row" id="new-user" v-show="showConfig.showNewUserForm" - v-on:submit.prevent="createUser" :disabled="loading" + v-on:submit.prevent="createUser" :disabled="loading.all" :class="{'sticky': scrolled && showConfig.showNewUserForm}"> - <div :class="loading?'icon-loading-small':'icon-add'"></div> + <div :class="loading.all?'icon-loading-small':'icon-add'"></div> <div class="name"> <input id="newusername" type="text" required v-model="newUser.id" :placeholder="t('settings', 'Username')" name="username" @@ -74,12 +74,13 @@ <div class="groups"> <!-- hidden input trick for vanilla html5 form validation --> <input type="text" :value="newUser.groups" v-if="!settings.isAdmin" - tabindex="-1" id="newgroups" :required="!settings.isAdmin" /> - <multiselect :options="canAddGroups" v-model="newUser.groups" - :placeholder="t('settings', 'Add user in group')" - label="name" track-by="id" class="multiselect-vue" - :multiple="true" :close-on-select="false" - :allowEmpty="settings.isAdmin"> + tabindex="-1" id="newgroups" :required="!settings.isAdmin" + :class="{'icon-loading-small': loading.groups}"/> + <multiselect v-model="newUser.groups" :options="canAddGroups" :disabled="loading.groups||loading.all" + tag-placeholder="create" :placeholder="t('settings', 'Add user in group')" + label="name" track-by="id" class="multiselect-vue" + :multiple="true" :taggable="true" :close-on-select="false" + @tag="createGroup"> <!-- If user is not admin, he is a subadmin. Subadmins can't create users outside their groups Therefore, empty select is forbidden --> @@ -154,7 +155,10 @@ export default { return { unlimitedQuota: unlimitedQuota, defaultQuota: defaultQuota, - loading: false, + loading: { + all: false, + groups: false + }, scrolled: false, searchQuery: '', newUser: { @@ -318,10 +322,10 @@ export default { resetForm() { // revert form to original state Object.assign(this.newUser, this.$options.data.call(this).newUser); - this.loading = false; + this.loading.all = false; }, createUser() { - this.loading = true; + this.loading.all = true; this.$store.dispatch('addUser', { userid: this.newUser.id, password: this.newUser.password, @@ -332,7 +336,7 @@ export default { quota: this.newUser.quota.id, language: this.newUser.language.code, }).then(() => this.resetForm()) - .catch(() => this.loading = false); + .catch(() => this.loading.all = false); }, setNewUserDefaultGroup(value) { if (value && value.length > 0) { @@ -345,6 +349,25 @@ export default { } // fallback, empty selected group this.newUser.groups = []; + }, + + /** + * Create a new group + * + * @param {string} groups Group id + * @returns {Promise} + */ + createGroup(gid) { + this.loading.groups = true; + this.$store.dispatch('addGroup', gid) + .then((group) => { + this.newUser.groups.push(this.groups.find(group => group.id === gid)) + this.loading.groups = false; + }) + .catch(() => { + this.loading.groups = false; + }); + return this.$store.getters.getGroups[this.groups.length]; } } } diff --git a/settings/src/store/users.js b/settings/src/store/users.js index f6e7f9ff650..ba2611b3eec 100644 --- a/settings/src/store/users.js +++ b/settings/src/store/users.js @@ -296,7 +296,10 @@ const actions = { addGroup(context, gid) { return api.requireAdmin().then((response) => { return api.post(OC.linkToOCS(`cloud/groups`, 2), {groupid: gid}) - .then((response) => context.commit('addGroup', {gid: gid, displayName: gid})) + .then((response) => { + context.commit('addGroup', {gid: gid, displayName: gid}) + return {gid: gid, displayName: gid} + }) .catch((error) => {throw error;}); }).catch((error) => { context.commit('API_FAILURE', { gid, error }); diff --git a/settings/src/views/Apps.vue b/settings/src/views/Apps.vue index f6553160c95..790b5db73b8 100644 --- a/settings/src/views/Apps.vue +++ b/settings/src/views/Apps.vue @@ -34,7 +34,7 @@ <script> -import appNavigation from '../components/appNavigation'; +import { AppNavigation } from 'nextcloud-vue'; import appList from '../components/appList'; import Vue from 'vue'; import VueLocalStorage from 'vue-localstorage' @@ -43,7 +43,6 @@ import api from '../store/api'; import AppDetails from '../components/appDetails'; Vue.use(VueLocalStorage) -Vue.use(VueLocalStorage) export default { name: 'Apps', @@ -59,7 +58,7 @@ export default { }, components: { AppDetails, - appNavigation, + AppNavigation, appList, }, methods: { diff --git a/settings/src/views/Users.vue b/settings/src/views/Users.vue index 3b624d32aff..2bc68b90657 100644 --- a/settings/src/views/Users.vue +++ b/settings/src/views/Users.vue @@ -57,7 +57,7 @@ </template> <script> -import appNavigation from '../components/appNavigation'; +import { AppNavigation } from 'nextcloud-vue'; import userList from '../components/userList'; import Vue from 'vue'; import VueLocalStorage from 'vue-localstorage' @@ -65,13 +65,12 @@ import Multiselect from 'vue-multiselect'; import api from '../store/api'; Vue.use(VueLocalStorage) -Vue.use(VueLocalStorage) export default { name: 'Users', props: ['selectedGroup'], components: { - appNavigation, + AppNavigation, userList, Multiselect }, @@ -101,6 +100,8 @@ export default { // temporary value used for multiselect change selectedQuota: false, externalActions: [], + showAddGroupEntry: false, + loadingAddGroup: false, showConfig: { showStoragePath: false, showUserBackend: false, @@ -198,6 +199,26 @@ export default { action: action }); return this.externalActions; + }, + + /** + * Create a new group + * + * @param {Object} event The form submit event + * @returns {Promise} + */ + createGroup(event) { + let gid = event.target[0].value; + this.loadingAddGroup = true; + this.$store.dispatch('addGroup', gid) + .then(() => { + this.showAddGroupEntry = false; + this.loadingAddGroup = false; + }) + .catch(() => { + this.loadingAddGroup = false; + }); + return this.$store.getters.getGroups[this.groups.length]; } }, computed: { @@ -276,6 +297,7 @@ export default { // BUILD APP NAVIGATION MENU OBJECT menu() { // Data provided php side + let self = this; let groups = this.$store.getters.getGroups; groups = Array.isArray(groups) ? groups : []; @@ -302,31 +324,19 @@ export default { if (item.id !== 'admin' && item.id !== 'disabled' && this.settings.isAdmin) { // add delete button on real groups - let self = this; item.utils.actions = [{ icon: 'icon-delete', text: t('settings', 'Remove group'), - action: function() {self.removeGroup(group.id)} + action: function() { + self.removeGroup(group.id) + } }]; }; return item; }); - // Adjust data - let adminGroup = groups.find(group => group.id == 'admin'); - let disabledGroupIndex = groups.findIndex(group => group.id == 'disabled'); - let disabledGroup = groups[disabledGroupIndex]; - if (adminGroup && adminGroup.text) { - adminGroup.text = t('settings', 'Admins'); // rename admin group - adminGroup.icon = 'icon-user-admin'; // set icon - } - if (disabledGroup && disabledGroup.text) { - disabledGroup.text = t('settings', 'Disabled users'); // rename disabled group - disabledGroup.icon = 'icon-disabled-users'; // set icon - if (!disabledGroup.utils.counter) { - groups.splice(disabledGroupIndex, 1); // remove disabled if empty - } - } + // Every item is added on top of the array, so we're going backward + // Groups, separator, disabled, admin, everyone // Add separator let realGroups = groups.find((group) => {return group.id !== 'disabled' && group.id !== 'admin'}); @@ -340,6 +350,26 @@ export default { groups.unshift(separator); } + // Adjust admin and disabled groups + let adminGroup = groups.find(group => group.id == 'admin'); + let disabledGroup = groups.find(group => group.id == 'disabled'); + + // filter out admin and disabled + groups = groups.filter(group => ['admin', 'disabled'].indexOf(group.id) === -1); + + if (adminGroup && adminGroup.text) { + adminGroup.text = t('settings', 'Admins'); // rename admin group + adminGroup.icon = 'icon-user-admin'; // set icon + groups.unshift(adminGroup); // add admin group if present + } + if (disabledGroup && disabledGroup.text) { + disabledGroup.text = t('settings', 'Disabled users'); // rename disabled group + disabledGroup.icon = 'icon-disabled-users'; // set icon + if (disabledGroup.utils && disabledGroup.utils.counter > 0) { + groups.unshift(disabledGroup); // add disabled if not empty + } + } + // Add everyone group let everyoneGroup = { @@ -351,10 +381,35 @@ export default { }; // users count if (this.userCount > 0) { - everyoneGroup.utils = {counter: this.userCount}; + Vue.set(everyoneGroup, 'utils', { + counter: this.userCount + }); } groups.unshift(everyoneGroup); + let addGroup = { + id: 'addgroup', + key: 'addgroup', + icon: 'icon-add', + text: t('settings', 'Add group'), + classes: this.loadingAddGroup ? 'icon-loading-small' : '' + }; + if (this.showAddGroupEntry) { + Vue.set(addGroup, 'edit', { + text: t('settings', 'Add group'), + action: this.createGroup, + reset: function() { + self.showAddGroupEntry = false + } + }); + addGroup.classes = 'editing'; + } else { + Vue.set(addGroup, 'action', function() { + self.showAddGroupEntry = true + }) + } + groups.unshift(addGroup); + // Return return { id: 'usergrouplist', |