You cannot select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
	
	
		
			164 lines
		
	
	
		
			4.8 KiB
		
	
	
	
		
			TypeScript
		
	
			
		
		
	
	
			164 lines
		
	
	
		
			4.8 KiB
		
	
	
	
		
			TypeScript
		
	
| import { useCallback, useState } from 'react';
 | |
| import { useDispatch } from 'react-redux';
 | |
| import useInterval from 'react-use/lib/useInterval';
 | |
| import useMount from 'react-use/lib/useMount';
 | |
| import styled from 'styled-components';
 | |
| import { useIsDetailMessageView } from '../../../../contexts/isDetailViewContext';
 | |
| import { Data } from '../../../../data/data';
 | |
| import { useMessageExpirationPropsById } from '../../../../hooks/useParamSelector';
 | |
| import { MessageModelType } from '../../../../models/messageType';
 | |
| import { getConversationController } from '../../../../session/conversations';
 | |
| import { PropsForExpiringMessage, messagesExpired } from '../../../../state/ducks/conversations';
 | |
| import { getIncrement } from '../../../../util/timer';
 | |
| import { ExpireTimer } from '../../ExpireTimer';
 | |
| import { ReadableMessage, ReadableMessageProps } from './ReadableMessage';
 | |
| 
 | |
| const EXPIRATION_CHECK_MINIMUM = 2000;
 | |
| 
 | |
| function useIsExpired(
 | |
|   props: Omit<PropsForExpiringMessage, 'messageId' | 'direction'> & {
 | |
|     messageId: string | undefined;
 | |
|     direction: MessageModelType | undefined;
 | |
|   }
 | |
| ) {
 | |
|   const {
 | |
|     convoId,
 | |
|     messageId,
 | |
|     expirationDurationMs,
 | |
|     expirationTimestamp,
 | |
|     isExpired: isExpiredProps,
 | |
|   } = props;
 | |
| 
 | |
|   const dispatch = useDispatch();
 | |
| 
 | |
|   const [isExpired] = useState(isExpiredProps);
 | |
| 
 | |
|   const checkExpired = useCallback(async () => {
 | |
|     const now = Date.now();
 | |
| 
 | |
|     if (!messageId || !expirationTimestamp || !expirationDurationMs) {
 | |
|       return;
 | |
|     }
 | |
| 
 | |
|     if (isExpired || now >= expirationTimestamp) {
 | |
|       await Data.removeMessage(messageId);
 | |
|       if (convoId) {
 | |
|         dispatch(
 | |
|           messagesExpired([
 | |
|             {
 | |
|               conversationKey: convoId,
 | |
|               messageId,
 | |
|             },
 | |
|           ])
 | |
|         );
 | |
|         const convo = getConversationController().get(convoId);
 | |
|         convo?.updateLastMessage();
 | |
|       }
 | |
|     }
 | |
|   }, [messageId, expirationTimestamp, expirationDurationMs, isExpired, convoId, dispatch]);
 | |
| 
 | |
|   let checkFrequency: number | null = null;
 | |
|   if (expirationDurationMs) {
 | |
|     const increment = getIncrement(expirationDurationMs || EXPIRATION_CHECK_MINIMUM);
 | |
|     checkFrequency = Math.max(EXPIRATION_CHECK_MINIMUM, increment);
 | |
|   }
 | |
| 
 | |
|   useMount(() => {
 | |
|     void checkExpired();
 | |
|   }); // check on mount
 | |
| 
 | |
|   useInterval(checkExpired, checkFrequency); // check every 2sec or sooner if needed
 | |
| 
 | |
|   return { isExpired };
 | |
| }
 | |
| 
 | |
| const StyledReadableMessage = styled(ReadableMessage)<{
 | |
|   isIncoming: boolean;
 | |
| }>`
 | |
|   display: flex;
 | |
|   justify-content: flex-end; // ${props => (props.isIncoming ? 'flex-start' : 'flex-end')};
 | |
|   align-items: ${props => (props.isIncoming ? 'flex-start' : 'flex-end')};
 | |
|   width: 100%;
 | |
|   flex-direction: column;
 | |
| `;
 | |
| 
 | |
| export interface ExpirableReadableMessageProps
 | |
|   extends Omit<ReadableMessageProps, 'receivedAt' | 'isUnread'> {
 | |
|   messageId: string;
 | |
|   isControlMessage?: boolean;
 | |
| }
 | |
| 
 | |
| function ExpireTimerControlMessage({
 | |
|   expirationTimestamp,
 | |
|   expirationDurationMs,
 | |
|   isControlMessage,
 | |
| }: {
 | |
|   expirationDurationMs: number | null | undefined;
 | |
|   expirationTimestamp: number | null | undefined;
 | |
|   isControlMessage: boolean | undefined;
 | |
| }) {
 | |
|   if (!isControlMessage) {
 | |
|     return null;
 | |
|   }
 | |
|   return (
 | |
|     <ExpireTimer
 | |
|       expirationDurationMs={expirationDurationMs || undefined}
 | |
|       expirationTimestamp={expirationTimestamp}
 | |
|     />
 | |
|   );
 | |
| }
 | |
| 
 | |
| export const ExpirableReadableMessage = (props: ExpirableReadableMessageProps) => {
 | |
|   const selected = useMessageExpirationPropsById(props.messageId);
 | |
|   const isDetailView = useIsDetailMessageView();
 | |
| 
 | |
|   const { isControlMessage, onClick, onDoubleClickCapture, role, dataTestId } = props;
 | |
| 
 | |
|   const { isExpired } = useIsExpired({
 | |
|     convoId: selected?.convoId,
 | |
|     messageId: selected?.messageId,
 | |
|     direction: selected?.direction,
 | |
|     expirationTimestamp: selected?.expirationTimestamp,
 | |
|     expirationDurationMs: selected?.expirationDurationMs,
 | |
|     isExpired: selected?.isExpired,
 | |
|   });
 | |
| 
 | |
|   if (!selected || isExpired) {
 | |
|     return null;
 | |
|   }
 | |
| 
 | |
|   const {
 | |
|     messageId,
 | |
|     direction: _direction,
 | |
|     receivedAt,
 | |
|     isUnread,
 | |
|     expirationDurationMs,
 | |
|     expirationTimestamp,
 | |
|   } = selected;
 | |
| 
 | |
|   // NOTE we want messages on the left in the message detail view regardless of direction
 | |
|   const direction = isDetailView ? 'incoming' : _direction;
 | |
|   const isIncoming = direction === 'incoming';
 | |
| 
 | |
|   return (
 | |
|     <StyledReadableMessage
 | |
|       messageId={messageId}
 | |
|       receivedAt={receivedAt}
 | |
|       isUnread={!!isUnread}
 | |
|       isIncoming={isIncoming}
 | |
|       onClick={onClick}
 | |
|       onDoubleClickCapture={onDoubleClickCapture}
 | |
|       role={role}
 | |
|       key={`readable-message-${messageId}`}
 | |
|       dataTestId={dataTestId}
 | |
|     >
 | |
|       <ExpireTimerControlMessage
 | |
|         expirationDurationMs={expirationDurationMs}
 | |
|         expirationTimestamp={expirationTimestamp}
 | |
|         isControlMessage={isControlMessage}
 | |
|       />
 | |
|       {props.children}
 | |
|     </StyledReadableMessage>
 | |
|   );
 | |
| };
 |