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

Chore: Refactor Composer Collapsed Formatters and Actions #28039

Merged
merged 2 commits into from
Feb 14, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
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
Original file line number Diff line number Diff line change
Expand Up @@ -41,12 +41,9 @@ import { useChat } from '../../../../contexts/ChatContext';
import BlazeTemplate from '../../../BlazeTemplate';
import ComposerUserActionIndicator from '../ComposerUserActionIndicator';
import { useAutoGrow } from '../RoomComposer/hooks/useAutoGrow';
import MessageBoxDropdown from './MessageBoxDropdown';
import MessageBoxActionsToolbar from './MessageBoxActionsToolbar';
import MessageBoxFormattingToolbar from './MessageBoxFormattingToolbar';
import MessageBoxReplies from './MessageBoxReplies';
import AudioMessageAction from './actions/AudioMessageAction';
import FileUploadAction from './actions/FileUploadAction';
import VideoMessageAction from './actions/VideoMessageAction';

const reducer = (_: unknown, event: FormEvent<HTMLInputElement>): boolean => {
const target = event.target as HTMLInputElement;
Expand Down Expand Up @@ -362,10 +359,14 @@ const MessageBox = ({
/>
)}
<MessageComposerActionsDivider />
<VideoMessageAction isRecording={isRecording} />
<AudioMessageAction disabled={!canSend || typing || isRecording} />
<FileUploadAction isRecording={isRecording} />
<MessageBoxDropdown isRecording={isRecording} rid={rid} tmid={tmid} />
<MessageBoxActionsToolbar
variant={sizes.inlineSize < 480 ? 'small' : 'large'}
isRecording={isRecording}
typing={typing}
canSend={canSend}
rid={rid}
tmid={tmid}
/>
</MessageComposerToolbarActions>
<MessageComposerToolbarSubmit>
{!canSend && (
Expand Down
Original file line number Diff line number Diff line change
@@ -1,24 +1,25 @@
import type { IRoom } from '@rocket.chat/core-typings';
import { Dropdown, IconButton, Option, OptionTitle, OptionIcon, OptionContent } from '@rocket.chat/fuselage';
import { useTranslation, useUserRoom } from '@rocket.chat/ui-contexts';
import type { ComponentProps } from 'react';
import type { ComponentProps, ReactNode } from 'react';
import React, { useRef, Fragment } from 'react';

import { messageBox } from '../../../../../../../app/ui-utils/client';
import type { ChatAPI } from '../../../../../../lib/chats/ChatAPI';
import { useDropdownVisibility } from '../../../../../../sidebar/header/hooks/useDropdownVisibility';
import { messageBox } from '../../../../../../../../app/ui-utils/client';
import type { ChatAPI } from '../../../../../../../lib/chats/ChatAPI';
import { useDropdownVisibility } from '../../../../../../../sidebar/header/hooks/useDropdownVisibility';
import CreateDiscussionAction from './actions/CreateDiscussionAction';
import ShareLocationAction from './actions/ShareLocationAction';
import WebdavAction from './actions/WebdavAction';

type MessageBoxDropdownProps = {
type ActionsToolbarDropdownProps = {
chatContext?: ChatAPI;
rid: IRoom['_id'];
isRecording?: boolean;
tmid?: string;
actions?: ReactNode[];
};

const MessageBoxDropdown = ({ chatContext, isRecording, rid, tmid }: MessageBoxDropdownProps) => {
const ActionsToolbarDropdown = ({ chatContext, isRecording, rid, tmid, actions }: ActionsToolbarDropdownProps) => {
const t = useTranslation();
const reference = useRef(null);
const target = useRef(null);
Expand Down Expand Up @@ -50,6 +51,7 @@ const MessageBoxDropdown = ({ chatContext, isRecording, rid, tmid }: MessageBoxD
<Dropdown reference={reference} ref={target} placement='bottom-start'>
<OptionTitle>{t('Create_new')}</OptionTitle>
{room && <CreateDiscussionAction room={room} />}
{actions}
<WebdavAction chatContext={chatContext} />
{room && <ShareLocationAction room={room} tmid={tmid} />}
{messageBoxActions?.map((actionGroup, index) => (
Expand All @@ -69,4 +71,4 @@ const MessageBoxDropdown = ({ chatContext, isRecording, rid, tmid }: MessageBoxD
);
};

export default MessageBoxDropdown;
export default ActionsToolbarDropdown;
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import type { IRoom } from '@rocket.chat/core-typings';
import React, { memo } from 'react';

import ActionsToolbarDropdown from './ActionsToolbarDropdown';
import AudioMessageAction from './actions/AudioMessageAction';
import FileUploadAction from './actions/FileUploadAction';
import VideoMessageAction from './actions/VideoMessageAction';

type MessageBoxActionsToolbarProps = {
variant: 'small' | 'large';
isRecording: boolean;
typing: boolean;
canSend: boolean;
rid: IRoom['_id'];
tmid?: string;
};

const MessageBoxActionsToolbar = ({ variant = 'large', isRecording, typing, canSend, rid, tmid }: MessageBoxActionsToolbarProps) => {
const actions = [
<VideoMessageAction key='video' collapsed={variant === 'small'} isRecording={isRecording} />,
<AudioMessageAction key='audio' disabled={!canSend || typing || isRecording} />,
<FileUploadAction key='file' collapsed={variant === 'small'} isRecording={isRecording} />,
];

let featuredAction;
if (variant === 'small') {
featuredAction = actions.splice(1, 1);
}

return (
<>
{variant !== 'small' && actions}
{variant === 'small' && featuredAction}
<ActionsToolbarDropdown {...(variant === 'small' && { actions })} isRecording={isRecording} rid={rid} tmid={tmid} />
</>
);
};

export default memo(MessageBoxActionsToolbar);
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ import { useTranslation } from '@rocket.chat/ui-contexts';
import type { AllHTMLAttributes } from 'react';
import React from 'react';

import type { ChatAPI } from '../../../../../../../lib/chats/ChatAPI';
import { useChat } from '../../../../../contexts/ChatContext';
import type { ChatAPI } from '../../../../../../../../lib/chats/ChatAPI';
import { useChat } from '../../../../../../contexts/ChatContext';

type AudioMessageActionProps = {
chatContext?: ChatAPI;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { Option, OptionIcon, OptionContent } from '@rocket.chat/fuselage';
import { useTranslation, useSetting, usePermission, useSetModal } from '@rocket.chat/ui-contexts';
import React from 'react';

import CreateDiscussion from '../../../../../../../components/CreateDiscussion';
import CreateDiscussion from '../../../../../../../../components/CreateDiscussion';

const CreateDiscussionAction = ({ room }: { room: IRoom }) => {
const setModal = useSetModal();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,24 +1,26 @@
import { Option, OptionContent, OptionIcon } from '@rocket.chat/fuselage';
import { MessageComposerAction } from '@rocket.chat/ui-composer';
import { useTranslation, useSetting } from '@rocket.chat/ui-contexts';
import type { ChangeEvent } from 'react';
import React, { useRef } from 'react';

import type { ChatAPI } from '../../../../../../../lib/chats/ChatAPI';
import { useChat } from '../../../../../contexts/ChatContext';
import type { ChatAPI } from '../../../../../../../../lib/chats/ChatAPI';
import { useChat } from '../../../../../../contexts/ChatContext';

type FileUploadActionProps = {
collapsed?: boolean;
isRecording: boolean;
chatContext?: ChatAPI; // TODO: remove this when the composer is migrated to React
};

const FileUploadAction = ({ chatContext, isRecording }: FileUploadActionProps) => {
const FileUploadAction = ({ collapsed, chatContext, isRecording }: FileUploadActionProps) => {
const t = useTranslation();
const fileUploadEnabled = useSetting('FileUpload_Enabled');
const fileInputRef = useRef<HTMLInputElement>(null);
const chat = useChat() ?? chatContext;

const handleUploadChange = async (e: ChangeEvent<HTMLInputElement>) => {
const { mime } = await import('../../../../../../../../app/utils/lib/mimeTypes');
const { mime } = await import('../../../../../../../../../app/utils/lib/mimeTypes');
const filesToUpload = Array.from(e.target.files ?? []).map((file) => {
Object.defineProperty(file, 'type', {
value: mime.lookup(file.name),
Expand All @@ -38,6 +40,19 @@ const FileUploadAction = ({ chatContext, isRecording }: FileUploadActionProps) =
}
};

if (collapsed) {
return (
<Option
{...((!fileUploadEnabled || isRecording) && { title: t('Not_Available') })}
disabled={!fileUploadEnabled || isRecording}
onClick={handleUpload}
>
<OptionIcon name='clip' />
<OptionContent>{t('File')}</OptionContent>
</Option>
);
}

return (
<>
<MessageComposerAction
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { Option, OptionTitle, OptionIcon, OptionContent } from '@rocket.chat/fus
import { useSetting, useSetModal, useTranslation } from '@rocket.chat/ui-contexts';
import React from 'react';

import ShareLocationModal from '../../../../../ShareLocation/ShareLocationModal';
import ShareLocationModal from '../../../../../../ShareLocation/ShareLocationModal';

const ShareLocationAction = ({ room, tmid }: { room: IRoom; tmid?: string }) => {
const t = useTranslation();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,18 @@
import { Option, OptionIcon, OptionContent } from '@rocket.chat/fuselage';
import { MessageComposerAction } from '@rocket.chat/ui-composer';
import { useTranslation, useSetting } from '@rocket.chat/ui-contexts';
import React from 'react';

import type { ChatAPI } from '../../../../../../../lib/chats/ChatAPI';
import { useChat } from '../../../../../contexts/ChatContext';
import type { ChatAPI } from '../../../../../../../../lib/chats/ChatAPI';
import { useChat } from '../../../../../../contexts/ChatContext';

type VideoMessageActionProps = {
collapsed?: boolean;
isRecording: boolean;
chatContext?: ChatAPI; // TODO: remove this when the composer is migrated to React
};

const VideoMessageAction = ({ chatContext, isRecording }: VideoMessageActionProps) => {
const VideoMessageAction = ({ collapsed, chatContext, isRecording }: VideoMessageActionProps) => {
const t = useTranslation();
const fileUploadEnabled = useSetting('FileUpload_Enabled');
const messageVideoRecorderEnabled = useSetting('Message_VideoRecorderEnabled');
Expand Down Expand Up @@ -38,6 +40,19 @@ const VideoMessageAction = ({ chatContext, isRecording }: VideoMessageActionProp
return null;
}

if (collapsed) {
return (
<Option
{...((!enableVideoMessage || isRecording) && { title: t('Not_Available') })}
disabled={!enableVideoMessage || isRecording}
onClick={handleOpenVideoMessage}
>
<OptionIcon name='video' />
<OptionContent>{t('Video_message')}</OptionContent>
</Option>
);
}

return (
<MessageComposerAction
data-qa-id='video-message'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,12 @@ import { Option, OptionIcon, OptionContent } from '@rocket.chat/fuselage';
import { useTranslation, useSetting, useSetModal } from '@rocket.chat/ui-contexts';
import React from 'react';

import { WebdavAccounts } from '../../../../../../../../app/models/client';
import { useReactiveValue } from '../../../../../../../hooks/useReactiveValue';
import type { ChatAPI } from '../../../../../../../lib/chats/ChatAPI';
import { useChat } from '../../../../../contexts/ChatContext';
import AddWebdavAccountModal from '../../../../../webdav/AddWebdavAccountModal';
import WebdavFilePickerModal from '../../../../../webdav/WebdavFilePickerModal';
import { WebdavAccounts } from '../../../../../../../../../app/models/client';
import { useReactiveValue } from '../../../../../../../../hooks/useReactiveValue';
import type { ChatAPI } from '../../../../../../../../lib/chats/ChatAPI';
import { useChat } from '../../../../../../contexts/ChatContext';
import AddWebdavAccountModal from '../../../../../../webdav/AddWebdavAccountModal';
import WebdavFilePickerModal from '../../../../../../webdav/WebdavFilePickerModal';

const getWebdavAccounts = (): IWebdavAccountIntegration[] => WebdavAccounts.find().fetch();

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default } from './MessageBoxActionsToolbar';
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { Dropdown, IconButton, Option, OptionTitle, OptionIcon, OptionContent } from '@rocket.chat/fuselage';
import { useTranslation } from '@rocket.chat/ui-contexts';
import React, { useRef } from 'react';

import type { FormattingButton } from '../../../../../../../../app/ui-message/client/messageBox/messageBoxFormatting';
import type { ComposerAPI } from '../../../../../../../lib/chats/ChatAPI';
import { useDropdownVisibility } from '../../../../../../../sidebar/header/hooks/useDropdownVisibility';

type FormattingToolbarDropdownProps = {
composer: ComposerAPI;
items: FormattingButton[];
};

const FormattingToolbarDropdown = ({ composer, items, ...props }: FormattingToolbarDropdownProps) => {
const t = useTranslation();
const reference = useRef(null);
const target = useRef(null);

const { isVisible, toggle } = useDropdownVisibility({ reference, target });

return (
<>
<IconButton {...props} small ref={reference} icon='meatballs' onClick={() => toggle()} />
{isVisible && (
<Dropdown reference={reference} ref={target} placement='bottom-start'>
<OptionTitle>{t('Message_Formatting_Toolbox')}</OptionTitle>
{items.map((formatter, index) => {
const handleFormattingAction = () => {
if ('link' in formatter) {
window.open(formatter.link, '_blank', 'rel=noreferrer noopener');
return;
}
composer.wrapSelection(formatter.pattern);
};

return (
<Option key={index} onClick={handleFormattingAction}>
<OptionIcon name={'icon' in formatter ? formatter.icon : 'link'} />
<OptionContent>{t(formatter.label)}</OptionContent>
</Option>
);
})}
</Dropdown>
)}
</>
);
};

export default FormattingToolbarDropdown;
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ import { MessageComposerAction } from '@rocket.chat/ui-composer';
import { useTranslation } from '@rocket.chat/ui-contexts';
import React, { memo } from 'react';

import type { FormattingButton } from '../../../../../../../app/ui-message/client/messageBox/messageBoxFormatting';
import { popover } from '../../../../../../../app/ui-utils/client';
import type { ComposerAPI } from '../../../../../../lib/chats/ChatAPI';
import type { FormattingButton } from '../../../../../../../../app/ui-message/client/messageBox/messageBoxFormatting';
import type { ComposerAPI } from '../../../../../../../lib/chats/ChatAPI';
import FormattingToolbarDropdown from './FormattingToolbarDropdown';

type MessageBoxFormattingToolbarProps = {
disabled: boolean;
Expand All @@ -17,50 +17,23 @@ export const MessageBoxFormattingToolbar = ({ items, variant = 'large', composer
const t = useTranslation();

if (variant === 'small') {
return (
<MessageComposerAction
{...props}
onClick={(event): void => {
const config = {
popoverClass: 'message-box',
columns: [
{
groups: [
{
title: t('Message_Formatting_Toolbox'),
items: items.map((item) =>
'icon' in item
? {
icon: item.icon,
name: t(item.label),
type: 'messagebox-action',
id: item.label,
action: () => composer.wrapSelection(item.pattern),
}
: {
icon: 'link',
name: item.label,
type: 'messagebox-action',
id: item.label,
action: () => window.open(item.link, '_blank'),
},
),
},
],
},
],
offsetVertical: 10,
direction: 'top-inverted',
currentTarget: event.currentTarget,
activeElement: event.currentTarget,
};
const collapsedItems = [...items];
const featuredFormatter = collapsedItems.splice(0, 1)[0];

popover.open(config);
}}
icon='bold'
/>
return (
<>
{'icon' in featuredFormatter && (
<MessageComposerAction
{...props}
onClick={() => composer.wrapSelection(featuredFormatter.pattern)}
icon={featuredFormatter.icon}
/>
)}
<FormattingToolbarDropdown {...props} composer={composer} items={collapsedItems} />;
</>
);
}

return (
<>
{items.map((formatter) =>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default } from './MessageBoxFormattingToolbar';