diff --git a/client/contexts/ServerContext/endpoints.ts b/client/contexts/ServerContext/endpoints.ts index 4354dcbf1a123..0fc3e1a17f06c 100644 --- a/client/contexts/ServerContext/endpoints.ts +++ b/client/contexts/ServerContext/endpoints.ts @@ -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'; @@ -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; diff --git a/client/contexts/ServerContext/endpoints/v1/rooms/autocompleteChannelAndPrivate.ts b/client/contexts/ServerContext/endpoints/v1/rooms/autocompleteChannelAndPrivate.ts new file mode 100644 index 0000000000000..0b84607ca8d98 --- /dev/null +++ b/client/contexts/ServerContext/endpoints/v1/rooms/autocompleteChannelAndPrivate.ts @@ -0,0 +1,5 @@ +import { IRoom } from '../../../../../../definition/IRoom'; + +export type AutocompleteChannelAndPrivateEndpoint = { + GET: (params: { selector: string }) => { items: IRoom[] }; +}; diff --git a/client/contexts/ServerContext/endpoints/v1/teams/addRooms.ts b/client/contexts/ServerContext/endpoints/v1/teams/addRooms.ts new file mode 100644 index 0000000000000..cc1a8fc9ef475 --- /dev/null +++ b/client/contexts/ServerContext/endpoints/v1/teams/addRooms.ts @@ -0,0 +1,9 @@ +import { IRoom } from '../../../../../../definition/IRoom'; + +export type AddRoomsEndpoint = { + POST: (params: { rooms: IRoom['_id'][]; teamId: string }) => { + success: true; + statusCode: 200; + body: IRoom[]; + }; +}; diff --git a/client/main.js b/client/main.js index 6b00aa64b0347..9ee80d00c408e 100644 --- a/client/main.js +++ b/client/main.js @@ -21,5 +21,5 @@ import './startup'; import './views/admin'; import './views/login'; import './views/room/adapters'; -import './adapters'; import './views/teams'; +import './adapters'; diff --git a/client/sidebar/header/CreateChannel.js b/client/sidebar/header/CreateChannel.js index c5515e10c478d..c762558580d4e 100644 --- a/client/sidebar/header/CreateChannel.js +++ b/client/sidebar/header/CreateChannel.js @@ -129,6 +129,7 @@ export const CreateChannel = ({ export default memo(({ onClose, + teamId = '', }) => { const createChannel = useEndpointActionExperimental('POST', 'channels.create'); const createPrivateChannel = useEndpointActionExperimental('POST', 'groups.create'); @@ -206,6 +207,7 @@ export default memo(({ name, members: users, readOnly, + ...teamId && { teamId }, extraData: { broadcast, encrypted, @@ -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) { @@ -238,6 +240,7 @@ export default memo(({ readOnly, setChannelDescription, setPrivateChannelDescription, + teamId, type, users, ]); diff --git a/client/views/directory/TeamsTab.js b/client/views/directory/TeamsTab.js index e8e3c8317c4f5..a2334f3cad45a 100644 --- a/client/views/directory/TeamsTab.js +++ b/client/views/directory/TeamsTab.js @@ -91,7 +91,6 @@ function TeamsTable() { return } renderRow={renderRow} results={data.teams} setParams={setParams} diff --git a/client/views/room/index.js b/client/views/room/index.js index 593ebec9cbf70..b272971bbfd5c 100644 --- a/client/views/room/index.js +++ b/client/views/room/index.js @@ -31,7 +31,7 @@ const Room = () => { { tab && { typeof tab.template === 'string' && } - { typeof tab.template !== 'string' && } + { typeof tab.template !== 'string' && } } ; }; diff --git a/client/views/room/providers/ToolboxProvider.tsx b/client/views/room/providers/ToolboxProvider.tsx index 4861ed551183c..56a66de40f12e 100644 --- a/client/views/room/providers/ToolboxProvider.tsx +++ b/client/views/room/providers/ToolboxProvider.tsx @@ -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(() => { diff --git a/client/views/teams/contextualBar/TeamChannelItem.js b/client/views/teams/contextualBar/TeamChannelItem.js new file mode 100644 index 0000000000000..495fde584a8c3 --- /dev/null +++ b/client/views/teams/contextualBar/TeamChannelItem.js @@ -0,0 +1,122 @@ +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'; +import { roomTypes } from '../../../../app/utils/client'; + +export const useReactModal = (Component, props) => { + const setModal = useSetModal(); + + return useMutableCallback(() => { + const handleClose = () => { + setModal(null); + }; + + setModal(() => ); + }); +}; + +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: {t('Team_Remove_from_team_modal_content', { teamName: roomTypes.getRoomName(room.t, room) })}, + }); + + const DeleteChannelAction = useReactModal(ConfirmationModal, { + onConfirmAction: () => { + deleteRoomEndpoint({ roomId: room._id }); + }, + labelButton: t('Delete'), + content: <> + {t('Team_Delete_Channel_modal_content_danger')} + {t('Team_Delete_Channel_modal_content')} + , + }); + + 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 + (icon.match(/hash/) + ? + : )} + options={menuOptions} + />; +}; + +export const TeamChannelItem = ({ room }) => { + const [showButton, setShowButton] = useState(); + + const isReduceMotionEnabled = usePrefersReducedMotion(); + const handleMenuEvent = { [isReduceMotionEnabled ? 'onMouseEnter' : 'onTransitionEnd']: setShowButton }; + + return ( + + ); +}; + +TeamChannelItem.Skeleton = Option.Skeleton; diff --git a/client/views/teams/contextualBar/TeamChannels.js b/client/views/teams/contextualBar/TeamChannels.js new file mode 100644 index 0000000000000..c3f116089242c --- /dev/null +++ b/client/views/teams/contextualBar/TeamChannels.js @@ -0,0 +1,172 @@ +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(function Row({ room }) { + if (!room) { + return ; + } + + return ; +}); + +const BaseTeamChannels = ({ + 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 ( + <> + + + {t('Channels')} + { onClickClose && } + + + + + + + }/> +