[FIX] Revert AutoComplete (#24812)

Co-authored-by: Guilherme Gazzo <guilhermegazzo@gmail.com>
pull/24814/head
Júlia Jaeger Foresti 3 years ago committed by Pierre Lehnen
parent 76e0ec2562
commit b46dd2a47f
  1. 10
      client/components/CreateDiscussion/CreateDiscussion.tsx
  2. 32
      client/components/UserAutoComplete/UserAutoComplete.js
  3. 82
      client/components/UserAutoCompleteMultiple/UserAutoCompleteMultiple.js
  4. 10
      client/sidebar/header/CreateChannelWithData.js
  5. 18
      client/sidebar/header/CreateDirectMessage.tsx
  6. 10
      client/views/room/contextualBar/ExportMessages/MailExportForm.js
  7. 10
      client/views/room/contextualBar/PruneMessages/PruneMessagesWithData.js
  8. 10
      client/views/room/contextualBar/RoomMembers/AddUsers/AddUsersWithData.js
  9. 20
      client/views/teams/CreateTeamModal/CreateTeamModal.tsx
  10. 90
      client/views/teams/CreateTeamModal/UsersInput.tsx
  11. 10
      ee/client/audit/AuditPage.js

@ -65,8 +65,14 @@ const CreateDiscussion = ({ onClose, defaultParentRoom, parentMessageId, nameSug
}
});
const onChangeUsers = useMutableCallback((value: CreateDiscussionFormValues['usernames']) => {
handleUsernames(value);
const onChangeUsers = useMutableCallback((value, action) => {
if (!action) {
if (usernames.includes(value)) {
return;
}
return handleUsernames([...usernames, value]);
}
handleUsernames(usernames.filter((current) => current !== value));
});
return (

@ -1,4 +1,4 @@
import { SelectFiltered, Option, Box, Chip } from '@rocket.chat/fuselage';
import { AutoComplete, Option, Box, Chip, Avatar } from '@rocket.chat/fuselage';
import { useDebouncedValue } from '@rocket.chat/fuselage-hooks';
import React, { memo, useMemo, useState } from 'react';
@ -17,29 +17,29 @@ const UserAutoComplete = (props) => {
useMemo(() => query(debouncedFilter, conditions), [filter]),
);
const options = useMemo(() => (data && data.items.map((user) => [user.username, user.name])) || [], [data]);
const options = useMemo(() => (data && data.items.map((user) => ({ value: user.username, label: user.name }))) || [], [data]);
const renderSelected = ({ value, label }) =>
value ? (
return (
<AutoComplete
{...props}
filter={filter}
setFilter={setFilter}
renderSelected={({ value, label }) => {
if (!value) {
return '';
}
return (
<Chip height='x20' value={value} onClick={() => props.onChange()} mie='x4'>
<UserAvatar size='x20' username={value} />
<Box verticalAlign='middle' is='span' margin='none' mi='x4'>
{label}
</Box>
</Chip>
) : null;
const renderItem = ({ value, ...props }) => <Option key={value} {...props} avatar={<UserAvatar size='x20' username={value} />} />;
return (
<SelectFiltered
{...props}
);
}}
renderItem={({ value, ...props }) => <Option key={value} {...props} avatar={<Avatar value={value} />} />}
options={options}
filter={filter}
setFilter={setFilter}
addonIcon='magnifier'
renderItem={renderItem}
renderSelected={renderSelected}
/>
);
};

@ -1,70 +1,52 @@
import { MultiSelectFiltered, Box, Option, OptionAvatar, OptionContent, OptionDescription, Chip, CheckBox } from '@rocket.chat/fuselage';
import { useDebouncedValue } from '@rocket.chat/fuselage-hooks';
import React, { memo, useEffect, useMemo, useState } from 'react';
import { AutoComplete, Box, Option, Chip } from '@rocket.chat/fuselage';
import { useMutableCallback, useDebouncedValue } from '@rocket.chat/fuselage-hooks';
import React, { memo, useMemo, useState } from 'react';
import { useTranslation } from '../../contexts/TranslationContext';
import { useEndpointData } from '../../hooks/useEndpointData';
import { AsyncStatePhase } from '../../lib/asyncState';
import UserAvatar from '../avatar/UserAvatar';
const query = (term = '') => ({ selector: JSON.stringify({ term }) });
const UserAutoCompleteMultiple = ({ valueIsId = false, ...props }) => {
const t = useTranslation();
const UserAutoCompleteMultiple = (props) => {
const [filter, setFilter] = useState('');
const [labelData, setLabelData] = useState({});
const debouncedFilter = useDebouncedValue(filter, 1000);
const { value: data, phase } = useEndpointData(
const { value: data } = useEndpointData(
'users.autocomplete',
useMemo(() => query(debouncedFilter), [debouncedFilter]),
);
const options = useMemo(
() => (data && data.items.map((user) => [valueIsId ? user._id : user.username, user.name])) || [],
[data, valueIsId],
);
useEffect(() => {
const newLabelData = Object.fromEntries((data && data.items.map((user) => [user._id, user.username])) || []);
setLabelData((labelData) => ({ ...labelData, ...newLabelData }));
}, [data]);
const options = useMemo(() => (data && data.items.map((user) => ({ value: user.username, label: user.name }))) || [], [data]);
const onClickRemove = useMutableCallback((e) => {
e.stopPropagation();
e.preventDefault();
props.onChange(e.currentTarget.value, 'remove');
});
const renderItem = ({ value, label, selected, ...props }) => {
const username = valueIsId ? labelData[value] : value;
return (
<Option key={value} {...props}>
<OptionAvatar>
<UserAvatar username={username} size='x20' />
</OptionAvatar>
<OptionContent>
{label} <OptionDescription>({username})</OptionDescription>
</OptionContent>
<CheckBox checked={selected} />
</Option>
);
};
const renderSelected = ({ value, onMouseDown }) => {
const username = valueIsId ? labelData[value] : value;
return (
<Chip {...props} key={value} value={value} onClick={onMouseDown} margin='x4'>
<UserAvatar size='x20' username={username} />
<AutoComplete
{...props}
filter={filter}
setFilter={setFilter}
renderSelected={({ value: selected }) =>
selected?.map((value) => (
<Chip key={value} {...props} height='x20' value={value} onClick={onClickRemove} mie='x4'>
<UserAvatar size='x20' username={value} />
<Box is='span' margin='none' mis='x4'>
{username}
{value}
</Box>
</Chip>
);
};
return (
<MultiSelectFiltered
{...props}
))
}
renderItem={({ value, label, ...props }) => (
<Option key={value} {...props}>
<Option.Avatar>
<UserAvatar username={value} size='x20' />
</Option.Avatar>
<Option.Content>
{label} <Option.Description>({value})</Option.Description>
</Option.Content>
</Option>
)}
options={options}
filter={filter}
setFilter={setFilter}
renderSelected={renderSelected}
renderItem={renderItem}
addonIcon='magnifier'
customEmpty={phase === AsyncStatePhase.LOADING ? t('Loading') : t('None')} // TODO: add proper empty state
/>
);
};

@ -38,8 +38,14 @@ const CreateChannelWithData = ({ onClose, teamId = '', reload }) => {
const { users, name, description, type, readOnly, broadcast, encrypted } = values;
const { handleUsers, handleEncrypted, handleType, handleBroadcast, handleReadOnly } = handlers;
const onChangeUsers = useMutableCallback((value) => {
handleUsers(value);
const onChangeUsers = useMutableCallback((value, action) => {
if (!action) {
if (users.includes(value)) {
return;
}
return handleUsers([...users, value]);
}
handleUsers(users.filter((current) => current !== value));
});
const onChangeType = useMutableCallback((value) => {

@ -1,6 +1,6 @@
import { Box, Modal, ButtonGroup, Button } from '@rocket.chat/fuselage';
import { useMutableCallback } from '@rocket.chat/fuselage-hooks';
import React, { ReactElement, useState, memo } from 'react';
import React, { FC, useState, memo } from 'react';
import { IUser } from '../../../definition/IUser';
import UserAutoCompleteMultiple from '../../components/UserAutoCompleteMultiple';
@ -8,18 +8,26 @@ import { useTranslation } from '../../contexts/TranslationContext';
import { useEndpointActionExperimental } from '../../hooks/useEndpointActionExperimental';
import { goToRoomById } from '../../lib/utils/goToRoomById';
type Username = IUser['username'];
type CreateDirectMessageProps = {
onClose: () => void;
};
const CreateDirectMessage = ({ onClose }: CreateDirectMessageProps): ReactElement => {
const CreateDirectMessage: FC<CreateDirectMessageProps> = ({ onClose }) => {
const t = useTranslation();
const [users, setUsers] = useState<Array<IUser['username']>>([]);
const [users, setUsers] = useState<Array<Username>>([]);
const createDirect = useEndpointActionExperimental('POST', 'dm.create');
const onChangeUsers = useMutableCallback((value: IUser['username'][]) => {
setUsers(value);
const onChangeUsers = useMutableCallback((value: Username, action: string) => {
if (!action) {
if (users.includes(value)) {
return;
}
return setUsers([...users, value]);
}
setUsers(users.filter((current) => current !== value));
});
const onCreate = useMutableCallback(async () => {

@ -68,8 +68,14 @@ const MailExportForm = ({ onCancel, rid }) => {
const { handleToUsers, handleAdditionalEmails, handleSubject } = handlers;
const onChangeUsers = useMutableCallback((value) => {
handleToUsers(value);
const onChangeUsers = useMutableCallback((value, action) => {
if (!action) {
if (toUsers.includes(value)) {
return;
}
return handleToUsers([...toUsers, value]);
}
handleToUsers(toUsers.filter((current) => current !== value));
});
const roomsExport = useEndpoint('POST', 'rooms.export');

@ -65,8 +65,14 @@ const PruneMessagesWithData = ({ rid, tabBar }) => {
handleAttached,
} = handlers;
const onChangeUsers = useMutableCallback((value) => {
handleUsers(value);
const onChangeUsers = useMutableCallback((value, action) => {
if (!action) {
if (users.includes(value)) {
return;
}
return handleUsers([...users, value]);
}
handleUsers(users.filter((current) => current !== value));
});
const handlePrune = useMutableCallback(async () => {

@ -19,8 +19,14 @@ const AddUsersWithData = ({ rid, onClickBack, reload }) => {
const { users } = values;
const { handleUsers } = handlers;
const onChangeUsers = useMutableCallback((value) => {
handleUsers(value);
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 () => {

@ -3,7 +3,6 @@ import { useMutableCallback, useDebouncedCallback, useAutoFocus } from '@rocket.
import React, { FC, memo, useCallback, useEffect, useMemo, useState } from 'react';
import { IUser } from '../../../../definition/IUser';
import UserAutoCompleteMultiple from '../../../components/UserAutoCompleteMultiple';
import { usePermission } from '../../../contexts/AuthorizationContext';
import { useMethod } from '../../../contexts/ServerContext';
import { useSetting } from '../../../contexts/SettingsContext';
@ -12,6 +11,7 @@ import { useEndpointActionExperimental } from '../../../hooks/useEndpointActionE
import { useForm } from '../../../hooks/useForm';
import { goToRoomById } from '../../../lib/utils/goToRoomById';
import TeamNameInput from './TeamNameInput';
import UsersInput from './UsersInput';
type CreateTeamModalState = {
name: any;
@ -82,6 +82,10 @@ const useCreateTeamModalState = (onClose: () => void): CreateTeamModalState => {
async (name: string) => {
setNameError(undefined);
if (!hasUnsavedChanges) {
return;
}
if (!name || name.length === 0) {
setNameError(t('Field_required'));
return;
@ -132,10 +136,16 @@ const useCreateTeamModalState = (onClose: () => void): CreateTeamModalState => {
);
const onChangeMembers = useCallback(
(value) => {
handleMembers(value);
(value, action) => {
if (!action) {
if (members.includes(value)) {
return;
}
return handleMembers([...members, value]);
}
handleMembers(members.filter((current) => current !== value));
},
[handleMembers],
[handleMembers, members],
);
const canSave = hasUnsavedChanges && !nameError;
@ -303,7 +313,7 @@ const CreateTeamModal: FC<CreateTeamModalProps> = ({ onClose }) => {
({t('optional')})
</Box>
</Field.Label>
<UserAutoCompleteMultiple value={members} onChange={onChangeMembers} valueIsId={true} />
<UsersInput value={members} onChange={onChangeMembers} />
</Field>
</FieldGroup>
</Modal.Content>

@ -0,0 +1,90 @@
import { AutoComplete, Box, Option, Options, Chip, AutoCompleteProps } from '@rocket.chat/fuselage';
import { useDebouncedValue } from '@rocket.chat/fuselage-hooks';
import React, { FC, memo, useCallback, useMemo, useState } from 'react';
import UserAvatar from '../../../components/avatar/UserAvatar';
import { useEndpointData } from '../../../hooks/useEndpointData';
type UsersInputProps = {
value: unknown[];
onChange: (value: unknown, action: 'remove' | undefined) => void;
};
type AutocompleteData = [AutoCompleteProps['options'], { [key: string]: string | undefined }];
const useUsersAutoComplete = (term: string): AutocompleteData => {
const params = useMemo(
() => ({
selector: JSON.stringify({ term }),
}),
[term],
);
const { value: data } = useEndpointData('users.autocomplete', params);
return useMemo<AutocompleteData>(() => {
if (!data) {
return [[], {}];
}
const options =
data.items.map((user) => ({
label: user.name ?? '',
value: user._id ?? '',
})) || [];
const labelData = Object.fromEntries(data.items.map((user) => [user._id, user.username]) || []);
return [options, labelData];
}, [data]);
};
const UsersInput: FC<UsersInputProps> = ({ onChange, ...props }) => {
const [filter, setFilter] = useState('');
const [options, labelData] = useUsersAutoComplete(useDebouncedValue(filter, 1000));
const onClickSelected = useCallback(
(e) => {
e.stopPropagation();
e.preventDefault();
onChange(e.currentTarget.value, 'remove');
},
[onChange],
);
const renderSelected = useCallback<FC<{ value?: string[] }>>(
({ value: selected }) => (
<>
{selected?.map((value) => (
<Chip key={value} {...props} height='x20' value={value} onClick={onClickSelected} mie='x4'>
<UserAvatar size='x20' username={labelData[value] as string} />
<Box is='span' margin='none' mis='x4'>
{labelData[value]}
</Box>
</Chip>
))}
</>
),
[onClickSelected, props, labelData],
);
const renderItem = useCallback<FC<{ value: string }>>(
({ value, ...props }) => (
<Option key={value} {...props} avatar={<UserAvatar size={Options.AvatarSize} username={labelData[value] as string} />} />
),
[labelData],
);
return (
<AutoComplete
{...props}
filter={filter}
options={options}
renderSelected={renderSelected}
renderItem={renderItem}
setFilter={setFilter}
onChange={onChange}
/>
);
};
export default memo(UsersInput);

@ -38,8 +38,14 @@ const AuditPage = () => {
const { handleMsg, handleType, handleVisitor, handleAgent, handleUsers, handleRid, handleDateRange } = handlers;
const onChangeUsers = useMutableCallback((value) => {
handleUsers(value);
const onChangeUsers = useMutableCallback((value, action) => {
if (!action) {
if (users.includes(value)) {
return;
}
return handleUsers([...users, value]);
}
handleUsers(users.filter((current) => current !== value));
});
const apply = useMutableCallback(() => {

Loading…
Cancel
Save