diff options
author | Christopher Ng <chrng8@gmail.com> | 2023-07-07 11:31:23 -0700 |
---|---|---|
committer | Christopher Ng <chrng8@gmail.com> | 2023-07-12 17:30:11 -0700 |
commit | cbfe0c67e9072f18bb40b795032d47f1639decb9 (patch) | |
tree | 8090c18f58dd0f4794f0265907c5edc218af44df /apps/settings/src/components/Users/UserRow.vue | |
parent | 97a93c73cec09a72cf035e9f70a62d4396b09e82 (diff) | |
download | nextcloud-server-cbfe0c67e9072f18bb40b795032d47f1639decb9.tar.gz nextcloud-server-cbfe0c67e9072f18bb40b795032d47f1639decb9.zip |
enh(a11y): Users table
Signed-off-by: Christopher Ng <chrng8@gmail.com>
Diffstat (limited to 'apps/settings/src/components/Users/UserRow.vue')
-rw-r--r-- | apps/settings/src/components/Users/UserRow.vue | 785 |
1 files changed, 461 insertions, 324 deletions
diff --git a/apps/settings/src/components/Users/UserRow.vue b/apps/settings/src/components/Users/UserRow.vue index 6d5850068de..1a4879ccf26 100644 --- a/apps/settings/src/components/Users/UserRow.vue +++ b/apps/settings/src/components/Users/UserRow.vue @@ -1,9 +1,10 @@ <!-- - - @copyright Copyright (c) 2018 John Molakvoæ <skjnldsv@protonmail.com> - @copyright Copyright (c) 2019 Gary Kim <gary@garykim.dev> + - @copyright Copyright (c) 2018 John Molakvoæ <skjnldsv@protonmail.com> - - - @author John Molakvoæ <skjnldsv@protonmail.com> + - @author Christopher Ng <chrng8@gmail.com> - @author Gary Kim <gary@garykim.dev> + - @author John Molakvoæ <skjnldsv@protonmail.com> - - @license GNU AGPL version 3 or any later version - @@ -23,289 +24,352 @@ --> <template> - <!-- Obfuscated user: Logged in user does not have permissions to see all of the data --> - <div v-if="Object.keys(user).length ===1" :data-id="user.id" class="row"> - <div :class="{'icon-loading-small': loading.delete || loading.disable || loading.wipe}" - class="avatar"> - <img v-if="!loading.delete && !loading.disable && !loading.wipe" - :src="generateAvatar(user.id, isDarkTheme)" - alt="" - height="32" - width="32"> - </div> - <div class="name"> - {{ user.id }} - </div> - <div class="obfuscated"> - {{ t('settings','You do not have permissions to see the details of this user') }} - </div> - </div> - - <!-- User full data --> - <UserRowSimple v-else-if="!editing" - :editing.sync="editing" - :groups="groups" - :languages="languages" - :loading="loading" - :opened-menu.sync="openedMenu" - :settings="settings" - :show-config="showConfig" - :sub-admins-groups="subAdminsGroups" - :user-actions="userActions" - :user="user" - :is-dark-theme="isDarkTheme" - :class="{'row--menu-opened': openedMenu}" /> - <div v-else - :class="{ - 'disabled': loading.delete || loading.disable, - 'row--menu-opened': openedMenu - }" - :data-id="user.id" - class="row row--editable"> - <div :class="{'icon-loading-small': loading.delete || loading.disable || loading.wipe}" - class="avatar"> - <img v-if="!loading.delete && !loading.disable && !loading.wipe" - :src="generateAvatar(user.id, isDarkTheme)" - alt="" - height="32" - width="32"> - </div> - <!-- dirty hack to ellipsis on two lines --> - <div v-if="user.backendCapabilities.setDisplayName" class="displayName"> - <label class="hidden-visually" :for="'displayName'+user.id+rand">{{ t('settings', 'Edit display name') }}</label> - <NcTextField :id="'displayName'+user.id+rand" - :show-trailing-button="true" - class="user-row-text-field" - :class="{'icon-loading-small': loading.displayName}" - :disabled="loading.displayName||loading.all" - trailing-button-icon="arrowRight" - :value.sync="editedDisplayName" - autocapitalize="off" - autocomplete="off" - autocorrect="off" - spellcheck="false" - type="text" - @trailing-button-click="updateDisplayName" /> - </div> - <div v-else class="name"> - {{ user.id }} - <div class="displayName subtitle"> - <div :title="user.displayname.length > 20 ? user.displayname : ''" class="cellText"> + <Fragment> + <td class="row__cell row__cell--avatar"> + <NcLoadingIcon v-if="isLoadingUser" + :title="t('settings', 'Loading user …')" + :size="32" /> + <NcAvatar v-else + :key="user.id" + disable-menu + :show-user-status="false" + :user="user.id" /> + </td> + + <td class="row__cell row__cell--displayname"> + <template v-if="idState.editing && user.backendCapabilities.setDisplayName"> + <label class="hidden-visually" + :for="'displayName' + uniqueId"> + {{ t('settings', 'Edit display name') }} + </label> + <NcTextField :id="'displayName' + uniqueId" + ref="displayNameField" + :show-trailing-button="true" + class="user-row-text-field" + :class="{ 'icon-loading-small': idState.loading.displayName }" + :disabled="idState.loading.displayName || isLoadingField" + trailing-button-icon="arrowRight" + :value.sync="idState.editedDisplayName" + autocapitalize="off" + autocomplete="off" + autocorrect="off" + spellcheck="false" + type="text" + @trailing-button-click="updateDisplayName" /> + </template> + <template v-else> + <strong v-if="!isObfuscated" + :title="user.displayname?.length > 20 ? user.displayname : null"> {{ user.displayname }} - </div> - </div> - </div> - <div v-if="settings.canChangePassword && user.backendCapabilities.setPassword" class="password"> - <label class="hidden-visually" :for="'password'+user.id+rand">{{ t('settings', 'Add new password') }}</label> - <NcTextField :id="'password'+user.id+rand" - :show-trailing-button="true" - class="user-row-text-field" - :class="{'icon-loading-small': loading.password}" - :disabled="loading.password || loading.all" - :minlength="minPasswordLength" - maxlength="469" - :placeholder="t('settings', 'Add new password')" - trailing-button-icon="arrowRight" - :value.sync="editedPassword" - autocapitalize="off" - autocomplete="new-password" - autocorrect="off" - required - spellcheck="false" - type="password" - @trailing-button-click="updatePassword" /> - </div> - - <div v-else /> - - <div class="mailAddress"> - <label class="hidden-visually" :for="'mailAddress'+user.id+rand">{{ t('settings', 'Add new email address') }}</label> - <NcTextField :id="'mailAddress'+user.id+rand" - :show-trailing-button="true" - class="user-row-text-field" - :class="{'icon-loading-small': loading.mailAddress}" - :disabled="loading.mailAddress||loading.all" - :placeholder="t('settings', 'Add new email address')" - trailing-button-icon="arrowRight" - :value.sync="editedMail" - autocapitalize="off" - autocomplete="new-password" - autocorrect="off" - spellcheck="false" - type="email" - @trailing-button-click="updateEmail" /> - </div> - <div :class="{'icon-loading-small': loading.groups}" class="groups"> - <label class="hidden-visually" :for="'groups'+user.id+rand">{{ t('settings', 'Add user to group') }}</label> - <NcSelect :input-id="'groups'+user.id+rand" - :close-on-select="false" - :disabled="loading.groups||loading.all" - :multiple="true" - :options="availableGroups" - :placeholder="t('settings', 'Add user to group')" - :taggable="settings.isAdmin" - :value="userGroups" - class="select-vue" - label="name" - :no-wrap="true" - :selectable="() => userGroups.length < 2" - :create-option="(value) => ({ name: value, isCreating: true })" - @option:created="createGroup" - @option:selected="options => addUserGroup(options.at(-1))" - @option:deselected="removeUserGroup" /> - </div> - <div v-if="subAdminsGroups.length>0 && settings.isAdmin" - :class="{'icon-loading-small': loading.subadmins}" - class="subadmins"> - <label class="hidden-visually" :for="'subadmins'+user.id+rand">{{ t('settings', 'Set user as admin for') }}</label> - <NcSelect :id="'subadmins'+user.id+rand" - :close-on-select="false" - :disabled="loading.subadmins||loading.all" - label="name" - :multiple="true" - :no-wrap="true" - :selectable="() => userSubAdminsGroups.length < 2" - :options="subAdminsGroups" - :placeholder="t('settings', 'Set user as admin for')" - :value="userSubAdminsGroups" - class="select-vue" - @option:deselected="removeUserSubAdmin" - @option:selected="options => addUserSubAdmin(options.at(-1))" /> - </div> - <div :title="usedSpace" - :class="{'icon-loading-small': loading.quota}" - class="quota"> - <label class="hidden-visually" :for="'quota'+user.id+rand">{{ t('settings', 'Select user quota') }}</label> - <NcSelect v-model="userQuota" - :close-on-select="true" - :create-option="validateQuota" - :disabled="loading.quota||loading.all" - :input-id="'quota'+user.id+rand" - class="select-vue" - :options="quotaOptions" - :placeholder="t('settings', 'Select user quota')" - :taggable="true" - @option:selected="setUserQuota" /> - </div> - <div v-if="showConfig.showLanguages" - :class="{'icon-loading-small': loading.languages}" - class="languages"> - <label class="hidden-visually" :for="'language'+user.id+rand">{{ t('settings', 'Set the language') }}</label> - <NcSelect :id="'language'+user.id+rand" - :allow-empty="false" - :disabled="loading.languages||loading.all" - :options="availableLanguages" - :placeholder="t('settings', 'No language set')" - :value="userLanguage" - label="name" - class="select-vue" - @input="setUserLanguage" /> - </div> - - <div v-if="showConfig.showStoragePath || showConfig.showUserBackend" - class="storageLocation" /> - <div v-if="showConfig.showLastLogin" /> - - <div :class="{'icon-loading-small': loading.manager}" class="managers"> - <label class="hidden-visually" :for="'manager'+user.id+rand">{{ t('settings', 'Set the language') }}</label> - <NcSelect v-model="currentManager" - :input-id="'manager'+user.id+rand" - :close-on-select="true" - label="displayname" - :options="possibleManagers" - :placeholder="t('settings', 'Select manager')" - class="select-vue" - @search="searchUserManager" - @option:selected="updateUserManager" - @input="updateUserManager" /> - </div> - - <div class="userActions"> - <UserRowActions v-if="!loading.all" + </strong> + <span class="row__subtitle">{{ user.id }}</span> + </template> + </td> + + <td class="row__cell" + :class="{ 'row__cell--obfuscated': hasObfuscated }"> + <template v-if="idState.editing && settings.canChangePassword && user.backendCapabilities.setPassword"> + <label class="hidden-visually" + :for="'password' + uniqueId"> + {{ t('settings', 'Add new password') }} + </label> + <NcTextField :id="'password' + uniqueId" + :show-trailing-button="true" + class="user-row-text-field" + :class="{'icon-loading-small': idState.loading.password}" + :disabled="idState.loading.password || isLoadingField" + :minlength="minPasswordLength" + maxlength="469" + :placeholder="t('settings', 'Add new password')" + trailing-button-icon="arrowRight" + :value.sync="idState.editedPassword" + autocapitalize="off" + autocomplete="new-password" + autocorrect="off" + required + spellcheck="false" + type="password" + @trailing-button-click="updatePassword" /> + </template> + <span v-else-if="isObfuscated"> + {{ t('settings', 'You do not have permissions to see the details of this user') }} + </span> + </td> + + <td class="row__cell"> + <template v-if="idState.editing"> + <label class="hidden-visually" + :for="'mailAddress' + uniqueId"> + {{ t('settings', 'Add new email address') }} + </label> + <NcTextField :id="'mailAddress' + uniqueId" + :show-trailing-button="true" + class="user-row-text-field" + :class="{'icon-loading-small': idState.loading.mailAddress}" + :disabled="idState.loading.mailAddress || isLoadingField" + :placeholder="t('settings', 'Add new email address')" + trailing-button-icon="arrowRight" + :value.sync="idState.editedMail" + autocapitalize="off" + autocomplete="new-password" + autocorrect="off" + spellcheck="false" + type="email" + @trailing-button-click="updateEmail" /> + </template> + <span v-else-if="!isObfuscated" + :title="user.email?.length > 20 ? user.email : null"> + {{ user.email }} + </span> + </td> + + <td class="row__cell row__cell--large row__cell--multiline"> + <template v-if="idState.editing"> + <label class="hidden-visually" + :for="'groups' + uniqueId"> + {{ t('settings', 'Add user to group') }} + </label> + <NcSelect :input-id="'groups' + uniqueId" + :close-on-select="false" + :disabled="idState.loading.groups || isLoadingField" + :loading="idState.loading.groups" + :multiple="true" + :options="availableGroups" + :placeholder="t('settings', 'Add user to group')" + :taggable="settings.isAdmin" + :value="userGroups" + class="select-vue" + label="name" + :no-wrap="true" + :create-option="(value) => ({ name: value, isCreating: true })" + @option:created="createGroup" + @option:selected="options => addUserGroup(options.at(-1))" + @option:deselected="removeUserGroup" /> + </template> + <span v-else-if="!isObfuscated" + :title="userGroupsLabels?.length > 40 ? userGroupsLabels : null"> + {{ userGroupsLabels }} + </span> + </td> + + <td v-if="subAdminsGroups.length > 0 && settings.isAdmin" + class="row__cell row__cell--large row__cell--multiline"> + <template v-if="idState.editing && settings.isAdmin && subAdminsGroups.length > 0"> + <label class="hidden-visually" + :for="'subadmins' + uniqueId"> + {{ t('settings', 'Set user as admin for') }} + </label> + <NcSelect :id="'subadmins' + uniqueId" + :close-on-select="false" + :disabled="idState.loading.subadmins || isLoadingField" + :loading="idState.loading.subadmins" + label="name" + :multiple="true" + :no-wrap="true" + :options="subAdminsGroups" + :placeholder="t('settings', 'Set user as admin for')" + :value="userSubAdminsGroups" + class="select-vue" + @option:deselected="removeUserSubAdmin" + @option:selected="options => addUserSubAdmin(options.at(-1))" /> + </template> + <span v-else-if="!isObfuscated" + :title="userSubAdminsGroupsLabels?.length > 40 ? userSubAdminsGroupsLabels : null"> + {{ userSubAdminsGroupsLabels }} + </span> + </td> + + <td class="row__cell"> + <template v-if="idState.editing"> + <label class="hidden-visually" + :for="'quota' + uniqueId"> + {{ t('settings', 'Select user quota') }} + </label> + <NcSelect v-model="editedUserQuota" + :close-on-select="true" + :create-option="validateQuota" + :disabled="idState.loading.quota || isLoadingField" + :loading="idState.loading.quota" + :clearable="false" + :input-id="'quota' + uniqueId" + class="select-vue" + :options="quotaOptions" + :placeholder="t('settings', 'Select user quota')" + :taggable="true" + @option:selected="setUserQuota" /> + </template> + <template v-else-if="!isObfuscated"> + <label :for="'quota-progress' + uniqueId">{{ userQuota }} ({{ usedSpace }})</label> + <NcProgressBar class="row__progress" + :id="'quota-progress' + uniqueId" + :class="{ + 'row__progress--warn': usedQuota > 80, + }" + :value="usedQuota" /> + </template> + </td> + + <td v-if="showConfig.showLanguages" + class="row__cell row__cell--large"> + <template v-if="idState.editing"> + <label class="hidden-visually" + :for="'language' + uniqueId"> + {{ t('settings', 'Set the language') }} + </label> + <NcSelect :id="'language' + uniqueId" + :allow-empty="false" + :disabled="idState.loading.languages || isLoadingField" + :loading="idState.loading.languages" + :clearable="false" + :options="availableLanguages" + :placeholder="t('settings', 'No language set')" + :value="userLanguage" + label="name" + class="select-vue" + @input="setUserLanguage" /> + </template> + <span v-else-if="!isObfuscated"> + {{ userLanguage.name }} + </span> + </td> + + <td v-if="showConfig.showUserBackend || showConfig.showStoragePath" + class="row__cell row__cell--large"> + <template v-if="!isObfuscated"> + <span v-if="showConfig.showUserBackend">{{ user.backend }}</span> + <span v-if="showConfig.showStoragePath" + :title="user.storageLocation" + class="row__subtitle"> + {{ user.storageLocation }} + </span> + </template> + </td> + + <td v-if="showConfig.showLastLogin" + :title="userLastLoginTooltip" + class="row__cell"> + <span v-if="!isObfuscated">{{ userLastLogin }}</span> + </td> + + <td class="row__cell row__cell--large"> + <template v-if="idState.editing"> + <label class="hidden-visually" + :for="'manager' + uniqueId"> + {{ t('settings', 'Set the manager') }} + </label> + <NcSelect v-model="idState.currentManager" + :input-id="'manager' + uniqueId" + :close-on-select="true" + :disabled="idState.loading.manager || isLoadingField" + :loading="idState.loading.manager" + label="displayname" + :options="idState.possibleManagers" + :placeholder="t('settings', 'Select manager')" + class="select-vue" + @search="searchUserManager" + @option:selected="updateUserManager" + @input="updateUserManager" /> + </template> + <span v-else-if="!isObfuscated"> + {{ user.manager }} + </span> + </td> + + <td class="row__cell row__cell--actions"> + <UserRowActions v-if="!isObfuscated && canEdit && !idState.loading.all" :actions="userActions" - :edit="true" + :disabled="isLoadingField" + :edit="idState.editing" @update:edit="toggleEdit" /> - </div> - </div> + </td> + </Fragment> </template> <script> +import { Fragment } from 'vue-frag' +import { IdState } from 'vue-virtual-scroller' +import { getCurrentUser } from '@nextcloud/auth' import { showSuccess, showError } from '@nextcloud/dialogs' +import NcAvatar from '@nextcloud/vue/dist/Components/NcAvatar.js' +import NcLoadingIcon from '@nextcloud/vue/dist/Components/NcLoadingIcon.js' +import NcProgressBar from '@nextcloud/vue/dist/Components/NcProgressBar.js' import NcSelect from '@nextcloud/vue/dist/Components/NcSelect.js' import NcTextField from '@nextcloud/vue/dist/Components/NcTextField.js' -import ClickOutside from 'vue-click-outside' import UserRowActions from './UserRowActions.vue' -import UserRowSimple from './UserRowSimple.vue' + import UserRowMixin from '../../mixins/UserRowMixin.js' +import { isObfuscated, unlimitedQuota } from '../../utils/userUtils.ts' export default { name: 'UserRow', components: { + Fragment, + NcAvatar, + NcLoadingIcon, + NcProgressBar, NcSelect, NcTextField, UserRowActions, - UserRowSimple, }, - directives: { - ClickOutside, - }, - - mixins: [UserRowMixin], + mixins: [ + /** + * Use scoped `idState` instead of `data` which is reused between rows + * + * See https://github.com/Akryum/vue-virtual-scroller/tree/v1/packages/vue-virtual-scroller#why-is-this-useful + */ + IdState({ + idProp: vm => vm.user.id, + }), + UserRowMixin, + ], props: { + user: { + type: Object, + required: true, + }, users: { type: Array, required: true, }, - user: { - type: Object, + hasObfuscated: { + type: Boolean, required: true, }, - settings: { - type: Object, - default: () => ({}), - }, groups: { type: Array, default: () => [], }, subAdminsGroups: { type: Array, - default: () => [], + required: true, }, quotaOptions: { type: Array, - default: () => [], - }, - showConfig: { - type: Object, - default: () => ({}), + required: true, }, languages: { type: Array, required: true, }, + settings: { + type: Object, + required: true, + }, externalActions: { type: Array, default: () => [], }, - isDarkTheme: { - type: Boolean, - required: true, - }, }, - data() { + + idState() { return { - // default quota is set to unlimited - unlimitedQuota: { id: 'none', label: t('settings', 'Unlimited') }, - // temporary value used for multiselect change selectedQuota: false, - rand: parseInt(Math.random() * 1000), - openedMenu: false, + rand: Math.random().toString(36).substring(2), possibleManagers: [], currentManager: '', editing: false, @@ -330,7 +394,69 @@ export default { }, computed: { - /* USER POPOVERMENU ACTIONS */ + isObfuscated() { + return isObfuscated(this.user) + }, + + showConfig() { + return this.$store.getters.getShowConfig + }, + + isLoadingUser() { + return this.idState.loading.delete || this.idState.loading.disable || this.idState.loading.wipe + }, + + isLoadingField() { + return this.idState.loading.delete || this.idState.loading.disable || this.idState.loading.all + }, + + uniqueId() { + return this.user.id + this.idState.rand + }, + + userGroupsLabels() { + return this.userGroups + .map(group => group.name) + .join(', ') + }, + + userSubAdminsGroupsLabels() { + return this.userSubAdminsGroups + .map(group => group.name) + .join(', ') + }, + + usedSpace() { + if (this.user.quota?.used) { + return t('settings', '{size} used', { size: OC.Util.humanFileSize(this.user.quota?.used) }) + } + return t('settings', '{size} used', { size: OC.Util.humanFileSize(0) }) + }, + + canEdit() { + return getCurrentUser().uid !== this.user.id || this.settings.isAdmin + }, + + userQuota() { + let quota = this.user.quota?.quota + + if (quota === 'default') { + quota = this.settings.defaultQuota + if (quota !== 'none') { + // convert to numeric value to match what the server would usually return + quota = OC.Util.computerFileSize(quota) + } + } + + // when the default quota is unlimited, the server returns -3 here, map it to "none" + if (quota === 'none' || quota === -3) { + return t('settings', 'Unlimited') + } else if (quota >= 0) { + return OC.Util.humanFileSize(quota) + } + return OC.Util.humanFileSize(0) + }, + userActions() { const actions = [ { @@ -360,19 +486,19 @@ export default { }, // mapping saved values to objects - userQuota: { + editedUserQuota: { get() { - if (this.selectedQuota !== false) { - return this.selectedQuota + if (this.idState.selectedQuota !== false) { + return this.idState.selectedQuota } - if (this.settings.defaultQuota !== this.unlimitedQuota.id && OC.Util.computerFileSize(this.settings.defaultQuota) >= 0) { + if (this.settings.defaultQuota !== unlimitedQuota.id && OC.Util.computerFileSize(this.settings.defaultQuota) >= 0) { // if value is valid, let's map the quotaOptions or return custom quota return { id: this.settings.defaultQuota, label: this.settings.defaultQuota } } - return this.unlimitedQuota // unlimited + return unlimitedQuota // unlimited }, set(quota) { - this.selectedQuota = quota + this.idState.selectedQuota = quota }, }, @@ -390,14 +516,6 @@ export default { }, methods: { - /* MENU HANDLING */ - toggleMenu() { - this.openedMenu = !this.openedMenu - }, - hideMenu() { - this.openedMenu = false - }, - wipeUserDevices() { const userid = this.user.id OC.dialogs.confirmDestructive( @@ -411,13 +529,13 @@ export default { }, (result) => { if (result) { - this.loading.wipe = true - this.loading.all = true + this.idState.loading.wipe = true + this.idState.loading.all = true this.$store.dispatch('wipeUserDevices', userid) .then(() => showSuccess(t('settings', 'Wiped {userid}\'s devices', { userid })), { timeout: 2000 }) .finally(() => { - this.loading.wipe = false - this.loading.all = false + this.idState.loading.wipe = false + this.idState.loading.all = false }) } }, @@ -428,36 +546,38 @@ export default { filterManagers(managers) { return managers.filter((manager) => manager.id !== this.user.id) }, + async initManager(userId) { await this.$store.dispatch('getUser', userId).then(response => { - this.currentManager = response?.data.ocs.data + this.idState.currentManager = response?.data.ocs.data }) }, + async searchUserManager(query) { await this.$store.dispatch('searchUsers', { offset: 0, limit: 10, search: query }).then(response => { const users = response?.data ? this.filterManagers(Object.values(response?.data.ocs.data.users)) : [] if (users.length > 0) { - this.possibleManagers = users + this.idState.possibleManagers = users } }) }, updateUserManager(manager) { if (manager === null) { - this.currentManager = '' + this.idState.currentManager = '' } - this.loading.manager = true + this.idState.loading.manager = true try { this.$store.dispatch('setUserData', { userid: this.user.id, key: 'manager', - value: this.currentManager ? this.currentManager.id : '', + value: this.idState.currentManager ? this.idState.currentManager.id : '', }) } catch (error) { showError(t('setting', 'Update of user manager was failed')) console.error(error) } finally { - this.loading.manager = false + this.idState.loading.manager = false } }, @@ -474,12 +594,12 @@ export default { }, (result) => { if (result) { - this.loading.delete = true - this.loading.all = true + this.idState.loading.delete = true + this.idState.loading.all = true return this.$store.dispatch('deleteUser', userid) .then(() => { - this.loading.delete = false - this.loading.all = false + this.idState.loading.delete = false + this.idState.loading.all = false }) } }, @@ -488,8 +608,8 @@ export default { }, enableDisableUser() { - this.loading.delete = true - this.loading.all = true + this.idState.loading.delete = true + this.idState.loading.all = true const userid = this.user.id const enabled = !this.user.enabled return this.$store.dispatch('enableDisableUser', { @@ -497,8 +617,8 @@ export default { enabled, }) .then(() => { - this.loading.delete = false - this.loading.all = false + this.idState.loading.delete = false + this.idState.loading.all = false }) }, @@ -508,14 +628,14 @@ export default { * @param {string} displayName The display name */ updateDisplayName() { - this.loading.displayName = true + this.idState.loading.displayName = true this.$store.dispatch('setUserData', { userid: this.user.id, key: 'displayname', - value: this.editedDisplayName, + value: this.idState.editedDisplayName, }).then(() => { - this.loading.displayName = false - if (this.editedDisplayName === this.user.displayname) { + this.idState.loading.displayName = false + if (this.idState.editedDisplayName === this.user.displayname) { showSuccess(t('setting', 'Display name was successfully changed')) } }) @@ -527,18 +647,18 @@ export default { * @param {string} password The email address */ updatePassword() { - this.loading.password = true - if (this.editedPassword.length === 0) { + this.idState.loading.password = true + if (this.idState.editedPassword.length === 0) { showError(t('setting', "Password can't be empty")) - this.loading.password = false + this.idState.loading.password = false } else { this.$store.dispatch('setUserData', { userid: this.user.id, key: 'password', - value: this.editedPassword, + value: this.idState.editedPassword, }).then(() => { - this.loading.password = false - this.editedPassword = '' + this.idState.loading.password = false + this.idState.editedPassword = '' showSuccess(t('setting', 'Password was successfully changed')) }) } @@ -550,19 +670,19 @@ export default { * @param {string} mailAddress The email address */ updateEmail() { - this.loading.mailAddress = true - if (this.editedMail === '') { + this.idState.loading.mailAddress = true + if (this.idState.editedMail === '') { showError(t('setting', "Email can't be empty")) - this.loading.mailAddress = false - this.editedMail = this.user.email + this.idState.loading.mailAddress = false + this.idState.editedMail = this.user.email } else { this.$store.dispatch('setUserData', { userid: this.user.id, key: 'email', - value: this.editedMail, + value: this.idState.editedMail, }).then(() => { - this.loading.mailAddress = false - if (this.editedMail === this.user.email) { + this.idState.loading.mailAddress = false + if (this.idState.editedMail === this.user.email) { showSuccess(t('setting', 'Email was successfully changed')) } }) @@ -575,7 +695,7 @@ export default { * @param {string} gid Group id */ async createGroup({ name: gid }) { - this.loading = { groups: true, subadmins: true } + this.idState.loading = { groups: true, subadmins: true } try { await this.$store.dispatch('addGroup', gid) const userid = this.user.id @@ -583,7 +703,7 @@ export default { } catch (error) { console.error(error) } finally { - this.loading = { groups: false, subadmins: false } + this.idState.loading = { groups: false, subadmins: false } } return this.$store.getters.getGroups[this.groups.length] }, @@ -599,7 +719,7 @@ export default { // Ignore return } - this.loading.groups = true + this.idState.loading.groups = true const userid = this.user.id const gid = group.id if (group.canAdd === false) { @@ -610,7 +730,7 @@ export default { } catch (error) { console.error(error) } finally { - this.loading.groups = false + this.idState.loading.groups = false } }, @@ -623,7 +743,7 @@ export default { if (group.canRemove === false) { return false } - this.loading.groups = true + this.idState.loading.groups = true const userid = this.user.id const gid = group.id try { @@ -631,13 +751,13 @@ export default { userid, gid, }) - this.loading.groups = false + this.idState.loading.groups = false // remove user from current list if current list is the removed group if (this.$route.params.selectedGroup === gid) { this.$store.commit('deleteUser', userid) } } catch { - this.loading.groups = false + this.idState.loading.groups = false } }, @@ -647,7 +767,7 @@ export default { * @param {object} group Group object */ async addUserSubAdmin(group) { - this.loading.subadmins = true + this.idState.loading.subadmins = true const userid = this.user.id const gid = group.id try { @@ -655,7 +775,7 @@ export default { userid, gid, }) - this.loading.subadmins = false + this.idState.loading.subadmins = false } catch (error) { console.error(error) } @@ -667,7 +787,7 @@ export default { * @param {object} group Group object */ async removeUserSubAdmin(group) { - this.loading.subadmins = true + this.idState.loading.subadmins = true const userid = this.user.id const gid = group.id @@ -679,7 +799,7 @@ export default { } catch (error) { console.error(error) } finally { - this.loading.subadmins = false + this.idState.loading.subadmins = false } }, @@ -692,9 +812,9 @@ export default { async setUserQuota(quota = 'none') { // Make sure correct label is set for unlimited quota if (quota === 'none') { - quota = this.unlimitedQuota + quota = unlimitedQuota } - this.loading.quota = true + this.idState.loading.quota = true // ensure we only send the preset id quota = quota.id ? quota.id : quota @@ -707,7 +827,7 @@ export default { } catch (error) { console.error(error) } finally { - this.loading.quota = false + this.idState.loading.quota = false } return quota }, @@ -725,7 +845,7 @@ export default { // only used for new presets sent through @Tag const validQuota = OC.Util.computerFileSize(quota) if (validQuota === null) { - return this.unlimitedQuota + return unlimitedQuota } else { // unify format output quota = OC.Util.humanFileSize(OC.Util.computerFileSize(quota)) @@ -740,7 +860,7 @@ export default { * @return {object} */ async setUserLanguage(lang) { - this.loading.languages = true + this.idState.loading.languages = true // ensure we only send the preset id try { await this.$store.dispatch('setUserData', { @@ -748,7 +868,7 @@ export default { key: 'language', value: lang.code, }) - this.loading.languages = false + this.idState.loading.languages = false } catch (error) { console.error(error) } @@ -759,48 +879,65 @@ export default { * Dispatch new welcome mail request */ sendWelcomeMail() { - this.loading.all = true + this.idState.loading.all = true this.$store.dispatch('sendWelcomeMail', this.user.id) .then(() => showSuccess(t('setting', 'Welcome mail sent!'), { timeout: 2000 })) .finally(() => { - this.loading.all = false + this.idState.loading.all = false }) }, - toggleEdit() { - this.editing = false - if (this.editedDisplayName !== this.user.displayname) { - this.editedDisplayName = this.user.displayname - } else if (this.editedMail !== this.user.email) { - this.editedMail = this.user.email + async toggleEdit() { + this.idState.editing = !this.idState.editing + if (this.idState.editing) { + await this.$nextTick() + this.$refs.displayNameField?.$refs?.inputField?.$refs?.input?.focus() + } + if (this.idState.editedDisplayName !== this.user.displayname) { + this.idState.editedDisplayName = this.user.displayname + } else if (this.idState.editedMail !== this.user.email) { + this.idState.editedMail = this.user.email ?? '' } }, }, } </script> -<style scoped lang="scss"> - // Force menu to be above other rows - .row--menu-opened { - z-index: 1 !important; - } - .row :deep() { - .v-select.select { - // reset min width to 100% instead of X px - min-width: 100%; - } +<style lang="scss" scoped> +@import './shared/styles.scss'; - .mailAddress, - .password, - .displayName { +.row { + @include cell; + + &__cell { + :deep { .input-field, + .input-field__main-wrapper, .input-field__input { - height: 48px!important; + height: 48px !important; } + .button-vue--icon-only { - height: 44px!important; + height: 44px !important; + } + + .v-select.select { + min-width: var(--cell-min-width); } } - } + } + &__progress { + margin-top: 4px; + + &--warn { + &::-moz-progress-bar { + background: var(--color-warning) !important; + } + &::-webkit-progress-value { + background: var(--color-warning) !important; + } + } + } +} </style> |