Introduce one-time voice message (#4182)
This commit is contained in:
parent
d43a525a43
commit
96d4e7a437
@ -72,7 +72,18 @@ export function buildMessageTextContent(
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function buildMessageMediaContent(media: GramJs.TypeMessageMedia): MediaContent | undefined {
|
export function buildMessageMediaContent(media: GramJs.TypeMessageMedia): MediaContent | undefined {
|
||||||
if ('ttlSeconds' in media && media.ttlSeconds) {
|
const ttlSeconds = 'ttlSeconds' in media ? media.ttlSeconds : undefined;
|
||||||
|
|
||||||
|
const isExpiredVoice = isExpiredVoiceMessage(media);
|
||||||
|
if (isExpiredVoice) {
|
||||||
|
return { isExpiredVoice };
|
||||||
|
}
|
||||||
|
|
||||||
|
const voice = buildVoice(media);
|
||||||
|
if (voice) return { voice, ttlSeconds };
|
||||||
|
|
||||||
|
// Other disappearing media types are not supported
|
||||||
|
if (ttlSeconds !== undefined) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -93,9 +104,6 @@ export function buildMessageMediaContent(media: GramJs.TypeMessageMedia): MediaC
|
|||||||
const audio = buildAudio(media);
|
const audio = buildAudio(media);
|
||||||
if (audio) return { audio };
|
if (audio) return { audio };
|
||||||
|
|
||||||
const voice = buildVoice(media);
|
|
||||||
if (voice) return { voice };
|
|
||||||
|
|
||||||
const document = buildDocumentFromMedia(media);
|
const document = buildDocumentFromMedia(media);
|
||||||
if (document) return { document };
|
if (document) return { document };
|
||||||
|
|
||||||
@ -255,6 +263,13 @@ function buildAudio(media: GramJs.TypeMessageMedia): ApiAudio | undefined {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isExpiredVoiceMessage(media: GramJs.TypeMessageMedia): MediaContent['isExpiredVoice'] {
|
||||||
|
if (!(media instanceof GramJs.MessageMediaDocument)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return !media.document && media.voice;
|
||||||
|
}
|
||||||
|
|
||||||
function buildVoice(media: GramJs.TypeMessageMedia): ApiVoice | undefined {
|
function buildVoice(media: GramJs.TypeMessageMedia): ApiVoice | undefined {
|
||||||
if (
|
if (
|
||||||
!(media instanceof GramJs.MessageMediaDocument)
|
!(media instanceof GramJs.MessageMediaDocument)
|
||||||
|
|||||||
@ -475,6 +475,8 @@ export type MediaContent = {
|
|||||||
storyData?: ApiMessageStoryData;
|
storyData?: ApiMessageStoryData;
|
||||||
giveaway?: ApiGiveaway;
|
giveaway?: ApiGiveaway;
|
||||||
giveawayResults?: ApiGiveawayResults;
|
giveawayResults?: ApiGiveawayResults;
|
||||||
|
ttlSeconds?: number;
|
||||||
|
isExpiredVoice?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export interface ApiMessage {
|
export interface ApiMessage {
|
||||||
|
|||||||
6
src/assets/font-icons/view-once.svg
Normal file
6
src/assets/font-icons/view-once.svg
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32">
|
||||||
|
<g id="surface1">
|
||||||
|
<path style=" stroke:none;fill-rule:evenodd;fill:rgb(0%,0%,0%);fill-opacity:1;" d="M 16 29.328125 C 16.734375 29.328125 17.328125 28.734375 17.328125 28 C 17.328125 27.265625 16.734375 26.671875 16 26.671875 Z M 16 5.328125 C 16.734375 5.328125 17.328125 4.734375 17.328125 4 C 17.328125 3.265625 16.734375 2.671875 16 2.671875 Z M 16 26.671875 C 10.105469 26.671875 5.328125 21.894531 5.328125 16 L 2.671875 16 C 2.671875 23.359375 8.640625 29.328125 16 29.328125 Z M 5.328125 16 C 5.328125 10.105469 10.105469 5.328125 16 5.328125 L 16 2.671875 C 8.640625 2.671875 2.671875 8.640625 2.671875 16 Z M 5.328125 16 "/>
|
||||||
|
<path style=" stroke:none;fill-rule:evenodd;fill:rgb(0%,0%,0%);fill-opacity:1;" d="M 29.273438 14.777344 C 29.207031 14.046875 28.558594 13.507812 27.828125 13.574219 C 27.097656 13.640625 26.5625 14.289062 26.628906 15.019531 Z M 24.207031 9.179688 C 24.675781 9.742188 25.515625 9.820312 26.078125 9.351562 C 26.640625 8.882812 26.71875 8.042969 26.25 7.480469 Z M 24.519531 5.75 C 23.957031 5.28125 23.117188 5.359375 22.648438 5.921875 C 22.179688 6.484375 22.257812 7.324219 22.820312 7.792969 Z M 16.980469 5.371094 C 17.710938 5.4375 18.359375 4.902344 18.425781 4.171875 C 18.492188 3.441406 17.953125 2.792969 17.222656 2.726562 Z M 14.777344 2.726562 C 14.046875 2.792969 13.507812 3.441406 13.574219 4.171875 C 13.640625 4.902344 14.289062 5.4375 15.019531 5.371094 Z M 9.179688 7.792969 C 9.742188 7.324219 9.820312 6.484375 9.351562 5.921875 C 8.878906 5.359375 8.042969 5.28125 7.480469 5.75 Z M 5.75 7.480469 C 5.28125 8.042969 5.359375 8.882812 5.921875 9.351562 C 6.484375 9.820312 7.324219 9.742188 7.792969 9.179688 Z M 5.371094 15.019531 C 5.4375 14.289062 4.902344 13.640625 4.171875 13.574219 C 3.441406 13.507812 2.792969 14.046875 2.726562 14.777344 Z M 2.726562 17.222656 C 2.792969 17.953125 3.441406 18.492188 4.171875 18.425781 C 4.902344 18.359375 5.4375 17.710938 5.371094 16.980469 Z M 7.792969 22.820312 C 7.324219 22.257812 6.484375 22.179688 5.921875 22.648438 C 5.359375 23.121094 5.28125 23.957031 5.75 24.519531 Z M 7.480469 26.25 C 8.042969 26.71875 8.878906 26.640625 9.351562 26.078125 C 9.820312 25.515625 9.742188 24.675781 9.179688 24.207031 Z M 15.019531 26.628906 C 14.289062 26.5625 13.640625 27.097656 13.574219 27.828125 C 13.507812 28.558594 14.046875 29.207031 14.777344 29.273438 Z M 17.222656 29.273438 C 17.953125 29.207031 18.492188 28.558594 18.425781 27.828125 C 18.359375 27.097656 17.710938 26.5625 16.980469 26.628906 Z M 22.820312 24.207031 C 22.257812 24.675781 22.179688 25.515625 22.648438 26.078125 C 23.117188 26.640625 23.957031 26.71875 24.519531 26.25 Z M 26.25 24.519531 C 26.71875 23.957031 26.640625 23.121094 26.078125 22.648438 C 25.515625 22.179688 24.675781 22.257812 24.207031 22.820312 Z M 26.628906 16.980469 C 26.5625 17.710938 27.097656 18.359375 27.828125 18.425781 C 28.558594 18.492188 29.207031 17.953125 29.273438 17.222656 Z M 29.328125 16 C 29.328125 15.589844 29.308594 15.179688 29.273438 14.777344 L 26.628906 15.019531 C 26.65625 15.34375 26.671875 15.671875 26.671875 16 Z M 26.25 7.480469 C 25.726562 6.851562 25.148438 6.273438 24.519531 5.75 L 22.820312 7.792969 C 23.324219 8.210938 23.789062 8.675781 24.207031 9.179688 Z M 17.222656 2.726562 C 16.816406 2.691406 16.40625 2.671875 16 2.671875 L 16 5.328125 C 16.332031 5.328125 16.660156 5.34375 16.980469 5.371094 Z M 16 2.671875 C 15.589844 2.671875 15.179688 2.691406 14.777344 2.726562 L 15.019531 5.371094 C 15.34375 5.34375 15.671875 5.328125 16 5.328125 Z M 7.480469 5.75 C 6.851562 6.273438 6.273438 6.851562 5.75 7.480469 L 7.792969 9.179688 C 8.210938 8.675781 8.675781 8.210938 9.179688 7.792969 Z M 2.726562 14.777344 C 2.691406 15.183594 2.671875 15.59375 2.671875 16 L 5.328125 16 C 5.328125 15.667969 5.34375 15.339844 5.371094 15.019531 Z M 2.671875 16 C 2.671875 16.410156 2.691406 16.820312 2.726562 17.222656 L 5.371094 16.980469 C 5.34375 16.65625 5.328125 16.328125 5.328125 16 Z M 5.75 24.519531 C 6.273438 25.148438 6.851562 25.726562 7.480469 26.25 L 9.179688 24.207031 C 8.675781 23.789062 8.210938 23.324219 7.792969 22.820312 Z M 14.777344 29.273438 C 15.179688 29.308594 15.589844 29.328125 16 29.328125 L 16 26.671875 C 15.667969 26.671875 15.339844 26.65625 15.019531 26.628906 Z M 16 29.328125 C 16.410156 29.328125 16.820312 29.308594 17.222656 29.273438 L 16.980469 26.628906 C 16.65625 26.65625 16.328125 26.671875 16 26.671875 Z M 24.519531 26.25 C 25.148438 25.726562 25.726562 25.148438 26.25 24.519531 L 24.207031 22.820312 C 23.789062 23.324219 23.324219 23.789062 22.820312 24.207031 Z M 29.273438 17.222656 C 29.308594 16.820312 29.328125 16.410156 29.328125 16 L 26.671875 16 C 26.671875 16.332031 26.65625 16.660156 26.628906 16.980469 Z M 16.726562 22.996094 C 15.890625 22.996094 15.320312 22.433594 15.320312 21.582031 L 15.320312 11.898438 L 15.261719 11.898438 L 13.285156 13.273438 C 13.023438 13.460938 12.824219 13.527344 12.535156 13.527344 C 11.960938 13.527344 11.539062 13.117188 11.539062 12.515625 C 11.539062 12.085938 11.710938 11.765625 12.148438 11.457031 L 14.832031 9.601562 C 15.488281 9.148438 15.9375 9.066406 16.519531 9.066406 C 17.523438 9.066406 18.121094 9.675781 18.121094 10.648438 L 18.121094 21.582031 C 18.121094 22.433594 17.558594 22.996094 16.726562 22.996094 "/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 5.3 KiB |
BIN
src/assets/tgs/general/Flame.tgs
Normal file
BIN
src/assets/tgs/general/Flame.tgs
Normal file
Binary file not shown.
@ -83,3 +83,4 @@ export { default as Management } from '../components/right/management/Management
|
|||||||
export { default as PaymentModal } from '../components/payment/PaymentModal';
|
export { default as PaymentModal } from '../components/payment/PaymentModal';
|
||||||
export { default as ReceiptModal } from '../components/payment/ReceiptModal';
|
export { default as ReceiptModal } from '../components/payment/ReceiptModal';
|
||||||
export { default as InviteViaLinkModal } from '../components/main/InviteViaLinkModal';
|
export { default as InviteViaLinkModal } from '../components/main/InviteViaLinkModal';
|
||||||
|
export { default as OneTimeMediaModal } from '../components/modals/oneTimeMedia/OneTimeMediaModal';
|
||||||
|
|||||||
@ -24,6 +24,72 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.toogle-play-wrapper {
|
||||||
|
margin: 0;
|
||||||
|
|
||||||
|
.toggle-play {
|
||||||
|
margin-inline-end: 0.5rem;
|
||||||
|
|
||||||
|
&.translucent-white {
|
||||||
|
color: rgba(255, 255, 255, 0.8);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.smaller {
|
||||||
|
width: 3rem;
|
||||||
|
height: 3rem;
|
||||||
|
margin-inline-end: 0.75rem;
|
||||||
|
|
||||||
|
.icon {
|
||||||
|
font-size: 1.625rem;
|
||||||
|
|
||||||
|
&.icon-pause {
|
||||||
|
font-size: 1.5625rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon {
|
||||||
|
position: absolute;
|
||||||
|
|
||||||
|
&.icon-play {
|
||||||
|
margin-left: 0.1875rem;
|
||||||
|
@media (max-width: 600px) {
|
||||||
|
margin-left: 0.125rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-play,
|
||||||
|
.icon-pause,
|
||||||
|
.flame {
|
||||||
|
opacity: 1;
|
||||||
|
transform: scale(1);
|
||||||
|
transition: opacity 0.4s, transform 0.6s cubic-bezier(0.34, 1.56, 0.64, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.play .icon-pause,
|
||||||
|
&.pause .icon-play,
|
||||||
|
&.loading .icon-play,
|
||||||
|
&.loading .icon-pause,
|
||||||
|
&.loading .flame {
|
||||||
|
opacity: 0;
|
||||||
|
transform: scale(0.5);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-view-once {
|
||||||
|
position: absolute;
|
||||||
|
left: 2rem;
|
||||||
|
bottom: 0rem;
|
||||||
|
font-size: 1.1875rem;
|
||||||
|
border-radius: 50%;
|
||||||
|
color: var(--color-white);
|
||||||
|
background-color: var(--color-primary);
|
||||||
|
outline: var(--background-color) solid 0.125rem;
|
||||||
|
z-index: var(--z-badge);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
&.own {
|
&.own {
|
||||||
--color-text-secondary: var(--accent-color);
|
--color-text-secondary: var(--accent-color);
|
||||||
--color-interactive-active: var(--color-text-green);
|
--color-interactive-active: var(--color-text-green);
|
||||||
@ -35,7 +101,7 @@
|
|||||||
--color-text-green: var(--color-white);
|
--color-text-green: var(--color-white);
|
||||||
}
|
}
|
||||||
|
|
||||||
.Button {
|
.Button, .icon-view-once, .media-loading {
|
||||||
--color-primary: var(--color-text-green);
|
--color-primary: var(--color-text-green);
|
||||||
--color-primary-shade: var(--color-green);
|
--color-primary-shade: var(--color-green);
|
||||||
--color-primary-shade-darker: var(--color-green-darker);
|
--color-primary-shade-darker: var(--color-green-darker);
|
||||||
@ -48,54 +114,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.toggle-play {
|
|
||||||
margin-inline-end: 0.5rem;
|
|
||||||
|
|
||||||
&.translucent-white {
|
|
||||||
color: rgba(255, 255, 255, 0.8);
|
|
||||||
}
|
|
||||||
|
|
||||||
&.smaller {
|
|
||||||
width: 3rem;
|
|
||||||
height: 3rem;
|
|
||||||
margin-inline-end: 0.75rem;
|
|
||||||
|
|
||||||
.icon {
|
|
||||||
font-size: 1.625rem;
|
|
||||||
|
|
||||||
&.icon-pause {
|
|
||||||
font-size: 1.5625rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.icon {
|
|
||||||
position: absolute;
|
|
||||||
|
|
||||||
&.icon-play {
|
|
||||||
margin-left: 0.1875rem;
|
|
||||||
@media (max-width: 600px) {
|
|
||||||
margin-left: 0.125rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.icon-play,
|
|
||||||
.icon-pause {
|
|
||||||
opacity: 1;
|
|
||||||
transform: scale(1);
|
|
||||||
transition: opacity 0.4s, transform 0.6s cubic-bezier(0.34, 1.56, 0.64, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
&.play .icon-pause,
|
|
||||||
&.pause .icon-play,
|
|
||||||
&.loading .icon-play,
|
|
||||||
&.loading .icon-pause {
|
|
||||||
opacity: 0;
|
|
||||||
transform: scale(0.5);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.download-button {
|
.download-button {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
width: 1.1875rem !important;
|
width: 1.1875rem !important;
|
||||||
@ -227,6 +245,11 @@
|
|||||||
align-items: flex-end;
|
align-items: flex-end;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
&.non-interactive {
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
.meta,
|
.meta,
|
||||||
.performer,
|
.performer,
|
||||||
.date {
|
.date {
|
||||||
|
|||||||
@ -16,6 +16,7 @@ import {
|
|||||||
getMediaTransferState,
|
getMediaTransferState,
|
||||||
getMessageMediaFormat,
|
getMessageMediaFormat,
|
||||||
getMessageMediaHash,
|
getMessageMediaHash,
|
||||||
|
hasMessageTtl,
|
||||||
isMessageLocal,
|
isMessageLocal,
|
||||||
isOwnMessage,
|
isOwnMessage,
|
||||||
} from '../../global/helpers';
|
} from '../../global/helpers';
|
||||||
@ -24,6 +25,7 @@ import buildClassName from '../../util/buildClassName';
|
|||||||
import { captureEvents } from '../../util/captureEvents';
|
import { captureEvents } from '../../util/captureEvents';
|
||||||
import { formatMediaDateTime, formatMediaDuration, formatPastTimeShort } from '../../util/dateFormat';
|
import { formatMediaDateTime, formatMediaDuration, formatPastTimeShort } from '../../util/dateFormat';
|
||||||
import { decodeWaveform, interpolateArray } from '../../util/waveform';
|
import { decodeWaveform, interpolateArray } from '../../util/waveform';
|
||||||
|
import { LOCAL_TGS_URLS } from './helpers/animatedAssets';
|
||||||
import { getFileSizeString } from './helpers/documentInfo';
|
import { getFileSizeString } from './helpers/documentInfo';
|
||||||
import renderText from './helpers/renderText';
|
import renderText from './helpers/renderText';
|
||||||
import { MAX_EMPTY_WAVEFORM_POINTS, renderWaveform } from './helpers/waveform';
|
import { MAX_EMPTY_WAVEFORM_POINTS, renderWaveform } from './helpers/waveform';
|
||||||
@ -40,6 +42,8 @@ import useShowTransition from '../../hooks/useShowTransition';
|
|||||||
import Button from '../ui/Button';
|
import Button from '../ui/Button';
|
||||||
import Link from '../ui/Link';
|
import Link from '../ui/Link';
|
||||||
import ProgressSpinner from '../ui/ProgressSpinner';
|
import ProgressSpinner from '../ui/ProgressSpinner';
|
||||||
|
import AnimatedIcon from './AnimatedIcon';
|
||||||
|
import Icon from './Icon';
|
||||||
|
|
||||||
import './Audio.scss';
|
import './Audio.scss';
|
||||||
|
|
||||||
@ -61,8 +65,10 @@ type OwnProps = {
|
|||||||
canTranscribe?: boolean;
|
canTranscribe?: boolean;
|
||||||
isTranscriptionHidden?: boolean;
|
isTranscriptionHidden?: boolean;
|
||||||
isTranscriptionError?: boolean;
|
isTranscriptionError?: boolean;
|
||||||
|
autoPlay?: boolean;
|
||||||
onHideTranscription?: (isHidden: boolean) => void;
|
onHideTranscription?: (isHidden: boolean) => void;
|
||||||
onPlay: (messageId: number, chatId: string) => void;
|
onPlay: (messageId: number, chatId: string) => void;
|
||||||
|
onPause?: NoneToVoidFunction;
|
||||||
onReadMedia?: () => void;
|
onReadMedia?: () => void;
|
||||||
onCancelUpload?: () => void;
|
onCancelUpload?: () => void;
|
||||||
onDateClick?: (messageId: number, chatId: string) => void;
|
onDateClick?: (messageId: number, chatId: string) => void;
|
||||||
@ -92,15 +98,23 @@ const Audio: FC<OwnProps> = ({
|
|||||||
isTranscriptionError,
|
isTranscriptionError,
|
||||||
canDownload,
|
canDownload,
|
||||||
canTranscribe,
|
canTranscribe,
|
||||||
|
autoPlay,
|
||||||
onHideTranscription,
|
onHideTranscription,
|
||||||
onPlay,
|
onPlay,
|
||||||
|
onPause,
|
||||||
onReadMedia,
|
onReadMedia,
|
||||||
onCancelUpload,
|
onCancelUpload,
|
||||||
onDateClick,
|
onDateClick,
|
||||||
}) => {
|
}) => {
|
||||||
const { cancelMessageMediaDownload, downloadMessageMedia, transcribeAudio } = getActions();
|
const {
|
||||||
|
cancelMessageMediaDownload, downloadMessageMedia, transcribeAudio, openOneTimeMediaModal,
|
||||||
|
} = getActions();
|
||||||
|
|
||||||
const { content: { audio, voice, video }, isMediaUnread } = message;
|
const {
|
||||||
|
content: {
|
||||||
|
audio, voice, video,
|
||||||
|
}, isMediaUnread,
|
||||||
|
} = message;
|
||||||
const isVoice = Boolean(voice || video);
|
const isVoice = Boolean(voice || video);
|
||||||
const isSeeking = useRef<boolean>(false);
|
const isSeeking = useRef<boolean>(false);
|
||||||
// eslint-disable-next-line no-null/no-null
|
// eslint-disable-next-line no-null/no-null
|
||||||
@ -109,10 +123,13 @@ const Audio: FC<OwnProps> = ({
|
|||||||
const { isRtl } = lang;
|
const { isRtl } = lang;
|
||||||
|
|
||||||
const { isMobile } = useAppLayout();
|
const { isMobile } = useAppLayout();
|
||||||
const [isActivated, setIsActivated] = useState(false);
|
const [isActivated, setIsActivated] = useState(Boolean(autoPlay));
|
||||||
const shouldLoad = isActivated || PRELOAD;
|
const shouldLoad = isActivated || PRELOAD;
|
||||||
const coverHash = getMessageMediaHash(message, 'pictogram');
|
const coverHash = getMessageMediaHash(message, 'pictogram');
|
||||||
const coverBlobUrl = useMedia(coverHash, false, ApiMediaFormat.BlobUrl);
|
const coverBlobUrl = useMedia(coverHash, false, ApiMediaFormat.BlobUrl);
|
||||||
|
const hasTtl = hasMessageTtl(message);
|
||||||
|
const isOneTimeModalOrigin = origin === AudioOrigin.OneTimeModal;
|
||||||
|
const trackType = isVoice ? (hasTtl ? 'oneTimeVoice' : 'voice') : 'audio';
|
||||||
|
|
||||||
const mediaData = useMedia(
|
const mediaData = useMedia(
|
||||||
getMessageMediaHash(message, 'inline'),
|
getMessageMediaHash(message, 'inline'),
|
||||||
@ -139,12 +156,13 @@ const Audio: FC<OwnProps> = ({
|
|||||||
isBuffered, bufferedRanges, bufferingHandlers, checkBuffering,
|
isBuffered, bufferedRanges, bufferingHandlers, checkBuffering,
|
||||||
} = useBuffering();
|
} = useBuffering();
|
||||||
|
|
||||||
|
const noReset = isOneTimeModalOrigin;
|
||||||
const {
|
const {
|
||||||
isPlaying, playProgress, playPause, setCurrentTime, duration,
|
isPlaying, playProgress, playPause, setCurrentTime, duration,
|
||||||
} = useAudioPlayer(
|
} = useAudioPlayer(
|
||||||
makeTrackId(message),
|
makeTrackId(message),
|
||||||
getMediaDuration(message)!,
|
getMediaDuration(message)!,
|
||||||
isVoice ? 'voice' : 'audio',
|
trackType,
|
||||||
mediaData,
|
mediaData,
|
||||||
bufferingHandlers,
|
bufferingHandlers,
|
||||||
undefined,
|
undefined,
|
||||||
@ -152,12 +170,24 @@ const Audio: FC<OwnProps> = ({
|
|||||||
isActivated,
|
isActivated,
|
||||||
handleForcePlay,
|
handleForcePlay,
|
||||||
handleTrackChange,
|
handleTrackChange,
|
||||||
isMessageLocal(message),
|
isMessageLocal(message) || hasTtl,
|
||||||
|
undefined,
|
||||||
|
onPause,
|
||||||
|
noReset,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const reversePlayProgress = 1 - playProgress;
|
||||||
const isOwn = isOwnMessage(message);
|
const isOwn = isOwnMessage(message);
|
||||||
|
const isReverse = hasTtl && isOneTimeModalOrigin;
|
||||||
|
|
||||||
const waveformCanvasRef = useWaveformCanvas(
|
const waveformCanvasRef = useWaveformCanvas(
|
||||||
theme, voice, (isMediaUnread && !isOwn) ? 1 : playProgress, isOwn, !noAvatars, isMobile,
|
theme,
|
||||||
|
voice,
|
||||||
|
(isMediaUnread && !isOwn && !isReverse) ? 1 : playProgress,
|
||||||
|
isOwn,
|
||||||
|
!noAvatars,
|
||||||
|
isMobile,
|
||||||
|
isReverse,
|
||||||
);
|
);
|
||||||
|
|
||||||
const withSeekline = isPlaying || (playProgress > 0 && playProgress < 1);
|
const withSeekline = isPlaying || (playProgress > 0 && playProgress < 1);
|
||||||
@ -189,6 +219,13 @@ const Audio: FC<OwnProps> = ({
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (hasTtl) {
|
||||||
|
// Set new date to prevent saving state of the track
|
||||||
|
openOneTimeMediaModal({ message: { ...message, date: Date.now() } });
|
||||||
|
onReadMedia?.();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (!isPlaying) {
|
if (!isPlaying) {
|
||||||
onPlay(message.id, message.chatId);
|
onPlay(message.id, message.chatId);
|
||||||
}
|
}
|
||||||
@ -241,14 +278,14 @@ const Audio: FC<OwnProps> = ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!seekerRef.current || !withSeekline) return undefined;
|
if (!seekerRef.current || !withSeekline || isOneTimeModalOrigin) return undefined;
|
||||||
return captureEvents(seekerRef.current, {
|
return captureEvents(seekerRef.current, {
|
||||||
onCapture: handleStartSeek,
|
onCapture: handleStartSeek,
|
||||||
onRelease: handleStopSeek,
|
onRelease: handleStopSeek,
|
||||||
onClick: handleStopSeek,
|
onClick: handleStopSeek,
|
||||||
onDrag: handleSeek,
|
onDrag: handleSeek,
|
||||||
});
|
});
|
||||||
}, [withSeekline, handleStartSeek, handleSeek, handleStopSeek]);
|
}, [withSeekline, handleStartSeek, handleSeek, handleStopSeek, isOneTimeModalOrigin]);
|
||||||
|
|
||||||
function renderFirstLine() {
|
function renderFirstLine() {
|
||||||
if (isVoice) {
|
if (isVoice) {
|
||||||
@ -285,6 +322,7 @@ const Audio: FC<OwnProps> = ({
|
|||||||
const fullClassName = buildClassName(
|
const fullClassName = buildClassName(
|
||||||
'Audio',
|
'Audio',
|
||||||
className,
|
className,
|
||||||
|
isOneTimeModalOrigin && 'non-interactive',
|
||||||
origin === AudioOrigin.Inline && 'inline',
|
origin === AudioOrigin.Inline && 'inline',
|
||||||
isOwn && origin === AudioOrigin.Inline && 'own',
|
isOwn && origin === AudioOrigin.Inline && 'own',
|
||||||
(origin === AudioOrigin.Search || origin === AudioOrigin.SharedMedia) && 'bigger',
|
(origin === AudioOrigin.Search || origin === AudioOrigin.SharedMedia) && 'bigger',
|
||||||
@ -331,6 +369,40 @@ const Audio: FC<OwnProps> = ({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function renderTooglePlayWrapper() {
|
||||||
|
return (
|
||||||
|
<div className="toogle-play-wrapper">
|
||||||
|
<Button
|
||||||
|
round
|
||||||
|
ripple={!isMobile}
|
||||||
|
size="smaller"
|
||||||
|
color={coverBlobUrl ? 'translucent-white' : 'primary'}
|
||||||
|
className={buttonClassNames.join(' ')}
|
||||||
|
ariaLabel={isPlaying ? 'Pause audio' : 'Play audio'}
|
||||||
|
onClick={handleButtonClick}
|
||||||
|
isRtl={lang.isRtl}
|
||||||
|
backgroundImage={coverBlobUrl}
|
||||||
|
nonInteractive={isOneTimeModalOrigin}
|
||||||
|
>
|
||||||
|
{!isOneTimeModalOrigin && <Icon name="play" />}
|
||||||
|
{!isOneTimeModalOrigin && <Icon name="pause" />}
|
||||||
|
{isOneTimeModalOrigin && (
|
||||||
|
<AnimatedIcon
|
||||||
|
className="flame"
|
||||||
|
tgsUrl={LOCAL_TGS_URLS.Flame}
|
||||||
|
nonInteractive
|
||||||
|
noLoop={false}
|
||||||
|
size={40}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</Button>
|
||||||
|
{hasTtl && !isOneTimeModalOrigin && (
|
||||||
|
<Icon name="view-once" />
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={fullClassName} dir={lang.isRtl ? 'rtl' : 'ltr'}>
|
<div className={fullClassName} dir={lang.isRtl ? 'rtl' : 'ltr'}>
|
||||||
{isSelectable && (
|
{isSelectable && (
|
||||||
@ -338,31 +410,30 @@ const Audio: FC<OwnProps> = ({
|
|||||||
{isSelected && <i className="icon icon-select" />}
|
{isSelected && <i className="icon icon-select" />}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<Button
|
{renderTooglePlayWrapper()}
|
||||||
round
|
|
||||||
ripple={!isMobile}
|
|
||||||
size="smaller"
|
|
||||||
color={coverBlobUrl ? 'translucent-white' : 'primary'}
|
|
||||||
className={buttonClassNames.join(' ')}
|
|
||||||
ariaLabel={isPlaying ? 'Pause audio' : 'Play audio'}
|
|
||||||
onClick={handleButtonClick}
|
|
||||||
isRtl={lang.isRtl}
|
|
||||||
backgroundImage={coverBlobUrl}
|
|
||||||
>
|
|
||||||
<i className="icon icon-play" />
|
|
||||||
<i className="icon icon-pause" />
|
|
||||||
</Button>
|
|
||||||
{shouldRenderSpinner && (
|
{shouldRenderSpinner && (
|
||||||
<div className={buildClassName('media-loading', spinnerClassNames, shouldRenderCross && 'interactive')}>
|
<div className={buildClassName('media-loading', spinnerClassNames, shouldRenderCross && 'interactive')}>
|
||||||
<ProgressSpinner
|
<ProgressSpinner
|
||||||
progress={transferProgress}
|
progress={transferProgress}
|
||||||
transparent
|
transparent
|
||||||
|
withColor
|
||||||
size="m"
|
size="m"
|
||||||
onClick={shouldRenderCross ? handleButtonClick : undefined}
|
onClick={shouldRenderCross ? handleButtonClick : undefined}
|
||||||
noCross={!shouldRenderCross}
|
noCross={!shouldRenderCross}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
{isOneTimeModalOrigin && !shouldRenderSpinner && (
|
||||||
|
<div className={buildClassName('media-loading')}>
|
||||||
|
<ProgressSpinner
|
||||||
|
progress={playProgress}
|
||||||
|
transparent
|
||||||
|
size="m"
|
||||||
|
noCross
|
||||||
|
rotationOffset={3 / 4}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
{audio && canDownload && !isUploading && (
|
{audio && canDownload && !isUploading && (
|
||||||
<Button
|
<Button
|
||||||
round
|
round
|
||||||
@ -389,12 +460,12 @@ const Audio: FC<OwnProps> = ({
|
|||||||
onDateClick ? handleDateClick : undefined,
|
onDateClick ? handleDateClick : undefined,
|
||||||
)}
|
)}
|
||||||
{origin === AudioOrigin.SharedMedia && (voice || video) && renderWithTitle()}
|
{origin === AudioOrigin.SharedMedia && (voice || video) && renderWithTitle()}
|
||||||
{origin === AudioOrigin.Inline && voice && (
|
{(origin === AudioOrigin.Inline || isOneTimeModalOrigin) && voice && (
|
||||||
renderVoice(
|
renderVoice(
|
||||||
voice,
|
voice,
|
||||||
seekerRef,
|
seekerRef,
|
||||||
waveformCanvasRef,
|
waveformCanvasRef,
|
||||||
playProgress,
|
hasTtl ? reversePlayProgress : playProgress,
|
||||||
isMediaUnread,
|
isMediaUnread,
|
||||||
isTranscribing,
|
isTranscribing,
|
||||||
isTranscriptionHidden,
|
isTranscriptionHidden,
|
||||||
@ -402,6 +473,7 @@ const Audio: FC<OwnProps> = ({
|
|||||||
isTranscriptionError,
|
isTranscriptionError,
|
||||||
canTranscribe ? handleTranscribe : undefined,
|
canTranscribe ? handleTranscribe : undefined,
|
||||||
onHideTranscription,
|
onHideTranscription,
|
||||||
|
origin,
|
||||||
)
|
)
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@ -489,6 +561,7 @@ function renderVoice(
|
|||||||
isTranscriptionError?: boolean,
|
isTranscriptionError?: boolean,
|
||||||
onClickTranscribe?: VoidFunction,
|
onClickTranscribe?: VoidFunction,
|
||||||
onHideTranscription?: (isHidden: boolean) => void,
|
onHideTranscription?: (isHidden: boolean) => void,
|
||||||
|
origin?: AudioOrigin,
|
||||||
) {
|
) {
|
||||||
return (
|
return (
|
||||||
<div className="content">
|
<div className="content">
|
||||||
@ -537,8 +610,12 @@ function renderVoice(
|
|||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<p className={buildClassName('voice-duration', isMediaUnread && 'unread')} dir="auto">
|
<p
|
||||||
{playProgress === 0 ? formatMediaDuration(voice.duration) : formatMediaDuration(voice.duration * playProgress)}
|
className={buildClassName('voice-duration', origin !== AudioOrigin.OneTimeModal && isMediaUnread && 'unread')}
|
||||||
|
dir="auto"
|
||||||
|
>
|
||||||
|
{playProgress === 0 || playProgress === 1
|
||||||
|
? formatMediaDuration(voice.duration) : formatMediaDuration(voice.duration * playProgress)}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@ -551,6 +628,7 @@ function useWaveformCanvas(
|
|||||||
isOwn = false,
|
isOwn = false,
|
||||||
withAvatar = false,
|
withAvatar = false,
|
||||||
isMobile = false,
|
isMobile = false,
|
||||||
|
isReverse = false,
|
||||||
) {
|
) {
|
||||||
// eslint-disable-next-line no-null/no-null
|
// eslint-disable-next-line no-null/no-null
|
||||||
const canvasRef = useRef<HTMLCanvasElement>(null);
|
const canvasRef = useRef<HTMLCanvasElement>(null);
|
||||||
@ -588,12 +666,15 @@ function useWaveformCanvas(
|
|||||||
const progressFillColor = theme === 'dark' ? '#8774E1' : '#3390EC';
|
const progressFillColor = theme === 'dark' ? '#8774E1' : '#3390EC';
|
||||||
const progressFillOwnColor = theme === 'dark' ? '#FFFFFF' : '#4FAE4E';
|
const progressFillOwnColor = theme === 'dark' ? '#FFFFFF' : '#4FAE4E';
|
||||||
|
|
||||||
renderWaveform(canvas, spikes, playProgress, {
|
const fillStyle = isOwn ? fillOwnColor : fillColor;
|
||||||
|
const progressFillStyle = isOwn ? progressFillOwnColor : progressFillColor;
|
||||||
|
|
||||||
|
renderWaveform(canvas, spikes, isReverse ? 1 - playProgress : playProgress, {
|
||||||
peak,
|
peak,
|
||||||
fillStyle: isOwn ? fillOwnColor : fillColor,
|
fillStyle,
|
||||||
progressFillStyle: isOwn ? progressFillOwnColor : progressFillColor,
|
progressFillStyle,
|
||||||
});
|
});
|
||||||
}, [isOwn, peak, playProgress, spikes, theme]);
|
}, [isOwn, peak, playProgress, spikes, theme, isReverse]);
|
||||||
|
|
||||||
return canvasRef;
|
return canvasRef;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -7,6 +7,7 @@ import VoiceAllowTalk from '../../../assets/tgs/calls/VoiceAllowTalk.tgs';
|
|||||||
import VoiceMini from '../../../assets/tgs/calls/VoiceMini.tgs';
|
import VoiceMini from '../../../assets/tgs/calls/VoiceMini.tgs';
|
||||||
import VoiceMuted from '../../../assets/tgs/calls/VoiceMuted.tgs';
|
import VoiceMuted from '../../../assets/tgs/calls/VoiceMuted.tgs';
|
||||||
import VoiceOutlined from '../../../assets/tgs/calls/VoiceOutlined.tgs';
|
import VoiceOutlined from '../../../assets/tgs/calls/VoiceOutlined.tgs';
|
||||||
|
import Flame from '../../../assets/tgs/general/Flame.tgs';
|
||||||
import PartyPopper from '../../../assets/tgs/general/PartyPopper.tgs';
|
import PartyPopper from '../../../assets/tgs/general/PartyPopper.tgs';
|
||||||
import Invite from '../../../assets/tgs/invites/Invite.tgs';
|
import Invite from '../../../assets/tgs/invites/Invite.tgs';
|
||||||
import JoinRequest from '../../../assets/tgs/invites/Requests.tgs';
|
import JoinRequest from '../../../assets/tgs/invites/Requests.tgs';
|
||||||
@ -46,4 +47,5 @@ export const LOCAL_TGS_URLS = {
|
|||||||
Congratulations,
|
Congratulations,
|
||||||
Experimental,
|
Experimental,
|
||||||
PartyPopper,
|
PartyPopper,
|
||||||
|
Flame,
|
||||||
};
|
};
|
||||||
|
|||||||
@ -9,8 +9,10 @@ import type { TextPart } from '../../../types';
|
|||||||
|
|
||||||
import {
|
import {
|
||||||
getChatTitle,
|
getChatTitle,
|
||||||
|
getExpiredMessageDescription,
|
||||||
getMessageSummaryText,
|
getMessageSummaryText,
|
||||||
getUserFullName,
|
getUserFullName,
|
||||||
|
isExpiredMessage,
|
||||||
} from '../../../global/helpers';
|
} from '../../../global/helpers';
|
||||||
import { formatCurrency } from '../../../util/formatCurrency';
|
import { formatCurrency } from '../../../util/formatCurrency';
|
||||||
import trimText from '../../../util/trimText';
|
import trimText from '../../../util/trimText';
|
||||||
@ -45,6 +47,10 @@ export function renderActionMessageText(
|
|||||||
observeIntersectionForLoading?: ObserveFn,
|
observeIntersectionForLoading?: ObserveFn,
|
||||||
observeIntersectionForPlaying?: ObserveFn,
|
observeIntersectionForPlaying?: ObserveFn,
|
||||||
) {
|
) {
|
||||||
|
if (isExpiredMessage(message)) {
|
||||||
|
return getExpiredMessageDescription(lang, message);
|
||||||
|
}
|
||||||
|
|
||||||
if (!message.content.action) {
|
if (!message.content.action) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|||||||
@ -13,6 +13,7 @@ import type { LangFn } from '../../../../hooks/useLang';
|
|||||||
import { ANIMATION_END_DELAY, CHAT_HEIGHT_PX } from '../../../../config';
|
import { ANIMATION_END_DELAY, CHAT_HEIGHT_PX } from '../../../../config';
|
||||||
import { requestMutation } from '../../../../lib/fasterdom/fasterdom';
|
import { requestMutation } from '../../../../lib/fasterdom/fasterdom';
|
||||||
import {
|
import {
|
||||||
|
getExpiredMessageDescription,
|
||||||
getMessageIsSpoiler,
|
getMessageIsSpoiler,
|
||||||
getMessageMediaHash,
|
getMessageMediaHash,
|
||||||
getMessageMediaThumbDataUri,
|
getMessageMediaThumbDataUri,
|
||||||
@ -22,6 +23,7 @@ import {
|
|||||||
getMessageVideo,
|
getMessageVideo,
|
||||||
isActionMessage,
|
isActionMessage,
|
||||||
isChatChannel,
|
isChatChannel,
|
||||||
|
isExpiredMessage,
|
||||||
} from '../../../../global/helpers';
|
} from '../../../../global/helpers';
|
||||||
import { getMessageReplyInfo } from '../../../../global/helpers/replies';
|
import { getMessageReplyInfo } from '../../../../global/helpers/replies';
|
||||||
import buildClassName from '../../../../util/buildClassName';
|
import buildClassName from '../../../../util/buildClassName';
|
||||||
@ -127,6 +129,14 @@ export default function useChatListEntry({
|
|||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (isExpiredMessage(lastMessage)) {
|
||||||
|
return (
|
||||||
|
<p className="last-message shared-canvas-container" dir={lang.isRtl ? 'auto' : 'ltr'}>
|
||||||
|
{getExpiredMessageDescription(lang, lastMessage)}
|
||||||
|
</p>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if (isAction) {
|
if (isAction) {
|
||||||
const isChat = chat && (isChatChannel(chat) || lastMessage.senderId === lastMessage.chatId);
|
const isChat = chat && (isChatChannel(chat) || lastMessage.senderId === lastMessage.chatId);
|
||||||
|
|
||||||
|
|||||||
@ -80,6 +80,7 @@ import BoostModal from '../modals/boost/BoostModal.async';
|
|||||||
import ChatlistModal from '../modals/chatlist/ChatlistModal.async';
|
import ChatlistModal from '../modals/chatlist/ChatlistModal.async';
|
||||||
import GiftCodeModal from '../modals/giftcode/GiftCodeModal.async';
|
import GiftCodeModal from '../modals/giftcode/GiftCodeModal.async';
|
||||||
import MapModal from '../modals/map/MapModal.async';
|
import MapModal from '../modals/map/MapModal.async';
|
||||||
|
import OneTimeMediaModal from '../modals/oneTimeMedia/OneTimeMediaModal.async';
|
||||||
import UrlAuthModal from '../modals/urlAuth/UrlAuthModal.async';
|
import UrlAuthModal from '../modals/urlAuth/UrlAuthModal.async';
|
||||||
import WebAppModal from '../modals/webApp/WebAppModal.async';
|
import WebAppModal from '../modals/webApp/WebAppModal.async';
|
||||||
import PaymentModal from '../payment/PaymentModal.async';
|
import PaymentModal from '../payment/PaymentModal.async';
|
||||||
@ -163,6 +164,7 @@ type StateProps = {
|
|||||||
withInterfaceAnimations?: boolean;
|
withInterfaceAnimations?: boolean;
|
||||||
isSynced?: boolean;
|
isSynced?: boolean;
|
||||||
inviteViaLinkModal?: TabState['inviteViaLinkModal'];
|
inviteViaLinkModal?: TabState['inviteViaLinkModal'];
|
||||||
|
oneTimeMediaModal?: TabState['oneTimeMediaModal'];
|
||||||
};
|
};
|
||||||
|
|
||||||
const APP_OUTDATED_TIMEOUT_MS = 5 * 60 * 1000; // 5 min
|
const APP_OUTDATED_TIMEOUT_MS = 5 * 60 * 1000; // 5 min
|
||||||
@ -224,6 +226,7 @@ const Main: FC<OwnProps & StateProps> = ({
|
|||||||
noRightColumnAnimation,
|
noRightColumnAnimation,
|
||||||
isSynced,
|
isSynced,
|
||||||
inviteViaLinkModal,
|
inviteViaLinkModal,
|
||||||
|
oneTimeMediaModal,
|
||||||
}) => {
|
}) => {
|
||||||
const {
|
const {
|
||||||
initMain,
|
initMain,
|
||||||
@ -568,6 +571,7 @@ const Main: FC<OwnProps & StateProps> = ({
|
|||||||
/>
|
/>
|
||||||
<BoostModal info={boostModal} />
|
<BoostModal info={boostModal} />
|
||||||
<GiftCodeModal modal={giftCodeModal} />
|
<GiftCodeModal modal={giftCodeModal} />
|
||||||
|
<OneTimeMediaModal info={oneTimeMediaModal} />
|
||||||
<ChatlistModal info={chatlistModal} />
|
<ChatlistModal info={chatlistModal} />
|
||||||
<GameModal openedGame={openedGame} gameTitle={gameTitle} />
|
<GameModal openedGame={openedGame} gameTitle={gameTitle} />
|
||||||
<WebAppModal webApp={webApp} />
|
<WebAppModal webApp={webApp} />
|
||||||
@ -635,6 +639,7 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
boostModal,
|
boostModal,
|
||||||
giftCodeModal,
|
giftCodeModal,
|
||||||
inviteViaLinkModal,
|
inviteViaLinkModal,
|
||||||
|
oneTimeMediaModal,
|
||||||
} = selectTabState(global);
|
} = selectTabState(global);
|
||||||
|
|
||||||
const { chatId: audioChatId, messageId: audioMessageId } = audioPlayer;
|
const { chatId: audioChatId, messageId: audioMessageId } = audioPlayer;
|
||||||
@ -703,6 +708,7 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
noRightColumnAnimation,
|
noRightColumnAnimation,
|
||||||
isSynced: global.isSynced,
|
isSynced: global.isSynced,
|
||||||
inviteViaLinkModal,
|
inviteViaLinkModal,
|
||||||
|
oneTimeMediaModal,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
)(Main));
|
)(Main));
|
||||||
|
|||||||
@ -12,7 +12,9 @@ import type { ObserveFn } from '../../hooks/useIntersectionObserver';
|
|||||||
import type { FocusDirection } from '../../types';
|
import type { FocusDirection } from '../../types';
|
||||||
import type { PinnedIntersectionChangedCallback } from './hooks/usePinnedMessage';
|
import type { PinnedIntersectionChangedCallback } from './hooks/usePinnedMessage';
|
||||||
|
|
||||||
import { getChatTitle, getMessageHtmlId, isChatChannel } from '../../global/helpers';
|
import {
|
||||||
|
getChatTitle, getMessageHtmlId, isChatChannel,
|
||||||
|
} from '../../global/helpers';
|
||||||
import { getMessageReplyInfo } from '../../global/helpers/replies';
|
import { getMessageReplyInfo } from '../../global/helpers/replies';
|
||||||
import {
|
import {
|
||||||
selectCanPlayAnimatedEmojis,
|
selectCanPlayAnimatedEmojis,
|
||||||
|
|||||||
@ -14,6 +14,7 @@ import { PREVIEW_AVATAR_COUNT, SERVICE_NOTIFICATIONS_USER_ID } from '../../../co
|
|||||||
import {
|
import {
|
||||||
areReactionsEmpty,
|
areReactionsEmpty,
|
||||||
getMessageVideo,
|
getMessageVideo,
|
||||||
|
hasMessageTtl,
|
||||||
isActionMessage,
|
isActionMessage,
|
||||||
isChatChannel,
|
isChatChannel,
|
||||||
isChatGroup,
|
isChatGroup,
|
||||||
@ -649,6 +650,7 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
const isScheduled = messageListType === 'scheduled';
|
const isScheduled = messageListType === 'scheduled';
|
||||||
const isChannel = chat && isChatChannel(chat);
|
const isChannel = chat && isChatChannel(chat);
|
||||||
const isLocal = isMessageLocal(message);
|
const isLocal = isMessageLocal(message);
|
||||||
|
const hasTtl = hasMessageTtl(message);
|
||||||
const canShowSeenBy = Boolean(!isLocal
|
const canShowSeenBy = Boolean(!isLocal
|
||||||
&& chat
|
&& chat
|
||||||
&& seenByMaxChatMembers
|
&& seenByMaxChatMembers
|
||||||
@ -695,7 +697,7 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
canForward: !isScheduled && canForward,
|
canForward: !isScheduled && canForward,
|
||||||
canFaveSticker: !isScheduled && canFaveSticker,
|
canFaveSticker: !isScheduled && canFaveSticker,
|
||||||
canUnfaveSticker: !isScheduled && canUnfaveSticker,
|
canUnfaveSticker: !isScheduled && canUnfaveSticker,
|
||||||
canCopy: canCopyNumber || (!isProtected && canCopy),
|
canCopy: (canCopyNumber || (!isProtected && canCopy)),
|
||||||
canCopyLink: !isScheduled && canCopyLink,
|
canCopyLink: !isScheduled && canCopyLink,
|
||||||
canSelect,
|
canSelect,
|
||||||
canDownload: !isProtected && canDownload,
|
canDownload: !isProtected && canDownload,
|
||||||
@ -710,7 +712,8 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
isCurrentUserPremium,
|
isCurrentUserPremium,
|
||||||
hasFullInfo: Boolean(chatFullInfo),
|
hasFullInfo: Boolean(chatFullInfo),
|
||||||
canShowReactionsCount,
|
canShowReactionsCount,
|
||||||
canShowReactionList: !isLocal && !isAction && !isScheduled && chat?.id !== SERVICE_NOTIFICATIONS_USER_ID,
|
canShowReactionList: !isLocal && !isAction
|
||||||
|
&& !isScheduled && chat?.id !== SERVICE_NOTIFICATIONS_USER_ID && !hasTtl,
|
||||||
canBuyPremium: !isCurrentUserPremium && !selectIsPremiumPurchaseBlocked(global),
|
canBuyPremium: !isCurrentUserPremium && !selectIsPremiumPurchaseBlocked(global),
|
||||||
customEmojiSetsInfo,
|
customEmojiSetsInfo,
|
||||||
customEmojiSets,
|
customEmojiSets,
|
||||||
|
|||||||
@ -40,6 +40,7 @@ import {
|
|||||||
getMessageSingleRegularEmoji,
|
getMessageSingleRegularEmoji,
|
||||||
getSenderTitle,
|
getSenderTitle,
|
||||||
hasMessageText,
|
hasMessageText,
|
||||||
|
hasMessageTtl,
|
||||||
isAnonymousOwnMessage,
|
isAnonymousOwnMessage,
|
||||||
isChatChannel,
|
isChatChannel,
|
||||||
isChatGroup,
|
isChatGroup,
|
||||||
@ -524,6 +525,7 @@ const Message: FC<OwnProps & StateProps> = ({
|
|||||||
const messageColorPeer = originSender || sender;
|
const messageColorPeer = originSender || sender;
|
||||||
const senderPeer = (forwardInfo || message.content.storyData) ? originSender : messageSender;
|
const senderPeer = (forwardInfo || message.content.storyData) ? originSender : messageSender;
|
||||||
const hasText = hasMessageText(message);
|
const hasText = hasMessageText(message);
|
||||||
|
const hasTtl = hasMessageTtl(message);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
handleMouseDown,
|
handleMouseDown,
|
||||||
@ -671,7 +673,7 @@ const Message: FC<OwnProps & StateProps> = ({
|
|||||||
&& !isInDocumentGroupNotLast && messageListType === 'thread'
|
&& !isInDocumentGroupNotLast && messageListType === 'thread'
|
||||||
&& !noComments;
|
&& !noComments;
|
||||||
const withQuickReactionButton = !isTouchScreen && !phoneCall && !isInSelectMode && defaultReaction
|
const withQuickReactionButton = !isTouchScreen && !phoneCall && !isInSelectMode && defaultReaction
|
||||||
&& !isInDocumentGroupNotLast && !isStoryMention;
|
&& !isInDocumentGroupNotLast && !isStoryMention && !hasTtl;
|
||||||
|
|
||||||
const contentClassName = buildContentClassName(message, {
|
const contentClassName = buildContentClassName(message, {
|
||||||
hasSubheader,
|
hasSubheader,
|
||||||
@ -1107,7 +1109,7 @@ const Message: FC<OwnProps & StateProps> = ({
|
|||||||
isSelected={isSelected}
|
isSelected={isSelected}
|
||||||
noAvatars={noAvatars}
|
noAvatars={noAvatars}
|
||||||
onPlay={handleAudioPlay}
|
onPlay={handleAudioPlay}
|
||||||
onReadMedia={voice && (!isOwn || isChatWithSelf) ? handleReadMedia : undefined}
|
onReadMedia={voice && (!isOwn || isChatWithSelf || (isOwn && !hasTtl)) ? handleReadMedia : undefined}
|
||||||
onCancelUpload={handleCancelUpload}
|
onCancelUpload={handleCancelUpload}
|
||||||
isDownloading={isDownloading}
|
isDownloading={isDownloading}
|
||||||
isTranscribing={isTranscribing}
|
isTranscribing={isTranscribing}
|
||||||
@ -1116,7 +1118,7 @@ const Message: FC<OwnProps & StateProps> = ({
|
|||||||
isTranscriptionError={isTranscriptionError}
|
isTranscriptionError={isTranscriptionError}
|
||||||
canDownload={!isProtected}
|
canDownload={!isProtected}
|
||||||
onHideTranscription={setTranscriptionHidden}
|
onHideTranscription={setTranscriptionHidden}
|
||||||
canTranscribe={isPremium}
|
canTranscribe={isPremium && !hasTtl}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{document && (
|
{document && (
|
||||||
|
|||||||
@ -0,0 +1,18 @@
|
|||||||
|
import type { FC } from '../../../lib/teact/teact';
|
||||||
|
import React from '../../../lib/teact/teact';
|
||||||
|
|
||||||
|
import type { OwnProps } from './OneTimeMediaModal';
|
||||||
|
|
||||||
|
import { Bundles } from '../../../util/moduleLoader';
|
||||||
|
|
||||||
|
import useModuleLoader from '../../../hooks/useModuleLoader';
|
||||||
|
|
||||||
|
const OneTimeMediaModalAsync: FC<OwnProps> = (props) => {
|
||||||
|
const { info } = props;
|
||||||
|
const OneTimeMediaModal = useModuleLoader(Bundles.Extra, 'OneTimeMediaModal', !info);
|
||||||
|
|
||||||
|
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||||
|
return OneTimeMediaModal ? <OneTimeMediaModal {...props} /> : undefined;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default OneTimeMediaModalAsync;
|
||||||
@ -0,0 +1,49 @@
|
|||||||
|
|
||||||
|
.root {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
flex-direction: column;
|
||||||
|
backdrop-filter: blur(2rem);
|
||||||
|
animation: fade-in-opacity 0.3s ease;
|
||||||
|
background-color: rgba(0, 0, 0, 0.25);
|
||||||
|
z-index: var(--z-modal-confirm);
|
||||||
|
align-items: center;
|
||||||
|
transition: opacity 0.3s ease;
|
||||||
|
|
||||||
|
&.closing {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.main {
|
||||||
|
background-color: var(--color-background);
|
||||||
|
padding: 0.6875rem;
|
||||||
|
border-radius: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.closeBtn {
|
||||||
|
margin: 0 auto;
|
||||||
|
width: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes fade-in-opacity {
|
||||||
|
0% {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
91
src/components/modals/oneTimeMedia/OneTimeMediaModal.tsx
Normal file
91
src/components/modals/oneTimeMedia/OneTimeMediaModal.tsx
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
import React, { memo } from '../../../lib/teact/teact';
|
||||||
|
import { getActions, getGlobal } from '../../../global';
|
||||||
|
|
||||||
|
import type { TabState } from '../../../global/types';
|
||||||
|
import { AudioOrigin } from '../../../types';
|
||||||
|
|
||||||
|
import { isOwnMessage } from '../../../global/helpers';
|
||||||
|
import { selectTheme } from '../../../global/selectors';
|
||||||
|
import buildClassName from '../../../util/buildClassName';
|
||||||
|
|
||||||
|
import useCurrentOrPrev from '../../../hooks/useCurrentOrPrev';
|
||||||
|
import useLang from '../../../hooks/useLang';
|
||||||
|
import useLastCallback from '../../../hooks/useLastCallback';
|
||||||
|
import useShowTransition from '../../../hooks/useShowTransition';
|
||||||
|
|
||||||
|
import Audio from '../../common/Audio';
|
||||||
|
import Button from '../../ui/Button';
|
||||||
|
|
||||||
|
import styles from './OneTimeMediaModal.module.scss';
|
||||||
|
|
||||||
|
export type OwnProps = {
|
||||||
|
info: TabState['oneTimeMediaModal'];
|
||||||
|
};
|
||||||
|
|
||||||
|
const OneTimeMediaModal = ({
|
||||||
|
info,
|
||||||
|
}: OwnProps) => {
|
||||||
|
const {
|
||||||
|
closeOneTimeMediaModal,
|
||||||
|
} = getActions();
|
||||||
|
|
||||||
|
const lang = useLang();
|
||||||
|
const message = useCurrentOrPrev(info?.message, true);
|
||||||
|
|
||||||
|
const {
|
||||||
|
shouldRender,
|
||||||
|
transitionClassNames,
|
||||||
|
} = useShowTransition(Boolean(info));
|
||||||
|
|
||||||
|
const handlePlayVoice = useLastCallback(() => {
|
||||||
|
return undefined;
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleClose = useLastCallback(() => {
|
||||||
|
closeOneTimeMediaModal();
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!shouldRender || !message) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
const isOwn = isOwnMessage(message);
|
||||||
|
const theme = selectTheme(getGlobal());
|
||||||
|
const closeBtnTitle = isOwn ? lang('Chat.Voice.Single.Close') : lang('Chat.Voice.Single.DeleteAndClose');
|
||||||
|
|
||||||
|
function renderMedia() {
|
||||||
|
if (message?.content?.voice) {
|
||||||
|
return (
|
||||||
|
<Audio
|
||||||
|
theme={theme}
|
||||||
|
message={message}
|
||||||
|
origin={AudioOrigin.OneTimeModal}
|
||||||
|
autoPlay
|
||||||
|
onPlay={handlePlayVoice}
|
||||||
|
onPause={handleClose}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={buildClassName(styles.root, transitionClassNames)}>
|
||||||
|
<div className={styles.main}>{renderMedia()}</div>
|
||||||
|
<div className={styles.footer}>
|
||||||
|
<Button
|
||||||
|
faded
|
||||||
|
onClick={handleClose}
|
||||||
|
pill
|
||||||
|
size="smaller"
|
||||||
|
color={theme === 'dark' ? 'dark' : 'secondary'}
|
||||||
|
className={styles.closeBtn}
|
||||||
|
>
|
||||||
|
{closeBtnTitle}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default memo(OneTimeMediaModal);
|
||||||
@ -1,11 +1,13 @@
|
|||||||
.ProgressSpinner {
|
.ProgressSpinner {
|
||||||
position: relative;
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
flex-direction: column;
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
width: 3.375rem;
|
width: 3.375rem;
|
||||||
height: 3.375rem;
|
height: 3.375rem;
|
||||||
background: rgba(0, 0, 0, 0.25)
|
background-color: rgba(0, 0, 0, 0.25);
|
||||||
url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTkiIGhlaWdodD0iMTkiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZD0iTTEwLjcxNyA5Ljc1TDE4LjMgMi4xNjdhLjY4NC42ODQgMCAxMC0uOTY3LS45NjdMOS43NSA4Ljc4MyAyLjE2NyAxLjJhLjY4NC42ODQgMCAxMC0uOTY3Ljk2N0w4Ljc4MyA5Ljc1IDEuMiAxNy4zMzNhLjY4NC42ODQgMCAxMC45NjcuOTY3bDcuNTgzLTcuNTgzIDcuNTgzIDcuNTgzYS42ODEuNjgxIDAgMDAuOTY3IDAgLjY4NC42ODQgMCAwMDAtLjk2N0wxMC43MTcgOS43NXoiIGZpbGw9IiNGRkYiIGZpbGwtcnVsZT0ibm9uemVybyIgc3Ryb2tlPSIjRkZGIiBzdHJva2Utd2lkdGg9Ii43NSIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIvPjwvc3ZnPg==)
|
|
||||||
no-repeat 49% 49%;
|
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
cursor: var(--custom-cursor, pointer);
|
cursor: var(--custom-cursor, pointer);
|
||||||
|
|
||||||
@ -14,6 +16,12 @@
|
|||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.icon-close {
|
||||||
|
position: absolute;
|
||||||
|
font-size: 1.625rem;
|
||||||
|
color: var(--color-white);
|
||||||
|
}
|
||||||
|
|
||||||
&.square {
|
&.square {
|
||||||
background-image: none;
|
background-image: none;
|
||||||
|
|
||||||
@ -34,10 +42,6 @@
|
|||||||
&.size-m {
|
&.size-m {
|
||||||
width: auto;
|
width: auto;
|
||||||
height: auto;
|
height: auto;
|
||||||
/* stylelint-disable-next-line scss/operator-no-unspaced */
|
|
||||||
background: transparent
|
|
||||||
url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTUiIGhlaWdodD0iMTUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZD0iTTguMjE4IDcuNWw1LjYzMy01LjYzM2EuNTA4LjUwOCAwIDEwLS43MTgtLjcxOEw3LjUgNi43ODIgMS44NjcgMS4xNDlhLjUwOC41MDggMCAxMC0uNzE4LjcxOEw2Ljc4MiA3LjVsLTUuNjMzIDUuNjMzYS41MDguNTA4IDAgMTAuNzE4LjcxOEw3LjUgOC4yMThsNS42MzMgNS42MzNhLjUwNi41MDYgMCAwMC43MTggMCAuNTA4LjUwOCAwIDAwMC0uNzE4TDguMjE4IDcuNXoiIGZpbGw9IiNGRkYiIGZpbGwtcnVsZT0ibm9uemVybyIgc3Ryb2tlPSIjRkZGIiBzdHJva2UtbGluZWpvaW49InJvdW5kIi8+PC9zdmc+)
|
|
||||||
no-repeat 49% 49%;
|
|
||||||
|
|
||||||
&.square {
|
&.square {
|
||||||
background-image: none;
|
background-image: none;
|
||||||
@ -62,5 +66,6 @@
|
|||||||
&_canvas {
|
&_canvas {
|
||||||
display: block;
|
display: block;
|
||||||
background-color: transparent !important;
|
background-color: transparent !important;
|
||||||
|
color: var(--color-white);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -5,9 +5,12 @@ import { requestMutation } from '../../lib/fasterdom/fasterdom';
|
|||||||
import { animate, timingFunctions } from '../../util/animation';
|
import { animate, timingFunctions } from '../../util/animation';
|
||||||
import buildClassName from '../../util/buildClassName';
|
import buildClassName from '../../util/buildClassName';
|
||||||
|
|
||||||
|
import useDynamicColorListener from '../../hooks/stickers/useDynamicColorListener';
|
||||||
import { useStateRef } from '../../hooks/useStateRef';
|
import { useStateRef } from '../../hooks/useStateRef';
|
||||||
import useDevicePixelRatio from '../../hooks/window/useDevicePixelRatio';
|
import useDevicePixelRatio from '../../hooks/window/useDevicePixelRatio';
|
||||||
|
|
||||||
|
import Icon from '../common/Icon';
|
||||||
|
|
||||||
import './ProgressSpinner.scss';
|
import './ProgressSpinner.scss';
|
||||||
|
|
||||||
const SIZES = {
|
const SIZES = {
|
||||||
@ -27,6 +30,8 @@ const ProgressSpinner: FC<{
|
|||||||
square?: boolean;
|
square?: boolean;
|
||||||
transparent?: boolean;
|
transparent?: boolean;
|
||||||
noCross?: boolean;
|
noCross?: boolean;
|
||||||
|
rotationOffset?: number;
|
||||||
|
withColor?: boolean;
|
||||||
onClick?: (e: React.MouseEvent<HTMLElement, MouseEvent>) => void;
|
onClick?: (e: React.MouseEvent<HTMLElement, MouseEvent>) => void;
|
||||||
}> = ({
|
}> = ({
|
||||||
progress = 0,
|
progress = 0,
|
||||||
@ -34,6 +39,8 @@ const ProgressSpinner: FC<{
|
|||||||
square,
|
square,
|
||||||
transparent,
|
transparent,
|
||||||
noCross,
|
noCross,
|
||||||
|
rotationOffset,
|
||||||
|
withColor,
|
||||||
onClick,
|
onClick,
|
||||||
}) => {
|
}) => {
|
||||||
// eslint-disable-next-line no-null/no-null
|
// eslint-disable-next-line no-null/no-null
|
||||||
@ -43,6 +50,8 @@ const ProgressSpinner: FC<{
|
|||||||
|
|
||||||
const dpr = useDevicePixelRatio();
|
const dpr = useDevicePixelRatio();
|
||||||
|
|
||||||
|
const color = useDynamicColorListener(canvasRef, !withColor);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
let isFirst = true;
|
let isFirst = true;
|
||||||
let growFrom = MIN_PROGRESS;
|
let growFrom = MIN_PROGRESS;
|
||||||
@ -69,17 +78,18 @@ const ProgressSpinner: FC<{
|
|||||||
canvasRef.current,
|
canvasRef.current,
|
||||||
width * dpr,
|
width * dpr,
|
||||||
(size === 'xl' ? STROKE_WIDTH_XL : STROKE_WIDTH) * dpr,
|
(size === 'xl' ? STROKE_WIDTH_XL : STROKE_WIDTH) * dpr,
|
||||||
'white',
|
color ?? 'white',
|
||||||
currentProgress,
|
currentProgress,
|
||||||
dpr,
|
dpr,
|
||||||
isFirst,
|
isFirst,
|
||||||
|
rotationOffset,
|
||||||
);
|
);
|
||||||
|
|
||||||
isFirst = false;
|
isFirst = false;
|
||||||
|
|
||||||
return currentProgress < 1;
|
return currentProgress < 1;
|
||||||
}, requestMutation);
|
}, requestMutation);
|
||||||
}, [progressRef, size, width, dpr]);
|
}, [progressRef, size, width, dpr, rotationOffset, color]);
|
||||||
|
|
||||||
const className = buildClassName(
|
const className = buildClassName(
|
||||||
`ProgressSpinner size-${size}`,
|
`ProgressSpinner size-${size}`,
|
||||||
@ -93,6 +103,7 @@ const ProgressSpinner: FC<{
|
|||||||
className={className}
|
className={className}
|
||||||
onClick={onClick}
|
onClick={onClick}
|
||||||
>
|
>
|
||||||
|
{!noCross && <Icon name="close" />}
|
||||||
<canvas ref={canvasRef} className="ProgressSpinner_canvas" style={`width: ${width}; height: ${width}px;`} />
|
<canvas ref={canvasRef} className="ProgressSpinner_canvas" style={`width: ${width}; height: ${width}px;`} />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@ -106,11 +117,12 @@ function drawSpinnerArc(
|
|||||||
progress: number,
|
progress: number,
|
||||||
dpr: number,
|
dpr: number,
|
||||||
shouldInit = false,
|
shouldInit = false,
|
||||||
|
rotationOffset?: number,
|
||||||
) {
|
) {
|
||||||
const centerCoordinate = size / 2;
|
const centerCoordinate = size / 2;
|
||||||
const radius = (size - strokeWidth) / 2 - PADDING * dpr;
|
const radius = (size - strokeWidth) / 2 - PADDING * dpr;
|
||||||
const rotationOffset = (Date.now() % ROTATE_DURATION) / ROTATE_DURATION;
|
const offset = rotationOffset ?? (Date.now() % ROTATE_DURATION) / ROTATE_DURATION;
|
||||||
const startAngle = (2 * Math.PI) * rotationOffset;
|
const startAngle = (2 * Math.PI) * offset;
|
||||||
const endAngle = startAngle + (2 * Math.PI) * progress;
|
const endAngle = startAngle + (2 * Math.PI) * progress;
|
||||||
const ctx = canvas.getContext('2d')!;
|
const ctx = canvas.getContext('2d')!;
|
||||||
|
|
||||||
|
|||||||
@ -52,7 +52,7 @@ export const CUSTOM_EMOJI_PREVIEW_CACHE_DISABLED = false;
|
|||||||
export const CUSTOM_EMOJI_PREVIEW_CACHE_NAME = 'tt-custom-emoji-preview';
|
export const CUSTOM_EMOJI_PREVIEW_CACHE_NAME = 'tt-custom-emoji-preview';
|
||||||
export const MEDIA_CACHE_MAX_BYTES = 512 * 1024; // 512 KB
|
export const MEDIA_CACHE_MAX_BYTES = 512 * 1024; // 512 KB
|
||||||
export const CUSTOM_BG_CACHE_NAME = 'tt-custom-bg';
|
export const CUSTOM_BG_CACHE_NAME = 'tt-custom-bg';
|
||||||
export const LANG_CACHE_NAME = 'tt-lang-packs-v28';
|
export const LANG_CACHE_NAME = 'tt-lang-packs-v30';
|
||||||
export const ASSET_CACHE_NAME = 'tt-assets';
|
export const ASSET_CACHE_NAME = 'tt-assets';
|
||||||
export const AUTODOWNLOAD_FILESIZE_MB_LIMITS = [1, 5, 10, 50, 100, 500];
|
export const AUTODOWNLOAD_FILESIZE_MB_LIMITS = [1, 5, 10, 50, 100, 500];
|
||||||
export const DATA_BROADCAST_CHANNEL_NAME = 'tt-global';
|
export const DATA_BROADCAST_CHANNEL_NAME = 'tt-global';
|
||||||
|
|||||||
@ -445,6 +445,10 @@ addActionHandler('apiUpdate', (global, actions, update): ActionReturnType => {
|
|||||||
const chatId = selectCommonBoxChatId(global, id);
|
const chatId = selectCommonBoxChatId(global, id);
|
||||||
if (chatId) {
|
if (chatId) {
|
||||||
global = updateChatMessage(global, chatId, id, messageUpdate);
|
global = updateChatMessage(global, chatId, id, messageUpdate);
|
||||||
|
const message = selectChatMessage(global, chatId, id);
|
||||||
|
if (message) {
|
||||||
|
global = updateChatLastMessage(global, chatId, message);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -813,6 +813,24 @@ addActionHandler('copyMessagesByIds', (global, actions, payload): ActionReturnTy
|
|||||||
copyTextForMessages(global, chat.id, messageIds);
|
copyTextForMessages(global, chat.id, messageIds);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
addActionHandler('openOneTimeMediaModal', (global, actions, payload): ActionReturnType => {
|
||||||
|
const { message, tabId = getCurrentTabId() } = payload;
|
||||||
|
global = updateTabState(global, {
|
||||||
|
oneTimeMediaModal: {
|
||||||
|
message,
|
||||||
|
},
|
||||||
|
}, tabId);
|
||||||
|
setGlobal(global);
|
||||||
|
});
|
||||||
|
|
||||||
|
addActionHandler('closeOneTimeMediaModal', (global, actions, payload): ActionReturnType => {
|
||||||
|
const { tabId = getCurrentTabId() } = payload || {};
|
||||||
|
global = updateTabState(global, {
|
||||||
|
oneTimeMediaModal: undefined,
|
||||||
|
}, tabId);
|
||||||
|
setGlobal(global);
|
||||||
|
});
|
||||||
|
|
||||||
function copyTextForMessages(global: GlobalState, chatId: string, messageIds: number[]) {
|
function copyTextForMessages(global: GlobalState, chatId: string, messageIds: number[]) {
|
||||||
const { type: messageListType, threadId } = selectCurrentMessageList(global) || {};
|
const { type: messageListType, threadId } = selectCurrentMessageList(global) || {};
|
||||||
const lang = langProvider.translate;
|
const lang = langProvider.translate;
|
||||||
|
|||||||
@ -7,7 +7,9 @@ import { ApiMessageEntityTypes } from '../../api/types';
|
|||||||
import { CONTENT_NOT_SUPPORTED } from '../../config';
|
import { CONTENT_NOT_SUPPORTED } from '../../config';
|
||||||
import trimText from '../../util/trimText';
|
import trimText from '../../util/trimText';
|
||||||
import { getGlobal } from '../index';
|
import { getGlobal } from '../index';
|
||||||
import { getMessageText, getMessageTranscription } from './messages';
|
import {
|
||||||
|
getExpiredMessageDescription, getMessageText, getMessageTranscription, isExpiredMessage,
|
||||||
|
} from './messages';
|
||||||
import { getUserFirstOrLastName } from './users';
|
import { getUserFirstOrLastName } from './users';
|
||||||
|
|
||||||
const SPOILER_CHARS = ['⠺', '⠵', '⠞', '⠟'];
|
const SPOILER_CHARS = ['⠺', '⠵', '⠞', '⠟'];
|
||||||
@ -213,6 +215,13 @@ export function getMessageSummaryDescription(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (isExpiredMessage(message)) {
|
||||||
|
const expiredMessageText = getExpiredMessageDescription(lang, message);
|
||||||
|
if (expiredMessageText) {
|
||||||
|
summary = expiredMessageText;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return summary || CONTENT_NOT_SUPPORTED;
|
return summary || CONTENT_NOT_SUPPORTED;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -55,12 +55,12 @@ export function getMessageTranscription(message: ApiMessage) {
|
|||||||
export function hasMessageText(message: ApiMessage | ApiStory) {
|
export function hasMessageText(message: ApiMessage | ApiStory) {
|
||||||
const {
|
const {
|
||||||
text, sticker, photo, video, audio, voice, document, poll, webPage, contact, invoice, location,
|
text, sticker, photo, video, audio, voice, document, poll, webPage, contact, invoice, location,
|
||||||
game, action, storyData, giveaway, giveawayResults,
|
game, action, storyData, giveaway, giveawayResults, isExpiredVoice,
|
||||||
} = message.content;
|
} = message.content;
|
||||||
|
|
||||||
return Boolean(text) || !(
|
return Boolean(text) || !(
|
||||||
sticker || photo || video || audio || voice || document || contact || poll || webPage || invoice || location
|
sticker || photo || video || audio || voice || document || contact || poll || webPage || invoice || location
|
||||||
|| game || action?.phoneCall || storyData || giveaway || giveawayResults
|
|| game || action?.phoneCall || storyData || giveaway || giveawayResults || isExpiredVoice
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -173,7 +173,7 @@ export function isForwardedMessage(message: ApiMessage) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function isActionMessage(message: ApiMessage) {
|
export function isActionMessage(message: ApiMessage) {
|
||||||
return Boolean(message.content.action);
|
return Boolean(message.content.action) || isExpiredMessage(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isServiceNotificationMessage(message: ApiMessage) {
|
export function isServiceNotificationMessage(message: ApiMessage) {
|
||||||
@ -338,3 +338,19 @@ export function extractMessageText(message: ApiMessage | ApiStory, inChatList =
|
|||||||
|
|
||||||
return { text, entities };
|
return { text, entities };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getExpiredMessageDescription(langFn: LangFn, message: ApiMessage): string | undefined {
|
||||||
|
const { isExpiredVoice } = message.content;
|
||||||
|
if (isExpiredVoice) {
|
||||||
|
return langFn('Message.VoiceMessageExpired');
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isExpiredMessage(message: ApiMessage) {
|
||||||
|
return Boolean(message.content?.isExpiredVoice);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function hasMessageTtl(message: ApiMessage) {
|
||||||
|
return message.content?.ttlSeconds !== undefined;
|
||||||
|
}
|
||||||
|
|||||||
@ -17,6 +17,7 @@ import {
|
|||||||
areSortedArraysEqual, excludeSortedArray, omit, pick, pickTruthy, unique,
|
areSortedArraysEqual, excludeSortedArray, omit, pick, pickTruthy, unique,
|
||||||
} from '../../util/iteratees';
|
} from '../../util/iteratees';
|
||||||
import {
|
import {
|
||||||
|
hasMessageTtl,
|
||||||
isLocalMessageId, mergeIdRanges, orderHistoryIds, orderPinnedIds,
|
isLocalMessageId, mergeIdRanges, orderHistoryIds, orderPinnedIds,
|
||||||
} from '../helpers';
|
} from '../helpers';
|
||||||
import {
|
import {
|
||||||
@ -198,6 +199,15 @@ export function updateChatMessage<T extends GlobalState>(
|
|||||||
): T {
|
): T {
|
||||||
const byId = selectChatMessages(global, chatId) || {};
|
const byId = selectChatMessages(global, chatId) || {};
|
||||||
const message = byId[messageId];
|
const message = byId[messageId];
|
||||||
|
if (message && messageUpdate.isMediaUnread === false && hasMessageTtl(message)) {
|
||||||
|
if (message.content.voice) {
|
||||||
|
messageUpdate.content = {
|
||||||
|
...messageUpdate.content,
|
||||||
|
voice: undefined,
|
||||||
|
isExpiredVoice: true,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
const updatedMessage = {
|
const updatedMessage = {
|
||||||
...message,
|
...message,
|
||||||
...messageUpdate,
|
...messageUpdate,
|
||||||
|
|||||||
@ -37,6 +37,7 @@ import {
|
|||||||
getMessageWebPagePhoto,
|
getMessageWebPagePhoto,
|
||||||
getMessageWebPageVideo,
|
getMessageWebPageVideo,
|
||||||
getSendingState,
|
getSendingState,
|
||||||
|
hasMessageTtl,
|
||||||
isActionMessage,
|
isActionMessage,
|
||||||
isChatBasicGroup,
|
isChatBasicGroup,
|
||||||
isChatChannel,
|
isChatChannel,
|
||||||
@ -534,6 +535,7 @@ export function selectAllowedMessageActions<T extends GlobalState>(global: T, me
|
|||||||
const isOwn = isOwnMessage(message);
|
const isOwn = isOwnMessage(message);
|
||||||
const isForwarded = isForwardedMessage(message);
|
const isForwarded = isForwardedMessage(message);
|
||||||
const isAction = isActionMessage(message);
|
const isAction = isActionMessage(message);
|
||||||
|
const hasTtl = hasMessageTtl(message);
|
||||||
const { content } = message;
|
const { content } = message;
|
||||||
const messageTopic = selectTopicFromMessage(global, message);
|
const messageTopic = selectTopicFromMessage(global, message);
|
||||||
|
|
||||||
@ -607,7 +609,7 @@ export function selectAllowedMessageActions<T extends GlobalState>(global: T, me
|
|||||||
const isStoryForwardForbidden = story && ('isDeleted' in story || ('noForwards' in story && story.noForwards));
|
const isStoryForwardForbidden = story && ('isDeleted' in story || ('noForwards' in story && story.noForwards));
|
||||||
const canForward = (
|
const canForward = (
|
||||||
!isLocal && !isAction && !isChatProtected && !isStoryForwardForbidden
|
!isLocal && !isAction && !isChatProtected && !isStoryForwardForbidden
|
||||||
&& (message.isForwardingAllowed || isServiceNotification)
|
&& (message.isForwardingAllowed || isServiceNotification) && !hasTtl
|
||||||
);
|
);
|
||||||
|
|
||||||
const hasSticker = Boolean(message.content.sticker);
|
const hasSticker = Boolean(message.content.sticker);
|
||||||
@ -619,7 +621,8 @@ export function selectAllowedMessageActions<T extends GlobalState>(global: T, me
|
|||||||
const canSelect = !isLocal && !isAction;
|
const canSelect = !isLocal && !isAction;
|
||||||
|
|
||||||
const canDownload = Boolean(content.webPage?.document || content.webPage?.video || content.webPage?.photo
|
const canDownload = Boolean(content.webPage?.document || content.webPage?.video || content.webPage?.photo
|
||||||
|| content.audio || content.voice || content.photo || content.video || content.document || content.sticker);
|
|| content.audio || content.voice || content.photo || content.video || content.document || content.sticker)
|
||||||
|
&& !hasTtl;
|
||||||
|
|
||||||
const canSaveGif = message.content.video?.isGif;
|
const canSaveGif = message.content.video?.isGif;
|
||||||
|
|
||||||
@ -1113,7 +1116,9 @@ export function selectLastServiceNotification<T extends GlobalState>(global: T)
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function selectIsMessageProtected<T extends GlobalState>(global: T, message?: ApiMessage) {
|
export function selectIsMessageProtected<T extends GlobalState>(global: T, message?: ApiMessage) {
|
||||||
return Boolean(message && (message.isProtected || selectIsChatProtected(global, message.chatId)));
|
return Boolean(message && (
|
||||||
|
message.isProtected || selectIsChatProtected(global, message.chatId) || hasMessageTtl(message)
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
export function selectIsChatProtected<T extends GlobalState>(global: T, chatId: string) {
|
export function selectIsChatProtected<T extends GlobalState>(global: T, chatId: string) {
|
||||||
@ -1147,7 +1152,8 @@ export function selectCanForwardMessages<T extends GlobalState>(global: T, chatI
|
|||||||
|
|
||||||
return messageIds
|
return messageIds
|
||||||
.map((id) => messages[id])
|
.map((id) => messages[id])
|
||||||
.every((message) => message.isForwardingAllowed || isServiceNotificationMessage(message));
|
.every((message) => !hasMessageTtl(message)
|
||||||
|
&& (message.isForwardingAllowed || isServiceNotificationMessage(message)));
|
||||||
}
|
}
|
||||||
|
|
||||||
export function selectSponsoredMessage<T extends GlobalState>(global: T, chatId: string) {
|
export function selectSponsoredMessage<T extends GlobalState>(global: T, chatId: string) {
|
||||||
|
|||||||
@ -674,6 +674,10 @@ export type TabState = {
|
|||||||
restrictedUserIds: string[];
|
restrictedUserIds: string[];
|
||||||
chatId: string;
|
chatId: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
oneTimeMediaModal?: {
|
||||||
|
message: ApiMessage;
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export type GlobalState = {
|
export type GlobalState = {
|
||||||
@ -2679,6 +2683,9 @@ export interface ActionPayloads {
|
|||||||
updatePageTitle: WithTabId | undefined;
|
updatePageTitle: WithTabId | undefined;
|
||||||
closeInviteViaLinkModal: WithTabId | undefined;
|
closeInviteViaLinkModal: WithTabId | undefined;
|
||||||
|
|
||||||
|
openOneTimeMediaModal: TabState['oneTimeMediaModal'] & WithTabId;
|
||||||
|
closeOneTimeMediaModal: WithTabId | undefined;
|
||||||
|
|
||||||
// Calls
|
// Calls
|
||||||
joinGroupCall: {
|
joinGroupCall: {
|
||||||
chatId?: string;
|
chatId?: string;
|
||||||
|
|||||||
@ -32,6 +32,8 @@ const useAudioPlayer = (
|
|||||||
onTrackChange?: NoneToVoidFunction,
|
onTrackChange?: NoneToVoidFunction,
|
||||||
noPlaylist = false,
|
noPlaylist = false,
|
||||||
noProgressUpdates = false,
|
noProgressUpdates = false,
|
||||||
|
onPause?: NoneToVoidFunction,
|
||||||
|
noReset = false,
|
||||||
) => {
|
) => {
|
||||||
// eslint-disable-next-line no-null/no-null
|
// eslint-disable-next-line no-null/no-null
|
||||||
const controllerRef = useRef<ReturnType<typeof register>>(null);
|
const controllerRef = useRef<ReturnType<typeof register>>(null);
|
||||||
@ -54,8 +56,9 @@ const useAudioPlayer = (
|
|||||||
setVolume, setPlaybackRate, toggleMuted, proxy,
|
setVolume, setPlaybackRate, toggleMuted, proxy,
|
||||||
} = controllerRef.current!;
|
} = controllerRef.current!;
|
||||||
setIsPlaying(true);
|
setIsPlaying(true);
|
||||||
|
if (trackType !== 'oneTimeVoice') {
|
||||||
registerMediaSession(metadata, makeMediaHandlers(controllerRef));
|
registerMediaSession(metadata, makeMediaHandlers(controllerRef));
|
||||||
|
}
|
||||||
setPlaybackState('playing');
|
setPlaybackState('playing');
|
||||||
const { audioPlayer } = selectTabState(getGlobal());
|
const { audioPlayer } = selectTabState(getGlobal());
|
||||||
setVolume(audioPlayer.volume);
|
setVolume(audioPlayer.volume);
|
||||||
@ -84,9 +87,13 @@ const useAudioPlayer = (
|
|||||||
case 'onPause':
|
case 'onPause':
|
||||||
setIsPlaying(false);
|
setIsPlaying(false);
|
||||||
setPlaybackState('paused');
|
setPlaybackState('paused');
|
||||||
|
onPause?.();
|
||||||
break;
|
break;
|
||||||
case 'onTimeUpdate': {
|
case 'onTimeUpdate': {
|
||||||
const { proxy } = controllerRef.current!;
|
const { proxy } = controllerRef.current!;
|
||||||
|
if (noReset && proxy.currentTime === 0) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
const duration = proxy.duration && Number.isFinite(proxy.duration) ? proxy.duration : originalDuration;
|
const duration = proxy.duration && Number.isFinite(proxy.duration) ? proxy.duration : originalDuration;
|
||||||
if (!noProgressUpdates) setPlayProgress(proxy.currentTime / duration);
|
if (!noProgressUpdates) setPlayProgress(proxy.currentTime / duration);
|
||||||
break;
|
break;
|
||||||
@ -137,10 +144,13 @@ const useAudioPlayer = (
|
|||||||
|
|
||||||
// RAF progress
|
// RAF progress
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
if (noReset && proxy.currentTime === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (duration && !isSafariPatchInProgress(proxy) && !noProgressUpdates) {
|
if (duration && !isSafariPatchInProgress(proxy) && !noProgressUpdates) {
|
||||||
setPlayProgress(proxy.currentTime / duration);
|
setPlayProgress(proxy.currentTime / duration);
|
||||||
}
|
}
|
||||||
}, [duration, playProgress, proxy, noProgressUpdates]);
|
}, [duration, playProgress, proxy, noProgressUpdates, noReset]);
|
||||||
|
|
||||||
// Cleanup
|
// Cleanup
|
||||||
useEffect(() => () => {
|
useEffect(() => () => {
|
||||||
@ -149,7 +159,7 @@ const useAudioPlayer = (
|
|||||||
|
|
||||||
// Autoplay once `src` is present
|
// Autoplay once `src` is present
|
||||||
useEffectWithPrevDeps(([prevShouldPlay, prevSrc]) => {
|
useEffectWithPrevDeps(([prevShouldPlay, prevSrc]) => {
|
||||||
if (prevShouldPlay === shouldPlay && src === prevSrc) {
|
if (prevShouldPlay === shouldPlay && src === prevSrc && trackType !== 'oneTimeVoice') {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -161,7 +171,7 @@ const useAudioPlayer = (
|
|||||||
if (shouldPlay && src && !isPlaying) {
|
if (shouldPlay && src && !isPlaying) {
|
||||||
play(src);
|
play(src);
|
||||||
}
|
}
|
||||||
}, [shouldPlay, src, isPlaying, play, proxy.src, proxy.paused]);
|
}, [shouldPlay, src, isPlaying, play, proxy.src, proxy.paused, trackType]);
|
||||||
|
|
||||||
const playIfPresent = useLastCallback(() => {
|
const playIfPresent = useLastCallback(() => {
|
||||||
if (src) {
|
if (src) {
|
||||||
|
|||||||
@ -241,15 +241,16 @@ $icons-map: (
|
|||||||
"video-outlined": "\f1d2",
|
"video-outlined": "\f1d2",
|
||||||
"video-stop": "\f1d3",
|
"video-stop": "\f1d3",
|
||||||
"video": "\f1d4",
|
"video": "\f1d4",
|
||||||
"voice-chat": "\f1d5",
|
"view-once": "\f1d5",
|
||||||
"volume-1": "\f1d6",
|
"voice-chat": "\f1d6",
|
||||||
"volume-2": "\f1d7",
|
"volume-1": "\f1d7",
|
||||||
"volume-3": "\f1d8",
|
"volume-2": "\f1d8",
|
||||||
"web": "\f1d9",
|
"volume-3": "\f1d9",
|
||||||
"webapp": "\f1da",
|
"web": "\f1da",
|
||||||
"word-wrap": "\f1db",
|
"webapp": "\f1db",
|
||||||
"zoom-in": "\f1dc",
|
"word-wrap": "\f1dc",
|
||||||
"zoom-out": "\f1dd",
|
"zoom-in": "\f1dd",
|
||||||
|
"zoom-out": "\f1de",
|
||||||
);
|
);
|
||||||
|
|
||||||
.icon-active-sessions::before {
|
.icon-active-sessions::before {
|
||||||
@ -888,6 +889,9 @@ $icons-map: (
|
|||||||
.icon-video::before {
|
.icon-video::before {
|
||||||
content: map.get($icons-map, "video");
|
content: map.get($icons-map, "video");
|
||||||
}
|
}
|
||||||
|
.icon-view-once::before {
|
||||||
|
content: map.get($icons-map, "view-once");
|
||||||
|
}
|
||||||
.icon-voice-chat::before {
|
.icon-voice-chat::before {
|
||||||
content: map.get($icons-map, "voice-chat");
|
content: map.get($icons-map, "voice-chat");
|
||||||
}
|
}
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
@ -211,6 +211,7 @@ export type FontIconName =
|
|||||||
| 'video-outlined'
|
| 'video-outlined'
|
||||||
| 'video-stop'
|
| 'video-stop'
|
||||||
| 'video'
|
| 'video'
|
||||||
|
| 'view-once'
|
||||||
| 'voice-chat'
|
| 'voice-chat'
|
||||||
| 'volume-1'
|
| 'volume-1'
|
||||||
| 'volume-2'
|
| 'volume-2'
|
||||||
|
|||||||
@ -310,6 +310,7 @@ export enum AudioOrigin {
|
|||||||
Inline,
|
Inline,
|
||||||
SharedMedia,
|
SharedMedia,
|
||||||
Search,
|
Search,
|
||||||
|
OneTimeModal,
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum ChatCreationProgress {
|
export enum ChatCreationProgress {
|
||||||
|
|||||||
@ -17,7 +17,7 @@ export type TrackId = `${MessageKey}-${number}`;
|
|||||||
export interface Track {
|
export interface Track {
|
||||||
audio: HTMLAudioElement;
|
audio: HTMLAudioElement;
|
||||||
proxy: HTMLAudioElement;
|
proxy: HTMLAudioElement;
|
||||||
type: 'voice' | 'audio';
|
type: 'voice' | 'audio' | 'oneTimeVoice';
|
||||||
handlers: Handler[];
|
handlers: Handler[];
|
||||||
onForcePlay?: NoneToVoidFunction;
|
onForcePlay?: NoneToVoidFunction;
|
||||||
onTrackChange?: NoneToVoidFunction;
|
onTrackChange?: NoneToVoidFunction;
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
import { DEBUG, DEBUG_ALERT_MSG } from '../config';
|
import { DEBUG, DEBUG_ALERT_MSG } from '../config';
|
||||||
import { isMasterTab } from './establishMultitabRole';
|
import { isMasterTab } from './establishMultitabRole';
|
||||||
|
import { throttle } from './schedulers';
|
||||||
|
|
||||||
let showError = true;
|
let showError = true;
|
||||||
let error: Error | undefined;
|
let error: Error | undefined;
|
||||||
@ -27,16 +28,20 @@ if (DEBUG) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const throttleError = throttle((err) => {
|
||||||
|
if (showError) {
|
||||||
|
// eslint-disable-next-line no-alert
|
||||||
|
window.alert(getErrorMessage(err));
|
||||||
|
} else {
|
||||||
|
error = err;
|
||||||
|
}
|
||||||
|
}, 1500);
|
||||||
|
|
||||||
export function handleError(err: Error) {
|
export function handleError(err: Error) {
|
||||||
// eslint-disable-next-line no-console
|
// eslint-disable-next-line no-console
|
||||||
console.error(err);
|
console.error(err);
|
||||||
if (DEBUG) {
|
if (DEBUG) {
|
||||||
if (showError) {
|
throttleError(err);
|
||||||
// eslint-disable-next-line no-alert
|
|
||||||
window.alert(getErrorMessage(err));
|
|
||||||
} else {
|
|
||||||
error = err;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user