diff --git a/src/components/middle/composer/Composer.tsx b/src/components/middle/composer/Composer.tsx
index b443a5be1..eccd5c060 100644
--- a/src/components/middle/composer/Composer.tsx
+++ b/src/components/middle/composer/Composer.tsx
@@ -1,5 +1,5 @@
import React, {
- memo, useEffect, useLayoutEffect, useMemo, useRef, useState,
+ memo, useEffect, useMemo, useRef, useState,
} from '../../../lib/teact/teact';
import { requestMeasure, requestNextMutation } from '../../../lib/fasterdom/fasterdom';
import { getActions, withGlobal } from '../../../global';
@@ -229,8 +229,6 @@ const SELECT_MODE_TRANSITION_MS = 200;
const MESSAGE_MAX_LENGTH = 4096;
const SENDING_ANIMATION_DURATION = 350;
const MOUNT_ANIMATION_DURATION = 430;
-// eslint-disable-next-line max-len
-const APPENDIX = '
';
const Composer: FC
= ({
isOnActiveTab,
@@ -314,8 +312,6 @@ const Composer: FC = ({
const lang = useLang();
- // eslint-disable-next-line no-null/no-null
- const appendixRef = useRef(null);
// eslint-disable-next-line no-null/no-null
const inputRef = useRef(null);
@@ -364,12 +360,6 @@ const Composer: FC = ({
shouldAnimateSendAsButtonRef.current = Boolean(chatId === prevChatId && sendAsPeerIds && !prevSendAsPeerIds);
}, [chatId, sendAsPeerIds]);
- useLayoutEffect(() => {
- if (!appendixRef.current) return;
-
- appendixRef.current.innerHTML = APPENDIX;
- }, []);
-
const [attachments, setAttachments] = useState([]);
const hasAttachments = Boolean(attachments.length);
const [nextText, setNextText] = useState(undefined);
@@ -1339,7 +1329,29 @@ const Composer: FC = ({
onClose={closeBotCommandTooltip}
/>
-
+
' };
-
type OwnProps = {
message: ApiMessage;
peer?: ApiUser | ApiChat;
@@ -102,28 +101,14 @@ const Location: FC = ({
const updateCountdown = useLastCallback((countdownEl: HTMLDivElement) => {
if (type !== 'geoLive') return;
- const radius = 12;
- const circumference = radius * 2 * Math.PI;
- const svgEl = countdownEl.lastElementChild;
- const timerEl = countdownEl.firstElementChild as SVGElement;
+ const svgEl = countdownEl.lastElementChild!;
+ const timerEl = countdownEl.firstElementChild!;
const timeLeft = message.date + location.period - getServerTime();
- const strokeDashOffset = (1 - timeLeft / location.period) * circumference;
+ const strokeDashOffset = (1 - timeLeft / location.period) * TIMER_CIRCUMFERENCE;
const text = formatCountdownShort(lang, timeLeft * 1000);
-
- if (!svgEl || !timerEl) {
- countdownEl.innerHTML = `
- ${text}
- `;
- } else {
- timerEl.textContent = text;
- svgEl.firstElementChild!.setAttribute('stroke-dashoffset', `-${strokeDashOffset}`);
- }
+ timerEl.textContent = text;
+ svgEl.firstElementChild!.setAttribute('stroke-dashoffset', `-${strokeDashOffset}`);
});
useLayoutEffect(() => {
@@ -180,7 +165,22 @@ const Location: FC = ({
{formatLastUpdated(lang, serverTime, message.editDate)}
- {!isExpired && }
+ {!isExpired && (
+
+
+
+
+ )}
);
}
@@ -207,7 +207,8 @@ const Location: FC = ({
);
if (type === 'geoLive') {
return (
-
+
+
{location.heading !== undefined && (
@@ -221,7 +222,8 @@ const Location: FC
= ({
const iconSrc = getVenueIconUrl(location.venueType);
if (iconSrc) {
return (
-
+
+
);
@@ -264,4 +266,15 @@ const Location: FC
= ({
);
};
+function PinSvg() {
+ return (
+
+ );
+}
+
export default memo(Location);
diff --git a/src/components/middle/message/Message.tsx b/src/components/middle/message/Message.tsx
index 8da2b99d0..a9969cb61 100644
--- a/src/components/middle/message/Message.tsx
+++ b/src/components/middle/message/Message.tsx
@@ -3,7 +3,6 @@ import React, {
memo,
useCallback,
useEffect,
- useLayoutEffect,
useMemo,
useRef,
useState,
@@ -275,10 +274,6 @@ type QuickReactionPosition =
| 'in-meta';
const NBSP = '\u00A0';
-// eslint-disable-next-line max-len
-const APPENDIX_OWN = { __html: '' };
-// eslint-disable-next-line max-len
-const APPENDIX_NOT_OWN = { __html: '' };
const APPEARANCE_DELAY = 10;
const NO_MEDIA_CORNERS_THRESHOLD = 18;
const QUICK_REACTION_SIZE = 1.75 * REM;
@@ -287,9 +282,6 @@ const BOTTOM_FOCUS_SCROLL_THRESHOLD = 5;
const THROTTLE_MS = 300;
const RESIZE_ANIMATION_DURATION = 400;
-let appendixOwnCloned: SVGElement;
-let appendixNotOwnCloned: SVGElement;
-
const Message: FC = ({
message,
chatUsernames,
@@ -389,8 +381,6 @@ const Message: FC = ({
const bottomMarkerRef = useRef(null);
// eslint-disable-next-line no-null/no-null
const quickReactionRef = useRef(null);
- // eslint-disable-next-line no-null/no-null
- const appendixRef = useRef(null);
const messageHeightRef = useRef(0);
@@ -726,30 +716,6 @@ const Message: FC = ({
}
}, [hasUnreadReaction, messageId, animateUnreadReaction]);
- useLayoutEffect(() => {
- if (!withAppendix) return;
-
- const appendixEl = appendixRef.current!;
- const cloned = isOwn ? appendixOwnCloned : appendixNotOwnCloned;
- // eslint-disable-next-line no-underscore-dangle
- const html = isOwn ? APPENDIX_OWN.__html : APPENDIX_NOT_OWN.__html;
- let nextCloned: SVGElement;
-
- if (cloned) {
- nextCloned = cloned.cloneNode(true) as SVGElement;
- appendixEl.appendChild(cloned);
- } else {
- appendixEl.innerHTML = html;
- nextCloned = appendixEl.firstChild!.cloneNode(true) as SVGElement;
- }
-
- if (isOwn) {
- appendixOwnCloned = nextCloned;
- } else {
- appendixNotOwnCloned = nextCloned;
- }
- }, [isOwn, withAppendix]);
-
let style = '';
let calculatedWidth;
let reactionsMaxWidth;
@@ -1325,9 +1291,7 @@ const Message: FC = ({
) : undefined}
{withCommentButton && }
- {withAppendix && (
-
- )}
+ {withAppendix && }
{withQuickReactionButton && quickReactionPosition === 'in-content' && renderQuickReactionButton()}
{message.inlineButtons && (
@@ -1367,6 +1331,30 @@ const Message: FC = ({
);
};
+function MessageAppendix({ isOwn } : { isOwn: boolean }) {
+ const path = isOwn
+ ? 'M6 17H0V0c.193 2.84.876 5.767 2.05 8.782.904 2.325 2.446 4.485 4.625 6.48A1 1 0 016 17z'
+ : 'M3 17h6V0c-.193 2.84-.876 5.767-2.05 8.782-.904 2.325-2.446 4.485-4.625 6.48A1 1 0 003 17z';
+ return (
+
+ );
+}
+
export default memo(withGlobal(
(global, ownProps): StateProps => {
const {
diff --git a/src/components/middle/message/Poll.tsx b/src/components/middle/message/Poll.tsx
index 94bdc238c..fccd0d9d1 100644
--- a/src/components/middle/message/Poll.tsx
+++ b/src/components/middle/message/Poll.tsx
@@ -18,7 +18,7 @@ import { renderTextWithEntities } from '../../common/helpers/renderTextWithEntit
import { formatMediaDuration } from '../../../util/dateFormat';
import type { LangFn } from '../../../hooks/useLang';
import useLang from '../../../hooks/useLang';
-import { getServerTimeOffset } from '../../../util/serverTime';
+import { getServerTime } from '../../../util/serverTime';
import useLastCallback from '../../../hooks/useLastCallback';
@@ -44,6 +44,9 @@ type StateProps = {
const SOLUTION_CONTAINER_ID = '#middle-column-portals';
const SOLUTION_DURATION = 5000;
+const TIMER_RADIUS = 6;
+const TIMER_CIRCUMFERENCE = TIMER_RADIUS * 2 * Math.PI;
+const TIMER_UPDATE_INTERVAL = 1000;
const NBSP = '\u00A0';
const Poll: FC = ({
@@ -63,29 +66,27 @@ const Poll: FC = ({
const [wasSubmitted, setWasSubmitted] = useState(false);
const [closePeriod, setClosePeriod] = useState(
!summary.closed && summary.closeDate && summary.closeDate > 0
- ? Math.min(summary.closeDate - Math.floor(Date.now() / 1000) + getServerTimeOffset(), summary.closePeriod!)
+ ? Math.min(summary.closeDate - getServerTime(), summary.closePeriod!)
: 0,
);
// eslint-disable-next-line no-null/no-null
const countdownRef = useRef(null);
+ // eslint-disable-next-line no-null/no-null
+ const timerCircleRef = useRef(null);
const { results: voteResults, totalVoters } = results;
const hasVoted = voteResults && voteResults.some((r) => r.isChosen);
const canVote = !summary.closed && !hasVoted;
const canViewResult = !canVote && summary.isPublic && Number(results.totalVoters) > 0;
const isMultiple = canVote && summary.multipleChoice;
const maxVotersCount = voteResults ? Math.max(...voteResults.map((r) => r.votersCount)) : totalVoters;
- const correctResults = voteResults ? voteResults.reduce((answers: string[], r) => {
- if (r.isCorrect) {
- answers.push(r.option);
- }
-
- return answers;
- }, []) : [];
- const answers = summary.answers.map((a) => ({
+ const correctResults = useMemo(() => {
+ return voteResults?.filter((r) => r.isCorrect).map((r) => r.option) || [];
+ }, [voteResults]);
+ const answers = useMemo(() => summary.answers.map((a) => ({
label: a.text,
value: a.option,
hidden: Boolean(summary.quiz && summary.closePeriod && closePeriod <= 0),
- }));
+ })), [closePeriod, summary]);
useEffect(() => {
const chosen = poll.results.results?.find((result) => result.isChosen);
@@ -99,34 +100,16 @@ const Poll: FC = ({
useLayoutEffect(() => {
if (closePeriod > 0) {
- setTimeout(() => setClosePeriod(closePeriod - 1), 1000);
+ setTimeout(() => setClosePeriod(closePeriod - 1), TIMER_UPDATE_INTERVAL);
+ }
+ if (!timerCircleRef.current) return;
+
+ if (closePeriod <= 5) {
+ countdownRef.current!.classList.add('hurry-up');
}
- const countdownEl = countdownRef.current;
-
- if (countdownEl) {
- const circumference = 6 * 2 * Math.PI;
- const svgEl = countdownEl.lastElementChild;
- const timerEl = countdownEl.firstElementChild;
- if (closePeriod <= 5) {
- countdownEl.classList.add('hurry-up');
- }
-
- if (!svgEl || !timerEl) {
- countdownEl.innerHTML = `
- ${formatMediaDuration(closePeriod)}
- `;
- } else {
- const strokeDashOffset = ((summary.closePeriod! - closePeriod) / summary.closePeriod!) * circumference;
- timerEl.textContent = formatMediaDuration(closePeriod);
- (svgEl.firstElementChild as SVGElement).setAttribute('stroke-dashoffset', `-${strokeDashOffset}`);
- }
- }
+ const strokeDashOffset = ((summary.closePeriod! - closePeriod) / summary.closePeriod!) * TIMER_CIRCUMFERENCE;
+ timerCircleRef.current.setAttribute('stroke-dashoffset', `-${strokeDashOffset}`);
}, [closePeriod, summary.closePeriod]);
useEffect(() => {
@@ -255,7 +238,23 @@ const Poll: FC = ({
{lang(getPollTypeString(summary))}
{renderRecentVoters()}
- {closePeriod > 0 && canVote &&
}
+ {closePeriod > 0 && canVote && (
+
+ {formatMediaDuration(closePeriod)}
+
+
+ )}
{summary.quiz && poll.results.solution && !canVote && (
-
+ {shouldAnimate && (
+
+ )}
= ({
// eslint-disable-next-line no-null/no-null
const ref = useRef
(null);
// eslint-disable-next-line no-null/no-null
- const playingProgressRef = useRef(null);
- // eslint-disable-next-line no-null/no-null
const playerRef = useRef(null);
+ // eslint-disable-next-line no-null/no-null
+ const circleRef = useRef(null);
const video = message.content.video!;
@@ -106,29 +108,12 @@ const RoundVideo: FC = ({
}, [setProgress, isActivated, getThrottledProgress]);
useLayoutEffect(() => {
- if (!isActivated) {
+ if (!isActivated || !circleRef.current) {
return;
}
- const svgCenter = ROUND_VIDEO_DIMENSIONS_PX / 2;
- const svgMargin = 6;
- const circumference = (svgCenter - svgMargin) * 2 * Math.PI;
- const strokeDashOffset = circumference - getThrottledProgress() * circumference;
- const playingProgressEl = playingProgressRef.current!;
- const svgEl = playingProgressEl.firstElementChild;
-
- if (!svgEl) {
- playingProgressEl.innerHTML = `
- `;
- } else {
- (svgEl.firstElementChild as SVGElement).setAttribute('stroke-dashoffset', strokeDashOffset.toString());
- }
+ const strokeDashOffset = PROGRESS_CIRCUMFERENCE - getThrottledProgress() * PROGRESS_CIRCUMFERENCE;
+ circleRef.current.setAttribute('stroke-dashoffset', strokeDashOffset.toString());
}, [isActivated, getThrottledProgress]);
const shouldPlay = Boolean(mediaData && isIntersecting);
@@ -141,10 +126,6 @@ const RoundVideo: FC = ({
setIsActivated(false);
setProgress(0);
safePlay(playerRef.current);
-
- requestMutation(() => {
- playingProgressRef.current!.innerHTML = '';
- });
});
const capturePlaying = useLastCallback(() => {
@@ -221,7 +202,22 @@ const RoundVideo: FC = ({
className={buildClassName('thumbnail', thumbClassNames)}
style={`width: ${ROUND_VIDEO_DIMENSIONS_PX}px; height: ${ROUND_VIDEO_DIMENSIONS_PX}px`}
/>
-
+
+ {isActivated && (
+
+ )}
+
{shouldSpinnerRender && (
diff --git a/src/components/ui/ProgressSpinner.tsx b/src/components/ui/ProgressSpinner.tsx
index 9c4a3a51d..cdc27e4e7 100644
--- a/src/components/ui/ProgressSpinner.tsx
+++ b/src/components/ui/ProgressSpinner.tsx
@@ -1,5 +1,5 @@
+import React, { memo } from '../../lib/teact/teact';
import type { FC } from '../../lib/teact/teact';
-import React, { useRef, memo, useLayoutEffect } from '../../lib/teact/teact';
import buildClassName from '../../util/buildClassName';
@@ -31,36 +31,7 @@ const ProgressSpinner: FC<{
const circleRadius = radius - STROKE_WIDTH * 2;
const borderRadius = radius - 1;
const circumference = circleRadius * 2 * Math.PI;
- // eslint-disable-next-line no-null/no-null
- const containerRef = useRef
(null);
-
- useLayoutEffect(() => {
- const container = containerRef.current!;
- const svg = container.firstElementChild;
- const strokeDashOffset = circumference - Math.min(Math.max(MIN_PROGRESS, progress), MAX_PROGRESS) * circumference;
-
- if (!svg) {
- container.innerHTML = ``;
- } else {
- (svg.firstElementChild as SVGElement).setAttribute('stroke-dashoffset', strokeDashOffset.toString());
- }
- }, [containerRef, circumference, borderRadius, circleRadius, progress]);
+ const strokeDashOffset = circumference - Math.min(Math.max(MIN_PROGRESS, progress), MAX_PROGRESS) * circumference;
const className = buildClassName(
`ProgressSpinner size-${size}`,
@@ -71,10 +42,27 @@ const ProgressSpinner: FC<{
return (
+ >
+
+
);
};