Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[NEW] Teams Channels #21248

Merged
merged 32 commits into from
Mar 23, 2021
Merged
Show file tree
Hide file tree
Changes from 22 commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
5d9dba0
team channels button on header
tiagoevanp Mar 18, 2021
8007011
TeamChannels component
tiagoevanp Mar 20, 2021
f2e8f11
AddExistingModal
tiagoevanp Mar 20, 2021
663db2b
TS refactoring AddExistingModal
tiagoevanp Mar 20, 2021
fc20590
prepare new channel action button
tiagoevanp Mar 20, 2021
8fb4a10
[WIP] TeamChannelsItem component
tiagoevanp Mar 22, 2021
903bfca
Merge branch 'new/teams' of github.com:RocketChat/Rocket.Chat into ne…
tiagoevanp Mar 22, 2021
f6d9a95
fix last commit errors
tiagoevanp Mar 22, 2021
1a6e757
Fix build errors
tiagoevanp Mar 22, 2021
9a75455
fix TS definitions
tiagoevanp Mar 22, 2021
c872ea7
Merge branch 'new/teams' of github.com:RocketChat/Rocket.Chat into ne…
tiagoevanp Mar 22, 2021
01871ad
fname from room
tiagoevanp Mar 22, 2021
8a34fca
Merge branch 'new/teams' of github.com:RocketChat/Rocket.Chat into ne…
tiagoevanp Mar 22, 2021
ce7257d
[WIP] addExistingChannel action
tiagoevanp Mar 22, 2021
dba98a2
Merge branch 'new/teams' of github.com:RocketChat/Rocket.Chat into ne…
tiagoevanp Mar 22, 2021
18ccdbc
prevent redirect create channel from team
tiagoevanp Mar 22, 2021
afea633
create delete and remove actions from kebab
tiagoevanp Mar 22, 2021
1f26553
fix input filter
tiagoevanp Mar 22, 2021
a07cd40
Auto-Join component
tiagoevanp Mar 23, 2021
6066f61
Merge branch 'new/teams' of github.com:RocketChat/Rocket.Chat into ne…
tassoevan Mar 23, 2021
eccbc2e
Review
tassoevan Mar 23, 2021
616a602
Review
tassoevan Mar 23, 2021
65967bb
Review
tassoevan Mar 23, 2021
f0bd5ab
Review
tassoevan Mar 23, 2021
534d840
Review
tassoevan Mar 23, 2021
488ef45
Merge branch 'new/teams' of github.com:RocketChat/Rocket.Chat into ne…
tassoevan Mar 23, 2021
c87365b
Fix lint issues
tassoevan Mar 23, 2021
9e3f164
Merge branch 'new/teams' of github.com:RocketChat/Rocket.Chat into ne…
tassoevan Mar 23, 2021
38165ca
Review
tassoevan Mar 23, 2021
bb59a27
Review
tassoevan Mar 23, 2021
50d848e
Review
tassoevan Mar 23, 2021
1176132
Merge branch 'new/teams' of github.com:RocketChat/Rocket.Chat into ne…
tassoevan Mar 23, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions client/contexts/ServerContext/endpoints.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,14 @@ import { FollowMessageEndpoint as ChatFollowMessageEndpoint } from './endpoints/
import { GetMessageEndpoint as ChatGetMessageEndpoint } from './endpoints/v1/chat/getMessage';
import { UnfollowMessageEndpoint as ChatUnfollowMessageEndpoint } from './endpoints/v1/chat/unfollowMessage';
import { AutocompleteEndpoint as UsersAutocompleteEndpoint } from './endpoints/v1/users/autocomplete';
import { AutocompleteChannelAndPrivateEndpoint as RoomsAutocompleteEndpoint } from './endpoints/v1/rooms/autocompleteChannelAndPrivate';
import { AppearanceEndpoint as LivechatAppearanceEndpoint } from './endpoints/v1/livechat/appearance';
import { ListEndpoint as CustomUserStatusListEndpoint } from './endpoints/v1/custom-user-status/list';
import { ExternalComponentsEndpoint as AppsExternalComponentsEndpoint } from './endpoints/apps/externalComponents';
import { ManualRegisterEndpoint as CloudManualRegisterEndpoint } from './endpoints/v1/cloud/manualRegister';
import { FilesEndpoint as GroupsFilesEndpoint } from './endpoints/v1/groups/files';
import { FilesEndpoint as ImFilesEndpoint } from './endpoints/v1/im/files';
import { AddRoomsEndpoint as TeamsAddRoomsEndpoint } from './endpoints/v1/teams/addRooms';
import { FilesEndpoint as ChannelsFilesEndpoint } from './endpoints/v1/channels/files';
import { ListEndpoint as EmojiCustomListEndpoint } from './endpoints/v1/emoji-custom/list';
import { GetDiscussionsEndpoint as ChatGetDiscussionsEndpoint } from './endpoints/v1/chat/getDiscussions';
Expand All @@ -28,6 +30,8 @@ export type ServerEndpoints = {
'livechat/appearance': LivechatAppearanceEndpoint;
'custom-user-status.list': CustomUserStatusListEndpoint;
'/apps/externalComponents': AppsExternalComponentsEndpoint;
'rooms.autocomplete.channelAndPrivate': RoomsAutocompleteEndpoint;
'teams.addRooms': TeamsAddRoomsEndpoint;
};

export type ServerEndpointPath = keyof ServerEndpoints;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { IRoom } from '../../../../../../definition/IRoom';

export type AutocompleteChannelAndPrivateEndpoint = {
GET: (params: { selector: string }) => { items: IRoom[] };
};
9 changes: 9 additions & 0 deletions client/contexts/ServerContext/endpoints/v1/teams/addRooms.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { IRoom } from '../../../../../../definition/IRoom';

export type AddRoomsEndpoint = {
POST: (params: { rooms: string[]; teamId: string }) => {
success: true;
statusCode: 200;
body: IRoom[];
};
};
2 changes: 1 addition & 1 deletion client/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,5 +21,5 @@ import './startup';
import './views/admin';
import './views/login';
import './views/room/adapters';
import './adapters';
import './views/teams';
import './adapters';
7 changes: 5 additions & 2 deletions client/sidebar/header/CreateChannel.js
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,7 @@ export const CreateChannel = ({

export default memo(({
onClose,
teamId = '',
}) => {
const createChannel = useEndpointActionExperimental('POST', 'channels.create');
const createPrivateChannel = useEndpointActionExperimental('POST', 'groups.create');
Expand Down Expand Up @@ -206,6 +207,7 @@ export default memo(({
name,
members: users,
readOnly,
...teamId && { teamId },
extraData: {
broadcast,
encrypted,
Expand All @@ -215,10 +217,10 @@ export default memo(({

if (type) {
roomData = await createPrivateChannel(params);
goToRoom(roomData.group._id);
!teamId && goToRoom(roomData.group._id);
} else {
roomData = await createChannel(params);
goToRoom(roomData.channel._id);
!teamId && goToRoom(roomData.channel._id);
}

if (roomData.success && roomData.group && description) {
Expand All @@ -238,6 +240,7 @@ export default memo(({
readOnly,
setChannelDescription,
setPrivateChannelDescription,
teamId,
type,
users,
]);
Expand Down
2 changes: 1 addition & 1 deletion client/views/room/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ const Room = () => {
<RoomTemplate.Body><BlazeTemplate onClick={hideFlexTab ? close : undefined} name='roomOld' tabBar={tabBar} rid={room._id} _id={room._id} /></RoomTemplate.Body>
{ tab && <RoomTemplate.Aside data-qa-tabbar-name={tab.id}>
{ typeof tab.template === 'string' && <VerticalBarOldActions {...tab} name={tab.template} tabBar={tabBar} rid={room._id} _id={room._id} /> }
{ typeof tab.template !== 'string' && <LazyComponent template={tab.template} tabBar={tabBar} rid={room._id} _id={room._id} /> }
{ typeof tab.template !== 'string' && <LazyComponent template={tab.template} tabBar={tabBar} rid={room._id} teamId={room.teamId} _id={room._id} /> }
</RoomTemplate.Aside>}
</RoomTemplate>;
};
Expand Down
1 change: 1 addition & 0 deletions client/views/room/providers/ToolboxProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ const VirtualAction = React.memo(({ handleChange, room, action, id }: { id: stri
const config = typeof action === 'function' ? action({ room }) : action;

const group = getGroup(room);

const visible = config && (!config.groups || (groupsDict[room.t] && config.groups.includes(group as any)));

useLayoutEffect(() => {
Expand Down
121 changes: 121 additions & 0 deletions client/views/teams/contextualBar/TeamChannelItem.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
import React, { useMemo, useState } from 'react';
import { ActionButton, Box, CheckBox, Icon, Menu, Option } from '@rocket.chat/fuselage';
import { usePrefersReducedMotion, useMutableCallback } from '@rocket.chat/fuselage-hooks';

import { useTranslation } from '../../../contexts/TranslationContext';
import { useEndpoint } from '../../../contexts/ServerContext';
import { useSetModal } from '../../../contexts/ModalContext';
import RoomAvatar from '../../../components/avatar/RoomAvatar';
import ConfirmationModal from '../modals/ConfirmationModal';

export const useReactModal = (Component, props) => {
const setModal = useSetModal();

return useMutableCallback(() => {
const handleClose = () => {
setModal(null);
};

setModal(() => <Component
onClose={handleClose}
{...props}
/>);
});
};

const RoomActions = ({ room }) => {
const t = useTranslation();
const updateRoomEndpoint = useEndpoint('POST', 'teams.updateRoom');
const removeRoomEndpoint = useEndpoint('POST', 'teams.removeRoom');
const deleteRoomEndpoint = useEndpoint('POST', room.t === 'c' ? 'channels.delete' : 'groups.delete');

const RemoveFromTeamAction = useReactModal(ConfirmationModal, {
onConfirmAction: () => {
removeRoomEndpoint({ teamId: room.teamId, roomId: room._id });
},
labelButton: t('Remove'),
content: <Box is='span' size='14px'>{t('Team_Remove_from_team_modal_content')}</Box>,
});

const DeleteChannelAction = useReactModal(ConfirmationModal, {
onConfirmAction: () => {
deleteRoomEndpoint({ roomId: room._id });
},
labelButton: t('Delete'),
content: <>
<Box is='span' size='14px' color='danger-500' fontWeight='600'>{t('Team_Delete_Channel_modal_content_danger')}</Box>
<Box is='span' size='14px'> {t('Team_Delete_Channel_modal_content')}</Box>
</>,
});

const menuOptions = useMemo(() => {
const AutoJoinAction = () => {
updateRoomEndpoint({
roomId: room._id,
isDefault: !room.teamDefault,
});
};

return [{
label: {
label: t('Team_Auto-join'),
icon: room.t === 'c' ? 'hash' : 'hashtag-lock',
},
action: AutoJoinAction,
},
{
label: {
label: t('Team_Remove_from_team'),
icon: 'cross',
},
action: RemoveFromTeamAction,
},
{
label: {
label: t('Delete'),
icon: 'trash',
},
action: DeleteChannelAction,
}];
}, [DeleteChannelAction, RemoveFromTeamAction, room._id, room.t, room.teamDefault, t, updateRoomEndpoint]);

return <Menu
flexShrink={0}
key='menu'
tiny
renderItem={({ label: { label, icon }, ...props }) =>
(icon.match(/hash/)
? <Option {...props} label={label} icon={icon}><CheckBox checked={room.teamDefault} /></Option>
: <Box color='danger-600'><Option {...props} label={label} icon={icon} /></Box>)}
options={menuOptions}
/>;
};

export const TeamChannelItem = ({ room }) => {
const [showButton, setShowButton] = useState();

const isReduceMotionEnabled = usePrefersReducedMotion();
const handleMenuEvent = { [isReduceMotionEnabled ? 'onMouseEnter' : 'onTransitionEnd']: setShowButton };

return (
<Option
id={room._id}
{ ...handleMenuEvent }
>
<Option.Avatar>
<RoomAvatar room={room} size='x28' />
</Option.Avatar>
<Option.Column>{room.t === 'c' ? <Icon name='hash' size='x15'/> : <Icon name='hashtag-lock' size='x15'/>}</Option.Column>
<Option.Content>{room.fname || room.name}</Option.Content>
<Option.Menu>
{showButton ? <RoomActions room={room} /> : <ActionButton
ghost
tiny
icon='kebab'
/>}
</Option.Menu>
</Option>
);
};

TeamChannelItem.Skeleton = Option.Skeleton;
170 changes: 170 additions & 0 deletions client/views/teams/contextualBar/TeamChannels.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
import React, { memo, useCallback, useMemo, useState } from 'react';
import { Box, Icon, TextInput, Margins, Select, Throbber, ButtonGroup, Button } from '@rocket.chat/fuselage';
import { Virtuoso } from 'react-virtuoso';
import { useMutableCallback, useLocalStorage, useAutoFocus } from '@rocket.chat/fuselage-hooks';

import { useTranslation } from '../../../contexts/TranslationContext';
import { useEndpoint } from '../../../contexts/ServerContext';
import { useSetModal } from '../../../contexts/ModalContext';
import { useScrollableRecordList } from '../../../hooks/lists/useScrollableRecordList';
import { useRecordList } from '../../../hooks/lists/useRecordList';
import { RecordList } from '../../../lib/lists/RecordList.ts';
import ScrollableContentWrapper from '../../../components/ScrollableContentWrapper';
import VerticalBar from '../../../components/VerticalBar';
import AddExistingModal from '../modals/AddExistingModal';
import { TeamChannelItem } from './TeamChannelItem';
import CreateChannel from '../../../sidebar/header/CreateChannel';

const Row = memo(({ room }) => {
if (!room) {
return <TeamChannels.Option.Skeleton />;
}

return <TeamChannels.Option
room={room}
/>;
});

export const TeamChannels = ({
loading,
channels = [],
text,
type,
setText,
setType,
onClickClose,
onClickAddExisting,
onClickCreateNew,
total,
loadMoreItems,
}) => {
const t = useTranslation();
const inputRef = useAutoFocus(true);

const options = useMemo(() => [
['all', t('All')],
['autoJoin', t('Auto-join')],
], [t]);

const lm = useMutableCallback((start) => loadMoreItems(start + 1, Math.min(50, start + 1 - channels.length)));

return (
<>
<VerticalBar.Header>
<VerticalBar.Icon name='hash'/>
<VerticalBar.Text>{t('Channels')}</VerticalBar.Text>
{ onClickClose && <VerticalBar.Close onClick={onClickClose} /> }
</VerticalBar.Header>

<VerticalBar.Content p='x12'>
<Box display='flex' flexDirection='row' p='x12' flexShrink={0}>
<Box display='flex' flexDirection='row' flexGrow={1} mi='neg-x4'>
<Margins inline='x4'>
<TextInput placeholder={t('Search')} value={text} ref={inputRef} onChange={setText} addon={<Icon name='magnifier' size='x20'/>}/>
<Select
flexGrow={0}
width='110px'
onChange={setType}
value={type}
options={options} />
</Margins>
</Box>
</Box>

{loading && <Box pi='x24' pb='x12'><Throbber size='x12' /></Box>}
{!loading && channels.length <= 0 && <Box pi='x24' pb='x12'>{t('No_results_found')}</Box>}

<Box w='full' h='full' overflow='hidden' flexShrink={1}>
{!loading && channels && channels.length > 0 && <Virtuoso
style={{ height: '100%', width: '100%' }}
totalCount={total}
endReached={lm}
overscan={50}
data={channels}
components={{ Scroller: ScrollableContentWrapper }}
itemContent={(index, data) => <Row
room={data}
/>}
/>}
</Box>
</VerticalBar.Content>

<VerticalBar.Footer>
<ButtonGroup stretch>
{ onClickAddExisting && <Button onClick={onClickAddExisting} width='50%'>{t('Team_Add_existing')}</Button> }
{ onClickCreateNew && <Button onClick={onClickCreateNew} width='50%' primary>{t('Create_new')}</Button> }
</ButtonGroup>
</VerticalBar.Footer>
</>
);
};

TeamChannels.Option = TeamChannelItem;

export const useReactModal = (Component, props) => {
const setModal = useSetModal();

return useMutableCallback((e) => {
e.preventDefault();

const handleClose = () => {
setModal(null);
};

setModal(() => <Component
onClose={handleClose}
{...props}
/>);
});
};

export default ({ teamId, tabBar }) => {
const [type, setType] = useLocalStorage('channels-list-type', 'all');
const [text, setText] = useState('');
const [roomList] = useState(() => new RecordList());

const roomListEndpoint = useEndpoint('GET', 'teams.listRooms');

const fetchData = useCallback(async (/* start, end*/) => {
const { rooms, total } = await roomListEndpoint({ teamId });

const roomsDated = rooms.map((rooms) => {
rooms._updatedAt = new Date(rooms._updatedAt);
return { ...rooms };
});
return {
items: roomsDated,
itemCount: total,
};
}, [roomListEndpoint, teamId]);

const { loadMoreItems } = useScrollableRecordList(
roomList,
fetchData,
);
const { phase, items, itemCount } = useRecordList(roomList);

const handleTextChange = useCallback((event) => {
setText(event.currentTarget.value);
}, []);

const addExisting = useReactModal(AddExistingModal, { teamId });

const createNew = useReactModal(CreateChannel, { teamId });

return (
<TeamChannels
loading={phase === 'loading'}
type={type}
text={text}
setType={setType}
setText={handleTextChange}
channels={items}
total={itemCount}
onClickClose={tabBar.close}
onClickAddExisting={addExisting}
onClickCreateNew={createNew}
loadMoreItems={loadMoreItems}
/>
);
};
Loading