From edcc10d59ded69e613fbdfe8059ea75ab5925d8b Mon Sep 17 00:00:00 2001 From: Douglas Fabris Date: Tue, 8 Dec 2020 23:14:08 -0300 Subject: [PATCH] [IMPROVE] Rewrite contextualbar RoomMembers - AddUsers as React Component (#19803) Co-authored-by: Guilherme Gazzo --- app/ui-flextab/client/index.js | 2 - app/ui-flextab/client/tabs/inviteUsers.html | 26 --- app/ui-flextab/client/tabs/inviteUsers.js | 164 ------------------ app/ui-flextab/client/tabs/membersList.js | 2 +- client/components/AutoComplete.stories.js | 6 +- client/views/room/adapters.js | 4 + .../RoomMembers/AddUsers/AddUsers.js | 99 +++++++++++ .../RoomMembers/AddUsers/AddUsers.stories.js | 29 ++++ .../RoomMembers/AddUsers/index.js | 3 + .../RoomMembers/EditInvite/EditInvite.js | 1 - ee/client/audit/UserAutoCompleteMultiple.js | 5 +- packages/rocketchat-i18n/i18n/en.i18n.json | 2 + 12 files changed, 144 insertions(+), 199 deletions(-) delete mode 100644 app/ui-flextab/client/tabs/inviteUsers.html delete mode 100644 app/ui-flextab/client/tabs/inviteUsers.js create mode 100644 client/views/room/contextualBar/RoomMembers/AddUsers/AddUsers.js create mode 100644 client/views/room/contextualBar/RoomMembers/AddUsers/AddUsers.stories.js create mode 100644 client/views/room/contextualBar/RoomMembers/AddUsers/index.js diff --git a/app/ui-flextab/client/index.js b/app/ui-flextab/client/index.js index cda6675a583..5d93a9b0402 100644 --- a/app/ui-flextab/client/index.js +++ b/app/ui-flextab/client/index.js @@ -1,8 +1,6 @@ import './flexTabBar.html'; -import './tabs/inviteUsers.html'; import './tabs/membersList.html'; import './tabs/uploadedFilesList.html'; import './flexTabBar'; -import './tabs/inviteUsers'; import './tabs/membersList'; import './tabs/uploadedFilesList'; diff --git a/app/ui-flextab/client/tabs/inviteUsers.html b/app/ui-flextab/client/tabs/inviteUsers.html deleted file mode 100644 index 0aad57ebdae..00000000000 --- a/app/ui-flextab/client/tabs/inviteUsers.html +++ /dev/null @@ -1,26 +0,0 @@ - diff --git a/app/ui-flextab/client/tabs/inviteUsers.js b/app/ui-flextab/client/tabs/inviteUsers.js deleted file mode 100644 index e8e0783f068..00000000000 --- a/app/ui-flextab/client/tabs/inviteUsers.js +++ /dev/null @@ -1,164 +0,0 @@ -import { Meteor } from 'meteor/meteor'; -import { ReactiveVar } from 'meteor/reactive-var'; -import { Blaze } from 'meteor/blaze'; -import { Session } from 'meteor/session'; -import { Template } from 'meteor/templating'; -import { Deps } from 'meteor/deps'; -import toastr from 'toastr'; - -import { settings } from '../../../settings'; -import { t, handleError } from '../../../utils/client'; -import { AutoComplete } from '../../../meteor-autocomplete/client'; - -const acEvents = { - 'click .rc-popup-list__item'(e, t) { - t.ac.onItemClick(this, e); - }, - 'keydown [name="users"]'(e, t) { - if ([8, 46].includes(e.keyCode) && e.target.value === '') { - const users = t.selectedUsers; - const usersArr = users.get(); - usersArr.pop(); - return users.set(usersArr); - } - - t.ac.onKeyDown(e); - }, - 'keyup [name="users"]'(e, t) { - t.ac.onKeyUp(e); - }, - 'focus [name="users"]'(e, t) { - t.ac.onFocus(e); - }, - 'blur [name="users"]'(e, t) { - t.ac.onBlur(e); - }, -}; - -const filterNames = (old) => { - if (settings.get('UI_Allow_room_names_with_special_chars')) { - return old; - } - - const reg = new RegExp(`^${ settings.get('UTF8_Names_Validation') }$`); - return [...old.replace(' ', '').toLocaleLowerCase()].filter((f) => reg.test(f)).join(''); -}; - -Template.inviteUsers.helpers({ - disabled() { - return Template.instance().selectedUsers.get().length === 0; - }, - tAddUsers() { - return t('Add_users'); - }, - autocomplete(key) { - const instance = Template.instance(); - const param = instance.ac[key]; - return typeof param === 'function' ? param.apply(instance.ac) : param; - }, - items() { - return Template.instance().ac.filteredList(); - }, - config() { - const filter = Template.instance().userFilter.get(); - return { - filter, - noMatchTemplate: 'userSearchEmpty', - modifier(text) { - const f = filter; - return `@${ f.length === 0 ? text : text.replace(new RegExp(filter), function(part) { - return `${ part }`; - }) }`; - }, - }; - }, - selectedUsers() { - return Template.instance().selectedUsers.get(); - }, -}); - -Template.inviteUsers.events({ - - ...acEvents, - 'click .rc-tags__tag'({ target }, t) { - const { username } = Blaze.getData(target); - t.selectedUsers.set(t.selectedUsers.get().filter((user) => user.username !== username)); - }, - 'click .rc-tags__tag-icon'(e, t) { - const { username } = Blaze.getData(t.find('.rc-tags__tag-text')); - t.selectedUsers.set(t.selectedUsers.get().filter((user) => user.username !== username)); - }, - 'input [name="users"]'(e, t) { - const input = e.target; - const position = input.selectionEnd || input.selectionStart; - const { length } = input.value; - const modified = filterNames(input.value); - input.value = modified; - document.activeElement === input && e && /input/i.test(e.type) && (input.selectionEnd = position + input.value.length - length); - - t.userFilter.set(modified); - }, - 'click .js-add'(e, instance) { - const users = instance.selectedUsers.get().map(({ username }) => username); - - Meteor.call('addUsersToRoom', { - rid: Session.get('openedRoom'), - users, - }, function(err) { - if (err) { - return handleError(err); - } - toastr.success(t('Users_added')); - instance.selectedUsers.set([]); - }); - }, -}); - -Template.inviteUsers.onRendered(function() { - const users = this.selectedUsers; - - this.firstNode.querySelector('[name="users"]').focus(); - this.ac.element = this.firstNode.querySelector('[name="users"]'); - this.ac.$element = $(this.ac.element); - this.ac.$element.on('autocompleteselect', function(e, { item }) { - const usersArr = users.get(); - usersArr.push(item); - users.set(usersArr); - }); -}); - -Template.inviteUsers.onCreated(function() { - this.selectedUsers = new ReactiveVar([]); - const filter = { exceptions: [Meteor.user().username].concat(this.selectedUsers.get().map((u) => u.username)) }; - Deps.autorun(() => { - filter.exceptions = [Meteor.user().username].concat(this.selectedUsers.get().map((u) => u.username)); - }); - this.userFilter = new ReactiveVar(''); - - this.ac = new AutoComplete({ - selector: { - anchor: '.rc-input__label', - item: '.rc-popup-list__item', - container: '.rc-popup-list__list', - }, - position: 'fixed', - limit: 10, - inputDelay: 300, - rules: [ - { - // @TODO maybe change this 'collection' and/or template - collection: 'UserAndRoom', - endpoint: 'users.autocomplete', - field: 'username', - matchAll: true, - filter, - doNotChangeWidth: false, - selector(match) { - return { term: match }; - }, - sort: 'username', - }, - ], - }); - this.ac.tmplInst = this; -}); diff --git a/app/ui-flextab/client/tabs/membersList.js b/app/ui-flextab/client/tabs/membersList.js index 78ca4c10d16..4e902c6fa25 100644 --- a/app/ui-flextab/client/tabs/membersList.js +++ b/app/ui-flextab/client/tabs/membersList.js @@ -157,7 +157,7 @@ Template.membersList.helpers({ Template.membersList.events({ 'click .js-add'(e, instance) { - instance.tabBar.setTemplate('inviteUsers'); + instance.innerTab.set('AddUsers'); }, 'click .js-invite'(e, instance) { instance.innerTab.set('InviteUsers'); diff --git a/client/components/AutoComplete.stories.js b/client/components/AutoComplete.stories.js index 7ecef392b48..5030ecdf7fb 100644 --- a/client/components/AutoComplete.stories.js +++ b/client/components/AutoComplete.stories.js @@ -1,10 +1,10 @@ import React from 'react'; -import AutoComplete from './AutoComplete'; +import { UserAutoComplete } from './AutoComplete'; export default { title: 'components/AutoComplete', - component: AutoComplete, + component: UserAutoComplete, }; -export const Example = () => ; +export const Example = () => ; diff --git a/client/views/room/adapters.js b/client/views/room/adapters.js index b779d7d65ef..4624cc5c63a 100644 --- a/client/views/room/adapters.js +++ b/client/views/room/adapters.js @@ -38,6 +38,10 @@ createTemplateForComponent('EditInvite', () => import('./contextualBar/RoomMembe renderContainerView: () => HTML.DIV({ class: 'contextual-bar' }), // eslint-disable-line new-cap }); +createTemplateForComponent('AddUsers', () => import('./contextualBar/RoomMembers/AddUsers'), { + renderContainerView: () => HTML.DIV({ class: 'contextual-bar' }), // eslint-disable-line new-cap +}); + createTemplateForComponent('OTR', () => import('./contextualBar/OTR'), { renderContainerView: () => HTML.DIV({ class: 'contextual-bar' }), // eslint-disable-line new-cap }); diff --git a/client/views/room/contextualBar/RoomMembers/AddUsers/AddUsers.js b/client/views/room/contextualBar/RoomMembers/AddUsers/AddUsers.js new file mode 100644 index 00000000000..d95799d48ac --- /dev/null +++ b/client/views/room/contextualBar/RoomMembers/AddUsers/AddUsers.js @@ -0,0 +1,99 @@ +import React, { useState } from 'react'; +import { Field, Button } from '@rocket.chat/fuselage'; +import { useMutableCallback } from '@rocket.chat/fuselage-hooks'; + +import UserAutoCompleteMultiple from '../../../../../../ee/client/audit/UserAutoCompleteMultiple'; +import VerticalBar from '../../../../../components/VerticalBar'; +import { useTranslation } from '../../../../../contexts/TranslationContext'; +import { useForm } from '../../../../../hooks/useForm'; +import { useMethod } from '../../../../../contexts/ServerContext'; +import { useToastMessageDispatch } from '../../../../../contexts/ToastMessagesContext'; + +export const AddUsers = ({ + onClickClose, + onClickBack, + onClickSave, + value, + onChange, + errors, +}) => { + const t = useTranslation(); + + return ( + <> + + {onClickBack && } + {t('Add_users')} + {onClickClose && } + + + + {t('Choose_users')} + + {errors.users && + {errors.users} + } + + + + + + + ); +}; + +export default ({ + rid, + tabBar, + onClickBack, +}) => { + const t = useTranslation(); + const dispatchToastMessage = useToastMessageDispatch(); + const [errors, setErrors] = useState({}); + + const onClickClose = useMutableCallback(() => tabBar && tabBar.close()); + const saveAction = useMethod('addUsersToRoom'); + + const { values, handlers } = useForm({ users: [] }); + const { users } = values; + const { handleUsers } = handlers; + + const onChangeUsers = useMutableCallback((value, action) => { + if (!action) { + if (users.includes(value)) { + return; + } + return handleUsers([...users, value]); + } + handleUsers(users.filter((current) => current !== value)); + }); + + const handleSave = useMutableCallback(async () => { + if (users.length < 1) { + return setErrors({ + users: t('Select_at_least_one_user'), + }); + } + + try { + await saveAction({ rid, users }); + dispatchToastMessage({ type: 'success', message: t('Users_added') }); + onClickBack(); + } catch (e) { + dispatchToastMessage({ type: 'error', message: e }); + } + + setErrors({}); + }); + + return ( + + ); +}; diff --git a/client/views/room/contextualBar/RoomMembers/AddUsers/AddUsers.stories.js b/client/views/room/contextualBar/RoomMembers/AddUsers/AddUsers.stories.js new file mode 100644 index 00000000000..cc75af8f65c --- /dev/null +++ b/client/views/room/contextualBar/RoomMembers/AddUsers/AddUsers.stories.js @@ -0,0 +1,29 @@ +import React from 'react'; + +import { AddUsers } from './AddUsers'; +import VerticalBar from '../../../../../components/VerticalBar'; + +export default { + title: 'components/RoomMembers/AddUsers', + component: AddUsers, +}; + +export const Default = () => + +; + +export const Error = () => + +; diff --git a/client/views/room/contextualBar/RoomMembers/AddUsers/index.js b/client/views/room/contextualBar/RoomMembers/AddUsers/index.js new file mode 100644 index 00000000000..25fa89834bc --- /dev/null +++ b/client/views/room/contextualBar/RoomMembers/AddUsers/index.js @@ -0,0 +1,3 @@ +import AddUsers from './AddUsers'; + +export default AddUsers; diff --git a/client/views/room/contextualBar/RoomMembers/EditInvite/EditInvite.js b/client/views/room/contextualBar/RoomMembers/EditInvite/EditInvite.js index 3b9fe42e6d8..080b5f95547 100644 --- a/client/views/room/contextualBar/RoomMembers/EditInvite/EditInvite.js +++ b/client/views/room/contextualBar/RoomMembers/EditInvite/EditInvite.js @@ -72,7 +72,6 @@ export default ({ captionText, days: _days, maxUses: _maxUses, - }) => { const onClickClose = useMutableCallback(() => tabBar && tabBar.close()); diff --git a/ee/client/audit/UserAutoCompleteMultiple.js b/ee/client/audit/UserAutoCompleteMultiple.js index 37881df37f2..8fa1055ceaa 100644 --- a/ee/client/audit/UserAutoCompleteMultiple.js +++ b/ee/client/audit/UserAutoCompleteMultiple.js @@ -1,5 +1,5 @@ import React, { useMemo, useState } from 'react'; -import { AutoComplete, Option, Options, Chip } from '@rocket.chat/fuselage'; +import { AutoComplete, Box, Option, Options, Chip } from '@rocket.chat/fuselage'; import { useMutableCallback } from '@rocket.chat/fuselage-hooks'; import UserAvatar from '../../../client/components/avatar/UserAvatar'; @@ -18,11 +18,12 @@ const UserAutoCompleteMultiple = React.memo((props) => { e.preventDefault(); props.onChange(e.currentTarget.value, 'remove'); }); + return selected?.map((value) => {value})} + renderSelected={({ value: selected }) => selected?.map((value) => {value})} renderItem={({ value, ...props }) =>