Merge pull request #23529 from Simounet/feat/23397-settings-new-user-modal

Settings: new user row replaced by a modal
This commit is contained in:
John Molakvoæ 2021-04-21 16:38:46 +02:00 committed by GitHub
commit 3da956bcdc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 177 additions and 154 deletions

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -22,14 +22,12 @@
<template> <template>
<div id="app-content" class="user-list-grid" @scroll.passive="onScroll"> <div id="app-content" class="user-list-grid" @scroll.passive="onScroll">
<form v-show="showConfig.showNewUserForm" <Modal v-if="showConfig.showNewUserForm" @close="closeModal">
id="new-user" <form id="new-user"
:class="{'sticky': scrolled && showConfig.showNewUserForm}" :disabled="loading.all"
:disabled="loading.all" class="modal__content"
class="row" @submit.prevent="createUser">
@submit.prevent="createUser"> <h2>{{ t('settings','New user') }}</h2>
<div :class="loading.all?'icon-loading-small':'icon-add'" />
<div class="name">
<input id="newusername" <input id="newusername"
ref="newusername" ref="newusername"
v-model="newUser.id" v-model="newUser.id"
@ -40,22 +38,20 @@
autocapitalize="none" autocapitalize="none"
autocomplete="off" autocomplete="off"
autocorrect="off" autocorrect="off"
class="modal__item"
name="username" name="username"
pattern="[a-zA-Z0-9 _\.@\-']+" pattern="[a-zA-Z0-9 _\.@\-']+"
required required
type="text"> type="text">
<div class="displayName"> <input id="newdisplayname"
<input id="newdisplayname" v-model="newUser.displayName"
v-model="newUser.displayName" :placeholder="t('settings', 'Display name')"
:placeholder="t('settings', 'Display name')" autocapitalize="none"
autocapitalize="none" autocomplete="off"
autocomplete="off" autocorrect="off"
autocorrect="off" class="modal__item"
name="displayname" name="displayname"
type="text"> type="text">
</div>
</div>
<div class="password">
<input id="newuserpassword" <input id="newuserpassword"
ref="newuserpassword" ref="newuserpassword"
v-model="newUser.password" v-model="newUser.password"
@ -65,10 +61,9 @@
autocapitalize="none" autocapitalize="none"
autocomplete="new-password" autocomplete="new-password"
autocorrect="off" autocorrect="off"
class="modal__item"
name="password" name="password"
type="password"> type="password">
</div>
<div class="mailAddress">
<input id="newemail" <input id="newemail"
v-model="newUser.mailAddress" v-model="newUser.mailAddress"
:placeholder="t('settings', 'Email')" :placeholder="t('settings', 'Email')"
@ -76,91 +71,86 @@
autocapitalize="none" autocapitalize="none"
autocomplete="off" autocomplete="off"
autocorrect="off" autocorrect="off"
class="modal__item"
name="email" name="email"
type="email"> type="email">
</div> <div class="groups modal__item">
<div class="groups"> <!-- hidden input trick for vanilla html5 form validation -->
<!-- hidden input trick for vanilla html5 form validation --> <input v-if="!settings.isAdmin"
<input v-if="!settings.isAdmin" id="newgroups"
id="newgroups" :class="{'icon-loading-small': loading.groups}"
:class="{'icon-loading-small': loading.groups}" :required="!settings.isAdmin"
:required="!settings.isAdmin" :value="newUser.groups"
:value="newUser.groups" tabindex="-1"
tabindex="-1" type="text">
type="text"> <Multiselect v-model="newUser.groups"
<Multiselect v-model="newUser.groups" :close-on-select="false"
:close-on-select="false" :disabled="loading.groups||loading.all"
:disabled="loading.groups||loading.all" :multiple="true"
:multiple="true" :options="canAddGroups"
:options="canAddGroups" :placeholder="t('settings', 'Add user to group')"
:placeholder="t('settings', 'Add user to group')" :tag-width="60"
:tag-width="60" :taggable="true"
:taggable="true" class="multiselect-vue"
class="multiselect-vue" label="name"
label="name" tag-placeholder="create"
tag-placeholder="create" track-by="id"
track-by="id" @tag="createGroup">
@tag="createGroup"> <!-- If user is not admin, he is a subadmin.
<!-- If user is not admin, he is a subadmin. Subadmins can't create users outside their groups
Subadmins can't create users outside their groups Therefore, empty select is forbidden -->
Therefore, empty select is forbidden --> <span slot="noResult">{{ t('settings', 'No results') }}</span>
<span slot="noResult">{{ t('settings', 'No results') }}</span> </Multiselect>
</Multiselect>
</div>
<div v-if="subAdminsGroups.length>0 && settings.isAdmin"
class="subadmins">
<Multiselect v-model="newUser.subAdminsGroups"
:close-on-select="false"
:multiple="true"
:options="subAdminsGroups"
:placeholder="t('settings', 'Set user as admin for')"
:tag-width="60"
class="multiselect-vue"
label="name"
track-by="id">
<span slot="noResult">{{ t('settings', 'No results') }}</span>
</Multiselect>
</div>
<div class="quota">
<Multiselect v-model="newUser.quota"
:allow-empty="false"
:options="quotaOptions"
:placeholder="t('settings', 'Select user quota')"
:taggable="true"
class="multiselect-vue"
label="label"
track-by="id"
@tag="validateQuota" />
</div>
<div v-if="showConfig.showLanguages" class="languages">
<Multiselect v-model="newUser.language"
:allow-empty="false"
:options="languages"
:placeholder="t('settings', 'Default language')"
class="multiselect-vue"
group-label="label"
group-values="languages"
label="name"
track-by="code" />
</div>
<div v-if="showConfig.showStoragePath" class="storageLocation" />
<div v-if="showConfig.showUserBackend" class="userBackend" />
<div v-if="showConfig.showLastLogin" class="lastLogin" />
<div class="userActions">
<input id="newsubmit"
:title="t('settings', 'Add a new user')"
class="button primary icon-checkmark-white has-tooltip"
type="submit"
value="">
<div class="closeButton">
<Actions>
<ActionButton icon="icon-close" @click="onClose">
{{ t('settings', 'Close') }}
</ActionButton>
</Actions>
</div> </div>
</div> <div v-if="subAdminsGroups.length>0 && settings.isAdmin"
</form> class="subadmins modal__item">
<Multiselect v-model="newUser.subAdminsGroups"
:close-on-select="false"
:multiple="true"
:options="subAdminsGroups"
:placeholder="t('settings', 'Set user as admin for')"
:tag-width="60"
class="multiselect-vue"
label="name"
track-by="id">
<span slot="noResult">{{ t('settings', 'No results') }}</span>
</Multiselect>
</div>
<div class="quota modal__item">
<Multiselect v-model="newUser.quota"
:allow-empty="false"
:options="quotaOptions"
:placeholder="t('settings', 'Select user quota')"
:taggable="true"
class="multiselect-vue"
label="label"
track-by="id"
@tag="validateQuota" />
</div>
<div v-if="showConfig.showLanguages" class="languages modal__item">
<Multiselect v-model="newUser.language"
:allow-empty="false"
:options="languages"
:placeholder="t('settings', 'Default language')"
class="multiselect-vue"
group-label="label"
group-values="languages"
label="name"
track-by="code" />
</div>
<div v-if="showConfig.showStoragePath" class="storageLocation" />
<div v-if="showConfig.showUserBackend" class="userBackend" />
<div v-if="showConfig.showLastLogin" class="lastLogin" />
<div class="user-actions">
<button id="newsubmit"
class="button primary"
type="submit"
value="">
{{ t('settings', 'Add a new user') }}
</button>
</div>
</form>
</Modal>
<div id="grid-header" <div id="grid-header"
:class="{'sticky': scrolled && !showConfig.showNewUserForm}" :class="{'sticky': scrolled && !showConfig.showNewUserForm}"
class="row"> class="row">
@ -244,10 +234,9 @@
import { subscribe, unsubscribe } from '@nextcloud/event-bus' import { subscribe, unsubscribe } from '@nextcloud/event-bus'
import InfiniteLoading from 'vue-infinite-loading' import InfiniteLoading from 'vue-infinite-loading'
import Vue from 'vue' import Vue from 'vue'
import { Modal } from '@nextcloud/vue'
import Multiselect from '@nextcloud/vue/dist/Components/Multiselect' import Multiselect from '@nextcloud/vue/dist/Components/Multiselect'
import Actions from '@nextcloud/vue/dist/Components/Actions'
import ActionButton from '@nextcloud/vue/dist/Components/ActionButton'
import userRow from './UserList/UserRow' import userRow from './UserList/UserRow'
@ -276,11 +265,10 @@ const newUser = {
export default { export default {
name: 'UserList', name: 'UserList',
components: { components: {
Modal,
userRow, userRow,
Multiselect, Multiselect,
InfiniteLoading, InfiniteLoading,
Actions,
ActionButton,
}, },
props: { props: {
users: { users: {
@ -522,6 +510,7 @@ export default {
.then(() => { .then(() => {
this.resetForm() this.resetForm()
this.$refs.newusername.focus() this.$refs.newusername.focus()
this.closeModal()
}) })
.catch((error) => { .catch((error) => {
this.loading.all = false this.loading.all = false
@ -584,13 +573,45 @@ export default {
this.$refs.infiniteLoading.stateChanger.reset() this.$refs.infiniteLoading.stateChanger.reset()
} }
}, },
onClose() { closeModal() {
this.showConfig.showNewUserForm = false this.showConfig.showNewUserForm = false
}, },
}, },
} }
</script> </script>
<style scoped> <style scoped>
.modal-wrapper {
margin: 2vh 0;
align-items: flex-start;
}
.modal__content {
display: flex;
padding: 20px;
flex-direction: column;
align-items: center;
text-align: center;
overflow: auto;
}
.modal__item {
margin-bottom: 16px;
width: 100%;
}
.modal__item:not(:focus):not(:active) {
border-color: var(--color-border-dark);
}
.modal__item::v-deep .multiselect {
width: 100%;
}
.user-actions {
margin-top: 20px;
}
.modal__content::v-deep .multiselect__single {
text-align: left;
box-sizing: border-box;
}
.modal__content::v-deep .multiselect__content-wrapper {
box-sizing: border-box;
}
.row::v-deep .multiselect__single { .row::v-deep .multiselect__single {
z-index: auto !important; z-index: auto !important;
} }

View File

@ -26,7 +26,9 @@
<AppNavigationNew button-id="new-user-button" <AppNavigationNew button-id="new-user-button"
:text="t('settings','New user')" :text="t('settings','New user')"
button-class="icon-add" button-class="icon-add"
@click="toggleNewUserMenu" /> @click="showNewUserMenu"
@keyup.enter="showNewUserMenu"
@keyup.space="showNewUserMenu" />
<template #list> <template #list>
<AppNavigationItem <AppNavigationItem
id="addgroup" id="addgroup"
@ -348,8 +350,8 @@ export default {
}) })
}, },
methods: { methods: {
toggleNewUserMenu() { showNewUserMenu() {
this.showConfig.showNewUserForm = !this.showConfig.showNewUserForm this.showConfig.showNewUserForm = true
if (this.showConfig.showNewUserForm) { if (this.showConfig.showNewUserForm) {
Vue.nextTick(() => { Vue.nextTick(() => {
window.newusername.focus() window.newusername.focus()

View File

@ -74,7 +74,7 @@ class UsersSettingsContext implements Context, ActorAwareInterface {
* @return Locator * @return Locator
*/ */
public static function createNewUserButton() { public static function createNewUserButton() {
return Locator::forThe()->xpath("//form[@id = 'new-user']//input[@type = 'submit']")-> return Locator::forThe()->xpath("//form[@id = 'new-user']//button[@type = 'submit']")->
describedAs("Create user button in Users Settings"); describedAs("Create user button in Users Settings");
} }