feat: merge expiring stopwatch and messagestatus together
parent
00f93a2754
commit
615722434b
@ -1,34 +1,188 @@
|
|||||||
|
import { ipcRenderer } from 'electron';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { MessageRenderingProps } from '../../../../models/messageType';
|
import styled from 'styled-components';
|
||||||
import { OutgoingMessageStatus } from './OutgoingMessageStatus';
|
import { useMessageExpirationPropsById } from '../../../../hooks/useParamSelector';
|
||||||
import { useMessageDirection, useMessageStatus } from '../../../../state/selectors';
|
import { useMessageStatus } from '../../../../state/selectors';
|
||||||
|
|
||||||
|
import { SpacerXS } from '../../../basic/Text';
|
||||||
|
import { SessionIcon, SessionIconType } from '../../../icon';
|
||||||
|
import { ExpireTimer } from '../../ExpireTimer';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
isCorrectSide: boolean;
|
|
||||||
messageId: string;
|
messageId: string;
|
||||||
dataTestId?: string;
|
dataTestId?: string | undefined;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type MessageStatusSelectorProps = Pick<MessageRenderingProps, 'direction' | 'status'>;
|
|
||||||
|
|
||||||
export const MessageStatus = (props: Props) => {
|
export const MessageStatus = (props: Props) => {
|
||||||
const { isCorrectSide, dataTestId } = props;
|
const { dataTestId, messageId } = props;
|
||||||
const direction = useMessageDirection(props.messageId);
|
|
||||||
const status = useMessageStatus(props.messageId);
|
const status = useMessageStatus(props.messageId);
|
||||||
|
const selected = useMessageExpirationPropsById(props.messageId);
|
||||||
|
|
||||||
if (!props.messageId) {
|
if (!props.messageId || !selected) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
const isIncoming = selected.direction === 'incoming';
|
||||||
|
|
||||||
if (!isCorrectSide) {
|
if (isIncoming) {
|
||||||
return null;
|
if (selected.isUnread || !selected.expirationDurationMs || !selected.expirationTimestamp) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<MessageStatusRead dataTestId={dataTestId} messageId={messageId} reserveDirection={true} />
|
||||||
|
);
|
||||||
}
|
}
|
||||||
const isIncoming = direction === 'incoming';
|
|
||||||
|
|
||||||
const showStatus = !isIncoming && Boolean(status);
|
// this is the outgoing state: we display the text and the icon or the text and the expiretimer stopwatch when the message is expiring
|
||||||
if (!showStatus) {
|
switch (status) {
|
||||||
|
case 'sending':
|
||||||
|
return <MessageStatusSending dataTestId={dataTestId} messageId={messageId} />;
|
||||||
|
case 'sent':
|
||||||
|
return <MessageStatusSent dataTestId={dataTestId} messageId={messageId} />;
|
||||||
|
case 'read':
|
||||||
|
return <MessageStatusRead dataTestId={dataTestId} messageId={messageId} />;
|
||||||
|
case 'error':
|
||||||
|
return <MessageStatusError dataTestId={dataTestId} messageId={messageId} />;
|
||||||
|
default:
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const MessageStatusContainer = styled.div<{ reserveDirection?: boolean }>`
|
||||||
|
display: inline-block;
|
||||||
|
align-self: flex-end;
|
||||||
|
margin-bottom: 2px;
|
||||||
|
margin-inline-start: 5px;
|
||||||
|
cursor: pointer;
|
||||||
|
display: flex;
|
||||||
|
align-items: baseline;
|
||||||
|
flex-direction: ${props =>
|
||||||
|
props.reserveDirection
|
||||||
|
? 'row-reverse'
|
||||||
|
: 'row'}; // we want {icon}{text} for incoming read messages, but {text}{icon} for outgoing messages
|
||||||
|
`;
|
||||||
|
|
||||||
|
const StyledStatusText = styled.div`
|
||||||
|
color: var(--text-secondary-color);
|
||||||
|
font-size: small;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const TextDetails = ({ text }: { text: string }) => {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<StyledStatusText>{text}</StyledStatusText>
|
||||||
|
<SpacerXS />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
function IconDanger({ iconType }: { iconType: SessionIconType }) {
|
||||||
|
return <SessionIcon iconColor={'var(--danger-color'} iconType={iconType} iconSize="tiny" />;
|
||||||
|
}
|
||||||
|
|
||||||
|
function IconNormal({
|
||||||
|
iconType,
|
||||||
|
rotateDuration,
|
||||||
|
}: {
|
||||||
|
iconType: SessionIconType;
|
||||||
|
rotateDuration?: number | undefined;
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<SessionIcon
|
||||||
|
rotateDuration={rotateDuration}
|
||||||
|
iconColor={'var(--text-secondary-color)'}
|
||||||
|
iconType={iconType}
|
||||||
|
iconSize="tiny"
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function useIsExpiring(messageId: string) {
|
||||||
|
const selected = useMessageExpirationPropsById(messageId);
|
||||||
|
return (
|
||||||
|
selected && selected.expirationDurationMs && selected.expirationTimestamp && !selected.isExpired
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function MessageStatusExpireTimer(props: Props) {
|
||||||
|
const selected = useMessageExpirationPropsById(props.messageId);
|
||||||
|
if (
|
||||||
|
!selected ||
|
||||||
|
!selected.expirationDurationMs ||
|
||||||
|
!selected.expirationTimestamp ||
|
||||||
|
selected.isExpired
|
||||||
|
) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
return (
|
||||||
|
<ExpireTimer
|
||||||
|
expirationDurationMs={selected.expirationDurationMs}
|
||||||
|
expirationTimestamp={selected.expirationTimestamp}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const MessageStatusSending = ({ dataTestId }: Props) => {
|
||||||
|
// while sending, we do not display the expire timer at all.
|
||||||
|
return (
|
||||||
|
<MessageStatusContainer data-testid={dataTestId} data-testtype="sending">
|
||||||
|
<TextDetails text={window.i18n('sending')} />
|
||||||
|
<IconNormal rotateDuration={2} iconType="sending" />
|
||||||
|
</MessageStatusContainer>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const MessageStatusSent = ({ dataTestId, messageId }: Props) => {
|
||||||
|
const isExpiring = useIsExpiring(messageId);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<MessageStatusContainer data-testid={dataTestId} data-testtype="sent">
|
||||||
|
<TextDetails text={window.i18n('sent')} />
|
||||||
|
{isExpiring ? (
|
||||||
|
<MessageStatusExpireTimer messageId={messageId} />
|
||||||
|
) : (
|
||||||
|
<IconNormal iconType="circleCheck" />
|
||||||
|
)}
|
||||||
|
</MessageStatusContainer>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const MessageStatusRead = ({
|
||||||
|
dataTestId,
|
||||||
|
messageId,
|
||||||
|
reserveDirection,
|
||||||
|
}: Props & { reserveDirection?: boolean }) => {
|
||||||
|
const isExpiring = useIsExpiring(messageId);
|
||||||
|
return (
|
||||||
|
<MessageStatusContainer
|
||||||
|
data-testid={dataTestId}
|
||||||
|
data-testtype="read"
|
||||||
|
reserveDirection={reserveDirection}
|
||||||
|
>
|
||||||
|
<TextDetails text={window.i18n('read')} />
|
||||||
|
{isExpiring ? (
|
||||||
|
<MessageStatusExpireTimer messageId={messageId} />
|
||||||
|
) : (
|
||||||
|
<IconNormal iconType="doubleCheckCircleFilled" />
|
||||||
|
)}
|
||||||
|
</MessageStatusContainer>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const MessageStatusError = ({ dataTestId }: Props) => {
|
||||||
|
const showDebugLog = () => {
|
||||||
|
ipcRenderer.send('show-debug-log');
|
||||||
|
};
|
||||||
|
// when on errro, we do not display the expire timer at all.
|
||||||
|
|
||||||
return <OutgoingMessageStatus dataTestId={dataTestId} status={status} />;
|
return (
|
||||||
|
<MessageStatusContainer
|
||||||
|
data-testid={dataTestId}
|
||||||
|
data-testtype="failed"
|
||||||
|
onClick={showDebugLog}
|
||||||
|
title={window.i18n('sendFailed')}
|
||||||
|
>
|
||||||
|
<TextDetails text={window.i18n('failed')} />
|
||||||
|
<IconDanger iconType="error" />
|
||||||
|
</MessageStatusContainer>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
@ -1,75 +0,0 @@
|
|||||||
import { ipcRenderer } from 'electron';
|
|
||||||
import React from 'react';
|
|
||||||
import styled from 'styled-components';
|
|
||||||
import { LastMessageStatusType } from '../../../../state/ducks/conversations';
|
|
||||||
import { SessionIcon } from '../../../icon';
|
|
||||||
|
|
||||||
const MessageStatusSendingContainer = styled.div`
|
|
||||||
display: inline-block;
|
|
||||||
align-self: flex-end;
|
|
||||||
margin-bottom: 2px;
|
|
||||||
margin-inline-start: 5px;
|
|
||||||
cursor: pointer;
|
|
||||||
`;
|
|
||||||
|
|
||||||
const iconColor = 'var(--text-primary-color)';
|
|
||||||
|
|
||||||
const MessageStatusSending = ({ dataTestId }: { dataTestId?: string }) => {
|
|
||||||
return (
|
|
||||||
<MessageStatusSendingContainer data-testid={dataTestId} data-testtype="sending">
|
|
||||||
<SessionIcon rotateDuration={2} iconColor={iconColor} iconType="sending" iconSize="tiny" />
|
|
||||||
</MessageStatusSendingContainer>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const MessageStatusSent = ({ dataTestId }: { dataTestId?: string }) => {
|
|
||||||
return (
|
|
||||||
<MessageStatusSendingContainer data-testid={dataTestId} data-testtype="sent">
|
|
||||||
<SessionIcon iconColor={iconColor} iconType="circleCheck" iconSize="tiny" />
|
|
||||||
</MessageStatusSendingContainer>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const MessageStatusRead = ({ dataTestId }: { dataTestId?: string }) => {
|
|
||||||
return (
|
|
||||||
<MessageStatusSendingContainer data-testid={dataTestId} data-testtype="read">
|
|
||||||
<SessionIcon iconColor={iconColor} iconType="doubleCheckCircleFilled" iconSize="tiny" />
|
|
||||||
</MessageStatusSendingContainer>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const MessageStatusError = ({ dataTestId }: { dataTestId?: string }) => {
|
|
||||||
const showDebugLog = () => {
|
|
||||||
ipcRenderer.send('show-debug-log');
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<MessageStatusSendingContainer
|
|
||||||
data-testid={dataTestId}
|
|
||||||
data-testtype="failed"
|
|
||||||
onClick={showDebugLog}
|
|
||||||
title={window.i18n('sendFailed')}
|
|
||||||
>
|
|
||||||
<SessionIcon iconColor={'var(--danger-color'} iconType="error" iconSize="tiny" />
|
|
||||||
</MessageStatusSendingContainer>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const OutgoingMessageStatus = (props: {
|
|
||||||
status: LastMessageStatusType | null;
|
|
||||||
dataTestId?: string;
|
|
||||||
}) => {
|
|
||||||
const { status, dataTestId } = props;
|
|
||||||
switch (status) {
|
|
||||||
case 'sending':
|
|
||||||
return <MessageStatusSending dataTestId={dataTestId} />;
|
|
||||||
case 'sent':
|
|
||||||
return <MessageStatusSent dataTestId={dataTestId} />;
|
|
||||||
case 'read':
|
|
||||||
return <MessageStatusRead dataTestId={dataTestId} />;
|
|
||||||
case 'error':
|
|
||||||
return <MessageStatusError dataTestId={dataTestId} />;
|
|
||||||
default:
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
};
|
|
Loading…
Reference in New Issue