SVG: Use Teact rendering (#3585)
This commit is contained in:
parent
46d9278900
commit
7a5088e11c
@ -30,7 +30,8 @@
|
|||||||
"error",
|
"error",
|
||||||
{
|
{
|
||||||
"code": 120,
|
"code": 120,
|
||||||
"ignoreComments": true
|
"ignoreComments": true,
|
||||||
|
"ignorePattern": "\\sd=\".+\"" // Ignore lines with "d" attribute
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"array-bracket-newline": [
|
"array-bracket-newline": [
|
||||||
|
|||||||
@ -73,8 +73,6 @@ export const WITH_AVATAR_TINY_SCREEN_WIDTH_MQL = window.matchMedia('(max-width:
|
|||||||
const AVG_VOICE_DURATION = 10;
|
const AVG_VOICE_DURATION = 10;
|
||||||
// This is needed for browsers requiring user interaction before playing.
|
// This is needed for browsers requiring user interaction before playing.
|
||||||
const PRELOAD = true;
|
const PRELOAD = true;
|
||||||
// eslint-disable-next-line max-len
|
|
||||||
const TRANSCRIBE_SVG = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 24" class="loading-svg"><rect class="loading-rect" fill="transparent" width="32" height="24" stroke-width="3" stroke-linejoin="round" rx="6" ry="6" stroke="var(--accent-color)" stroke-dashoffset="1" stroke-dasharray="32,68"></rect></svg>';
|
|
||||||
|
|
||||||
const Audio: FC<OwnProps> = ({
|
const Audio: FC<OwnProps> = ({
|
||||||
theme,
|
theme,
|
||||||
@ -252,10 +250,6 @@ const Audio: FC<OwnProps> = ({
|
|||||||
});
|
});
|
||||||
}, [withSeekline, handleStartSeek, handleSeek, handleStopSeek]);
|
}, [withSeekline, handleStartSeek, handleSeek, handleStopSeek]);
|
||||||
|
|
||||||
const transcribeSvgMemo = useMemo(() => (
|
|
||||||
<div dangerouslySetInnerHTML={{ __html: TRANSCRIBE_SVG }} />
|
|
||||||
), []);
|
|
||||||
|
|
||||||
function renderFirstLine() {
|
function renderFirstLine() {
|
||||||
if (isVoice) {
|
if (isVoice) {
|
||||||
return senderTitle || 'Voice';
|
return senderTitle || 'Voice';
|
||||||
@ -406,7 +400,6 @@ const Audio: FC<OwnProps> = ({
|
|||||||
isTranscriptionHidden,
|
isTranscriptionHidden,
|
||||||
isTranscribed,
|
isTranscribed,
|
||||||
isTranscriptionError,
|
isTranscriptionError,
|
||||||
transcribeSvgMemo,
|
|
||||||
canTranscribe ? handleTranscribe : undefined,
|
canTranscribe ? handleTranscribe : undefined,
|
||||||
onHideTranscription,
|
onHideTranscription,
|
||||||
)
|
)
|
||||||
@ -494,7 +487,6 @@ function renderVoice(
|
|||||||
isTranscriptionHidden?: boolean,
|
isTranscriptionHidden?: boolean,
|
||||||
isTranscribed?: boolean,
|
isTranscribed?: boolean,
|
||||||
isTranscriptionError?: boolean,
|
isTranscriptionError?: boolean,
|
||||||
svgMemo?: React.ReactNode,
|
|
||||||
onClickTranscribe?: VoidFunction,
|
onClickTranscribe?: VoidFunction,
|
||||||
onHideTranscription?: (isHidden: boolean) => void,
|
onHideTranscription?: (isHidden: boolean) => void,
|
||||||
) {
|
) {
|
||||||
@ -525,7 +517,23 @@ function renderVoice(
|
|||||||
(isTranscribed || isTranscriptionError) && !isTranscriptionHidden && 'transcribe-shown',
|
(isTranscribed || isTranscriptionError) && !isTranscriptionHidden && 'transcribe-shown',
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
{isTranscribing && svgMemo}
|
{isTranscribing && (
|
||||||
|
<svg viewBox="0 0 32 24" className="loading-svg">
|
||||||
|
<rect
|
||||||
|
className="loading-rect"
|
||||||
|
fill="transparent"
|
||||||
|
width="32"
|
||||||
|
height="24"
|
||||||
|
stroke-width="3"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
rx="6"
|
||||||
|
ry="6"
|
||||||
|
stroke="var(--accent-color)"
|
||||||
|
stroke-dashoffset="1"
|
||||||
|
stroke-dasharray="32,68"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
)}
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -1,14 +1,11 @@
|
|||||||
import type { FC } from '../../lib/teact/teact';
|
import type { FC } from '../../lib/teact/teact';
|
||||||
import React, { memo, useMemo } from '../../lib/teact/teact';
|
import React, { memo } from '../../lib/teact/teact';
|
||||||
|
|
||||||
import generateUniqueId from '../../util/generateUniqueId';
|
|
||||||
|
|
||||||
import buildClassName from '../../util/buildClassName';
|
import buildClassName from '../../util/buildClassName';
|
||||||
|
|
||||||
import './PremiumIcon.scss';
|
import useUniqueId from '../../hooks/useUniqueId';
|
||||||
|
|
||||||
// eslint-disable-next-line max-len
|
import './PremiumIcon.scss';
|
||||||
const PREMIUM_ICON = { __html: '<svg width="14" height="15" viewBox="0 0 14 15" fill="none" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" clip-rule="evenodd" d="M6.63869 12.1902L3.50621 14.1092C3.18049 14.3087 2.75468 14.2064 2.55515 13.8807C2.45769 13.7216 2.42864 13.5299 2.47457 13.3491L2.95948 11.4405C3.13452 10.7515 3.60599 10.1756 4.24682 9.86791L7.6642 8.22716C7.82352 8.15067 7.89067 7.95951 7.81418 7.80019C7.75223 7.67116 7.61214 7.59896 7.47111 7.62338L3.66713 8.28194C2.89387 8.41581 2.1009 8.20228 1.49941 7.69823L0.297703 6.69116C0.00493565 6.44581 -0.0335059 6.00958 0.211842 5.71682C0.33117 5.57442 0.502766 5.48602 0.687982 5.47153L4.35956 5.18419C4.61895 5.16389 4.845 4.99974 4.94458 4.75937L6.36101 1.3402C6.5072 0.987302 6.91179 0.819734 7.26469 0.965925C7.43413 1.03612 7.56876 1.17075 7.63896 1.3402L9.05539 4.75937C9.15496 4.99974 9.38101 5.16389 9.6404 5.18419L13.3322 5.47311C13.713 5.50291 13.9975 5.83578 13.9677 6.2166C13.9534 6.39979 13.8667 6.56975 13.7269 6.68896L10.9114 9.08928C10.7131 9.25826 10.6267 9.52425 10.6876 9.77748L11.5532 13.3733C11.6426 13.7447 11.414 14.1182 11.0427 14.2076C10.8642 14.2506 10.676 14.2208 10.5195 14.1249L7.36128 12.1902C7.13956 12.0544 6.8604 12.0544 6.63869 12.1902Z" fill="var(--color-fill)"/></svg>' };
|
|
||||||
|
|
||||||
type OwnProps = {
|
type OwnProps = {
|
||||||
withGradient?: boolean;
|
withGradient?: boolean;
|
||||||
@ -17,15 +14,16 @@ type OwnProps = {
|
|||||||
onClick?: VoidFunction;
|
onClick?: VoidFunction;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// eslint-disable-next-line max-len
|
||||||
|
const STAR_PATH = 'M6.63869 12.1902L3.50621 14.1092C3.18049 14.3087 2.75468 14.2064 2.55515 13.8807C2.45769 13.7216 2.42864 13.5299 2.47457 13.3491L2.95948 11.4405C3.13452 10.7515 3.60599 10.1756 4.24682 9.86791L7.6642 8.22716C7.82352 8.15067 7.89067 7.95951 7.81418 7.80019C7.75223 7.67116 7.61214 7.59896 7.47111 7.62338L3.66713 8.28194C2.89387 8.41581 2.1009 8.20228 1.49941 7.69823L0.297703 6.69116C0.00493565 6.44581 -0.0335059 6.00958 0.211842 5.71682C0.33117 5.57442 0.502766 5.48602 0.687982 5.47153L4.35956 5.18419C4.61895 5.16389 4.845 4.99974 4.94458 4.75937L6.36101 1.3402C6.5072 0.987302 6.91179 0.819734 7.26469 0.965925C7.43413 1.03612 7.56876 1.17075 7.63896 1.3402L9.05539 4.75937C9.15496 4.99974 9.38101 5.16389 9.6404 5.18419L13.3322 5.47311C13.713 5.50291 13.9975 5.83578 13.9677 6.2166C13.9534 6.39979 13.8667 6.56975 13.7269 6.68896L10.9114 9.08928C10.7131 9.25826 10.6267 9.52425 10.6876 9.77748L11.5532 13.3733C11.6426 13.7447 11.414 14.1182 11.0427 14.2076C10.8642 14.2506 10.676 14.2208 10.5195 14.1249L7.36128 12.1902C7.13956 12.0544 6.8604 12.0544 6.63869 12.1902Z';
|
||||||
|
|
||||||
const PremiumIcon: FC<OwnProps> = ({
|
const PremiumIcon: FC<OwnProps> = ({
|
||||||
withGradient,
|
withGradient,
|
||||||
big,
|
big,
|
||||||
className,
|
className,
|
||||||
onClick,
|
onClick,
|
||||||
}) => {
|
}) => {
|
||||||
const html = useMemo(() => {
|
const randomId = useUniqueId();
|
||||||
return withGradient ? getPremiumIconGradient() : PREMIUM_ICON;
|
|
||||||
}, [withGradient]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<i
|
<i
|
||||||
@ -33,18 +31,26 @@ const PremiumIcon: FC<OwnProps> = ({
|
|||||||
className={buildClassName(
|
className={buildClassName(
|
||||||
'PremiumIcon', className, withGradient && 'gradient', onClick && 'clickable', big && 'big',
|
'PremiumIcon', className, withGradient && 'gradient', onClick && 'clickable', big && 'big',
|
||||||
)}
|
)}
|
||||||
dangerouslySetInnerHTML={html}
|
|
||||||
title="Premium"
|
title="Premium"
|
||||||
/>
|
>
|
||||||
|
{withGradient ? (
|
||||||
|
<svg width="14" height="15" viewBox="0 0 14 15" fill="none">
|
||||||
|
<defs>
|
||||||
|
<linearGradient id={randomId} x1="3" y1="63.5001" x2="84.1475" y2="-1.32262" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop stop-color="#6B93FF" />
|
||||||
|
<stop offset="0.439058" stop-color="#976FFF" />
|
||||||
|
<stop offset="1" stop-color="#E46ACE" />
|
||||||
|
</linearGradient>
|
||||||
|
</defs>
|
||||||
|
<path fill-rule="evenodd" clip-rule="evenodd" d={STAR_PATH} fill={`url(#${randomId})`} />
|
||||||
|
</svg>
|
||||||
|
) : (
|
||||||
|
<svg width="14" height="15" viewBox="0 0 14 15" fill="none">
|
||||||
|
<path fill-rule="evenodd" clip-rule="evenodd" d={STAR_PATH} fill="var(--color-fill)" />
|
||||||
|
</svg>
|
||||||
|
)}
|
||||||
|
</i>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
function getPremiumIconGradient() {
|
|
||||||
const id = generateUniqueId();
|
|
||||||
return {
|
|
||||||
// eslint-disable-next-line max-len
|
|
||||||
__html: `<svg width="14" height="15" viewBox="0 0 14 15" fill="none" xmlns="http://www.w3.org/2000/svg"><defs><linearGradient id="${id}" x1="3" y1="63.5001" x2="84.1475" y2="-1.32262" gradientUnits="userSpaceOnUse"><stop stop-color="#6B93FF"/><stop offset="0.439058" stop-color="#976FFF"/><stop offset="1" stop-color="#E46ACE"/></linearGradient></defs><path fill-rule="evenodd" clip-rule="evenodd" d="M6.63869 12.1902L3.50621 14.1092C3.18049 14.3087 2.75468 14.2064 2.55515 13.8807C2.45769 13.7216 2.42864 13.5299 2.47457 13.3491L2.95948 11.4405C3.13452 10.7515 3.60599 10.1756 4.24682 9.86791L7.6642 8.22716C7.82352 8.15067 7.89067 7.95951 7.81418 7.80019C7.75223 7.67116 7.61214 7.59896 7.47111 7.62338L3.66713 8.28194C2.89387 8.41581 2.1009 8.20228 1.49941 7.69823L0.297703 6.69116C0.00493565 6.44581 -0.0335059 6.00958 0.211842 5.71682C0.33117 5.57442 0.502766 5.48602 0.687982 5.47153L4.35956 5.18419C4.61895 5.16389 4.845 4.99974 4.94458 4.75937L6.36101 1.3402C6.5072 0.987302 6.91179 0.819734 7.26469 0.965925C7.43413 1.03612 7.56876 1.17075 7.63896 1.3402L9.05539 4.75937C9.15496 4.99974 9.38101 5.16389 9.6404 5.18419L13.3322 5.47311C13.713 5.50291 13.9975 5.83578 13.9677 6.2166C13.9534 6.39979 13.8667 6.56975 13.7269 6.68896L10.9114 9.08928C10.7131 9.25826 10.6267 9.52425 10.6876 9.77748L11.5532 13.3733C11.6426 13.7447 11.414 14.1182 11.0427 14.2076C10.8642 14.2506 10.676 14.2208 10.5195 14.1249L7.36128 12.1902C7.13956 12.0544 6.8604 12.0544 6.63869 12.1902Z" fill="url(#${id})"/></svg>`,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export default memo(PremiumIcon);
|
export default memo(PremiumIcon);
|
||||||
|
|||||||
@ -1,15 +1,14 @@
|
|||||||
import type { FC } from '../../lib/teact/teact';
|
|
||||||
import React from '../../lib/teact/teact';
|
import React from '../../lib/teact/teact';
|
||||||
|
import type { FC } from '../../lib/teact/teact';
|
||||||
|
|
||||||
import './VerifiedIcon.scss';
|
import './VerifiedIcon.scss';
|
||||||
|
|
||||||
// eslint-disable-next-line max-len
|
|
||||||
const VERIFIED_ICON = { __html: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M12.3 2.9c.1.1.2.1.3.2.7.6 1.3 1.1 2 1.7.3.2.6.4.9.4.9.1 1.7.2 2.6.2.5 0 .6.1.7.7.1.9.1 1.8.2 2.6 0 .4.2.7.4 1 .6.7 1.1 1.3 1.7 2 .3.4.3.5 0 .8-.5.6-1.1 1.3-1.6 1.9-.3.3-.5.7-.5 1.2-.1.8-.2 1.7-.2 2.5 0 .4-.2.5-.6.6-.8 0-1.6.1-2.5.2-.5 0-1 .2-1.4.5-.6.5-1.3 1.1-1.9 1.6-.3.3-.5.3-.8 0-.7-.6-1.4-1.2-2-1.8-.3-.2-.6-.4-.9-.4-.9-.1-1.8-.2-2.7-.2-.4 0-.5-.2-.6-.5 0-.9-.1-1.7-.2-2.6 0-.4-.2-.8-.4-1.1-.6-.6-1.1-1.3-1.6-2-.4-.4-.3-.5 0-1 .6-.6 1.1-1.3 1.7-1.9.3-.3.4-.6.4-1 0-.8.1-1.6.2-2.5 0-.5.1-.6.6-.6.9-.1 1.7-.1 2.6-.2.4 0 .7-.2 1-.4.7-.6 1.4-1.2 2.1-1.7.1-.2.3-.3.5-.2z" style="fill: var(--color-fill)"/><path class="lol" d="M16.4 10.1l-.2.2-5.4 5.4c-.1.1-.2.2-.4 0l-2.6-2.6c-.2-.2-.1-.3 0-.4.2-.2.5-.6.7-.6.3 0 .5.4.7.6l1.1 1.1c.2.2.3.2.5 0l4.3-4.3c.2-.2.4-.3.6 0 .1.2.3.3.4.5.2 0 .3.1.3.1z" style="fill: var(--color-checkmark)"/></svg>' };
|
|
||||||
|
|
||||||
const VerifiedIcon: FC = () => {
|
const VerifiedIcon: FC = () => {
|
||||||
return (
|
return (
|
||||||
// eslint-disable-next-line react/no-danger
|
<svg className="VerifiedIcon" viewBox="0 0 24 24">
|
||||||
<span className="VerifiedIcon" dangerouslySetInnerHTML={VERIFIED_ICON} />
|
<path d="M12.3 2.9c.1.1.2.1.3.2.7.6 1.3 1.1 2 1.7.3.2.6.4.9.4.9.1 1.7.2 2.6.2.5 0 .6.1.7.7.1.9.1 1.8.2 2.6 0 .4.2.7.4 1 .6.7 1.1 1.3 1.7 2 .3.4.3.5 0 .8-.5.6-1.1 1.3-1.6 1.9-.3.3-.5.7-.5 1.2-.1.8-.2 1.7-.2 2.5 0 .4-.2.5-.6.6-.8 0-1.6.1-2.5.2-.5 0-1 .2-1.4.5-.6.5-1.3 1.1-1.9 1.6-.3.3-.5.3-.8 0-.7-.6-1.4-1.2-2-1.8-.3-.2-.6-.4-.9-.4-.9-.1-1.8-.2-2.7-.2-.4 0-.5-.2-.6-.5 0-.9-.1-1.7-.2-2.6 0-.4-.2-.8-.4-1.1-.6-.6-1.1-1.3-1.6-2-.4-.4-.3-.5 0-1 .6-.6 1.1-1.3 1.7-1.9.3-.3.4-.6.4-1 0-.8.1-1.6.2-2.5 0-.5.1-.6.6-.6.9-.1 1.7-.1 2.6-.2.4 0 .7-.2 1-.4.7-.6 1.4-1.2 2.1-1.7.1-.2.3-.3.5-.2z" style="fill: var(--color-fill)" />
|
||||||
|
<path d="M16.4 10.1l-.2.2-5.4 5.4c-.1.1-.2.2-.4 0l-2.6-2.6c-.2-.2-.1-.3 0-.4.2-.2.5-.6.7-.6.3 0 .5.4.7.6l1.1 1.1c.2.2.3.2.5 0l4.3-4.3c.2-.2.4-.3.6 0 .1.2.3.3.4.5.2 0 .3.1.3.1z" style="fill: var(--color-checkmark)" />
|
||||||
|
</svg>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -1,14 +1,11 @@
|
|||||||
import type { FC } from '../../../../lib/teact/teact';
|
|
||||||
import React, { memo } from '../../../../lib/teact/teact';
|
import React, { memo } from '../../../../lib/teact/teact';
|
||||||
|
import type { FC } from '../../../../lib/teact/teact';
|
||||||
|
|
||||||
import buildClassName from '../../../../util/buildClassName';
|
import buildClassName from '../../../../util/buildClassName';
|
||||||
import useLang from '../../../../hooks/useLang';
|
import useLang from '../../../../hooks/useLang';
|
||||||
|
|
||||||
import styles from './PremiumLimitsCompare.module.scss';
|
import styles from './PremiumLimitsCompare.module.scss';
|
||||||
|
|
||||||
// eslint-disable-next-line max-len
|
|
||||||
const TRIANGLE_SVG = '<svg width="26" height="9" viewBox="0 0 26 9" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M0 0H26H24.4853C22.894 0 21.3679 0.632141 20.2426 1.75736L14.4142 7.58579C13.6332 8.36684 12.3668 8.36683 11.5858 7.58579L5.75736 1.75736C4.63214 0.632139 3.10602 0 1.51472 0H0Z" fill="#7E85FF"/></svg>';
|
|
||||||
|
|
||||||
type OwnProps = {
|
type OwnProps = {
|
||||||
floatingBadgeIcon?: string;
|
floatingBadgeIcon?: string;
|
||||||
leftValue?: string;
|
leftValue?: string;
|
||||||
@ -32,7 +29,11 @@ const PremiumLimitsCompare: FC<OwnProps> = ({
|
|||||||
<div className={styles.floatingBadge}>
|
<div className={styles.floatingBadge}>
|
||||||
<i className={buildClassName(styles.floatingBadgeIcon, floatingBadgeIcon, 'icon')} />
|
<i className={buildClassName(styles.floatingBadgeIcon, floatingBadgeIcon, 'icon')} />
|
||||||
<div className={styles.floatingBadgeValue} dir={lang.isRtl ? 'rtl' : undefined}>{leftValue}</div>
|
<div className={styles.floatingBadgeValue} dir={lang.isRtl ? 'rtl' : undefined}>{leftValue}</div>
|
||||||
<div className={styles.floatingBadgeTriangle} dangerouslySetInnerHTML={{ __html: TRIANGLE_SVG }} />
|
<div className={styles.floatingBadgeTriangle}>
|
||||||
|
<svg width="26" height="9" viewBox="0 0 26 9" fill="none">
|
||||||
|
<path d="M0 0H26H24.4853C22.894 0 21.3679 0.632141 20.2426 1.75736L14.4142 7.58579C13.6332 8.36684 12.3668 8.36683 11.5858 7.58579L5.75736 1.75736C4.63214 0.632139 3.10602 0 1.51472 0H0Z" fill="#7E85FF" />
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<div className={buildClassName(styles.line, styles.left)}>
|
<div className={buildClassName(styles.line, styles.left)}>
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import React, {
|
import React, {
|
||||||
memo, useEffect, useLayoutEffect, useMemo, useRef, useState,
|
memo, useEffect, useMemo, useRef, useState,
|
||||||
} from '../../../lib/teact/teact';
|
} from '../../../lib/teact/teact';
|
||||||
import { requestMeasure, requestNextMutation } from '../../../lib/fasterdom/fasterdom';
|
import { requestMeasure, requestNextMutation } from '../../../lib/fasterdom/fasterdom';
|
||||||
import { getActions, withGlobal } from '../../../global';
|
import { getActions, withGlobal } from '../../../global';
|
||||||
@ -229,8 +229,6 @@ const SELECT_MODE_TRANSITION_MS = 200;
|
|||||||
const MESSAGE_MAX_LENGTH = 4096;
|
const MESSAGE_MAX_LENGTH = 4096;
|
||||||
const SENDING_ANIMATION_DURATION = 350;
|
const SENDING_ANIMATION_DURATION = 350;
|
||||||
const MOUNT_ANIMATION_DURATION = 430;
|
const MOUNT_ANIMATION_DURATION = 430;
|
||||||
// eslint-disable-next-line max-len
|
|
||||||
const APPENDIX = '<svg width="9" height="20" xmlns="http://www.w3.org/2000/svg"><defs><filter x="-50%" y="-14.7%" width="200%" height="141.2%" filterUnits="objectBoundingBox" id="a"><feOffset dy="1" in="SourceAlpha" result="shadowOffsetOuter1"/><feGaussianBlur stdDeviation="1" in="shadowOffsetOuter1" result="shadowBlurOuter1"/><feColorMatrix values="0 0 0 0 0.0621962482 0 0 0 0 0.138574144 0 0 0 0 0.185037364 0 0 0 0.15 0" in="shadowBlurOuter1"/></filter></defs><g fill="none" fill-rule="evenodd"><path d="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" fill="#000" filter="url(#a)"/><path d="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" fill="#FFF" class="corner"/></g></svg>';
|
|
||||||
|
|
||||||
const Composer: FC<OwnProps & StateProps> = ({
|
const Composer: FC<OwnProps & StateProps> = ({
|
||||||
isOnActiveTab,
|
isOnActiveTab,
|
||||||
@ -314,8 +312,6 @@ const Composer: FC<OwnProps & StateProps> = ({
|
|||||||
|
|
||||||
const lang = useLang();
|
const lang = useLang();
|
||||||
|
|
||||||
// eslint-disable-next-line no-null/no-null
|
|
||||||
const appendixRef = useRef<HTMLDivElement>(null);
|
|
||||||
// eslint-disable-next-line no-null/no-null
|
// eslint-disable-next-line no-null/no-null
|
||||||
const inputRef = useRef<HTMLDivElement>(null);
|
const inputRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
@ -364,12 +360,6 @@ const Composer: FC<OwnProps & StateProps> = ({
|
|||||||
shouldAnimateSendAsButtonRef.current = Boolean(chatId === prevChatId && sendAsPeerIds && !prevSendAsPeerIds);
|
shouldAnimateSendAsButtonRef.current = Boolean(chatId === prevChatId && sendAsPeerIds && !prevSendAsPeerIds);
|
||||||
}, [chatId, sendAsPeerIds]);
|
}, [chatId, sendAsPeerIds]);
|
||||||
|
|
||||||
useLayoutEffect(() => {
|
|
||||||
if (!appendixRef.current) return;
|
|
||||||
|
|
||||||
appendixRef.current.innerHTML = APPENDIX;
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const [attachments, setAttachments] = useState<ApiAttachment[]>([]);
|
const [attachments, setAttachments] = useState<ApiAttachment[]>([]);
|
||||||
const hasAttachments = Boolean(attachments.length);
|
const hasAttachments = Boolean(attachments.length);
|
||||||
const [nextText, setNextText] = useState<ApiFormattedText | undefined>(undefined);
|
const [nextText, setNextText] = useState<ApiFormattedText | undefined>(undefined);
|
||||||
@ -1339,7 +1329,29 @@ const Composer: FC<OwnProps & StateProps> = ({
|
|||||||
onClose={closeBotCommandTooltip}
|
onClose={closeBotCommandTooltip}
|
||||||
/>
|
/>
|
||||||
<div id="message-compose">
|
<div id="message-compose">
|
||||||
<div className="svg-appendix" ref={appendixRef} />
|
<svg className="svg-appendix" width="9" height="20">
|
||||||
|
<defs>
|
||||||
|
<filter
|
||||||
|
x="-50%"
|
||||||
|
y="-14.7%"
|
||||||
|
width="200%"
|
||||||
|
height="141.2%"
|
||||||
|
filterUnits="objectBoundingBox"
|
||||||
|
id="composerAppendix"
|
||||||
|
>
|
||||||
|
<feOffset dy="1" in="SourceAlpha" result="shadowOffsetOuter1" />
|
||||||
|
<feGaussianBlur stdDeviation="1" in="shadowOffsetOuter1" result="shadowBlurOuter1" />
|
||||||
|
<feColorMatrix
|
||||||
|
values="0 0 0 0 0.0621962482 0 0 0 0 0.138574144 0 0 0 0 0.185037364 0 0 0 0.15 0"
|
||||||
|
in="shadowBlurOuter1"
|
||||||
|
/>
|
||||||
|
</filter>
|
||||||
|
</defs>
|
||||||
|
<g fill="none" fill-rule="evenodd">
|
||||||
|
<path d="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" fill="#000" filter="url(#composerAppendix)" />
|
||||||
|
<path d="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" fill="#FFF" className="corner" />
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
|
||||||
<InlineBotTooltip
|
<InlineBotTooltip
|
||||||
isOpen={isInlineBotTooltipOpen}
|
isOpen={isInlineBotTooltipOpen}
|
||||||
|
|||||||
@ -35,6 +35,8 @@ import './Location.scss';
|
|||||||
|
|
||||||
import mapPin from '../../../assets/map-pin.svg';
|
import mapPin from '../../../assets/map-pin.svg';
|
||||||
|
|
||||||
|
const TIMER_RADIUS = 12;
|
||||||
|
const TIMER_CIRCUMFERENCE = TIMER_RADIUS * 2 * Math.PI;
|
||||||
const MOVE_THRESHOLD = 0.0001; // ~11m
|
const MOVE_THRESHOLD = 0.0001; // ~11m
|
||||||
const DEFAULT_MAP_CONFIG = {
|
const DEFAULT_MAP_CONFIG = {
|
||||||
width: 400,
|
width: 400,
|
||||||
@ -43,9 +45,6 @@ const DEFAULT_MAP_CONFIG = {
|
|||||||
scale: 2,
|
scale: 2,
|
||||||
};
|
};
|
||||||
|
|
||||||
// eslint-disable-next-line max-len
|
|
||||||
const SVG_PIN = { __html: '<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" class="round-pin" style="enable-background:new 0 0 64 64" viewBox="0 0 64 64"><circle cx="32" cy="32" r="24.5"/><path d="M32 8c13.23 0 24 10.77 24 24S45.23 56 32 56 8 45.23 8 32 18.77 8 32 8m0-1C18.19 7 7 18.19 7 32s11.19 25 25 25 25-11.19 25-25S45.81 7 32 7z"/><path d="m29.38 57.67-1.98-1.59 3.02-1.66L32 51.54l1.58 2.88 3.02 1.66-1.91 1.53L32 60.73z"/><path d="m32 52.58 1.07 1.95.14.26.26.14 2.24 1.22-1.33 1.06-.07.06-.06.07L32 59.96l-2.24-2.61-.06-.07-.07-.06-1.33-1.06 2.24-1.22.26-.14.14-.26L32 52.58m0-2.08-1.94 3.56L26.5 56l2.5 2 3 3.5 3-3.5 2.5-2-3.56-1.94L32 50.5z"/></svg>' };
|
|
||||||
|
|
||||||
type OwnProps = {
|
type OwnProps = {
|
||||||
message: ApiMessage;
|
message: ApiMessage;
|
||||||
peer?: ApiUser | ApiChat;
|
peer?: ApiUser | ApiChat;
|
||||||
@ -102,28 +101,14 @@ const Location: FC<OwnProps> = ({
|
|||||||
|
|
||||||
const updateCountdown = useLastCallback((countdownEl: HTMLDivElement) => {
|
const updateCountdown = useLastCallback((countdownEl: HTMLDivElement) => {
|
||||||
if (type !== 'geoLive') return;
|
if (type !== 'geoLive') return;
|
||||||
const radius = 12;
|
const svgEl = countdownEl.lastElementChild!;
|
||||||
const circumference = radius * 2 * Math.PI;
|
const timerEl = countdownEl.firstElementChild!;
|
||||||
const svgEl = countdownEl.lastElementChild;
|
|
||||||
const timerEl = countdownEl.firstElementChild as SVGElement;
|
|
||||||
|
|
||||||
const timeLeft = message.date + location.period - getServerTime();
|
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);
|
const text = formatCountdownShort(lang, timeLeft * 1000);
|
||||||
|
timerEl.textContent = text;
|
||||||
if (!svgEl || !timerEl) {
|
svgEl.firstElementChild!.setAttribute('stroke-dashoffset', `-${strokeDashOffset}`);
|
||||||
countdownEl.innerHTML = `
|
|
||||||
<span class="geo-countdown-text">${text}</span>
|
|
||||||
<svg width="32px" height="32px">
|
|
||||||
<circle cx="16" cy="16" r="${radius}" class="geo-countdown-progress" transform="rotate(-90, 16, 16)"
|
|
||||||
stroke-dasharray="${circumference} ${circumference}"
|
|
||||||
stroke-dashoffset="-${strokeDashOffset}"
|
|
||||||
/>
|
|
||||||
</svg>`;
|
|
||||||
} else {
|
|
||||||
timerEl.textContent = text;
|
|
||||||
svgEl.firstElementChild!.setAttribute('stroke-dashoffset', `-${strokeDashOffset}`);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
useLayoutEffect(() => {
|
useLayoutEffect(() => {
|
||||||
@ -180,7 +165,22 @@ const Location: FC<OwnProps> = ({
|
|||||||
<div className="location-info-subtitle">
|
<div className="location-info-subtitle">
|
||||||
{formatLastUpdated(lang, serverTime, message.editDate)}
|
{formatLastUpdated(lang, serverTime, message.editDate)}
|
||||||
</div>
|
</div>
|
||||||
{!isExpired && <div className="geo-countdown" ref={countdownRef} />}
|
{!isExpired && (
|
||||||
|
<div className="geo-countdown" ref={countdownRef}>
|
||||||
|
<span className="geo-countdown-text" />
|
||||||
|
<svg width="32px" height="32px">
|
||||||
|
<circle
|
||||||
|
cx="16"
|
||||||
|
cy="16"
|
||||||
|
r={TIMER_RADIUS}
|
||||||
|
className="geo-countdown-progress"
|
||||||
|
transform="rotate(-90, 16, 16)"
|
||||||
|
stroke-dasharray={TIMER_CIRCUMFERENCE}
|
||||||
|
stroke-dashoffset="0"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -207,7 +207,8 @@ const Location: FC<OwnProps> = ({
|
|||||||
);
|
);
|
||||||
if (type === 'geoLive') {
|
if (type === 'geoLive') {
|
||||||
return (
|
return (
|
||||||
<div className={pinClassName} dangerouslySetInnerHTML={SVG_PIN}>
|
<div className={pinClassName}>
|
||||||
|
<PinSvg />
|
||||||
<Avatar peer={peer} className="location-avatar" />
|
<Avatar peer={peer} className="location-avatar" />
|
||||||
{location.heading !== undefined && (
|
{location.heading !== undefined && (
|
||||||
<div className="direction" style={`--direction: ${location.heading}deg`} />
|
<div className="direction" style={`--direction: ${location.heading}deg`} />
|
||||||
@ -221,7 +222,8 @@ const Location: FC<OwnProps> = ({
|
|||||||
const iconSrc = getVenueIconUrl(location.venueType);
|
const iconSrc = getVenueIconUrl(location.venueType);
|
||||||
if (iconSrc) {
|
if (iconSrc) {
|
||||||
return (
|
return (
|
||||||
<div className={pinClassName} dangerouslySetInnerHTML={SVG_PIN} style={`--pin-color: ${color}`}>
|
<div className={pinClassName} style={`--pin-color: ${color}`}>
|
||||||
|
<PinSvg />
|
||||||
<img src={iconSrc} className="venue-icon" alt="" />
|
<img src={iconSrc} className="venue-icon" alt="" />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@ -264,4 +266,15 @@ const Location: FC<OwnProps> = ({
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
function PinSvg() {
|
||||||
|
return (
|
||||||
|
<svg className="round-pin" style="enable-background:new 0 0 64 64" viewBox="0 0 64 64">
|
||||||
|
<circle cx="32" cy="32" r="24.5" />
|
||||||
|
<path d="M32 8c13.23 0 24 10.77 24 24S45.23 56 32 56 8 45.23 8 32 18.77 8 32 8m0-1C18.19 7 7 18.19 7 32s11.19 25 25 25 25-11.19 25-25S45.81 7 32 7z" />
|
||||||
|
<path d="m29.38 57.67-1.98-1.59 3.02-1.66L32 51.54l1.58 2.88 3.02 1.66-1.91 1.53L32 60.73z" />
|
||||||
|
<path d="m32 52.58 1.07 1.95.14.26.26.14 2.24 1.22-1.33 1.06-.07.06-.06.07L32 59.96l-2.24-2.61-.06-.07-.07-.06-1.33-1.06 2.24-1.22.26-.14.14-.26L32 52.58m0-2.08-1.94 3.56L26.5 56l2.5 2 3 3.5 3-3.5 2.5-2-3.56-1.94L32 50.5z" />
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
export default memo(Location);
|
export default memo(Location);
|
||||||
|
|||||||
@ -3,7 +3,6 @@ import React, {
|
|||||||
memo,
|
memo,
|
||||||
useCallback,
|
useCallback,
|
||||||
useEffect,
|
useEffect,
|
||||||
useLayoutEffect,
|
|
||||||
useMemo,
|
useMemo,
|
||||||
useRef,
|
useRef,
|
||||||
useState,
|
useState,
|
||||||
@ -275,10 +274,6 @@ type QuickReactionPosition =
|
|||||||
| 'in-meta';
|
| 'in-meta';
|
||||||
|
|
||||||
const NBSP = '\u00A0';
|
const NBSP = '\u00A0';
|
||||||
// eslint-disable-next-line max-len
|
|
||||||
const APPENDIX_OWN = { __html: '<svg width="9" height="20" xmlns="http://www.w3.org/2000/svg"><defs><filter x="-50%" y="-14.7%" width="200%" height="141.2%" filterUnits="objectBoundingBox" id="a"><feOffset dy="1" in="SourceAlpha" result="shadowOffsetOuter1"/><feGaussianBlur stdDeviation="1" in="shadowOffsetOuter1" result="shadowBlurOuter1"/><feColorMatrix values="0 0 0 0 0.0621962482 0 0 0 0 0.138574144 0 0 0 0 0.185037364 0 0 0 0.15 0" in="shadowBlurOuter1"/></filter></defs><g fill="none" fill-rule="evenodd"><path d="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" fill="#000" filter="url(#a)"/><path d="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" fill="#EEFFDE" class="corner"/></g></svg>' };
|
|
||||||
// eslint-disable-next-line max-len
|
|
||||||
const APPENDIX_NOT_OWN = { __html: '<svg width="9" height="20" xmlns="http://www.w3.org/2000/svg"><defs><filter x="-50%" y="-14.7%" width="200%" height="141.2%" filterUnits="objectBoundingBox" id="a"><feOffset dy="1" in="SourceAlpha" result="shadowOffsetOuter1"/><feGaussianBlur stdDeviation="1" in="shadowOffsetOuter1" result="shadowBlurOuter1"/><feColorMatrix values="0 0 0 0 0.0621962482 0 0 0 0 0.138574144 0 0 0 0 0.185037364 0 0 0 0.15 0" in="shadowBlurOuter1"/></filter></defs><g fill="none" fill-rule="evenodd"><path d="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" fill="#000" filter="url(#a)"/><path d="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" fill="#FFF" class="corner"/></g></svg>' };
|
|
||||||
const APPEARANCE_DELAY = 10;
|
const APPEARANCE_DELAY = 10;
|
||||||
const NO_MEDIA_CORNERS_THRESHOLD = 18;
|
const NO_MEDIA_CORNERS_THRESHOLD = 18;
|
||||||
const QUICK_REACTION_SIZE = 1.75 * REM;
|
const QUICK_REACTION_SIZE = 1.75 * REM;
|
||||||
@ -287,9 +282,6 @@ const BOTTOM_FOCUS_SCROLL_THRESHOLD = 5;
|
|||||||
const THROTTLE_MS = 300;
|
const THROTTLE_MS = 300;
|
||||||
const RESIZE_ANIMATION_DURATION = 400;
|
const RESIZE_ANIMATION_DURATION = 400;
|
||||||
|
|
||||||
let appendixOwnCloned: SVGElement;
|
|
||||||
let appendixNotOwnCloned: SVGElement;
|
|
||||||
|
|
||||||
const Message: FC<OwnProps & StateProps> = ({
|
const Message: FC<OwnProps & StateProps> = ({
|
||||||
message,
|
message,
|
||||||
chatUsernames,
|
chatUsernames,
|
||||||
@ -389,8 +381,6 @@ const Message: FC<OwnProps & StateProps> = ({
|
|||||||
const bottomMarkerRef = useRef<HTMLDivElement>(null);
|
const bottomMarkerRef = useRef<HTMLDivElement>(null);
|
||||||
// eslint-disable-next-line no-null/no-null
|
// eslint-disable-next-line no-null/no-null
|
||||||
const quickReactionRef = useRef<HTMLDivElement>(null);
|
const quickReactionRef = useRef<HTMLDivElement>(null);
|
||||||
// eslint-disable-next-line no-null/no-null
|
|
||||||
const appendixRef = useRef<HTMLDivElement>(null);
|
|
||||||
|
|
||||||
const messageHeightRef = useRef(0);
|
const messageHeightRef = useRef(0);
|
||||||
|
|
||||||
@ -726,30 +716,6 @@ const Message: FC<OwnProps & StateProps> = ({
|
|||||||
}
|
}
|
||||||
}, [hasUnreadReaction, messageId, animateUnreadReaction]);
|
}, [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 style = '';
|
||||||
let calculatedWidth;
|
let calculatedWidth;
|
||||||
let reactionsMaxWidth;
|
let reactionsMaxWidth;
|
||||||
@ -1325,9 +1291,7 @@ const Message: FC<OwnProps & StateProps> = ({
|
|||||||
</Button>
|
</Button>
|
||||||
) : undefined}
|
) : undefined}
|
||||||
{withCommentButton && <CommentButton threadInfo={repliesThreadInfo!} disabled={noComments} />}
|
{withCommentButton && <CommentButton threadInfo={repliesThreadInfo!} disabled={noComments} />}
|
||||||
{withAppendix && (
|
{withAppendix && <MessageAppendix isOwn={isOwn} />}
|
||||||
<div ref={appendixRef} className="svg-appendix" />
|
|
||||||
)}
|
|
||||||
{withQuickReactionButton && quickReactionPosition === 'in-content' && renderQuickReactionButton()}
|
{withQuickReactionButton && quickReactionPosition === 'in-content' && renderQuickReactionButton()}
|
||||||
</div>
|
</div>
|
||||||
{message.inlineButtons && (
|
{message.inlineButtons && (
|
||||||
@ -1367,6 +1331,30 @@ const Message: FC<OwnProps & StateProps> = ({
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
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 (
|
||||||
|
<svg width="9" height="20" className="svg-appendix">
|
||||||
|
<defs>
|
||||||
|
<filter x="-50%" y="-14.7%" width="200%" height="141.2%" filterUnits="objectBoundingBox" id="messageAppendix">
|
||||||
|
<feOffset dy="1" in="SourceAlpha" result="shadowOffsetOuter1" />
|
||||||
|
<feGaussianBlur stdDeviation="1" in="shadowOffsetOuter1" result="shadowBlurOuter1" />
|
||||||
|
<feColorMatrix
|
||||||
|
values="0 0 0 0 0.0621962482 0 0 0 0 0.138574144 0 0 0 0 0.185037364 0 0 0 0.15 0"
|
||||||
|
in="shadowBlurOuter1"
|
||||||
|
/>
|
||||||
|
</filter>
|
||||||
|
</defs>
|
||||||
|
<g fill="none" fill-rule="evenodd">
|
||||||
|
<path d={path} fill="#000" filter="url(#messageAppendix)" />
|
||||||
|
<path d={path} fill={isOwn ? '#EEFFDE' : 'FFF'} className="corner" />
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
export default memo(withGlobal<OwnProps>(
|
export default memo(withGlobal<OwnProps>(
|
||||||
(global, ownProps): StateProps => {
|
(global, ownProps): StateProps => {
|
||||||
const {
|
const {
|
||||||
|
|||||||
@ -18,7 +18,7 @@ import { renderTextWithEntities } from '../../common/helpers/renderTextWithEntit
|
|||||||
import { formatMediaDuration } from '../../../util/dateFormat';
|
import { formatMediaDuration } from '../../../util/dateFormat';
|
||||||
import type { LangFn } from '../../../hooks/useLang';
|
import type { LangFn } from '../../../hooks/useLang';
|
||||||
import useLang from '../../../hooks/useLang';
|
import useLang from '../../../hooks/useLang';
|
||||||
import { getServerTimeOffset } from '../../../util/serverTime';
|
import { getServerTime } from '../../../util/serverTime';
|
||||||
|
|
||||||
import useLastCallback from '../../../hooks/useLastCallback';
|
import useLastCallback from '../../../hooks/useLastCallback';
|
||||||
|
|
||||||
@ -44,6 +44,9 @@ type StateProps = {
|
|||||||
|
|
||||||
const SOLUTION_CONTAINER_ID = '#middle-column-portals';
|
const SOLUTION_CONTAINER_ID = '#middle-column-portals';
|
||||||
const SOLUTION_DURATION = 5000;
|
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 NBSP = '\u00A0';
|
||||||
|
|
||||||
const Poll: FC<OwnProps & StateProps> = ({
|
const Poll: FC<OwnProps & StateProps> = ({
|
||||||
@ -63,29 +66,27 @@ const Poll: FC<OwnProps & StateProps> = ({
|
|||||||
const [wasSubmitted, setWasSubmitted] = useState<boolean>(false);
|
const [wasSubmitted, setWasSubmitted] = useState<boolean>(false);
|
||||||
const [closePeriod, setClosePeriod] = useState<number>(
|
const [closePeriod, setClosePeriod] = useState<number>(
|
||||||
!summary.closed && summary.closeDate && summary.closeDate > 0
|
!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,
|
: 0,
|
||||||
);
|
);
|
||||||
// eslint-disable-next-line no-null/no-null
|
// eslint-disable-next-line no-null/no-null
|
||||||
const countdownRef = useRef<HTMLDivElement>(null);
|
const countdownRef = useRef<HTMLDivElement>(null);
|
||||||
|
// eslint-disable-next-line no-null/no-null
|
||||||
|
const timerCircleRef = useRef<SVGCircleElement>(null);
|
||||||
const { results: voteResults, totalVoters } = results;
|
const { results: voteResults, totalVoters } = results;
|
||||||
const hasVoted = voteResults && voteResults.some((r) => r.isChosen);
|
const hasVoted = voteResults && voteResults.some((r) => r.isChosen);
|
||||||
const canVote = !summary.closed && !hasVoted;
|
const canVote = !summary.closed && !hasVoted;
|
||||||
const canViewResult = !canVote && summary.isPublic && Number(results.totalVoters) > 0;
|
const canViewResult = !canVote && summary.isPublic && Number(results.totalVoters) > 0;
|
||||||
const isMultiple = canVote && summary.multipleChoice;
|
const isMultiple = canVote && summary.multipleChoice;
|
||||||
const maxVotersCount = voteResults ? Math.max(...voteResults.map((r) => r.votersCount)) : totalVoters;
|
const maxVotersCount = voteResults ? Math.max(...voteResults.map((r) => r.votersCount)) : totalVoters;
|
||||||
const correctResults = voteResults ? voteResults.reduce((answers: string[], r) => {
|
const correctResults = useMemo(() => {
|
||||||
if (r.isCorrect) {
|
return voteResults?.filter((r) => r.isCorrect).map((r) => r.option) || [];
|
||||||
answers.push(r.option);
|
}, [voteResults]);
|
||||||
}
|
const answers = useMemo(() => summary.answers.map((a) => ({
|
||||||
|
|
||||||
return answers;
|
|
||||||
}, []) : [];
|
|
||||||
const answers = summary.answers.map((a) => ({
|
|
||||||
label: a.text,
|
label: a.text,
|
||||||
value: a.option,
|
value: a.option,
|
||||||
hidden: Boolean(summary.quiz && summary.closePeriod && closePeriod <= 0),
|
hidden: Boolean(summary.quiz && summary.closePeriod && closePeriod <= 0),
|
||||||
}));
|
})), [closePeriod, summary]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const chosen = poll.results.results?.find((result) => result.isChosen);
|
const chosen = poll.results.results?.find((result) => result.isChosen);
|
||||||
@ -99,34 +100,16 @@ const Poll: FC<OwnProps & StateProps> = ({
|
|||||||
|
|
||||||
useLayoutEffect(() => {
|
useLayoutEffect(() => {
|
||||||
if (closePeriod > 0) {
|
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;
|
const strokeDashOffset = ((summary.closePeriod! - closePeriod) / summary.closePeriod!) * TIMER_CIRCUMFERENCE;
|
||||||
|
timerCircleRef.current.setAttribute('stroke-dashoffset', `-${strokeDashOffset}`);
|
||||||
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 = `
|
|
||||||
<span>${formatMediaDuration(closePeriod)}</span>
|
|
||||||
<svg width="16px" height="16px">
|
|
||||||
<circle cx="8" cy="8" r="6" class="poll-countdown-progress" transform="rotate(-90, 8, 8)"
|
|
||||||
stroke-dasharray="${circumference} ${circumference}"
|
|
||||||
stroke-dashoffset="0"
|
|
||||||
/>
|
|
||||||
</svg>`;
|
|
||||||
} else {
|
|
||||||
const strokeDashOffset = ((summary.closePeriod! - closePeriod) / summary.closePeriod!) * circumference;
|
|
||||||
timerEl.textContent = formatMediaDuration(closePeriod);
|
|
||||||
(svgEl.firstElementChild as SVGElement).setAttribute('stroke-dashoffset', `-${strokeDashOffset}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, [closePeriod, summary.closePeriod]);
|
}, [closePeriod, summary.closePeriod]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -255,7 +238,23 @@ const Poll: FC<OwnProps & StateProps> = ({
|
|||||||
<div className="poll-type">
|
<div className="poll-type">
|
||||||
{lang(getPollTypeString(summary))}
|
{lang(getPollTypeString(summary))}
|
||||||
{renderRecentVoters()}
|
{renderRecentVoters()}
|
||||||
{closePeriod > 0 && canVote && <div ref={countdownRef} className="poll-countdown" />}
|
{closePeriod > 0 && canVote && (
|
||||||
|
<div ref={countdownRef} className="poll-countdown">
|
||||||
|
<span>{formatMediaDuration(closePeriod)}</span>
|
||||||
|
<svg width="16px" height="16px">
|
||||||
|
<circle
|
||||||
|
ref={timerCircleRef}
|
||||||
|
cx="8"
|
||||||
|
cy="8"
|
||||||
|
r={TIMER_RADIUS}
|
||||||
|
className="poll-countdown-progress"
|
||||||
|
transform="rotate(-90, 8, 8)"
|
||||||
|
stroke-dasharray={TIMER_CIRCUMFERENCE}
|
||||||
|
stroke-dashoffset="0"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
{summary.quiz && poll.results.solution && !canVote && (
|
{summary.quiz && poll.results.solution && !canVote && (
|
||||||
<Button
|
<Button
|
||||||
round
|
round
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import type { FC } from '../../../lib/teact/teact';
|
import type { FC } from '../../../lib/teact/teact';
|
||||||
import React, {
|
import React, {
|
||||||
useState, useEffect, useRef, useLayoutEffect,
|
useState, useEffect,
|
||||||
} from '../../../lib/teact/teact';
|
} from '../../../lib/teact/teact';
|
||||||
|
|
||||||
import type { ApiPollAnswer, ApiPollResult } from '../../../api/types';
|
import type { ApiPollAnswer, ApiPollResult } from '../../../api/types';
|
||||||
@ -32,8 +32,6 @@ const PollOption: FC<OwnProps> = ({
|
|||||||
const showIcon = (correctResults.length > 0 && correctAnswer) || (result?.isChosen);
|
const showIcon = (correctResults.length > 0 && correctAnswer) || (result?.isChosen);
|
||||||
const answerPercent = result ? getPercentage(result.votersCount, totalVoters || 0) : 0;
|
const answerPercent = result ? getPercentage(result.votersCount, totalVoters || 0) : 0;
|
||||||
const [finalPercent, setFinalPercent] = useState(shouldAnimate ? 0 : answerPercent);
|
const [finalPercent, setFinalPercent] = useState(shouldAnimate ? 0 : answerPercent);
|
||||||
// eslint-disable-next-line no-null/no-null
|
|
||||||
const lineRef = useRef<HTMLDivElement>(null);
|
|
||||||
const lineWidth = result ? getPercentage(result.votersCount, maxVotersCount || 0) : 0;
|
const lineWidth = result ? getPercentage(result.votersCount, maxVotersCount || 0) : 0;
|
||||||
const isAnimationDoesNotStart = finalPercent !== answerPercent;
|
const isAnimationDoesNotStart = finalPercent !== answerPercent;
|
||||||
|
|
||||||
@ -43,24 +41,6 @@ const PollOption: FC<OwnProps> = ({
|
|||||||
}
|
}
|
||||||
}, [shouldAnimate, answerPercent]);
|
}, [shouldAnimate, answerPercent]);
|
||||||
|
|
||||||
useLayoutEffect(() => {
|
|
||||||
const lineEl = lineRef.current;
|
|
||||||
|
|
||||||
if (lineEl && shouldAnimate) {
|
|
||||||
const svgEl = lineEl.firstElementChild;
|
|
||||||
|
|
||||||
const style = isAnimationDoesNotStart ? '' : 'stroke-dasharray: 100% 200%; stroke-dashoffset: -44';
|
|
||||||
if (!svgEl) {
|
|
||||||
lineEl.innerHTML = `
|
|
||||||
<svg class="poll-line" xmlns="http://www.w3.org/2000/svg" style="${style}">
|
|
||||||
<path d="M4.47 5.33v13.6a9 9 0 009 9h13"/>
|
|
||||||
</svg>`;
|
|
||||||
} else {
|
|
||||||
svgEl.setAttribute('style', style);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, [isAnimationDoesNotStart, shouldAnimate]);
|
|
||||||
|
|
||||||
if (!voteResults || !result) {
|
if (!voteResults || !result) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
@ -87,7 +67,14 @@ const PollOption: FC<OwnProps> = ({
|
|||||||
{renderText(answer.text)}
|
{renderText(answer.text)}
|
||||||
</div>
|
</div>
|
||||||
<div className={buildClassName('poll-option-answer', showIcon && !correctAnswer && 'wrong')}>
|
<div className={buildClassName('poll-option-answer', showIcon && !correctAnswer && 'wrong')}>
|
||||||
<div className="poll-option-corner" ref={lineRef} />
|
{shouldAnimate && (
|
||||||
|
<svg
|
||||||
|
className="poll-line"
|
||||||
|
style={!isAnimationDoesNotStart ? 'stroke-dasharray: 100% 200%; stroke-dashoffset: -44' : ''}
|
||||||
|
>
|
||||||
|
<path d="M4.47 5.33v13.6a9 9 0 009 9h13" />
|
||||||
|
</svg>
|
||||||
|
)}
|
||||||
<div
|
<div
|
||||||
className="poll-option-line"
|
className="poll-option-line"
|
||||||
style={lineStyle}
|
style={lineStyle}
|
||||||
|
|||||||
@ -5,7 +5,6 @@ import React, {
|
|||||||
useRef,
|
useRef,
|
||||||
useState,
|
useState,
|
||||||
} from '../../../lib/teact/teact';
|
} from '../../../lib/teact/teact';
|
||||||
import { requestMutation } from '../../../lib/fasterdom/fasterdom';
|
|
||||||
import { getActions } from '../../../global';
|
import { getActions } from '../../../global';
|
||||||
|
|
||||||
import type { ApiMessage } from '../../../api/types';
|
import type { ApiMessage } from '../../../api/types';
|
||||||
@ -42,6 +41,9 @@ type OwnProps = {
|
|||||||
isDownloading?: boolean;
|
isDownloading?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const PROGRESS_CENTER = ROUND_VIDEO_DIMENSIONS_PX / 2;
|
||||||
|
const PROGRESS_MARGIN = 6;
|
||||||
|
const PROGRESS_CIRCUMFERENCE = (PROGRESS_CENTER - PROGRESS_MARGIN) * 2 * Math.PI;
|
||||||
const PROGRESS_THROTTLE = 16; // Min period needed for `playerEl.currentTime` to update
|
const PROGRESS_THROTTLE = 16; // Min period needed for `playerEl.currentTime` to update
|
||||||
|
|
||||||
let stopPrevious: NoneToVoidFunction;
|
let stopPrevious: NoneToVoidFunction;
|
||||||
@ -55,9 +57,9 @@ const RoundVideo: FC<OwnProps> = ({
|
|||||||
// eslint-disable-next-line no-null/no-null
|
// eslint-disable-next-line no-null/no-null
|
||||||
const ref = useRef<HTMLDivElement>(null);
|
const ref = useRef<HTMLDivElement>(null);
|
||||||
// eslint-disable-next-line no-null/no-null
|
// eslint-disable-next-line no-null/no-null
|
||||||
const playingProgressRef = useRef<HTMLDivElement>(null);
|
|
||||||
// eslint-disable-next-line no-null/no-null
|
|
||||||
const playerRef = useRef<HTMLVideoElement>(null);
|
const playerRef = useRef<HTMLVideoElement>(null);
|
||||||
|
// eslint-disable-next-line no-null/no-null
|
||||||
|
const circleRef = useRef<SVGCircleElement>(null);
|
||||||
|
|
||||||
const video = message.content.video!;
|
const video = message.content.video!;
|
||||||
|
|
||||||
@ -106,29 +108,12 @@ const RoundVideo: FC<OwnProps> = ({
|
|||||||
}, [setProgress, isActivated, getThrottledProgress]);
|
}, [setProgress, isActivated, getThrottledProgress]);
|
||||||
|
|
||||||
useLayoutEffect(() => {
|
useLayoutEffect(() => {
|
||||||
if (!isActivated) {
|
if (!isActivated || !circleRef.current) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const svgCenter = ROUND_VIDEO_DIMENSIONS_PX / 2;
|
const strokeDashOffset = PROGRESS_CIRCUMFERENCE - getThrottledProgress() * PROGRESS_CIRCUMFERENCE;
|
||||||
const svgMargin = 6;
|
circleRef.current.setAttribute('stroke-dashoffset', strokeDashOffset.toString());
|
||||||
const circumference = (svgCenter - svgMargin) * 2 * Math.PI;
|
|
||||||
const strokeDashOffset = circumference - getThrottledProgress() * circumference;
|
|
||||||
const playingProgressEl = playingProgressRef.current!;
|
|
||||||
const svgEl = playingProgressEl.firstElementChild;
|
|
||||||
|
|
||||||
if (!svgEl) {
|
|
||||||
playingProgressEl.innerHTML = `
|
|
||||||
<svg width="${ROUND_VIDEO_DIMENSIONS_PX}px" height="${ROUND_VIDEO_DIMENSIONS_PX}px">
|
|
||||||
<circle cx="${svgCenter}" cy="${svgCenter}" r="${svgCenter - svgMargin}" class="progress-circle"
|
|
||||||
transform="rotate(-90, ${svgCenter}, ${svgCenter})"
|
|
||||||
stroke-dasharray="${circumference} ${circumference}"
|
|
||||||
stroke-dashoffset="${circumference}"
|
|
||||||
/>
|
|
||||||
</svg>`;
|
|
||||||
} else {
|
|
||||||
(svgEl.firstElementChild as SVGElement).setAttribute('stroke-dashoffset', strokeDashOffset.toString());
|
|
||||||
}
|
|
||||||
}, [isActivated, getThrottledProgress]);
|
}, [isActivated, getThrottledProgress]);
|
||||||
|
|
||||||
const shouldPlay = Boolean(mediaData && isIntersecting);
|
const shouldPlay = Boolean(mediaData && isIntersecting);
|
||||||
@ -141,10 +126,6 @@ const RoundVideo: FC<OwnProps> = ({
|
|||||||
setIsActivated(false);
|
setIsActivated(false);
|
||||||
setProgress(0);
|
setProgress(0);
|
||||||
safePlay(playerRef.current);
|
safePlay(playerRef.current);
|
||||||
|
|
||||||
requestMutation(() => {
|
|
||||||
playingProgressRef.current!.innerHTML = '';
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const capturePlaying = useLastCallback(() => {
|
const capturePlaying = useLastCallback(() => {
|
||||||
@ -221,7 +202,22 @@ const RoundVideo: FC<OwnProps> = ({
|
|||||||
className={buildClassName('thumbnail', thumbClassNames)}
|
className={buildClassName('thumbnail', thumbClassNames)}
|
||||||
style={`width: ${ROUND_VIDEO_DIMENSIONS_PX}px; height: ${ROUND_VIDEO_DIMENSIONS_PX}px`}
|
style={`width: ${ROUND_VIDEO_DIMENSIONS_PX}px; height: ${ROUND_VIDEO_DIMENSIONS_PX}px`}
|
||||||
/>
|
/>
|
||||||
<div className="progress" ref={playingProgressRef} />
|
<div className="progress">
|
||||||
|
{isActivated && (
|
||||||
|
<svg width={ROUND_VIDEO_DIMENSIONS_PX} height={ROUND_VIDEO_DIMENSIONS_PX}>
|
||||||
|
<circle
|
||||||
|
ref={circleRef}
|
||||||
|
cx={PROGRESS_CENTER}
|
||||||
|
cy={PROGRESS_CENTER}
|
||||||
|
r={PROGRESS_CENTER - PROGRESS_MARGIN}
|
||||||
|
className="progress-circle"
|
||||||
|
transform={`rotate(-90, ${PROGRESS_CENTER}, ${PROGRESS_CENTER})`}
|
||||||
|
stroke-dasharray={PROGRESS_CIRCUMFERENCE}
|
||||||
|
stroke-dashoffset={PROGRESS_CIRCUMFERENCE}
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
{shouldSpinnerRender && (
|
{shouldSpinnerRender && (
|
||||||
<div className={`media-loading ${spinnerClassNames}`}>
|
<div className={`media-loading ${spinnerClassNames}`}>
|
||||||
<ProgressSpinner progress={isDownloading ? downloadProgress : loadProgress} />
|
<ProgressSpinner progress={isDownloading ? downloadProgress : loadProgress} />
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
|
import React, { memo } from '../../lib/teact/teact';
|
||||||
import type { FC } 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';
|
import buildClassName from '../../util/buildClassName';
|
||||||
|
|
||||||
@ -31,36 +31,7 @@ const ProgressSpinner: FC<{
|
|||||||
const circleRadius = radius - STROKE_WIDTH * 2;
|
const circleRadius = radius - STROKE_WIDTH * 2;
|
||||||
const borderRadius = radius - 1;
|
const borderRadius = radius - 1;
|
||||||
const circumference = circleRadius * 2 * Math.PI;
|
const circumference = circleRadius * 2 * Math.PI;
|
||||||
// eslint-disable-next-line no-null/no-null
|
const strokeDashOffset = circumference - Math.min(Math.max(MIN_PROGRESS, progress), MAX_PROGRESS) * circumference;
|
||||||
const containerRef = useRef<HTMLDivElement>(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 = `<svg
|
|
||||||
viewBox="0 0 ${borderRadius * 2} ${borderRadius * 2}"
|
|
||||||
height="${borderRadius * 2}"
|
|
||||||
width="${borderRadius * 2}"
|
|
||||||
>
|
|
||||||
<circle
|
|
||||||
stroke="white"
|
|
||||||
fill="transparent"
|
|
||||||
stroke-width=${STROKE_WIDTH}
|
|
||||||
stroke-dasharray="${circumference} ${circumference}"}
|
|
||||||
stroke-dashoffset="${strokeDashOffset}"
|
|
||||||
stroke-linecap="round"
|
|
||||||
r=${circleRadius}
|
|
||||||
cx=${borderRadius}
|
|
||||||
cy=${borderRadius}
|
|
||||||
/>
|
|
||||||
</svg>`;
|
|
||||||
} else {
|
|
||||||
(svg.firstElementChild as SVGElement).setAttribute('stroke-dashoffset', strokeDashOffset.toString());
|
|
||||||
}
|
|
||||||
}, [containerRef, circumference, borderRadius, circleRadius, progress]);
|
|
||||||
|
|
||||||
const className = buildClassName(
|
const className = buildClassName(
|
||||||
`ProgressSpinner size-${size}`,
|
`ProgressSpinner size-${size}`,
|
||||||
@ -71,10 +42,27 @@ const ProgressSpinner: FC<{
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
ref={containerRef}
|
|
||||||
className={className}
|
className={className}
|
||||||
onClick={onClick}
|
onClick={onClick}
|
||||||
/>
|
>
|
||||||
|
<svg
|
||||||
|
viewBox={`0 0 ${borderRadius * 2} ${borderRadius * 2}`}
|
||||||
|
height={borderRadius * 2}
|
||||||
|
width={borderRadius * 2}
|
||||||
|
>
|
||||||
|
<circle
|
||||||
|
stroke="white"
|
||||||
|
fill="transparent"
|
||||||
|
stroke-width={STROKE_WIDTH}
|
||||||
|
stroke-dasharray={`${circumference} ${circumference}`}
|
||||||
|
stroke-dashoffset={strokeDashOffset}
|
||||||
|
stroke-linecap="round"
|
||||||
|
r={circleRadius}
|
||||||
|
cx={borderRadius}
|
||||||
|
cy={borderRadius}
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user