diff --git a/src/assets/turbulence.png b/src/assets/turbulence.png deleted file mode 100644 index e8a9a7b0d..000000000 Binary files a/src/assets/turbulence.png and /dev/null differ diff --git a/src/assets/turbulence_1x.png b/src/assets/turbulence_1x.png new file mode 100644 index 000000000..6e54b25dd Binary files /dev/null and b/src/assets/turbulence_1x.png differ diff --git a/src/assets/turbulence_2x.png b/src/assets/turbulence_2x.png index 200ba282b..a734476a2 100644 Binary files a/src/assets/turbulence_2x.png and b/src/assets/turbulence_2x.png differ diff --git a/src/assets/turbulence_3x.png b/src/assets/turbulence_3x.png new file mode 100644 index 000000000..51ed141cf Binary files /dev/null and b/src/assets/turbulence_3x.png differ diff --git a/src/components/middle/MessageSelectToolbar.tsx b/src/components/middle/MessageSelectToolbar.tsx index 0e17906e2..a2985d1c5 100644 --- a/src/components/middle/MessageSelectToolbar.tsx +++ b/src/components/middle/MessageSelectToolbar.tsx @@ -7,20 +7,21 @@ import type { MessageListType } from '../../global/types'; import { selectCanDeleteSelectedMessages, selectCanDownloadSelectedMessages, + selectCanForwardMessages, selectCanReportSelectedMessages, selectCurrentMessageList, selectHasProtectedMessage, selectSelectedMessagesCount, } from '../../global/selectors'; -import useFlag from '../../hooks/useFlag'; import captureKeyboardListeners from '../../util/captureKeyboardListeners'; import buildClassName from '../../util/buildClassName'; + +import useFlag from '../../hooks/useFlag'; import usePrevious from '../../hooks/usePrevious'; import useLang from '../../hooks/useLang'; import useCopySelectedMessages from './hooks/useCopySelectedMessages'; import Button from '../ui/Button'; - import DeleteSelectedMessageModal from './DeleteSelectedMessageModal'; import ReportModal from '../common/ReportModal'; @@ -38,6 +39,7 @@ type StateProps = { canDeleteMessages?: boolean; canReportMessages?: boolean; canDownloadMessages?: boolean; + canForwardMessages?: boolean; hasProtectedMessage?: boolean; isAnyModalOpen?: boolean; selectedMessageIds?: number[]; @@ -52,6 +54,7 @@ const MessageSelectToolbar: FC = ({ canDeleteMessages, canReportMessages, canDownloadMessages, + canForwardMessages, hasProtectedMessage, isAnyModalOpen, selectedMessageIds, @@ -107,7 +110,7 @@ const MessageSelectToolbar: FC = ({ ); const renderButton = ( - icon: string, label: string, onClick: AnyToVoidFunction, disabled?: boolean, destructive?: boolean, + icon: string, label: string, onClick: AnyToVoidFunction, destructive?: boolean, ) => { return (
= ({ tabIndex={0} className={buildClassName( 'item', - disabled && 'disabled', destructive && 'destructive', )} - onClick={!disabled ? onClick : undefined} + onClick={onClick} title={label} > @@ -143,19 +145,23 @@ const MessageSelectToolbar: FC = ({ {Boolean(selectedMessagesCount) && (
- {messageListType !== 'scheduled' && ( + {messageListType !== 'scheduled' && canForwardMessages && ( renderButton( - 'forward', lang('Chat.ForwardActionHeader'), openForwardMenuForSelectedMessages, hasProtectedMessage, + 'forward', lang('Chat.ForwardActionHeader'), openForwardMenuForSelectedMessages, ) )} {canReportMessages && ( renderButton('flag', lang('Conversation.ReportMessages'), openReportModal) )} - {canDownloadMessages && ( - renderButton('download', lang('lng_media_download'), handleDownload, hasProtectedMessage) + {canDownloadMessages && !hasProtectedMessage && ( + renderButton('download', lang('lng_media_download'), handleDownload) + )} + {!hasProtectedMessage && ( + renderButton('copy', lang('lng_context_copy_selected_items'), handleCopy) + )} + {canDeleteMessages && ( + renderButton('delete', lang('EditAdminGroupDeleteMessages'), openDeleteModal, true) )} - {renderButton('copy', lang('lng_context_copy_selected_items'), handleCopy, hasProtectedMessage)} - {renderButton('delete', lang('EditAdminGroupDeleteMessages'), openDeleteModal, !canDeleteMessages, true)}
)}
@@ -182,6 +188,7 @@ export default memo(withGlobal( const canDownload = selectCanDownloadSelectedMessages(global); const { messageIds: selectedMessageIds } = global.selectedMessages || {}; const hasProtectedMessage = chatId ? selectHasProtectedMessage(global, chatId, selectedMessageIds) : false; + const canForward = chatId ? selectCanForwardMessages(global, chatId, selectedMessageIds) : false; const isForwardModalOpen = global.forwardMessages.isModalShown; const isAnyModalOpen = Boolean(isForwardModalOpen || global.requestedDraft || global.requestedAttachBotInChat || global.requestedAttachBotInstall); @@ -192,6 +199,7 @@ export default memo(withGlobal( canDeleteMessages: canDelete, canReportMessages: canReport, canDownloadMessages: canDownload, + canForwardMessages: canForward, selectedMessageIds, hasProtectedMessage, isAnyModalOpen, diff --git a/src/components/middle/message/InvoiceMediaPreview.module.scss b/src/components/middle/message/InvoiceMediaPreview.module.scss index ed949b51a..ac287b389 100644 --- a/src/components/middle/message/InvoiceMediaPreview.module.scss +++ b/src/components/middle/message/InvoiceMediaPreview.module.scss @@ -13,20 +13,23 @@ width: 100%; height: 100%; - --background-url: url('../../../assets/turbulence.png'); + --background-url: url('../../../assets/turbulence_1x.png'); --background-size: 256px; - background: rgba(0, 0, 0, 0.3) var(--background-url); + background: rgba(0, 0, 0, 0.25) var(--background-url); background-size: var(--background-size) var(--background-size); z-index: 1; + @media (-webkit-min-device-pixel-ratio: 2) { + --background-url: url('../../../assets/turbulence_2x.png'); + } + + @media (-webkit-min-device-pixel-ratio: 3) { + --background-url: url('../../../assets/turbulence_3x.png'); + } + --x-direction: var(--background-size); --y-direction: 0; - animation: 10s linear infinite dots; - - &.highres { - --background-url: url('../../../assets/turbulence_2x.png'); - --background-size: 128px; - } + animation: 20s linear infinite dots; &::before { content: ''; @@ -40,7 +43,7 @@ --x-direction: 0; --y-direction: var(--background-size); - animation: 10s linear -4s infinite dots; + animation: 20s linear -7s infinite dots; } &::after { @@ -55,7 +58,7 @@ --x-direction: calc(-1 * var(--background-size)); --y-direction: calc(-1 * var(--background-size)); - animation: 10s linear -8s infinite dots; + animation: 20s linear -14s infinite dots; } } @@ -85,16 +88,13 @@ left: 50%; transform: translate(-50%, -50%); - padding: 0.375rem 0.625rem; + padding: 0.5rem 0.875rem; - background-color: rgba(0, 0, 0, 0.5); + background-color: rgba(0, 0, 0, 0.75); color: #FFFFFF; border-radius: 1rem; white-space: nowrap; -} - -.lock { - margin-bottom: 1px; + line-height: 1rem; } .canvas { @@ -105,13 +105,8 @@ @keyframes dots { 0% { background-position: 0 0; - opacity: 1; - } - 50% { - opacity: 0.75; } 100% { background-position: var(--x-direction) var(--y-direction); - opacity: 1; } } diff --git a/src/components/middle/message/InvoiceMediaPreview.tsx b/src/components/middle/message/InvoiceMediaPreview.tsx index 48acfd080..c406f7ac0 100644 --- a/src/components/middle/message/InvoiceMediaPreview.tsx +++ b/src/components/middle/message/InvoiceMediaPreview.tsx @@ -8,7 +8,6 @@ import { getMessageInvoice } from '../../../global/helpers'; import { formatCurrency } from '../../../util/formatCurrency'; import { formatMediaDuration } from '../../../util/dateFormat'; import buildClassName from '../../../util/buildClassName'; -import { DPR } from '../../../util/environment'; import useLang from '../../../hooks/useLang'; import useCanvasBlur from '../../../hooks/useCanvasBlur'; @@ -66,7 +65,7 @@ const InvoiceMediaPreview: FC = ({ onClick={handleClick} > -
1 && styles.highres)} /> +
{Boolean(duration) &&
{formatMediaDuration(duration)}
}
diff --git a/src/global/selectors/messages.ts b/src/global/selectors/messages.ts index 4e4b66e9c..c75172d53 100644 --- a/src/global/selectors/messages.ts +++ b/src/global/selectors/messages.ts @@ -399,7 +399,7 @@ export function selectAllowedMessageActions(global: GlobalState, message: ApiMes || getServerTime(global.serverTimeOffset) - message.date < MESSAGE_EDIT_ALLOWED_TIME ) && !( content.sticker || content.contact || content.poll || content.action || content.audio - || (content.video?.isRound) || content.location + || (content.video?.isRound) || content.location || content.invoice ) && !isForwardedMessage(message) && !message.viaBotId @@ -922,6 +922,20 @@ export function selectHasProtectedMessage(global: GlobalState, chatId: string, m return messageIds.some((messageId) => messages[messageId]?.isProtected); } +export function selectCanForwardMessages(global: GlobalState, chatId: string, messageIds?: number[]) { + if (selectChat(global, chatId)?.isProtected) { + return false; + } + + if (!messageIds) { + return false; + } + + const messages = selectChatMessages(global, chatId); + + return messageIds.every((messageId) => messages[messageId]?.isForwardingAllowed); +} + export function selectSponsoredMessage(global: GlobalState, chatId: string) { const chat = selectChat(global, chatId); const message = chat && isChatChannel(chat) ? global.messages.sponsoredByChatId[chatId] : undefined;