[IMPROVE] Rewrite contextualbar RoomMembers - AddUsers as React Component (#19803)
Co-authored-by: Guilherme Gazzo <guilherme@gazzo.xyz>header_user
parent
4e72f02a6b
commit
edcc10d59d
@ -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'; |
||||
|
||||
@ -1,26 +0,0 @@ |
||||
<template name="inviteUsers"> |
||||
|
||||
<div class="rc-input"> |
||||
<label class="rc-input__label"> |
||||
<div class="rc-input__title">{{_ "Invite_Users"}}</div> |
||||
<div class="rc-input__wrapper"> |
||||
<div class="rc-tags rc-tags--no-icon"> |
||||
{{#each user in selectedUsers}} |
||||
{{> tag user}} |
||||
{{/each}} |
||||
<input type="text" class="rc-tags__input" placeholder="{{_ "Username_Placeholder"}}" |
||||
name="users" autocomplete="off"/> |
||||
</div> |
||||
</div> |
||||
{{#with config}} |
||||
{{#if autocomplete 'isShowing'}} |
||||
{{> popupList data=config items=items ready=(autocomplete 'isLoaded')}} |
||||
{{/if}} |
||||
{{/with}} |
||||
</label> |
||||
</div> |
||||
<div class="rc-user-info__flex rc-user-info__row"> |
||||
<button class="rc-button rc-button--outline js-close" title="{{_ 'Cancel'}}">{{_ 'Cancel'}}</button> |
||||
<button class="rc-button rc-button--primary js-add" disabled={{disabled}}>{{> icon block="rc-input__icon-svg" icon="plus"}}{{tAddUsers}}</button> |
||||
</div> |
||||
</template> |
||||
@ -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 `<strong>${ part }</strong>`; |
||||
}) }`;
|
||||
}, |
||||
}; |
||||
}, |
||||
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; |
||||
}); |
||||
@ -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 = () => <AutoComplete/>; |
||||
export const Example = () => <UserAutoComplete/>; |
||||
|
||||
@ -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 ( |
||||
<> |
||||
<VerticalBar.Header> |
||||
{onClickBack && <VerticalBar.Back onClick={onClickBack} />} |
||||
<VerticalBar.Text>{t('Add_users')}</VerticalBar.Text> |
||||
{onClickClose && <VerticalBar.Close onClick={onClickClose} />} |
||||
</VerticalBar.Header> |
||||
<VerticalBar.ScrollableContent> |
||||
<Field > |
||||
<Field.Label flexGrow={0}>{t('Choose_users')}</Field.Label> |
||||
<UserAutoCompleteMultiple errors={errors.users} value={value} onChange={onChange} placeholder={t('Choose_users')} /> |
||||
{errors.users && <Field.Error> |
||||
{errors.users} |
||||
</Field.Error>} |
||||
</Field> |
||||
</VerticalBar.ScrollableContent> |
||||
<VerticalBar.Footer> |
||||
<Button primary disabled={!value || value.length === 0} onClick={onClickSave}>{t('Add_users')}</Button> |
||||
</VerticalBar.Footer> |
||||
</> |
||||
); |
||||
}; |
||||
|
||||
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 ( |
||||
<AddUsers |
||||
onClickClose={onClickClose} |
||||
onClickBack={onClickBack} |
||||
onClickSave={handleSave} |
||||
value={users} |
||||
onChange={onChangeUsers} |
||||
errors={errors} |
||||
/> |
||||
); |
||||
}; |
||||
@ -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 = () => <VerticalBar> |
||||
<AddUsers |
||||
onClickBack={alert} |
||||
onClickClose={alert} |
||||
onClickSave={alert} |
||||
value={[]} |
||||
errors={{}} |
||||
/> |
||||
</VerticalBar>; |
||||
|
||||
export const Error = () => <VerticalBar> |
||||
<AddUsers |
||||
onClickBack={alert} |
||||
onClickClose={alert} |
||||
onClickSave={alert} |
||||
value={[]} |
||||
errors={{ users: 'With Test Error' }} |
||||
/> |
||||
</VerticalBar>; |
||||
@ -0,0 +1,3 @@ |
||||
import AddUsers from './AddUsers'; |
||||
|
||||
export default AddUsers; |
||||
Loading…
Reference in new issue