265 lines
8.6 KiB
TypeScript
265 lines
8.6 KiB
TypeScript
import type { FC } from '../../../lib/teact/teact';
|
|
import React, {
|
|
memo, useEffect,
|
|
useMemo,
|
|
} from '../../../lib/teact/teact';
|
|
|
|
import type { ApiAttachMenuPeerType, ApiMessage } from '../../../api/types';
|
|
import type { GlobalState } from '../../../global/types';
|
|
import type { ISettings, ThreadId } from '../../../types';
|
|
|
|
import {
|
|
CONTENT_TYPES_WITH_PREVIEW, DEBUG_LOG_FILENAME, SUPPORTED_AUDIO_CONTENT_TYPES,
|
|
SUPPORTED_PHOTO_CONTENT_TYPES,
|
|
SUPPORTED_VIDEO_CONTENT_TYPES,
|
|
} from '../../../config';
|
|
import {
|
|
getMessageAudio, getMessageDocument,
|
|
getMessagePhoto,
|
|
getMessageVideo, getMessageVoice,
|
|
getMessageWebPagePhoto,
|
|
getMessageWebPageVideo,
|
|
} from '../../../global/helpers';
|
|
import { getDebugLogs } from '../../../util/debugConsole';
|
|
import { validateFiles } from '../../../util/files';
|
|
import { openSystemFilesDialog } from '../../../util/systemFilesDialog';
|
|
import { IS_TOUCH_ENV } from '../../../util/windowEnvironment';
|
|
|
|
import useFlag from '../../../hooks/useFlag';
|
|
import useLastCallback from '../../../hooks/useLastCallback';
|
|
import useMouseInside from '../../../hooks/useMouseInside';
|
|
import useOldLang from '../../../hooks/useOldLang';
|
|
|
|
import Icon from '../../common/icons/Icon';
|
|
import Menu from '../../ui/Menu';
|
|
import MenuItem from '../../ui/MenuItem';
|
|
import ResponsiveHoverButton from '../../ui/ResponsiveHoverButton';
|
|
import AttachBotItem from './AttachBotItem';
|
|
|
|
import './AttachMenu.scss';
|
|
|
|
export type OwnProps = {
|
|
chatId: string;
|
|
threadId?: ThreadId;
|
|
isButtonVisible: boolean;
|
|
canAttachMedia: boolean;
|
|
canAttachPolls: boolean;
|
|
canSendPhotos: boolean;
|
|
canSendVideos: boolean;
|
|
canSendDocuments: boolean;
|
|
canSendAudios: boolean;
|
|
isScheduled?: boolean;
|
|
attachBots?: GlobalState['attachMenu']['bots'];
|
|
peerType?: ApiAttachMenuPeerType;
|
|
shouldCollectDebugLogs?: boolean;
|
|
theme: ISettings['theme'];
|
|
onFileSelect: (files: File[], shouldSuggestCompression?: boolean) => void;
|
|
onPollCreate: NoneToVoidFunction;
|
|
onMenuOpen: NoneToVoidFunction;
|
|
onMenuClose: NoneToVoidFunction;
|
|
hasReplaceableMedia?: boolean;
|
|
editingMessage?: ApiMessage;
|
|
};
|
|
|
|
const AttachMenu: FC<OwnProps> = ({
|
|
chatId,
|
|
threadId,
|
|
isButtonVisible,
|
|
canAttachMedia,
|
|
canAttachPolls,
|
|
canSendPhotos,
|
|
canSendVideos,
|
|
canSendDocuments,
|
|
canSendAudios,
|
|
attachBots,
|
|
peerType,
|
|
isScheduled,
|
|
theme,
|
|
shouldCollectDebugLogs,
|
|
onFileSelect,
|
|
onMenuOpen,
|
|
onMenuClose,
|
|
onPollCreate,
|
|
hasReplaceableMedia,
|
|
editingMessage,
|
|
}) => {
|
|
const [isAttachMenuOpen, openAttachMenu, closeAttachMenu] = useFlag();
|
|
const [handleMouseEnter, handleMouseLeave, markMouseInside] = useMouseInside(isAttachMenuOpen, closeAttachMenu);
|
|
|
|
const canSendVideoAndPhoto = canSendPhotos && canSendVideos;
|
|
const canSendVideoOrPhoto = canSendPhotos || canSendVideos;
|
|
|
|
const [isAttachmentBotMenuOpen, markAttachmentBotMenuOpen, unmarkAttachmentBotMenuOpen] = useFlag();
|
|
const isMenuOpen = isAttachMenuOpen || isAttachmentBotMenuOpen;
|
|
|
|
const isPhotoOrVideo = editingMessage && editingMessage?.groupedId
|
|
&& Boolean(getMessagePhoto(editingMessage) || getMessageWebPagePhoto(editingMessage)
|
|
|| Boolean(getMessageVideo(editingMessage) || getMessageWebPageVideo(editingMessage)));
|
|
const isFile = editingMessage && editingMessage?.groupedId && Boolean(getMessageAudio(editingMessage)
|
|
|| getMessageVoice(editingMessage) || getMessageDocument(editingMessage));
|
|
|
|
useEffect(() => {
|
|
if (isAttachMenuOpen) {
|
|
markMouseInside();
|
|
}
|
|
}, [isAttachMenuOpen, markMouseInside]);
|
|
|
|
useEffect(() => {
|
|
if (isMenuOpen) {
|
|
onMenuOpen();
|
|
} else {
|
|
onMenuClose();
|
|
}
|
|
}, [isMenuOpen, onMenuClose, onMenuOpen]);
|
|
|
|
const handleToggleAttachMenu = useLastCallback(() => {
|
|
if (isAttachMenuOpen) {
|
|
closeAttachMenu();
|
|
} else {
|
|
openAttachMenu();
|
|
}
|
|
});
|
|
|
|
const handleFileSelect = useLastCallback((e: Event, shouldSuggestCompression?: boolean) => {
|
|
const { files } = e.target as HTMLInputElement;
|
|
const validatedFiles = validateFiles(files);
|
|
|
|
if (validatedFiles?.length) {
|
|
onFileSelect(validatedFiles, shouldSuggestCompression);
|
|
}
|
|
});
|
|
|
|
const handleQuickSelect = useLastCallback(() => {
|
|
openSystemFilesDialog(
|
|
Array.from(canSendVideoAndPhoto ? CONTENT_TYPES_WITH_PREVIEW : (
|
|
canSendPhotos ? SUPPORTED_PHOTO_CONTENT_TYPES : SUPPORTED_VIDEO_CONTENT_TYPES
|
|
)).join(','),
|
|
(e) => handleFileSelect(e, true),
|
|
);
|
|
});
|
|
|
|
const handleDocumentSelect = useLastCallback(() => {
|
|
openSystemFilesDialog(!canSendDocuments && canSendAudios
|
|
? Array.from(SUPPORTED_AUDIO_CONTENT_TYPES).join(',') : (
|
|
'*'
|
|
), (e) => handleFileSelect(e, false));
|
|
});
|
|
|
|
const handleSendLogs = useLastCallback(() => {
|
|
const file = new File([getDebugLogs()], DEBUG_LOG_FILENAME, { type: 'text/plain' });
|
|
onFileSelect([file]);
|
|
});
|
|
|
|
const bots = useMemo(() => {
|
|
return attachBots
|
|
? Object.values(attachBots).filter((bot) => {
|
|
if (!peerType || !bot.isForAttachMenu) return false;
|
|
if (peerType === 'bots' && bot.id === chatId && bot.attachMenuPeerTypes.includes('self')) {
|
|
return true;
|
|
}
|
|
return bot.attachMenuPeerTypes!.includes(peerType);
|
|
})
|
|
: undefined;
|
|
}, [attachBots, chatId, peerType]);
|
|
|
|
const lang = useOldLang();
|
|
|
|
if (!isButtonVisible) {
|
|
return undefined;
|
|
}
|
|
|
|
return (
|
|
<div className="AttachMenu">
|
|
{
|
|
editingMessage && hasReplaceableMedia ? (
|
|
<ResponsiveHoverButton
|
|
id="replace-menu-button"
|
|
className={isAttachMenuOpen ? 'AttachMenu--button activated' : 'AttachMenu--button'}
|
|
round
|
|
color="translucent"
|
|
onActivate={handleToggleAttachMenu}
|
|
ariaLabel="Replace an attachment"
|
|
ariaControls="replace-menu-controls"
|
|
hasPopup
|
|
>
|
|
<Icon name="replace" />
|
|
</ResponsiveHoverButton>
|
|
) : (
|
|
<ResponsiveHoverButton
|
|
id="attach-menu-button"
|
|
disabled={Boolean(editingMessage)}
|
|
className={isAttachMenuOpen ? 'AttachMenu--button activated' : 'AttachMenu--button'}
|
|
round
|
|
color="translucent"
|
|
onActivate={handleToggleAttachMenu}
|
|
ariaLabel="Add an attachment"
|
|
ariaControls="attach-menu-controls"
|
|
hasPopup
|
|
>
|
|
<Icon name="attach" />
|
|
</ResponsiveHoverButton>
|
|
)
|
|
}
|
|
<Menu
|
|
id="attach-menu-controls"
|
|
isOpen={isMenuOpen}
|
|
autoClose
|
|
positionX="right"
|
|
positionY="bottom"
|
|
onClose={closeAttachMenu}
|
|
className="AttachMenu--menu fluid"
|
|
onCloseAnimationEnd={closeAttachMenu}
|
|
onMouseEnter={!IS_TOUCH_ENV ? handleMouseEnter : undefined}
|
|
onMouseLeave={!IS_TOUCH_ENV ? handleMouseLeave : undefined}
|
|
noCloseOnBackdrop={!IS_TOUCH_ENV}
|
|
ariaLabelledBy="attach-menu-button"
|
|
>
|
|
{/*
|
|
** Using ternary operator here causes some attributes from first clause
|
|
** transferring to the fragment content in the second clause
|
|
*/}
|
|
{!canAttachMedia && (
|
|
<MenuItem className="media-disabled" disabled>Posting media content is not allowed in this group.</MenuItem>
|
|
)}
|
|
{canAttachMedia && (
|
|
<>
|
|
{canSendVideoOrPhoto && !isFile && (
|
|
<MenuItem icon="photo" onClick={handleQuickSelect}>
|
|
{lang(canSendVideoAndPhoto ? 'AttachmentMenu.PhotoOrVideo'
|
|
: (canSendPhotos ? 'InputAttach.Popover.Photo' : 'InputAttach.Popover.Video'))}
|
|
</MenuItem>
|
|
)}
|
|
{((canSendDocuments || canSendAudios) && !isPhotoOrVideo)
|
|
&& (
|
|
<MenuItem icon="document" onClick={handleDocumentSelect}>
|
|
{lang(!canSendDocuments && canSendAudios ? 'InputAttach.Popover.Music' : 'AttachDocument')}
|
|
</MenuItem>
|
|
)}
|
|
{canSendDocuments && shouldCollectDebugLogs && (
|
|
<MenuItem icon="bug" onClick={handleSendLogs}>
|
|
{lang('DebugSendLogs')}
|
|
</MenuItem>
|
|
)}
|
|
</>
|
|
)}
|
|
{canAttachPolls && !editingMessage && (
|
|
<MenuItem icon="poll" onClick={onPollCreate}>{lang('Poll')}</MenuItem>
|
|
)}
|
|
|
|
{!editingMessage && !hasReplaceableMedia && !isScheduled && bots?.map((bot) => (
|
|
<AttachBotItem
|
|
bot={bot}
|
|
chatId={chatId}
|
|
threadId={threadId}
|
|
theme={theme}
|
|
onMenuOpened={markAttachmentBotMenuOpen}
|
|
onMenuClosed={unmarkAttachmentBotMenuOpen}
|
|
/>
|
|
))}
|
|
</Menu>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default memo(AttachMenu);
|