187 lines
6.1 KiB
TypeScript
187 lines
6.1 KiB
TypeScript
import { memo, useRef } from '../../lib/teact/teact';
|
|
import { getActions } from '../../global';
|
|
|
|
import type { ApiPeer } from '../../api/types';
|
|
import { StoryViewerOrigin } from '../../types';
|
|
|
|
import { getPeerTitle } from '../../global/helpers/peers';
|
|
import buildClassName from '../../util/buildClassName';
|
|
import { formatMediaDuration } from '../../util/dates/dateFormat';
|
|
import { isUserId } from '../../util/entities/ids';
|
|
import { getServerTime } from '../../util/serverTime';
|
|
import { preventMessageInputBlurWithBubbling } from '../middle/helpers/preventMessageInputBlur';
|
|
|
|
import useContextMenuHandlers from '../../hooks/useContextMenuHandlers';
|
|
import useLang from '../../hooks/useLang';
|
|
import useLastCallback from '../../hooks/useLastCallback';
|
|
import useStoryPreloader from './hooks/useStoryPreloader';
|
|
|
|
import Avatar from '../common/Avatar';
|
|
import Menu from '../ui/Menu';
|
|
import MenuItem from '../ui/MenuItem';
|
|
|
|
import styles from './StoryRibbon.module.scss';
|
|
|
|
interface OwnProps {
|
|
peer: ApiPeer;
|
|
isArchived?: boolean;
|
|
stealthModeActiveUntil?: number;
|
|
}
|
|
|
|
const STEALTH_MODE_NOTIFICATION_DURATION = 1000;
|
|
|
|
function StoryRibbonButton({ peer, isArchived, stealthModeActiveUntil }: OwnProps) {
|
|
const {
|
|
openChat,
|
|
openChatWithInfo,
|
|
openStoryViewer,
|
|
toggleStoriesHidden,
|
|
openStealthModal,
|
|
showNotification,
|
|
} = getActions();
|
|
|
|
const lang = useLang();
|
|
const ref = useRef<HTMLDivElement>();
|
|
|
|
const isSelf = 'isSelf' in peer && peer.isSelf;
|
|
const isChannel = !isUserId(peer.id);
|
|
|
|
useStoryPreloader(peer.id);
|
|
|
|
const {
|
|
isContextMenuOpen, contextMenuAnchor,
|
|
handleBeforeContextMenu, handleContextMenu,
|
|
handleContextMenuClose, handleContextMenuHide,
|
|
} = useContextMenuHandlers(ref);
|
|
|
|
const getTriggerElement = useLastCallback(() => ref.current);
|
|
const getRootElement = useLastCallback(() => document.body);
|
|
const getMenuElement = useLastCallback(() => ref.current!.querySelector('.story-peer-context-menu .bubble'));
|
|
const getLayout = useLastCallback(() => ({ withPortal: true, isDense: true }));
|
|
|
|
const handleClick = useLastCallback(() => {
|
|
if (isContextMenuOpen) return;
|
|
|
|
openStoryViewer({ peerId: peer.id, origin: StoryViewerOrigin.StoryRibbon });
|
|
});
|
|
|
|
const handleMouseDown = useLastCallback((e: React.MouseEvent<HTMLElement>) => {
|
|
preventMessageInputBlurWithBubbling(e);
|
|
handleBeforeContextMenu(e);
|
|
});
|
|
|
|
const handleSavedStories = useLastCallback(() => {
|
|
openChatWithInfo({ id: peer.id, shouldReplaceHistory: true, profileTab: 'stories' });
|
|
});
|
|
|
|
const handleArchivedStories = useLastCallback(() => {
|
|
openChatWithInfo({ id: peer.id, shouldReplaceHistory: true, profileTab: 'storiesArchive' });
|
|
});
|
|
|
|
const handleOpenChat = useLastCallback(() => {
|
|
openChat({ id: peer.id, shouldReplaceHistory: true });
|
|
});
|
|
|
|
const handleOpenProfile = useLastCallback(() => {
|
|
openChatWithInfo({ id: peer.id, shouldReplaceHistory: true });
|
|
});
|
|
|
|
const handleArchivePeer = useLastCallback(() => {
|
|
toggleStoriesHidden({ peerId: peer.id, isHidden: !isArchived });
|
|
});
|
|
|
|
const handleOpenStealth = useLastCallback(() => {
|
|
const diff = stealthModeActiveUntil ? stealthModeActiveUntil - getServerTime() : 0;
|
|
if (diff > 0) {
|
|
showNotification({
|
|
title: lang('StealthModeOnTitle'),
|
|
message: lang('StealthModeOnHint', { time: formatMediaDuration(diff) }),
|
|
duration: STEALTH_MODE_NOTIFICATION_DURATION,
|
|
});
|
|
openStoryViewer({ peerId: peer.id, origin: StoryViewerOrigin.StoryRibbon });
|
|
return;
|
|
}
|
|
|
|
openStealthModal({ targetPeerId: peer.id });
|
|
});
|
|
|
|
return (
|
|
<div
|
|
ref={ref}
|
|
role="button"
|
|
data-peer-id={peer.id}
|
|
tabIndex={0}
|
|
className={styles.peer}
|
|
onMouseDown={handleMouseDown}
|
|
onClick={handleClick}
|
|
onContextMenu={handleContextMenu}
|
|
>
|
|
<Avatar
|
|
peer={peer}
|
|
withStory
|
|
storyViewerOrigin={StoryViewerOrigin.StoryRibbon}
|
|
storyViewerMode="full"
|
|
/>
|
|
<div className={buildClassName(styles.name, peer.hasUnreadStories && styles.name_hasUnreadStory)}>
|
|
{isSelf ? lang('StoryRibbonMyStory') : getPeerTitle(lang, peer)}
|
|
</div>
|
|
{contextMenuAnchor !== undefined && (
|
|
<Menu
|
|
isOpen={isContextMenuOpen}
|
|
anchor={contextMenuAnchor}
|
|
getTriggerElement={getTriggerElement}
|
|
getRootElement={getRootElement}
|
|
getMenuElement={getMenuElement}
|
|
getLayout={getLayout}
|
|
className={buildClassName('story-peer-context-menu', styles.contextMenu)}
|
|
autoClose
|
|
withPortal
|
|
onClose={handleContextMenuClose}
|
|
onCloseAnimationEnd={handleContextMenuHide}
|
|
>
|
|
{isSelf ? (
|
|
<>
|
|
<MenuItem onClick={handleSavedStories} icon="play-story">
|
|
{lang('StoryMenuSavedStories')}
|
|
</MenuItem>
|
|
<MenuItem onClick={handleArchivedStories} icon="archive">
|
|
{lang('StoryMenuArchivedStories')}
|
|
</MenuItem>
|
|
</>
|
|
) : (
|
|
<>
|
|
{!isChannel && (
|
|
<MenuItem onClick={handleOpenChat} icon="message">
|
|
{lang('StoryMenuSendMessage')}
|
|
</MenuItem>
|
|
)}
|
|
{isChannel ? (
|
|
<MenuItem onClick={handleOpenProfile} icon="channel">
|
|
{lang('StoryMenuViewChannel')}
|
|
</MenuItem>
|
|
) : (
|
|
<MenuItem onClick={handleOpenProfile} icon="user">
|
|
{lang('StoryMenuViewProfile')}
|
|
</MenuItem>
|
|
)}
|
|
{!isChannel && (
|
|
<MenuItem onClick={handleOpenStealth} icon="eye-crossed-outline">
|
|
{lang('StoryMenuOpenStealth')}
|
|
</MenuItem>
|
|
)}
|
|
<MenuItem
|
|
onClick={handleArchivePeer}
|
|
icon={isArchived ? 'unarchive' : 'archive'}
|
|
>
|
|
{lang(isArchived ? 'StoryMenuUnarchivePeer' : 'StoryMenuArchivePeer')}
|
|
</MenuItem>
|
|
</>
|
|
)}
|
|
</Menu>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|
|
|
|
export default memo(StoryRibbonButton);
|