Unique Gift Header: Add manage buttons (#6168)

This commit is contained in:
Alexander Zinchuk 2025-09-09 20:26:11 +02:00
parent dbe78ad9e7
commit 2d238be17b
20 changed files with 615 additions and 235 deletions

View File

@ -29,13 +29,14 @@ export default tseslint.config(
quoteProps: 'as-needed',
}),
globalIgnores([
'src/lib/rlottie/rlottie-wasm.js',
'src/lib/rlottie/**',
'src/lib/video-preview/polyfill',
'src/lib/fasttextweb/fasttext-wasm.cjs',
'src/lib/fasttextweb/**',
'src/lib/gramjs/tl/',
'src/lib/lovely-chart',
'src/lib/lovely-chart/**',
'src/lib/music-metadata-browser',
'src/lib/secret-sauce/',
'src/lib/fastBlur.js',
'src/types/language.d.ts',
'dist/',
'dist-electron/',
@ -56,6 +57,7 @@ export default tseslint.config(
'no-template-curly-in-string': 'error',
'object-shorthand': 'error',
curly: ['error', 'multi-line'],
eqeqeq: ['error', 'always'],
'no-implicit-coercion': [
'error',
{

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" viewBox="0 0 32 32"><path d="M22.4 16.4c-3.3 0-6 2.7-6 6s2.7 6 6 6 6-2.7 6-6-2.7-6-6-6m3.2 6.8-2.2 2.2c-.4.4-1.1.4-1.6 0-.4-.4-.4-1.1 0-1.6l.4-.4h-2.6c-.6 0-1.1-.5-1.1-1.1s.5-1.1 1.1-1.1h2.6l-.3-.2c-.4-.4-.4-1.1 0-1.6.4-.4 1.1-.4 1.6 0l2.2 2.2c.4.5.4 1.2-.1 1.6M22.4 10l-2.8-6.5h4.8c.5 0 1 .3 1.3.7l4.1 5.8zm-1.3 1.6-.9 3.2c-3.3 1-5.8 4-5.8 7.7 0 .6.1 1.2.2 1.8l-3.7-12.6h10.2zM11.3 10l2.3-5.5c.3-.6.8-1 1.5-1h1.6c.6 0 1.2.4 1.5 1l2.3 5.5zm19.1 1.6L27.1 16c-1.3-.9-2.9-1.5-4.7-1.5h-.5l.8-2.9zm-21 .7L14 27.9 1.6 11.6h7.7zm.2-2.3H2.1l4.1-5.8c.4-.4.8-.7 1.4-.7h4.8z"/></svg>

After

Width:  |  Height:  |  Size: 633 B

View File

@ -1469,7 +1469,7 @@
"GiftInfoPeerDescriptionOutConverted_one" = "{peer} converted this gift to **{amount}** Star.";
"GiftInfoPeerDescriptionOutConverted_other" = "{peer} converted this gift to **{amount}** Stars.";
"GiftInfoDescriptionFreeUpgrade" = "Upgrade this gift for free to turn it to a unique collectible.";
"GiftInfoDescriptionUpgrade" = "Upgrade this gift to turn it to a unique collectible.";
"GiftInfoDescriptionUpgrade2" = "Upgrade this gift to turn it to a unique collectible.";
"GiftInfoPeerDescriptionFreeUpgradeOut" = "{peer} can turn this gift to a unique collectible";
"GiftInfoDescriptionUpgraded" = "This gift was turned into a unique collectible.";
"GiftInfoFrom" = "From";

View File

@ -6,6 +6,7 @@ import type {
} from '../../../api/types';
import { DEFAULT_STATUS_ICON_ID, TME_LINK_PREFIX } from '../../../config';
import { STARS_CURRENCY_CODE } from '../../../config';
import { copyTextToClipboard } from '../../../util/clipboard';
import { formatDateAtTime } from '../../../util/dates/dateFormat';
import { getServerTime } from '../../../util/serverTime';
@ -125,7 +126,7 @@ const GiftMenuItems = ({
if (!savedGift || savedGift.gift.type !== 'starGiftUnique' || !savedGift.inputGift) return;
closeGiftInfoModal();
updateStarGiftPrice({ gift: savedGift.inputGift, price: {
currency: 'XTR', amount: 0, nanos: 0,
currency: STARS_CURRENCY_CODE, amount: 0, nanos: 0,
} });
showNotification({
icon: 'unlist-outline',

View File

@ -29,6 +29,7 @@ type OwnProps = {
footer?: TeactNode;
buttonText?: string;
className?: string;
contentClassName?: string;
hasBackdrop?: boolean;
onClose: NoneToVoidFunction;
onButtonClick?: NoneToVoidFunction;
@ -47,6 +48,7 @@ const TableInfoModal = ({
footer,
buttonText,
className,
contentClassName,
hasBackdrop,
onClose,
onButtonClick,
@ -70,7 +72,7 @@ const TableInfoModal = ({
header={modalHeader}
title={title}
className={className}
contentClassName={styles.content}
contentClassName={buildClassName(styles.content, contentClassName)}
onClose={onClose}
withBalanceBar={withBalanceBar}
currencyInBalanceBar={currencyInBalanceBar}

View File

@ -9,6 +9,24 @@
height: var(--_height);
margin-bottom: 0.5rem;
padding-bottom: 1rem;
&.withManageButtons {
--_height: 18.5rem;
.radialPattern {
inset: -8rem -5% -5% -5%;
}
.sticker {
margin-top: 2.5rem;
}
}
:global {
canvas {
transition: 250ms transform;
}
}
}
.subtitleBadge {

View File

@ -4,6 +4,7 @@ import { getActions } from '../../../global';
import type {
ApiPeer,
ApiSavedStarGift,
ApiStarGiftAttributeBackdrop, ApiStarGiftAttributeModel, ApiStarGiftAttributePattern,
ApiTypeCurrencyAmount } from '../../../api/types';
@ -15,7 +16,7 @@ import buildClassName from '../../../util/buildClassName';
import buildStyle from '../../../util/buildStyle';
import { useTransitionActiveKey } from '../../../hooks/animations/useTransitionActiveKey';
import useFlag from '../../../hooks/useFlag.ts';
import useFlag from '../../../hooks/useFlag';
import useLang from '../../../hooks/useLang';
import AnimatedIconFromSticker from '../../common/AnimatedIconFromSticker';
@ -23,6 +24,7 @@ import Icon from '../../common/icons/Icon';
import StarIcon from '../../common/icons/StarIcon';
import RadialPatternBackground from '../../common/profile/RadialPatternBackground';
import Transition from '../../ui/Transition';
import UniqueGiftManageButtons from './UniqueGiftManageButtons';
import styles from './UniqueGiftHeader.module.scss';
@ -35,6 +37,8 @@ type OwnProps = {
subtitlePeer?: ApiPeer;
className?: string;
resellPrice?: ApiTypeCurrencyAmount;
showManageButtons?: boolean;
savedGift?: ApiSavedStarGift;
};
const STICKER_SIZE = 120;
@ -48,13 +52,15 @@ const UniqueGiftHeader = ({
subtitlePeer,
className,
resellPrice,
showManageButtons,
savedGift,
}: OwnProps) => {
const {
openChat,
} = getActions();
const lang = useLang();
const [isHover, markHover, unmarkHover] = useFlag();
const [isGiftHover, markGiftHover, unmarkGiftHover] = useFlag(false);
const activeKey = useTransitionActiveKey([modelAttribute, backdropAttribute, patternAttribute]);
const subtitleColor = backdropAttribute?.textColor;
@ -73,10 +79,14 @@ const UniqueGiftHeader = ({
}, [backdropAttribute, patternAttribute]);
return (
<div className={buildClassName(styles.root, className)}>
<div className={buildClassName(styles.root,
isGiftHover && 'interactive-gift',
showManageButtons && styles.withManageButtons,
className)}
>
<Transition
className={styles.transition}
slideClassName={buildClassName('interactive-gift', styles.transitionSlide)}
slideClassName={buildClassName(styles.transitionSlide)}
activeKey={activeKey}
direction={1}
name="zoomBounceSemiFade"
@ -86,9 +96,9 @@ const UniqueGiftHeader = ({
className={styles.sticker}
sticker={modelAttribute.sticker}
size={STICKER_SIZE}
noLoop={!isHover}
onMouseEnter={!IS_TOUCH_ENV ? markHover : undefined}
onMouseLeave={!IS_TOUCH_ENV ? unmarkHover : undefined}
noLoop={!isGiftHover}
onMouseEnter={!IS_TOUCH_ENV ? markGiftHover : undefined}
onMouseLeave={!IS_TOUCH_ENV ? unmarkGiftHover : undefined}
/>
</Transition>
{title && <h1 className={styles.title}>{title}</h1>}
@ -105,6 +115,11 @@ const UniqueGiftHeader = ({
{subtitle}
</div>
)}
{savedGift && showManageButtons && (
<UniqueGiftManageButtons
savedGift={savedGift}
/>
)}
{resellPrice && (
<p className={styles.amount}>
<span>

View File

@ -0,0 +1,24 @@
.manageButtons {
z-index: 1;
display: flex;
gap: 0.5rem;
width: 100%;
margin-top: 1rem;
}
.manageButton {
flex-basis: 0;
flex-grow: 1;
}
.text {
font-size: 0.875rem;
font-weight: var(--font-weight-normal);
text-transform: lowercase;
}
.icon {
font-size: 1.5rem;
}

View File

@ -0,0 +1,212 @@
import { memo, useMemo } from '../../../lib/teact/teact';
import { getActions, getGlobal, withGlobal } from '../../../global';
import type {
ApiEmojiStatusCollectible,
ApiEmojiStatusType,
ApiSavedStarGift,
} from '../../../api/types';
import { DEFAULT_STATUS_ICON_ID } from '../../../config';
import { STARS_CURRENCY_CODE } from '../../../config';
import { selectTabState, selectUser } from '../../../global/selectors';
import { formatDateAtTime } from '../../../util/dates/dateFormat';
import { getServerTime } from '../../../util/serverTime';
import useLang from '../../../hooks/useLang';
import useLastCallback from '../../../hooks/useLastCallback';
import useOldLang from '../../../hooks/useOldLang';
import Button from '../../ui/Button';
import styles from './UniqueGiftManageButtons.module.scss';
type OwnProps = {
savedGift?: ApiSavedStarGift;
};
type StateProps = {
currentUserEmojiStatus?: ApiEmojiStatusType;
collectibleEmojiStatuses?: ApiEmojiStatusType[];
};
const UniqueGiftManageButtons = ({
savedGift,
currentUserEmojiStatus,
collectibleEmojiStatuses,
}: OwnProps & StateProps) => {
const {
openGiftTransferModal,
openGiftResalePriceComposerModal,
openGiftStatusInfoModal,
setEmojiStatus,
updateStarGiftPrice,
showNotification,
closeGiftInfoModal,
} = getActions();
const lang = useLang();
const oldLang = useOldLang();
const gift = savedGift?.gift;
const isGiftUnique = gift?.type === 'starGiftUnique';
const giftResalePrice = isGiftUnique ? gift.resellPrice : undefined;
const global = getGlobal();
const modal = selectTabState(global).giftInfoModal;
const peerId = modal?.peerId;
const starGiftUniqueSlug = gift?.type === 'starGiftUnique' ? gift.slug : undefined;
const userCollectibleStatus = useMemo(() => {
if (!starGiftUniqueSlug) return undefined;
return collectibleEmojiStatuses?.find((status) =>
status.type === 'collectible' && status.slug === starGiftUniqueSlug,
) as ApiEmojiStatusCollectible | undefined;
}, [starGiftUniqueSlug, collectibleEmojiStatuses]);
const currentUniqueEmojiStatusSlug = currentUserEmojiStatus?.type === 'collectible'
? currentUserEmojiStatus.slug : undefined;
const canTakeOff = starGiftUniqueSlug !== undefined && currentUniqueEmojiStatusSlug === starGiftUniqueSlug;
const canWear = Boolean(userCollectibleStatus) && !canTakeOff;
const handleTransfer = useLastCallback(() => {
if (!savedGift || savedGift?.gift.type !== 'starGiftUnique') return;
if (savedGift.canTransferAt && savedGift.canTransferAt > getServerTime()) {
showNotification({
message: {
key: 'NotificationGiftCanTransferAt',
variables: { date: formatDateAtTime(oldLang, savedGift.canTransferAt * 1000) },
},
});
return;
}
openGiftTransferModal({ gift: savedGift });
});
const handleWear = useLastCallback(() => {
if (canTakeOff) {
setEmojiStatus({
emojiStatus: { type: 'regular', documentId: DEFAULT_STATUS_ICON_ID },
});
} else if (userCollectibleStatus) {
openGiftStatusInfoModal({ emojiStatus: userCollectibleStatus });
}
});
const handleSell = useLastCallback(() => {
if (!savedGift || !peerId) return;
if (savedGift.canResellAt && savedGift.canResellAt > getServerTime()) {
showNotification({
message: {
key: 'NotificationGiftCanResellAt',
variables: { date: formatDateAtTime(oldLang, savedGift.canResellAt * 1000) },
},
});
return;
}
openGiftResalePriceComposerModal({ peerId, gift: savedGift });
});
const handleUnlist = useLastCallback(() => {
if (!savedGift || savedGift.gift.type !== 'starGiftUnique' || !savedGift.inputGift) return;
closeGiftInfoModal();
updateStarGiftPrice({ gift: savedGift.inputGift, price: {
currency: STARS_CURRENCY_CODE, amount: 0, nanos: 0,
} });
showNotification({
icon: 'unlist-outline',
message: {
key: 'NotificationGiftIsUnlist',
variables: { gift: lang('GiftUnique', { title: savedGift.gift.title, number: savedGift.gift.number }) },
},
});
});
return (
<div className={styles.manageButtons}>
<Button
color="transparentBlured"
iconName="gift-transfer-inline"
iconAlignment="top"
iconClassName={styles.icon}
onClick={handleTransfer}
ariaLabel={lang('GiftInfoTransfer')}
noForcedUpperCase
fluid
className={styles.manageButton}
>
<span className={styles.text}>
{lang('GiftInfoTransfer')}
</span>
</Button>
{(canWear || !canTakeOff) && (
<Button
color="transparentBlured"
iconName={canTakeOff ? 'crown-take-off' : 'crown-wear'}
iconAlignment="top"
iconClassName={styles.icon}
onClick={canWear || canTakeOff ? handleWear : undefined}
disabled={!canWear && !canTakeOff}
ariaLabel={lang(canTakeOff ? 'GiftInfoTakeOff' : 'GiftInfoWear')}
noForcedUpperCase
fluid
className={styles.manageButton}
>
<span className={styles.text}>
{lang(canTakeOff ? 'GiftInfoTakeOff' : 'GiftInfoWear')}
</span>
</Button>
)}
{!giftResalePrice && (
<Button
color="transparentBlured"
iconName="sell"
iconAlignment="top"
iconClassName={styles.icon}
onClick={handleSell}
ariaLabel={lang('Sell')}
noForcedUpperCase
fluid
className={styles.manageButton}
>
<span className={styles.text}>
{lang('Sell')}
</span>
</Button>
)}
{Boolean(giftResalePrice) && (
<Button
color="transparentBlured"
iconName="unlist"
iconAlignment="top"
iconClassName={styles.icon}
onClick={handleUnlist}
ariaLabel={lang('GiftInfoUnlist')}
noForcedUpperCase
fluid
className={styles.manageButton}
>
<span className={styles.text}>
{lang('GiftInfoUnlist')}
</span>
</Button>
)}
</div>
);
};
export default memo(withGlobal<OwnProps>(
(global): StateProps => {
const { currentUserId } = global;
const currentUser = currentUserId ? selectUser(global, currentUserId) : undefined;
const currentUserEmojiStatus = currentUser?.emojiStatus;
const collectibleEmojiStatuses = global.collectibleEmojiStatuses?.statuses;
return {
currentUserEmojiStatus,
collectibleEmojiStatuses,
};
},
)(UniqueGiftManageButtons));

View File

@ -36,11 +36,21 @@
color: var(--color-error);
}
.modalContent {
position: relative;
max-height: min(92vh, 46rem) !important;
}
.headerSplitButton {
position: absolute;
right: 0.375rem;
display: flex;
flex-direction: row;
border-radius: 1rem;
backdrop-filter: blur(0.5rem);
}
.headerButton,

View File

@ -145,9 +145,22 @@ const GiftInfoModal = ({
return lang('GiftInfoCollectible', { number: gift.number });
}, [gift, releasedByPeer, lang]);
const starGiftUniqueSlug = gift?.type === 'starGiftUnique' ? gift.slug : undefined;
const selfCollectibleStatus = useMemo(() => {
if (!starGiftUniqueSlug) return undefined;
return collectibleEmojiStatuses?.find((status) =>
status.type === 'collectible' && status.slug === starGiftUniqueSlug);
}, [starGiftUniqueSlug, collectibleEmojiStatuses]);
const isSelfUnique = Boolean(selfCollectibleStatus);
const canFocusUpgrade = Boolean(savedGift?.upgradeMsgId);
const canManage = !canFocusUpgrade && savedGift?.inputGift && (
isTargetChat ? hasAdminRights : renderingTargetPeer?.id === currentUserId
isTargetChat ? hasAdminRights
: gift?.type === 'starGift'
? renderingTargetPeer?.id === currentUserId
: gift?.ownerId === currentUserId || isSelfUnique
);
function getResalePrice(shouldPayInTon?: boolean) {
@ -164,7 +177,8 @@ const GiftInfoModal = ({
const resellPrice = getResalePrice();
const confirmPrice = getResalePrice(shouldPayInTon);
const canBuyGift = gift?.type === 'starGiftUnique' && gift.ownerId !== currentUserId && Boolean(resellPrice);
const canBuyGift = !isSelfUnique && gift?.type === 'starGiftUnique'
&& gift.ownerId !== currentUserId && Boolean(resellPrice);
const giftOwnerTitle = (() => {
if (!isGiftUnique) return undefined;
@ -277,7 +291,7 @@ const GiftInfoModal = ({
);
}
if (canManage && savedGift.canUpgrade && !savedGift.upgradeMsgId) {
if (canManage && savedGift?.canUpgrade && !savedGift.upgradeMsgId) {
return (
<Button isShiny onClick={handleOpenUpgradeModal}>
{lang('GiftInfoUpgrade')}
@ -327,7 +341,7 @@ const GiftInfoModal = ({
if (savedGift.upgradeMsgId) return lang('GiftInfoDescriptionUpgraded');
if (canManage && savedGift.canUpgrade && savedGift.alreadyPaidUpgradeStars && !savedGift.upgradeMsgId) {
return lang('GiftInfoDescriptionUpgrade');
return lang('GiftInfoDescriptionUpgrade2');
}
if (savedGift.canUpgrade && canManage) {
return canManage
@ -470,6 +484,8 @@ const GiftInfoModal = ({
title={gift.title}
subtitle={giftSubtitle}
subtitlePeer={releasedByPeer}
showManageButtons={canManage}
savedGift={savedGift}
/>
</div>
);
@ -779,6 +795,7 @@ const GiftInfoModal = ({
tableData={modalData?.tableData}
footer={modalData?.footer}
className={styles.modal}
contentClassName={styles.modalContent}
onClose={handleClose}
withBalanceBar={Boolean(canBuyGift)}
currencyInBalanceBar={confirmPrice?.currency}

View File

@ -137,13 +137,13 @@ const StarsBalanceModal = ({
const modalHeight = useMemo(() => {
const transactionCount = history?.all?.transactions.length || 0;
if (transactionCount == 1) {
if (transactionCount === 1) {
return '35.5rem';
}
if (transactionCount == 2) {
if (transactionCount === 2) {
return '39.25rem';
}
if (transactionCount == 3) {
if (transactionCount === 3) {
return '43rem';
}
return '45rem';

View File

@ -324,6 +324,16 @@
backdrop-filter: blur(50px);
}
&.transparentBlured {
color: white;
background-color: rgba(0, 0, 0, 0.2);
backdrop-filter: blur(0.5rem);
&:hover {
background-color: rgba(0, 0, 0, 0.1);
}
}
&.smaller {
height: 2.5rem;
padding: 0.3125rem;
@ -338,23 +348,13 @@
}
&.with-icon {
padding-right: 1.25rem;
padding-left: 0.75rem;
padding-inline-start: 0.75rem;
padding-inline-end: 1.25rem;
.icon {
margin-right: 0.5rem;
margin-inline-end: 0.5rem;
font-size: 1.5rem;
}
&[dir="rtl"] {
padding-right: 0.75rem;
padding-left: 1.25rem;
.icon {
margin-right: 0;
margin-left: 0.5rem;
}
}
}
}
@ -391,6 +391,11 @@
}
}
&.content-with-icon-bottom,
&.content-with-icon-top {
height: auto;
}
&.fluid {
width: auto;
padding-right: 1.75rem;
@ -471,4 +476,38 @@
&.rectangular {
border-radius: 0;
}
.with-icon-top {
display: flex;
flex-direction: column;
.icon {
margin-bottom: 0.25rem;
}
}
.with-icon-bottom {
display: flex;
flex-direction: column-reverse;
.icon {
margin-top: 0.25rem;
}
}
.with-icon-start {
display: flex;
.icon {
margin-inline-end: 0.25rem;
}
}
.with-icon-end {
display: flex;
flex-direction: row-reverse;
.icon {
margin-inline-start: 0.25rem;
}
}
}

View File

@ -3,6 +3,8 @@ import type { ElementRef, FC } from '../../lib/teact/teact';
import type React from '../../lib/teact/teact';
import { useRef, useState } from '../../lib/teact/teact';
import type { IconName } from '../../types/icons';
import { IS_TOUCH_ENV, MouseButton } from '../../util/browser/windowEnvironment';
import buildClassName from '../../util/buildClassName';
import buildStyle from '../../util/buildStyle';
@ -10,6 +12,7 @@ import buildStyle from '../../util/buildStyle';
import useLastCallback from '../../hooks/useLastCallback';
import useOldLang from '../../hooks/useOldLang';
import Icon from '../common/icons/Icon';
import Sparkles from '../common/Sparkles';
import RippleEffect from './RippleEffect';
import Spinner from './Spinner';
@ -23,7 +26,7 @@ export type OwnProps = {
size?: 'default' | 'smaller' | 'tiny';
color?: (
'primary' | 'secondary' | 'gray' | 'danger' | 'translucent' | 'translucent-white' | 'translucent-black'
| 'translucent-bordered' | 'dark' | 'green' | 'adaptive' | 'stars' | 'bluredStarsBadge'
| 'translucent-bordered' | 'dark' | 'green' | 'adaptive' | 'stars' | 'bluredStarsBadge' | 'transparentBlured'
);
backgroundImage?: string;
id?: string;
@ -55,6 +58,9 @@ export type OwnProps = {
noForcedUpperCase?: boolean;
shouldStopPropagation?: boolean;
style?: string;
iconName?: IconName;
iconAlignment?: 'top' | 'bottom' | 'start' | 'end';
iconClassName?: string;
onClick?: (e: ReactMouseEvent<HTMLButtonElement, MouseEvent>) => void;
onContextMenu?: (e: ReactMouseEvent<HTMLButtonElement, MouseEvent>) => void;
onMouseDown?: (e: ReactMouseEvent<HTMLButtonElement>) => void;
@ -112,6 +118,9 @@ const Button: FC<OwnProps> = ({
shouldStopPropagation,
noForcedUpperCase,
style,
iconName,
iconAlignment = 'start',
iconClassName,
}) => {
let elementRef = useRef<HTMLButtonElement | HTMLAnchorElement>();
if (ref) {
@ -146,6 +155,7 @@ const Button: FC<OwnProps> = ({
withPremiumGradient && 'premium',
isRectangular && 'rectangular',
noForcedUpperCase && 'no-upper-case',
iconAlignment && iconName && `content-with-icon-${iconAlignment}`,
);
const handleClick = useLastCallback((e: ReactMouseEvent<HTMLButtonElement, MouseEvent>) => {
@ -173,15 +183,39 @@ const Button: FC<OwnProps> = ({
}
});
const content = (
<>
{withSparkleEffect && <Sparkles preset="button" />}
{isLoading ? (
const renderIcon = () => {
if (!iconName) return undefined;
return <Icon name={iconName} className={iconClassName} />;
};
const renderContent = () => {
if (isLoading) {
return (
<div>
<span dir={isRtl ? 'auto' : undefined}>{lang('Cache.ClearProgress')}</span>
<Spinner color={isText ? 'blue' : 'white'} />
</div>
) : children}
);
}
const icon = renderIcon();
if (!icon) {
return children;
}
return (
<div className={`with-icon-${iconAlignment}`}>
{icon}
{children}
</div>
);
};
const content = (
<>
{withSparkleEffect && <Sparkles preset="button" />}
{renderContent()}
{!isNotInteractive && ripple && (
<RippleEffect />
)}

View File

@ -133,198 +133,199 @@ $icons-map: (
"frozen-time": "\f160",
"fullscreen": "\f161",
"gifs": "\f162",
"gift": "\f163",
"group-filled": "\f164",
"group": "\f165",
"grouped-disable": "\f166",
"grouped": "\f167",
"hand-stop": "\f168",
"hashtag": "\f169",
"hd-photo": "\f16a",
"heart-outline": "\f16b",
"heart": "\f16c",
"help": "\f16d",
"info-filled": "\f16e",
"info": "\f16f",
"install": "\f170",
"italic": "\f171",
"key": "\f172",
"keyboard": "\f173",
"lamp": "\f174",
"language": "\f175",
"large-pause": "\f176",
"large-play": "\f177",
"link-badge": "\f178",
"link-broken": "\f179",
"link": "\f17a",
"location": "\f17b",
"lock-badge": "\f17c",
"lock": "\f17d",
"logout": "\f17e",
"loop": "\f17f",
"mention": "\f180",
"message-failed": "\f181",
"message-pending": "\f182",
"message-read": "\f183",
"message-succeeded": "\f184",
"message": "\f185",
"microphone-alt": "\f186",
"microphone": "\f187",
"monospace": "\f188",
"more-circle": "\f189",
"more": "\f18a",
"move-caption-down": "\f18b",
"move-caption-up": "\f18c",
"mute": "\f18d",
"muted": "\f18e",
"my-notes": "\f18f",
"new-chat-filled": "\f190",
"next": "\f191",
"nochannel": "\f192",
"noise-suppression": "\f193",
"non-contacts": "\f194",
"one-filled": "\f195",
"open-in-new-tab": "\f196",
"password-off": "\f197",
"pause": "\f198",
"permissions": "\f199",
"phone-discard-outline": "\f19a",
"phone-discard": "\f19b",
"phone": "\f19c",
"photo": "\f19d",
"pin-badge": "\f19e",
"pin-list": "\f19f",
"pin": "\f1a0",
"pinned-chat": "\f1a1",
"pinned-message": "\f1a2",
"pip": "\f1a3",
"play-story": "\f1a4",
"play": "\f1a5",
"poll": "\f1a6",
"previous": "\f1a7",
"privacy-policy": "\f1a8",
"proof-of-ownership": "\f1a9",
"quote-text": "\f1aa",
"quote": "\f1ab",
"radial-badge": "\f1ac",
"rating-icons-level1": "\f1ad",
"rating-icons-level10": "\f1ae",
"rating-icons-level2": "\f1af",
"rating-icons-level20": "\f1b0",
"rating-icons-level3": "\f1b1",
"rating-icons-level30": "\f1b2",
"rating-icons-level4": "\f1b3",
"rating-icons-level40": "\f1b4",
"rating-icons-level5": "\f1b5",
"rating-icons-level50": "\f1b6",
"rating-icons-level6": "\f1b7",
"rating-icons-level60": "\f1b8",
"rating-icons-level7": "\f1b9",
"rating-icons-level70": "\f1ba",
"rating-icons-level8": "\f1bb",
"rating-icons-level80": "\f1bc",
"rating-icons-level9": "\f1bd",
"rating-icons-level90": "\f1be",
"rating-icons-negative": "\f1bf",
"readchats": "\f1c0",
"recent": "\f1c1",
"refund": "\f1c2",
"reload": "\f1c3",
"remove-quote": "\f1c4",
"remove": "\f1c5",
"reopen-topic": "\f1c6",
"replace": "\f1c7",
"replies": "\f1c8",
"reply-filled": "\f1c9",
"reply": "\f1ca",
"revenue-split": "\f1cb",
"revote": "\f1cc",
"save-story": "\f1cd",
"saved-messages": "\f1ce",
"schedule": "\f1cf",
"sd-photo": "\f1d0",
"search": "\f1d1",
"select": "\f1d2",
"sell-outline": "\f1d3",
"sell": "\f1d4",
"send-outline": "\f1d5",
"send": "\f1d6",
"settings-filled": "\f1d7",
"settings": "\f1d8",
"share-filled": "\f1d9",
"share-screen-outlined": "\f1da",
"share-screen-stop": "\f1db",
"share-screen": "\f1dc",
"show-message": "\f1dd",
"sidebar": "\f1de",
"skip-next": "\f1df",
"skip-previous": "\f1e0",
"smallscreen": "\f1e1",
"smile": "\f1e2",
"sort-by-date": "\f1e3",
"sort-by-number": "\f1e4",
"sort-by-price": "\f1e5",
"sort": "\f1e6",
"speaker-muted-story": "\f1e7",
"speaker-outline": "\f1e8",
"speaker-story": "\f1e9",
"speaker": "\f1ea",
"spoiler-disable": "\f1eb",
"spoiler": "\f1ec",
"sport": "\f1ed",
"star": "\f1ee",
"stars-lock": "\f1ef",
"stats": "\f1f0",
"stealth-future": "\f1f1",
"stealth-past": "\f1f2",
"stickers": "\f1f3",
"stop-raising-hand": "\f1f4",
"stop": "\f1f5",
"story-caption": "\f1f6",
"story-expired": "\f1f7",
"story-priority": "\f1f8",
"story-reply": "\f1f9",
"strikethrough": "\f1fa",
"tag-add": "\f1fb",
"tag-crossed": "\f1fc",
"tag-filter": "\f1fd",
"tag-name": "\f1fe",
"tag": "\f1ff",
"timer": "\f200",
"toncoin": "\f201",
"trade": "\f202",
"transcribe": "\f203",
"truck": "\f204",
"unarchive": "\f205",
"underlined": "\f206",
"understood": "\f207",
"unique-profile": "\f208",
"unlist-outline": "\f209",
"unlist": "\f20a",
"unlock-badge": "\f20b",
"unlock": "\f20c",
"unmute": "\f20d",
"unpin": "\f20e",
"unread": "\f20f",
"up": "\f210",
"user-filled": "\f211",
"user-online": "\f212",
"user-stars": "\f213",
"user": "\f214",
"video-outlined": "\f215",
"video-stop": "\f216",
"video": "\f217",
"view-once": "\f218",
"voice-chat": "\f219",
"volume-1": "\f21a",
"volume-2": "\f21b",
"volume-3": "\f21c",
"warning": "\f21d",
"web": "\f21e",
"webapp": "\f21f",
"word-wrap": "\f220",
"zoom-in": "\f221",
"zoom-out": "\f222",
"gift-transfer-inline": "\f163",
"gift": "\f164",
"group-filled": "\f165",
"group": "\f166",
"grouped-disable": "\f167",
"grouped": "\f168",
"hand-stop": "\f169",
"hashtag": "\f16a",
"hd-photo": "\f16b",
"heart-outline": "\f16c",
"heart": "\f16d",
"help": "\f16e",
"info-filled": "\f16f",
"info": "\f170",
"install": "\f171",
"italic": "\f172",
"key": "\f173",
"keyboard": "\f174",
"lamp": "\f175",
"language": "\f176",
"large-pause": "\f177",
"large-play": "\f178",
"link-badge": "\f179",
"link-broken": "\f17a",
"link": "\f17b",
"location": "\f17c",
"lock-badge": "\f17d",
"lock": "\f17e",
"logout": "\f17f",
"loop": "\f180",
"mention": "\f181",
"message-failed": "\f182",
"message-pending": "\f183",
"message-read": "\f184",
"message-succeeded": "\f185",
"message": "\f186",
"microphone-alt": "\f187",
"microphone": "\f188",
"monospace": "\f189",
"more-circle": "\f18a",
"more": "\f18b",
"move-caption-down": "\f18c",
"move-caption-up": "\f18d",
"mute": "\f18e",
"muted": "\f18f",
"my-notes": "\f190",
"new-chat-filled": "\f191",
"next": "\f192",
"nochannel": "\f193",
"noise-suppression": "\f194",
"non-contacts": "\f195",
"one-filled": "\f196",
"open-in-new-tab": "\f197",
"password-off": "\f198",
"pause": "\f199",
"permissions": "\f19a",
"phone-discard-outline": "\f19b",
"phone-discard": "\f19c",
"phone": "\f19d",
"photo": "\f19e",
"pin-badge": "\f19f",
"pin-list": "\f1a0",
"pin": "\f1a1",
"pinned-chat": "\f1a2",
"pinned-message": "\f1a3",
"pip": "\f1a4",
"play-story": "\f1a5",
"play": "\f1a6",
"poll": "\f1a7",
"previous": "\f1a8",
"privacy-policy": "\f1a9",
"proof-of-ownership": "\f1aa",
"quote-text": "\f1ab",
"quote": "\f1ac",
"radial-badge": "\f1ad",
"rating-icons-level1": "\f1ae",
"rating-icons-level10": "\f1af",
"rating-icons-level2": "\f1b0",
"rating-icons-level20": "\f1b1",
"rating-icons-level3": "\f1b2",
"rating-icons-level30": "\f1b3",
"rating-icons-level4": "\f1b4",
"rating-icons-level40": "\f1b5",
"rating-icons-level5": "\f1b6",
"rating-icons-level50": "\f1b7",
"rating-icons-level6": "\f1b8",
"rating-icons-level60": "\f1b9",
"rating-icons-level7": "\f1ba",
"rating-icons-level70": "\f1bb",
"rating-icons-level8": "\f1bc",
"rating-icons-level80": "\f1bd",
"rating-icons-level9": "\f1be",
"rating-icons-level90": "\f1bf",
"rating-icons-negative": "\f1c0",
"readchats": "\f1c1",
"recent": "\f1c2",
"refund": "\f1c3",
"reload": "\f1c4",
"remove-quote": "\f1c5",
"remove": "\f1c6",
"reopen-topic": "\f1c7",
"replace": "\f1c8",
"replies": "\f1c9",
"reply-filled": "\f1ca",
"reply": "\f1cb",
"revenue-split": "\f1cc",
"revote": "\f1cd",
"save-story": "\f1ce",
"saved-messages": "\f1cf",
"schedule": "\f1d0",
"sd-photo": "\f1d1",
"search": "\f1d2",
"select": "\f1d3",
"sell-outline": "\f1d4",
"sell": "\f1d5",
"send-outline": "\f1d6",
"send": "\f1d7",
"settings-filled": "\f1d8",
"settings": "\f1d9",
"share-filled": "\f1da",
"share-screen-outlined": "\f1db",
"share-screen-stop": "\f1dc",
"share-screen": "\f1dd",
"show-message": "\f1de",
"sidebar": "\f1df",
"skip-next": "\f1e0",
"skip-previous": "\f1e1",
"smallscreen": "\f1e2",
"smile": "\f1e3",
"sort-by-date": "\f1e4",
"sort-by-number": "\f1e5",
"sort-by-price": "\f1e6",
"sort": "\f1e7",
"speaker-muted-story": "\f1e8",
"speaker-outline": "\f1e9",
"speaker-story": "\f1ea",
"speaker": "\f1eb",
"spoiler-disable": "\f1ec",
"spoiler": "\f1ed",
"sport": "\f1ee",
"star": "\f1ef",
"stars-lock": "\f1f0",
"stats": "\f1f1",
"stealth-future": "\f1f2",
"stealth-past": "\f1f3",
"stickers": "\f1f4",
"stop-raising-hand": "\f1f5",
"stop": "\f1f6",
"story-caption": "\f1f7",
"story-expired": "\f1f8",
"story-priority": "\f1f9",
"story-reply": "\f1fa",
"strikethrough": "\f1fb",
"tag-add": "\f1fc",
"tag-crossed": "\f1fd",
"tag-filter": "\f1fe",
"tag-name": "\f1ff",
"tag": "\f200",
"timer": "\f201",
"toncoin": "\f202",
"trade": "\f203",
"transcribe": "\f204",
"truck": "\f205",
"unarchive": "\f206",
"underlined": "\f207",
"understood": "\f208",
"unique-profile": "\f209",
"unlist-outline": "\f20a",
"unlist": "\f20b",
"unlock-badge": "\f20c",
"unlock": "\f20d",
"unmute": "\f20e",
"unpin": "\f20f",
"unread": "\f210",
"up": "\f211",
"user-filled": "\f212",
"user-online": "\f213",
"user-stars": "\f214",
"user": "\f215",
"video-outlined": "\f216",
"video-stop": "\f217",
"video": "\f218",
"view-once": "\f219",
"voice-chat": "\f21a",
"volume-1": "\f21b",
"volume-2": "\f21c",
"volume-3": "\f21d",
"warning": "\f21e",
"web": "\f21f",
"webapp": "\f220",
"word-wrap": "\f221",
"zoom-in": "\f222",
"zoom-out": "\f223",
);
.icon-active-sessions::before {
@ -621,6 +622,9 @@ $icons-map: (
.icon-gifs::before {
content: map.get($icons-map, "gifs");
}
.icon-gift-transfer-inline::before {
content: map.get($icons-map, "gift-transfer-inline");
}
.icon-gift::before {
content: map.get($icons-map, "gift");
}

Binary file not shown.

Binary file not shown.

View File

@ -97,6 +97,7 @@ export type FontIconName =
| 'frozen-time'
| 'fullscreen'
| 'gifs'
| 'gift-transfer-inline'
| 'gift'
| 'group-filled'
| 'group'

View File

@ -1230,7 +1230,7 @@ export interface LangPair {
'GiftInfoDescriptionRegular': undefined;
'GiftInfoDescriptionUpgradeRegular': undefined;
'GiftInfoDescriptionFreeUpgrade': undefined;
'GiftInfoDescriptionUpgrade': undefined;
'GiftInfoDescriptionUpgrade2': undefined;
'GiftInfoDescriptionUpgraded': undefined;
'GiftInfoFrom': undefined;
'GiftInfoDate': undefined;

View File

@ -26,7 +26,7 @@ export function rgb2hex(param: [number, number, number]) {
const p0 = param[0].toString(16);
const p1 = param[1].toString(16);
const p2 = param[2].toString(16);
return (p0.length == 1 ? '0' + p0 : p0) + (p1.length == 1 ? '0' + p1 : p1) + (p2.length == 1 ? '0' + p2 : p2);
return (p0.length === 1 ? '0' + p0 : p0) + (p1.length === 1 ? '0' + p1 : p1) + (p2.length === 1 ? '0' + p2 : p2);
}
/**
@ -49,9 +49,9 @@ export function rgb2hsb([r, g, b]: [number, number, number]): [number, number, n
let h!: number, s: number, v: number = max;
let d = max - min;
s = max == 0 ? 0 : d / max;
s = max === 0 ? 0 : d / max;
if (max == min) {
if (max === min) {
h = 0; // achromatic
} else {
switch (max) {