diff --git a/app/theme/client/imports/components/contextual-bar.css b/app/theme/client/imports/components/contextual-bar.css index f5bb2b4b0ad..ecc6e8a8be1 100644 --- a/app/theme/client/imports/components/contextual-bar.css +++ b/app/theme/client/imports/components/contextual-bar.css @@ -1,18 +1,33 @@ .contextual-bar { - z-index: 10; + &.contextual-bar { + z-index: 10; - display: flex; + display: flex; + + overflow: hidden; + flex-direction: column; + flex: 0 0 var(--flex-tab-width); - overflow: hidden; - flex-direction: column; - flex: 0 0 var(--flex-tab-width); + width: var(--flex-tab-width); + height: 100%; - width: var(--flex-tab-width); - height: 100%; + background: var(--color-white); - background: var(--color-white); + border-inline-start: 2px solid var(--color-gray-lightest); - border-inline-start: 2px solid var(--color-gray-lightest); + & > .flex-tab { + width: 100%; + } + + & & { + margin-left: -2px; + } + + .rtl & & { + margin-right: -2px; + margin-left: 0; + } + } &-wrap { position: relative; @@ -24,10 +39,6 @@ max-height: 100%; } - & > .flex-tab { - width: 100%; - } - &__content { display: flex; overflow: auto; diff --git a/app/ui-flextab/client/index.js b/app/ui-flextab/client/index.js index 1e5bd021637..3def11b4ad8 100644 --- a/app/ui-flextab/client/index.js +++ b/app/ui-flextab/client/index.js @@ -3,12 +3,9 @@ import './tabs/inviteUsers.html'; import './tabs/createInviteLink.html'; import './tabs/membersList.html'; import './tabs/uploadedFilesList.html'; -import './tabs/userEdit.html'; import './flexTabBar'; import './tabs/inviteUsers'; import './tabs/createInviteLink'; import './tabs/membersList'; import './tabs/uploadedFilesList'; -import './tabs/userEdit'; -import './tabs/userInfo'; import './tabs/keyboardShortcuts.html'; diff --git a/app/ui-flextab/client/tabs/membersList.html b/app/ui-flextab/client/tabs/membersList.html index 27fa9b62432..e7708dd1426 100644 --- a/app/ui-flextab/client/tabs/membersList.html +++ b/app/ui-flextab/client/tabs/membersList.html @@ -60,7 +60,9 @@ {{/with}} {{/if}} + {{#if userInfoDetail}}
+ {{/if}} diff --git a/app/ui-flextab/client/tabs/membersList.js b/app/ui-flextab/client/tabs/membersList.js index 54d8bf1a297..f7e8c22d4a8 100644 --- a/app/ui-flextab/client/tabs/membersList.js +++ b/app/ui-flextab/client/tabs/membersList.js @@ -2,6 +2,7 @@ import { Meteor } from 'meteor/meteor'; import { ReactiveVar } from 'meteor/reactive-var'; import { Session } from 'meteor/session'; import { Template } from 'meteor/templating'; +import { HTML } from 'meteor/htmljs'; import { getActions } from './userActions'; import { RoomManager, popover } from '../../../ui-utils/client'; @@ -10,6 +11,12 @@ import { settings } from '../../../settings/client'; import { t, isRtl, handleError, roomTypes, getUserAvatarURL } from '../../../utils/client'; import { WebRTC } from '../../../webrtc/client'; import { hasPermission } from '../../../authorization/client'; +import { createTemplateForComponent } from '../../../../client/reactAdapters'; + +createTemplateForComponent('UserInfoWithData', () => import('../../../../client/channel/UserInfo'), { + // eslint-disable-next-line new-cap + renderContainerView: () => HTML.DIV({ class: 'contextual-bar', style: 'flex-grow: 1;' }), +}); Template.membersList.helpers({ ignored() { @@ -107,13 +114,15 @@ Template.membersList.helpers({ userInfoDetail() { const room = ChatRoom.findOne(this.rid, { fields: { t: 1, usernames: 1 } }); - + const username = Template.instance().userDetail.get(); + if (!username) { + return; + } return { tabBar: Template.currentData().tabBar, - username: Template.instance().userDetail.get(), - clear: Template.instance().clearUserDetail, - showAll: roomTypes.getConfig(room.t).userDetailShowAll(room) || false, - hideAdminControls: roomTypes.getConfig(room.t).userDetailShowAdmin(room) || false, + username, + rid: Template.currentData().rid, + onClose: Template.instance().clearUserDetail, video: ['d'].includes(room && room.t), showBackButton: roomTypes.getConfig(room.t).isGroupChat(room), }; @@ -291,6 +300,7 @@ Template.membersList.onCreated(function() { this.clearUserDetail = () => { this.showDetail.set(false); + this.userDetail.set(null); this.tabBar.setData({ label: 'Members', icon: 'team', @@ -316,7 +326,7 @@ Template.membersList.onCreated(function() { }); Template.membersList.onRendered(function() { - this.firstNode.parentNode.querySelector('#user-search').focus(); + this.firstNode.parentNode.querySelector('#user-search')?.focus(); this.autorun(() => { const showAllUsers = this.showAllUsers.get(); const statusTypeSelect = this.find('.js-type'); diff --git a/app/ui-flextab/client/tabs/userEdit.html b/app/ui-flextab/client/tabs/userEdit.html deleted file mode 100644 index f4e17c2aef7..00000000000 --- a/app/ui-flextab/client/tabs/userEdit.html +++ /dev/null @@ -1,215 +0,0 @@ - - - diff --git a/app/ui-flextab/client/tabs/userEdit.js b/app/ui-flextab/client/tabs/userEdit.js deleted file mode 100644 index ec37c0d816a..00000000000 --- a/app/ui-flextab/client/tabs/userEdit.js +++ /dev/null @@ -1,316 +0,0 @@ -import { Meteor } from 'meteor/meteor'; -import { ReactiveVar } from 'meteor/reactive-var'; -import { Random } from 'meteor/random'; -import { Template } from 'meteor/templating'; -import { TAPi18n } from 'meteor/rocketchat:tap-i18n'; -import toastr from 'toastr'; -import s from 'underscore.string'; - -import { t, handleError } from '../../../utils'; -import { Roles } from '../../../models'; -import { Notifications } from '../../../notifications'; -import { hasAtLeastOnePermission } from '../../../authorization'; -import { settings } from '../../../settings/client'; -import { callbacks } from '../../../callbacks/client'; -import { modal } from '../../../ui-utils/client'; - -Template.userEdit.helpers({ - - disabled(cursor) { - return cursor.count() === 0 ? 'disabled' : ''; - }, - canEditOrAdd() { - return (Template.instance().user && hasAtLeastOnePermission('edit-other-user-info')) || (!Template.instance().user && hasAtLeastOnePermission('create-user')); - }, - - selectUrl() { - return Template.instance().url.get().trim() ? '' : 'disabled'; - }, - - user() { - return Template.instance().user; - }, - - initialsUsername() { - const { user } = Template.instance(); - return `@${ user && user.username }`; - }, - - avatarPreview() { - return Template.instance().avatar.get(); - }, - - requirePasswordChange() { - return !Template.instance().user || Template.instance().user.requirePasswordChange; - }, - - requirePasswordChangeDisabled() { - // when setting a random password, requiring a password change is mandatory - return Template.instance().setRandomPassword.get(); - }, - - setRandomPassword() { - return !Template.instance().user || Template.instance().user.setRandomPassword; - }, - - role() { - const roles = Template.instance().roles.get(); - return Roles.find({ _id: { $nin: roles }, scope: 'Users' }, { sort: { description: 1, _id: 1 } }); - }, - - userRoles() { - return Template.instance().roles.get(); - }, - - name() { - return this.description || this._id; - }, -}); - -Template.userEdit.events({ - 'click .js-select-avatar-initials'(e, template) { - template.avatar.set({ - service: 'initials', - blob: `@${ template.user.username }`, - }); - }, - - 'click .js-select-avatar-url'(e, template) { - const url = template.url.get().trim(); - if (!url) { - return; - } - - template.avatar.set({ - service: 'url', - contentType: '', - blob: url, - }); - }, - - 'input .js-avatar-url-input'(e, template) { - const text = e.target.value; - template.url.set(text); - }, - - 'change #setRandomPassword'(e, template) { - const requiring = e.currentTarget.checked; - template.setRandomPassword.set(requiring); - - if (requiring) { - $(e.currentTarget.form).find('#changePassword')[0].checked = true; - $(e.currentTarget.form).find('#password')[0].value = ''; - } - }, - - 'click #randomPassword'(e) { - e.stopPropagation(); - e.preventDefault(); - e.target.classList.add('loading'); - $('#password').val(''); - setTimeout(() => { - $('#password').val(Random.id()); - e.target.classList.remove('loading'); - }, 1000); - }, - - 'change .js-select-avatar-upload [type=file]'(event, template) { - const e = event.originalEvent || event; - let { files } = e.target; - if (!files || files.length === 0) { - files = (e.dataTransfer && e.dataTransfer.files) || []; - } - Object.keys(files).forEach((key) => { - const blob = files[key]; - if (!/image\/.+/.test(blob.type)) { - return; - } - const reader = new FileReader(); - reader.readAsDataURL(blob); - reader.onloadend = function() { - template.avatar.set({ - service: 'upload', - contentType: blob.type, - blob: reader.result, - }); - }; - }); - }, - - 'click .cancel'(e, t) { - e.stopPropagation(); - e.preventDefault(); - t.roles.set([]); - t.cancel(t.find('form')); - }, - - 'click .remove-role'(e, t) { - e.stopPropagation(); - e.preventDefault(); - let roles = t.roles.get(); - roles = roles.filter((el) => el !== this.valueOf()); - t.roles.set(roles); - $(`[title=${ this }]`).remove(); - }, - - 'mouseover #password'(e) { - e.target.type = 'text'; - }, - - 'mouseout #password'(e) { - e.target.type = 'password'; - }, - - 'change #roleSelect'(e, instance) { - const select = $('#roleSelect'); - e.stopPropagation(); - e.preventDefault(); - if (select.find(':selected').is(':disabled')) { - return; - } - const userRoles = [...instance.roles.get()]; - userRoles.push(select.val()); - instance.roles.set(userRoles); - select.val('placeholder'); - }, - - 'submit form'(e, t) { - e.stopPropagation(); - e.preventDefault(); - t.save(e.currentTarget); - }, -}); - -Template.userEdit.onCreated(function() { - this.user = this.data != null ? this.data.user : undefined; - this.roles = this.user ? new ReactiveVar(this.user.roles) : new ReactiveVar([]); - this.avatar = new ReactiveVar(); - this.url = new ReactiveVar(''); - this.setRandomPassword = new ReactiveVar(!this.user); - - Notifications.onLogged('updateAvatar', () => this.avatar.set()); - - const { tabBar } = Template.currentData(); - - this.cancel = (form, data) => { - form.reset(); - this.$('input[type=checkbox]').prop('checked', true); - if (this.user) { - return this.data.back(data); - } - return tabBar.close(); - }; - - this.getUserData = () => { - const userData = { _id: this.user != null ? this.user._id : undefined }; - userData.name = s.trim(this.$('#name').val()); - userData.username = s.trim(this.$('#username').val()); - userData.statusText = s.trim(this.$('#status').val()); - userData.bio = s.trim(this.$('#bio').val()); - userData.email = s.trim(this.$('#email').val()); - userData.verified = this.$('#verified:checked').length > 0; - userData.password = s.trim(this.$('#password').val()); - userData.setRandomPassword = this.$('#setRandomPassword:checked').length > 0; - userData.requirePasswordChange = this.$('#changePassword:checked').length > 0; - userData.joinDefaultChannels = this.$('#joinDefaultChannels:checked').length > 0; - userData.sendWelcomeEmail = this.$('#sendWelcomeEmail:checked').length > 0; - const roleSelect = this.$('.remove-role').toArray(); - - if (roleSelect.length > 0) { - const notSorted = roleSelect.map((role) => role.title); - // Remove duplicate strings from the array - userData.roles = notSorted.filter((el, index) => notSorted.indexOf(el) === index); - } - return userData; - }; - - this.validate = () => { - const userData = this.getUserData(); - - const errors = []; - if (settings.get('Accounts_RequireNameForSignUp') && !userData.name) { - errors.push('Name'); - } - if (!userData.username) { - errors.push('Username'); - } - if (!userData.email) { - errors.push('Email'); - } - - if (!userData.roles) { - errors.push('Roles'); - } - - for (const error of Array.from(errors)) { - toastr.error(TAPi18n.__('error-the-field-is-required', { field: TAPi18n.__(error) })); - } - - return errors.length === 0; - }; - - this.save = (form) => { - if (!this.validate()) { - return; - } - const userData = this.getUserData(); - if (this.user != null) { - for (const key in userData) { - if (key) { - const value = userData[key]; - if (!['_id'].includes(key)) { - if (value === this.user[key]) { - delete userData[key]; - } - } - } - } - } - - const avatar = this.avatar.get(); - if (avatar) { - let method; - const params = []; - - if (avatar.service === 'initials') { - method = 'resetAvatar'; - } else { - method = 'setAvatarFromService'; - params.push(avatar.blob, avatar.contentType, avatar.service); - } - - Meteor.call(method, ...params, Template.instance().user._id, function(err) { - if (err && err.details) { - toastr.error(t(err.message)); - } else { - toastr.success(t('Avatar_changed_successfully')); - callbacks.run('userAvatarSet', avatar.service); - } - }); - } - - Meteor.call('insertOrUpdateUser', userData, (error) => { - if (error) { - if (error.error === 'error-max-guests-number-reached') { - const message = TAPi18n.__('You_reached_the_maximum_number_of_guest_users_allowed_by_your_license.'); - const url = 'https://go.rocket.chat/i/guest-limit-exceeded'; - const email = 'sales@rocket.chat'; - const linkText = TAPi18n.__('Click_here_for_more_details_or_contact_sales_for_a_new_license', { url, email }); - - modal.open({ - type: 'error', - title: TAPi18n.__('Maximum_number_of_guests_reached'), - text: `${ message } ${ linkText }`, - html: true, - }); - - return true; - } - - return handleError(error); - } - toastr.success(userData._id ? t('User_updated_successfully') : t('User_added_successfully')); - this.cancel(form, userData); - }); - }; -}); diff --git a/app/ui-flextab/client/tabs/userInfo.html b/app/ui-flextab/client/tabs/userInfo.html deleted file mode 100644 index 6071ec168a8..00000000000 --- a/app/ui-flextab/client/tabs/userInfo.html +++ /dev/null @@ -1,128 +0,0 @@ - - {{#unless hideHeader}} -