Gifts: Add attribute preview modal (#6710)
This commit is contained in:
parent
52da4643c1
commit
64c5b740d3
@ -190,7 +190,7 @@ const NewComp = (props: OwnProps & StateProps) => { … }
|
|||||||
```
|
```
|
||||||
|
|
||||||
### 5. Memoization
|
### 5. Memoization
|
||||||
* Wrap most components with `memo()` to avoid unnecessary updates.
|
* Wrap most components with `memo()` to avoid unnecessary updates. Consider skipping memo for simple wrapper components whose children change on almost every render.
|
||||||
* Don't pass freshly created objects or arrays as props to memoized components.
|
* Don't pass freshly created objects or arrays as props to memoized components.
|
||||||
* **Exceptions** (no memo): `ListItem`, `Button`, `MenuItem`, etc.
|
* **Exceptions** (no memo): `ListItem`, `Button`, `MenuItem`, etc.
|
||||||
|
|
||||||
|
|||||||
@ -19,6 +19,7 @@ import {
|
|||||||
buildApiResaleGifts,
|
buildApiResaleGifts,
|
||||||
buildApiSavedStarGift,
|
buildApiSavedStarGift,
|
||||||
buildApiStarGift,
|
buildApiStarGift,
|
||||||
|
buildApiStarGiftAttribute,
|
||||||
buildApiStarGiftAuctionAcquiredGift,
|
buildApiStarGiftAuctionAcquiredGift,
|
||||||
buildApiStarGiftAuctionState,
|
buildApiStarGiftAuctionState,
|
||||||
buildApiStarGiftCollection,
|
buildApiStarGiftCollection,
|
||||||
@ -629,3 +630,21 @@ export function resolveStarGiftOffer({
|
|||||||
shouldReturnTrue: true,
|
shouldReturnTrue: true,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function fetchStarGiftUpgradeAttributes({
|
||||||
|
giftId,
|
||||||
|
}: {
|
||||||
|
giftId: string;
|
||||||
|
}) {
|
||||||
|
const result = await invokeRequest(new GramJs.payments.GetStarGiftUpgradeAttributes({
|
||||||
|
giftId: BigInt(giftId),
|
||||||
|
}));
|
||||||
|
|
||||||
|
if (!result) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
attributes: result.attributes.map(buildApiStarGiftAttribute).filter(Boolean),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|||||||
@ -2665,3 +2665,17 @@
|
|||||||
"ChatListAuctionView" = "View";
|
"ChatListAuctionView" = "View";
|
||||||
"BotAuthSuccessTitle" = "Login Successful";
|
"BotAuthSuccessTitle" = "Login Successful";
|
||||||
"BotAuthSuccessText" = "You've successfully logged into **{url}**";
|
"BotAuthSuccessText" = "You've successfully logged into **{url}**";
|
||||||
|
"GiftPreviewSelectedTraits" = "Selected traits";
|
||||||
|
"GiftPreviewCountModels_one" = "This collection features **{count}** unique model";
|
||||||
|
"GiftPreviewCountModels_other" = "This collection features **{count}** unique models";
|
||||||
|
"GiftPreviewCountCraftableModels_one" = "This collection features **{count}** craftable model";
|
||||||
|
"GiftPreviewCountCraftableModels_other" = "This collection features **{count}** craftable models";
|
||||||
|
"GiftPreviewCountPatterns_one" = "This collection features **{count}** unique pattern";
|
||||||
|
"GiftPreviewCountPatterns_other" = "This collection features **{count}** unique patterns";
|
||||||
|
"GiftPreviewCountBackdrops_one" = "This collection features **{count}** unique backdrop";
|
||||||
|
"GiftPreviewCountBackdrops_other" = "This collection features **{count}** unique backdrops";
|
||||||
|
"GiftUpgradeViewAll" = "View all variants >";
|
||||||
|
"GiftPreviewToggleCraftableModels" = "View Craftable Models >";
|
||||||
|
"GiftPreviewToggleRegularModels" = "View Primary Models >";
|
||||||
|
"AriaGiftPreviewPlay" = "Play random previews";
|
||||||
|
"AriaGiftPreviewStop" = "Pause random previews";
|
||||||
|
|||||||
@ -13,6 +13,7 @@ export { default as GiftInfoValueModal } from '../components/modals/gift/value/G
|
|||||||
export { default as GiftLockedModal } from '../components/modals/gift/locked/GiftLockedModal';
|
export { default as GiftLockedModal } from '../components/modals/gift/locked/GiftLockedModal';
|
||||||
export { default as GiftResalePriceComposerModal } from '../components/modals/gift/resale/GiftResalePriceComposerModal';
|
export { default as GiftResalePriceComposerModal } from '../components/modals/gift/resale/GiftResalePriceComposerModal';
|
||||||
export { default as GiftUpgradeModal } from '../components/modals/gift/upgrade/GiftUpgradeModal';
|
export { default as GiftUpgradeModal } from '../components/modals/gift/upgrade/GiftUpgradeModal';
|
||||||
|
export { default as GiftPreviewModal } from '../components/modals/gift/preview/GiftPreviewModal';
|
||||||
export { default as GiftAuctionModal } from '../components/modals/gift/auction/GiftAuctionModal';
|
export { default as GiftAuctionModal } from '../components/modals/gift/auction/GiftAuctionModal';
|
||||||
export { default as GiftAuctionBidModal } from '../components/modals/gift/auction/GiftAuctionBidModal';
|
export { default as GiftAuctionBidModal } from '../components/modals/gift/auction/GiftAuctionBidModal';
|
||||||
export { default as GiftAuctionInfoModal } from '../components/modals/gift/auction/GiftAuctionInfoModal';
|
export { default as GiftAuctionInfoModal } from '../components/modals/gift/auction/GiftAuctionInfoModal';
|
||||||
|
|||||||
@ -1,5 +1,4 @@
|
|||||||
import type { FC } from '../../../lib/teact/teact';
|
import type { TeactNode } from '../../../lib/teact/teact';
|
||||||
import type React from '../../../lib/teact/teact';
|
|
||||||
import { memo } from '../../../lib/teact/teact';
|
import { memo } from '../../../lib/teact/teact';
|
||||||
|
|
||||||
import type { IconName } from '../../../types/icons';
|
import type { IconName } from '../../../types/icons';
|
||||||
@ -12,18 +11,17 @@ import Button from '../../ui/Button';
|
|||||||
import styles from './PhoneCallButton.module.scss';
|
import styles from './PhoneCallButton.module.scss';
|
||||||
|
|
||||||
type OwnProps = {
|
type OwnProps = {
|
||||||
onClick: VoidFunction;
|
|
||||||
label: string;
|
label: string;
|
||||||
icon?: IconName;
|
icon?: IconName;
|
||||||
iconClassName?: string;
|
iconClassName?: string;
|
||||||
customIcon?: React.ReactNode;
|
customIcon?: TeactNode;
|
||||||
className?: string;
|
className?: string;
|
||||||
isDisabled?: boolean;
|
isDisabled?: boolean;
|
||||||
isActive?: boolean;
|
isActive?: boolean;
|
||||||
|
onClick: VoidFunction;
|
||||||
};
|
};
|
||||||
|
|
||||||
const PhoneCallButton: FC<OwnProps> = ({
|
const PhoneCallButton = ({
|
||||||
onClick,
|
|
||||||
label,
|
label,
|
||||||
customIcon,
|
customIcon,
|
||||||
icon,
|
icon,
|
||||||
@ -31,7 +29,8 @@ const PhoneCallButton: FC<OwnProps> = ({
|
|||||||
className,
|
className,
|
||||||
isDisabled,
|
isDisabled,
|
||||||
isActive,
|
isActive,
|
||||||
}) => {
|
onClick,
|
||||||
|
}: OwnProps) => {
|
||||||
return (
|
return (
|
||||||
<div className={styles.root}>
|
<div className={styles.root}>
|
||||||
<Button
|
<Button
|
||||||
|
|||||||
@ -1,21 +1,32 @@
|
|||||||
.root {
|
.crafted {
|
||||||
&.uncommon {
|
--_color: revert;
|
||||||
color: var(--color-gift-uncommon);
|
--_background-color: revert;
|
||||||
background-color: var(--color-gift-uncommon-bg);
|
|
||||||
}
|
|
||||||
|
|
||||||
&.rare {
|
color: var(--_color);
|
||||||
color: var(--color-gift-rare);
|
background-color: var(--_background-color);
|
||||||
background-color: var(--color-gift-rare-bg);
|
}
|
||||||
}
|
|
||||||
|
.inverted {
|
||||||
&.epic {
|
color: var(--color-white);
|
||||||
color: var(--color-gift-epic);
|
background-color: var(--_color);
|
||||||
background-color: var(--color-gift-epic-bg);
|
}
|
||||||
}
|
|
||||||
|
.uncommon {
|
||||||
&.legendary {
|
--_color: var(--color-gift-uncommon);
|
||||||
color: var(--color-gift-legendary);
|
--_background-color: var(--color-gift-uncommon-bg);
|
||||||
background-color: var(--color-gift-legendary-bg);
|
}
|
||||||
}
|
|
||||||
|
.rare {
|
||||||
|
--_color: var(--color-gift-rare);
|
||||||
|
--_background-color: var(--color-gift-rare-bg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.epic {
|
||||||
|
--_color: var(--color-gift-epic);
|
||||||
|
--_background-color: var(--color-gift-epic-bg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.legendary {
|
||||||
|
--_color: var(--color-gift-legendary);
|
||||||
|
--_background-color: var(--color-gift-legendary-bg);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,5 +1,3 @@
|
|||||||
import { memo } from '../../lib/teact/teact';
|
|
||||||
|
|
||||||
import type { ApiStarGiftAttributeRarity } from '../../api/types';
|
import type { ApiStarGiftAttributeRarity } from '../../api/types';
|
||||||
|
|
||||||
import buildClassName from '../../util/buildClassName';
|
import buildClassName from '../../util/buildClassName';
|
||||||
@ -13,16 +11,28 @@ import styles from './GiftRarityBadge.module.scss';
|
|||||||
|
|
||||||
type OwnProps = {
|
type OwnProps = {
|
||||||
rarity: ApiStarGiftAttributeRarity;
|
rarity: ApiStarGiftAttributeRarity;
|
||||||
|
shouldInvertRare?: boolean;
|
||||||
|
className?: string;
|
||||||
|
onClick?: NoneToVoidFunction;
|
||||||
};
|
};
|
||||||
|
|
||||||
const GiftRarityBadge = ({ rarity }: OwnProps) => {
|
const GiftRarityBadge = ({ rarity, shouldInvertRare, className, onClick }: OwnProps) => {
|
||||||
const lang = useLang();
|
const lang = useLang();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<BadgeButton className={buildClassName(styles.root, rarity.type !== 'regular' && styles[rarity.type])}>
|
<BadgeButton
|
||||||
|
className={buildClassName(
|
||||||
|
styles.root,
|
||||||
|
rarity.type !== 'regular' && styles[rarity.type],
|
||||||
|
rarity.type !== 'regular' && styles.crafted,
|
||||||
|
shouldInvertRare && rarity.type !== 'regular' && styles.inverted,
|
||||||
|
className,
|
||||||
|
)}
|
||||||
|
onClick={onClick}
|
||||||
|
>
|
||||||
{getGiftRarityTitle(lang, rarity)}
|
{getGiftRarityTitle(lang, rarity)}
|
||||||
</BadgeButton>
|
</BadgeButton>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default memo(GiftRarityBadge);
|
export default GiftRarityBadge;
|
||||||
|
|||||||
@ -32,6 +32,7 @@ import GiftInfoModal from './gift/info/GiftInfoModal.async';
|
|||||||
import GiftLockedModal from './gift/locked/GiftLockedModal.async';
|
import GiftLockedModal from './gift/locked/GiftLockedModal.async';
|
||||||
import GiftDescriptionRemoveModal from './gift/message/GiftDescriptionRemoveModal.async';
|
import GiftDescriptionRemoveModal from './gift/message/GiftDescriptionRemoveModal.async';
|
||||||
import GiftOfferAcceptModal from './gift/offer/GiftOfferAcceptModal.async';
|
import GiftOfferAcceptModal from './gift/offer/GiftOfferAcceptModal.async';
|
||||||
|
import GiftPreviewModal from './gift/preview/GiftPreviewModal.async';
|
||||||
import GiftRecipientPicker from './gift/recipient/GiftRecipientPicker.async';
|
import GiftRecipientPicker from './gift/recipient/GiftRecipientPicker.async';
|
||||||
import GiftResalePriceComposerModal from './gift/resale/GiftResalePriceComposerModal.async';
|
import GiftResalePriceComposerModal from './gift/resale/GiftResalePriceComposerModal.async';
|
||||||
import StarGiftPriceDecreaseInfoModal from './gift/StarGiftPriceDecreaseInfoModal.async';
|
import StarGiftPriceDecreaseInfoModal from './gift/StarGiftPriceDecreaseInfoModal.async';
|
||||||
@ -105,6 +106,7 @@ type ModalKey = keyof Pick<TabState,
|
|||||||
'emojiStatusAccessModal' |
|
'emojiStatusAccessModal' |
|
||||||
'locationAccessModal' |
|
'locationAccessModal' |
|
||||||
'aboutAdsModal' |
|
'aboutAdsModal' |
|
||||||
|
'giftPreviewModal' |
|
||||||
'giftUpgradeModal' |
|
'giftUpgradeModal' |
|
||||||
'giftAuctionModal' |
|
'giftAuctionModal' |
|
||||||
'giftAuctionBidModal' |
|
'giftAuctionBidModal' |
|
||||||
@ -184,6 +186,7 @@ const MODALS: ModalRegistry = {
|
|||||||
emojiStatusAccessModal: EmojiStatusAccessModal,
|
emojiStatusAccessModal: EmojiStatusAccessModal,
|
||||||
locationAccessModal: LocationAccessModal,
|
locationAccessModal: LocationAccessModal,
|
||||||
aboutAdsModal: AboutAdsModal,
|
aboutAdsModal: AboutAdsModal,
|
||||||
|
giftPreviewModal: GiftPreviewModal,
|
||||||
giftUpgradeModal: GiftUpgradeModal,
|
giftUpgradeModal: GiftUpgradeModal,
|
||||||
giftAuctionModal: GiftAuctionModal,
|
giftAuctionModal: GiftAuctionModal,
|
||||||
giftAuctionBidModal: GiftAuctionBidModal,
|
giftAuctionBidModal: GiftAuctionBidModal,
|
||||||
|
|||||||
@ -4,7 +4,6 @@
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 1rem;
|
gap: 1rem;
|
||||||
|
|
||||||
max-height: min(92vh, 45rem) !important;
|
|
||||||
padding-inline: 1rem !important;
|
padding-inline: 1rem !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
91
src/components/modals/gift/GiftAttributeItem.module.scss
Normal file
91
src/components/modals/gift/GiftAttributeItem.module.scss
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
.root {
|
||||||
|
--_selected-color: var(--color-primary);
|
||||||
|
|
||||||
|
cursor: var(--custom-cursor, pointer);
|
||||||
|
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
|
padding: 0.875rem;
|
||||||
|
border-radius: 1rem;
|
||||||
|
|
||||||
|
color: #fff;
|
||||||
|
|
||||||
|
background-color: var(--color-background-secondary);
|
||||||
|
outline: 0.125rem solid transparent;
|
||||||
|
|
||||||
|
transition: outline-color 0.15s ease-out;
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
pointer-events: none;
|
||||||
|
content: "";
|
||||||
|
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
|
||||||
|
border-radius: inherit;
|
||||||
|
|
||||||
|
opacity: 0;
|
||||||
|
background-color: var(--color-hover-overlay);
|
||||||
|
|
||||||
|
transition: opacity 0.15s;
|
||||||
|
}
|
||||||
|
|
||||||
|
&::after {
|
||||||
|
pointer-events: none;
|
||||||
|
content: "";
|
||||||
|
|
||||||
|
position: absolute;
|
||||||
|
inset: 0.125rem;
|
||||||
|
|
||||||
|
border: 0.125rem solid transparent;
|
||||||
|
border-radius: 0.9375rem;
|
||||||
|
|
||||||
|
transition: border-color 0.15s linear;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.hasBackground {
|
||||||
|
--_selected-color: var(--color-background-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
:global(html.theme-dark) & {
|
||||||
|
background-color: rgb(33, 33, 33);
|
||||||
|
|
||||||
|
&.hasBackground {
|
||||||
|
--_selected-color: rgb(33, 33, 33);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover::before {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.selected::after {
|
||||||
|
border-color: var(--_selected-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.radialPattern {
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stickerWrapper {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rarity {
|
||||||
|
position: absolute;
|
||||||
|
top: 0.5rem;
|
||||||
|
right: 0.4375rem;
|
||||||
|
backdrop-filter: blur(0.5rem);
|
||||||
|
}
|
||||||
|
|
||||||
|
.regular {
|
||||||
|
color: white;
|
||||||
|
backdrop-filter: blur(0.5rem) brightness(0.8);
|
||||||
|
}
|
||||||
123
src/components/modals/gift/GiftAttributeItem.tsx
Normal file
123
src/components/modals/gift/GiftAttributeItem.tsx
Normal file
@ -0,0 +1,123 @@
|
|||||||
|
import type { ElementRef, TeactNode } from '@teact';
|
||||||
|
import { memo, useMemo, useRef } from '@teact';
|
||||||
|
|
||||||
|
import type {
|
||||||
|
ApiStarGiftAttributeBackdrop,
|
||||||
|
ApiStarGiftAttributeRarity,
|
||||||
|
ApiSticker,
|
||||||
|
} from '../../../api/types';
|
||||||
|
|
||||||
|
import buildClassName from '../../../util/buildClassName';
|
||||||
|
|
||||||
|
import useDynamicColorListener from '../../../hooks/stickers/useDynamicColorListener';
|
||||||
|
import { type ObserveFn } from '../../../hooks/useIntersectionObserver';
|
||||||
|
import useLastCallback from '../../../hooks/useLastCallback';
|
||||||
|
|
||||||
|
import GiftRarityBadge from '../../common/GiftRarityBadge';
|
||||||
|
import RadialPatternBackground from '../../common/profile/RadialPatternBackground';
|
||||||
|
import StickerView from '../../common/StickerView';
|
||||||
|
|
||||||
|
import styles from './GiftAttributeItem.module.scss';
|
||||||
|
|
||||||
|
type OwnProps<T> = {
|
||||||
|
ref?: ElementRef<HTMLDivElement>;
|
||||||
|
children?: TeactNode;
|
||||||
|
backdrop?: ApiStarGiftAttributeBackdrop;
|
||||||
|
patternSticker?: ApiSticker;
|
||||||
|
sticker?: ApiSticker;
|
||||||
|
stickerSize?: number;
|
||||||
|
stickerNoPlay?: boolean;
|
||||||
|
rarity?: ApiStarGiftAttributeRarity;
|
||||||
|
isSelected?: boolean;
|
||||||
|
className?: string;
|
||||||
|
clickArg?: T;
|
||||||
|
observeIntersection?: ObserveFn;
|
||||||
|
onClick?: (arg: T) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
const DEFAULT_STICKER_SIZE = 90;
|
||||||
|
|
||||||
|
const GiftAttributeItem = <T,>({
|
||||||
|
ref,
|
||||||
|
backdrop,
|
||||||
|
patternSticker,
|
||||||
|
sticker,
|
||||||
|
stickerSize = DEFAULT_STICKER_SIZE,
|
||||||
|
stickerNoPlay,
|
||||||
|
rarity,
|
||||||
|
isSelected,
|
||||||
|
className,
|
||||||
|
clickArg,
|
||||||
|
children,
|
||||||
|
observeIntersection,
|
||||||
|
onClick,
|
||||||
|
}: OwnProps<T>) => {
|
||||||
|
const stickerRef = useRef<HTMLDivElement>();
|
||||||
|
|
||||||
|
const customColor = useDynamicColorListener(stickerRef, undefined, !sticker?.shouldUseTextColor);
|
||||||
|
|
||||||
|
const radialPatternBackdrop = useMemo(() => {
|
||||||
|
if (!backdrop) return undefined;
|
||||||
|
|
||||||
|
const backdropColors: [string, string] = [backdrop.centerColor, backdrop.edgeColor];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<RadialPatternBackground
|
||||||
|
className={styles.radialPattern}
|
||||||
|
backgroundColors={backdropColors}
|
||||||
|
patternIcon={patternSticker}
|
||||||
|
ringsCount={1}
|
||||||
|
ovalFactor={1}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}, [backdrop, patternSticker]);
|
||||||
|
|
||||||
|
const handleClick = useLastCallback(() => {
|
||||||
|
onClick?.(clickArg!);
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
ref={ref}
|
||||||
|
className={buildClassName(
|
||||||
|
styles.root,
|
||||||
|
isSelected && styles.selected,
|
||||||
|
radialPatternBackdrop && styles.hasBackground,
|
||||||
|
className,
|
||||||
|
)}
|
||||||
|
role={onClick ? 'button' : undefined}
|
||||||
|
tabIndex={onClick ? 0 : undefined}
|
||||||
|
onClick={handleClick}
|
||||||
|
>
|
||||||
|
{radialPatternBackdrop}
|
||||||
|
|
||||||
|
<div
|
||||||
|
ref={stickerRef}
|
||||||
|
className={styles.stickerWrapper}
|
||||||
|
style={`width: ${stickerSize}px; height: ${stickerSize}px`}
|
||||||
|
>
|
||||||
|
{sticker && (
|
||||||
|
<StickerView
|
||||||
|
containerRef={stickerRef}
|
||||||
|
sticker={sticker}
|
||||||
|
size={stickerSize}
|
||||||
|
observeIntersectionForPlaying={observeIntersection}
|
||||||
|
observeIntersectionForLoading={observeIntersection}
|
||||||
|
shouldPreloadPreview
|
||||||
|
noPlay={stickerNoPlay}
|
||||||
|
customColor={customColor}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
{rarity && (
|
||||||
|
<GiftRarityBadge
|
||||||
|
rarity={rarity}
|
||||||
|
className={buildClassName(styles.rarity, rarity.type === 'regular' && styles.regular)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default memo(GiftAttributeItem);
|
||||||
@ -51,6 +51,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.starGift {
|
.starGift {
|
||||||
|
overflow: visible;
|
||||||
|
flex-basis: auto;
|
||||||
padding: 0.875rem;
|
padding: 0.875rem;
|
||||||
padding-bottom: 0.625rem;
|
padding-bottom: 0.625rem;
|
||||||
}
|
}
|
||||||
@ -90,11 +92,11 @@
|
|||||||
|
|
||||||
.auction,
|
.auction,
|
||||||
.premiumRequired {
|
.premiumRequired {
|
||||||
outline: 0.125rem solid #D18D21;
|
outline: 0.125rem solid #D18D21 !important;
|
||||||
outline-offset: -0.125rem;
|
outline-offset: -0.125rem;
|
||||||
|
|
||||||
&:focus-visible {
|
&:focus-visible {
|
||||||
outline: 0.125rem solid #D18D21;
|
outline: 0.125rem solid #D18D21 !important;
|
||||||
outline-offset: -0.125rem;
|
outline-offset: -0.125rem;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -5,22 +5,18 @@ import type { ApiStarGift, ApiTypeCurrencyAmount } from '../../../api/types';
|
|||||||
|
|
||||||
import { STARS_CURRENCY_CODE, TON_CURRENCY_CODE } from '../../../config';
|
import { STARS_CURRENCY_CODE, TON_CURRENCY_CODE } from '../../../config';
|
||||||
import { selectIsCurrentUserPremium } from '../../../global/selectors';
|
import { selectIsCurrentUserPremium } from '../../../global/selectors';
|
||||||
import { IS_TOUCH_ENV } from '../../../util/browser/windowEnvironment.ts';
|
|
||||||
import buildClassName from '../../../util/buildClassName';
|
import buildClassName from '../../../util/buildClassName';
|
||||||
import { formatStarsAsIcon, formatTonAsIcon } from '../../../util/localization/format';
|
import { formatStarsAsIcon, formatTonAsIcon } from '../../../util/localization/format';
|
||||||
|
|
||||||
import Icon from '../../common/icons/Icon'; ;
|
|
||||||
import { getGiftAttributes, getStickerFromGift } from '../../common/helpers/gifts';
|
import { getGiftAttributes, getStickerFromGift } from '../../common/helpers/gifts';
|
||||||
|
|
||||||
import useFlag from '../../../hooks/useFlag.ts';
|
|
||||||
import { type ObserveFn, useOnIntersect } from '../../../hooks/useIntersectionObserver';
|
import { type ObserveFn, useOnIntersect } from '../../../hooks/useIntersectionObserver';
|
||||||
import useLang from '../../../hooks/useLang';
|
import useLang from '../../../hooks/useLang';
|
||||||
import useLastCallback from '../../../hooks/useLastCallback';
|
import useLastCallback from '../../../hooks/useLastCallback';
|
||||||
|
|
||||||
import GiftRibbon from '../../common/gift/GiftRibbon';
|
import GiftRibbon from '../../common/gift/GiftRibbon';
|
||||||
import RadialPatternBackground from '../../common/profile/RadialPatternBackground';
|
import Icon from '../../common/icons/Icon';
|
||||||
import StickerView from '../../common/StickerView';
|
|
||||||
import Button from '../../ui/Button';
|
import Button from '../../ui/Button';
|
||||||
|
import GiftAttributeItem from './GiftAttributeItem';
|
||||||
|
|
||||||
import styles from './GiftItem.module.scss';
|
import styles from './GiftItem.module.scss';
|
||||||
|
|
||||||
@ -48,7 +44,6 @@ function GiftItemStar({
|
|||||||
} = getActions();
|
} = getActions();
|
||||||
|
|
||||||
const ref = useRef<HTMLDivElement>();
|
const ref = useRef<HTMLDivElement>();
|
||||||
const stickerRef = useRef<HTMLDivElement>();
|
|
||||||
|
|
||||||
function getPriceAmount(amounts?: ApiTypeCurrencyAmount[]) {
|
function getPriceAmount(amounts?: ApiTypeCurrencyAmount[]) {
|
||||||
if (!amounts) return { amount: 0, currency: STARS_CURRENCY_CODE };
|
if (!amounts) return { amount: 0, currency: STARS_CURRENCY_CODE };
|
||||||
@ -65,7 +60,6 @@ function GiftItemStar({
|
|||||||
const lang = useLang();
|
const lang = useLang();
|
||||||
|
|
||||||
const [isVisible, setIsVisible] = useState(false);
|
const [isVisible, setIsVisible] = useState(false);
|
||||||
const [isHover, markHover, unmarkHover] = useFlag();
|
|
||||||
|
|
||||||
const sticker = getStickerFromGift(gift);
|
const sticker = getStickerFromGift(gift);
|
||||||
const isGiftUnique = gift.type === 'starGiftUnique';
|
const isGiftUnique = gift.type === 'starGiftUnique';
|
||||||
@ -132,38 +126,19 @@ function GiftItemStar({
|
|||||||
onClick?.(gift, isResale ? 'resell' : 'original');
|
onClick?.(gift, isResale ? 'resell' : 'original');
|
||||||
});
|
});
|
||||||
|
|
||||||
const radialPatternBackdrop = useMemo(() => {
|
const giftAttrs = useMemo(() => getGiftAttributes(gift), [gift]);
|
||||||
const { backdrop, pattern } = getGiftAttributes(gift) || {};
|
|
||||||
|
|
||||||
if (!backdrop || !pattern) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
const backdropColors = [backdrop.centerColor, backdrop.edgeColor];
|
|
||||||
|
|
||||||
return (
|
|
||||||
<RadialPatternBackground
|
|
||||||
className={styles.radialPattern}
|
|
||||||
backgroundColors={backdropColors}
|
|
||||||
patternIcon={pattern.sticker}
|
|
||||||
ringsCount={1}
|
|
||||||
ovalFactor={1}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}, [gift]);
|
|
||||||
|
|
||||||
const giftNumber = isGiftUnique ? gift.number : 0;
|
const giftNumber = isGiftUnique ? gift.number : 0;
|
||||||
const isLocked = Boolean(gift.type === 'starGift' && gift.lockedUntilDate);
|
const isLocked = Boolean(gift.type === 'starGift' && gift.lockedUntilDate);
|
||||||
|
|
||||||
const giftRibbon = useMemo(() => {
|
const giftRibbon = useMemo(() => {
|
||||||
if (isGiftUnique) {
|
if (isGiftUnique) {
|
||||||
const { backdrop } = getGiftAttributes(gift) || {};
|
if (!giftAttrs?.backdrop) {
|
||||||
if (!backdrop) {
|
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<GiftRibbon
|
<GiftRibbon
|
||||||
color={[backdrop.centerColor, backdrop.edgeColor]}
|
color={[giftAttrs.backdrop.centerColor, giftAttrs.backdrop.edgeColor]}
|
||||||
text={
|
text={
|
||||||
lang('GiftSavedNumber', { number: giftNumber })
|
lang('GiftSavedNumber', { number: giftNumber })
|
||||||
}
|
}
|
||||||
@ -186,7 +161,7 @@ function GiftItemStar({
|
|||||||
return <GiftRibbon color="blue" text={lang('GiftLimited')} />;
|
return <GiftRibbon color="blue" text={lang('GiftLimited')} />;
|
||||||
}
|
}
|
||||||
return undefined;
|
return undefined;
|
||||||
}, [isGiftUnique, isResale, gift, isSoldOut,
|
}, [isGiftUnique, isResale, giftAttrs, isSoldOut,
|
||||||
isLimited, lang, giftNumber, isPremiumRequired, isAuction]);
|
isLimited, lang, giftNumber, isPremiumRequired, isAuction]);
|
||||||
|
|
||||||
useOnIntersect(ref, observeIntersection, (entry) => {
|
useOnIntersect(ref, observeIntersection, (entry) => {
|
||||||
@ -217,8 +192,13 @@ function GiftItemStar({
|
|||||||
}, [withTransferBadge, priceCurrency, formattedPrice, isAuction, lang]);
|
}, [withTransferBadge, priceCurrency, formattedPrice, isAuction, lang]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<GiftAttributeItem
|
||||||
ref={ref}
|
ref={ref}
|
||||||
|
backdrop={giftAttrs?.backdrop}
|
||||||
|
patternSticker={giftAttrs?.pattern?.sticker}
|
||||||
|
sticker={sticker}
|
||||||
|
stickerSize={GIFT_STICKER_SIZE}
|
||||||
|
observeIntersection={observeIntersection}
|
||||||
className={buildClassName(
|
className={buildClassName(
|
||||||
'interactive-gift',
|
'interactive-gift',
|
||||||
styles.container,
|
styles.container,
|
||||||
@ -228,32 +208,8 @@ function GiftItemStar({
|
|||||||
isAuction && styles.auction,
|
isAuction && styles.auction,
|
||||||
noClickable && styles.noClickable,
|
noClickable && styles.noClickable,
|
||||||
)}
|
)}
|
||||||
tabIndex={noClickable ? undefined : 0}
|
|
||||||
role={noClickable ? undefined : 'button'}
|
|
||||||
onClick={noClickable ? undefined : handleGiftClick}
|
onClick={noClickable ? undefined : handleGiftClick}
|
||||||
onMouseEnter={!IS_TOUCH_ENV && !noClickable ? markHover : undefined}
|
|
||||||
onMouseLeave={!IS_TOUCH_ENV && !noClickable ? unmarkHover : undefined}
|
|
||||||
>
|
>
|
||||||
{radialPatternBackdrop}
|
|
||||||
|
|
||||||
<div
|
|
||||||
ref={stickerRef}
|
|
||||||
className={styles.stickerWrapper}
|
|
||||||
style={`width: ${GIFT_STICKER_SIZE}px; height: ${GIFT_STICKER_SIZE}px`}
|
|
||||||
>
|
|
||||||
{sticker && (
|
|
||||||
<StickerView
|
|
||||||
observeIntersectionForPlaying={observeIntersection}
|
|
||||||
observeIntersectionForLoading={observeIntersection}
|
|
||||||
containerRef={stickerRef}
|
|
||||||
sticker={sticker}
|
|
||||||
size={GIFT_STICKER_SIZE}
|
|
||||||
shouldLoop={isHover}
|
|
||||||
shouldPreloadPreview
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
|
|
||||||
</div>
|
|
||||||
{!hideBadge && (
|
{!hideBadge && (
|
||||||
<Button
|
<Button
|
||||||
className={buildClassName(
|
className={buildClassName(
|
||||||
@ -272,7 +228,7 @@ function GiftItemStar({
|
|||||||
)}
|
)}
|
||||||
{giftRibbon}
|
{giftRibbon}
|
||||||
{isLocked && <Icon name="lock-badge" className={styles.lockIcon} />}
|
{isLocked && <Icon name="lock-badge" className={styles.lockIcon} />}
|
||||||
</div>
|
</GiftAttributeItem>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,8 +1,10 @@
|
|||||||
.root {
|
.root {
|
||||||
--_height: 15rem;
|
--_height: 15rem;
|
||||||
|
|
||||||
|
overflow: hidden;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
flex-shrink: 0;
|
||||||
gap: 0.25rem;
|
gap: 0.25rem;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|
||||||
@ -10,6 +12,7 @@
|
|||||||
height: var(--_height);
|
height: var(--_height);
|
||||||
margin-bottom: 0.5rem;
|
margin-bottom: 0.5rem;
|
||||||
padding-bottom: 1rem;
|
padding-bottom: 1rem;
|
||||||
|
border-radius: 0 0 0.625rem 0.625rem;
|
||||||
|
|
||||||
&.withManageButtons {
|
&.withManageButtons {
|
||||||
--_height: 19.25rem;
|
--_height: 19.25rem;
|
||||||
@ -114,6 +117,7 @@
|
|||||||
.subtitle {
|
.subtitle {
|
||||||
font-size: 1rem;
|
font-size: 1rem;
|
||||||
line-height: 1.375rem;
|
line-height: 1.375rem;
|
||||||
|
color: var(--tinted-text-color);
|
||||||
text-align: center;
|
text-align: center;
|
||||||
text-wrap: balance;
|
text-wrap: balance;
|
||||||
|
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
import type React from '@teact';
|
||||||
import type { TeactNode } from '@teact';
|
import type { TeactNode } from '@teact';
|
||||||
import { memo, useMemo } from '@teact';
|
import { memo, useMemo } from '@teact';
|
||||||
import { getActions } from '../../../global';
|
import { getActions } from '../../../global';
|
||||||
@ -6,7 +7,8 @@ import type {
|
|||||||
ApiPeer,
|
ApiPeer,
|
||||||
ApiSavedStarGift,
|
ApiSavedStarGift,
|
||||||
ApiStarGiftAttributeBackdrop, ApiStarGiftAttributeModel, ApiStarGiftAttributePattern,
|
ApiStarGiftAttributeBackdrop, ApiStarGiftAttributeModel, ApiStarGiftAttributePattern,
|
||||||
ApiTypeCurrencyAmount } from '../../../api/types';
|
ApiTypeCurrencyAmount,
|
||||||
|
} from '../../../api/types';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
formatStarsTransactionAmount,
|
formatStarsTransactionAmount,
|
||||||
@ -42,6 +44,7 @@ type OwnProps = {
|
|||||||
resellPrice?: ApiTypeCurrencyAmount;
|
resellPrice?: ApiTypeCurrencyAmount;
|
||||||
showManageButtons?: boolean;
|
showManageButtons?: boolean;
|
||||||
savedGift?: ApiSavedStarGift;
|
savedGift?: ApiSavedStarGift;
|
||||||
|
children?: React.ReactNode;
|
||||||
};
|
};
|
||||||
|
|
||||||
const STICKER_SIZE = 120;
|
const STICKER_SIZE = 120;
|
||||||
@ -58,6 +61,7 @@ const UniqueGiftHeader = ({
|
|||||||
resellPrice,
|
resellPrice,
|
||||||
showManageButtons,
|
showManageButtons,
|
||||||
savedGift,
|
savedGift,
|
||||||
|
children,
|
||||||
}: OwnProps) => {
|
}: OwnProps) => {
|
||||||
const {
|
const {
|
||||||
openChat,
|
openChat,
|
||||||
@ -88,11 +92,13 @@ const UniqueGiftHeader = ({
|
|||||||
}, [backdropAttribute, patternAttribute, isMobile]);
|
}, [backdropAttribute, patternAttribute, isMobile]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={buildClassName(styles.root,
|
<div
|
||||||
'interactive-gift',
|
className={buildClassName(styles.root,
|
||||||
showManageButtons && styles.withManageButtons,
|
'interactive-gift',
|
||||||
badge && styles.withBadge,
|
showManageButtons && styles.withManageButtons,
|
||||||
className)}
|
badge && styles.withBadge,
|
||||||
|
className)}
|
||||||
|
style={buildStyle(subtitleColor && `--tinted-text-color: ${subtitleColor}`)}
|
||||||
>
|
>
|
||||||
<Transition
|
<Transition
|
||||||
className={styles.transition}
|
className={styles.transition}
|
||||||
@ -118,7 +124,6 @@ const UniqueGiftHeader = ({
|
|||||||
{Boolean(subtitle) && (
|
{Boolean(subtitle) && (
|
||||||
<div
|
<div
|
||||||
className={buildClassName(styles.subtitle, subtitlePeer && styles.subtitleBadge)}
|
className={buildClassName(styles.subtitle, subtitlePeer && styles.subtitleBadge)}
|
||||||
style={buildStyle(subtitleColor && `color: ${subtitleColor}`)}
|
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (subtitlePeer) {
|
if (subtitlePeer) {
|
||||||
openChat({ id: subtitlePeer.id });
|
openChat({ id: subtitlePeer.id });
|
||||||
@ -142,6 +147,7 @@ const UniqueGiftHeader = ({
|
|||||||
{resellPrice.currency === 'TON' && <Icon name="toncoin" />}
|
{resellPrice.currency === 'TON' && <Icon name="toncoin" />}
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
|
{children}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,3 +1,7 @@
|
|||||||
|
.root {
|
||||||
|
z-index: calc(var(--z-modal-low-priority) + 1);
|
||||||
|
}
|
||||||
|
|
||||||
.content {
|
.content {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
|||||||
@ -333,6 +333,7 @@ const GiftAuctionBidModal = ({
|
|||||||
isOpen={isOpen}
|
isOpen={isOpen}
|
||||||
hasAbsoluteCloseButton
|
hasAbsoluteCloseButton
|
||||||
isSlim
|
isSlim
|
||||||
|
className={styles.root}
|
||||||
contentClassName={styles.content}
|
contentClassName={styles.content}
|
||||||
onClose={closeGiftAuctionBidModal}
|
onClose={closeGiftAuctionBidModal}
|
||||||
isLowStackPriority
|
isLowStackPriority
|
||||||
|
|||||||
@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
.modalContent {
|
.modalContent {
|
||||||
position: relative;
|
position: relative;
|
||||||
max-height: min(97vh, 48rem) !important;
|
max-height: min(97vh, 50rem) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.header {
|
.header {
|
||||||
|
|||||||
@ -56,11 +56,6 @@
|
|||||||
color: var(--color-error) !important;
|
color: var(--color-error) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.modalContent {
|
|
||||||
position: relative;
|
|
||||||
max-height: min(97vh, 48rem) !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.moreMenuButton {
|
.moreMenuButton {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
|
|||||||
@ -24,7 +24,7 @@ import {
|
|||||||
import { CUSTOM_PEER_HIDDEN } from '../../../../util/objects/customPeer';
|
import { CUSTOM_PEER_HIDDEN } from '../../../../util/objects/customPeer';
|
||||||
import { getServerTime } from '../../../../util/serverTime';
|
import { getServerTime } from '../../../../util/serverTime';
|
||||||
import { renderGiftOriginalInfo } from '../../../common/helpers/giftOriginalInfo';
|
import { renderGiftOriginalInfo } from '../../../common/helpers/giftOriginalInfo';
|
||||||
import { getGiftAttributes, getGiftRarityTitle, getStickerFromGift } from '../../../common/helpers/gifts';
|
import { getGiftAttributes, getStickerFromGift } from '../../../common/helpers/gifts';
|
||||||
import { renderTextWithEntities } from '../../../common/helpers/renderTextWithEntities';
|
import { renderTextWithEntities } from '../../../common/helpers/renderTextWithEntities';
|
||||||
|
|
||||||
import useCurrentOrPrev from '../../../../hooks/useCurrentOrPrev';
|
import useCurrentOrPrev from '../../../../hooks/useCurrentOrPrev';
|
||||||
@ -95,9 +95,8 @@ const GiftInfoModal = ({
|
|||||||
buyStarGift,
|
buyStarGift,
|
||||||
closeGiftModal,
|
closeGiftModal,
|
||||||
openGiftInfoValueModal,
|
openGiftInfoValueModal,
|
||||||
updateResaleGiftsFilter,
|
|
||||||
openGiftInMarket,
|
|
||||||
openGiftDescriptionRemoveModal,
|
openGiftDescriptionRemoveModal,
|
||||||
|
openGiftPreviewModal,
|
||||||
} = getActions();
|
} = getActions();
|
||||||
|
|
||||||
const [isConvertConfirmOpen, openConvertConfirm, closeConvertConfirm] = useFlag();
|
const [isConvertConfirmOpen, openConvertConfirm, closeConvertConfirm] = useFlag();
|
||||||
@ -109,57 +108,6 @@ const GiftInfoModal = ({
|
|||||||
|
|
||||||
const uniqueGiftHeaderRef = useRef<HTMLDivElement>();
|
const uniqueGiftHeaderRef = useRef<HTMLDivElement>();
|
||||||
|
|
||||||
const handleSymbolClick = useLastCallback(() => {
|
|
||||||
if (!gift || !giftAttributes?.pattern) return;
|
|
||||||
|
|
||||||
openGiftInMarket({ gift });
|
|
||||||
updateResaleGiftsFilter({
|
|
||||||
filter: {
|
|
||||||
sortType: 'byDate',
|
|
||||||
modelAttributes: [],
|
|
||||||
backdropAttributes: [],
|
|
||||||
patternAttributes: [{
|
|
||||||
type: 'pattern',
|
|
||||||
documentId: giftAttributes.pattern.sticker.id,
|
|
||||||
}],
|
|
||||||
},
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
const handleBackdropClick = useLastCallback(() => {
|
|
||||||
if (!gift || !giftAttributes?.backdrop) return;
|
|
||||||
|
|
||||||
openGiftInMarket({ gift });
|
|
||||||
updateResaleGiftsFilter({
|
|
||||||
filter: {
|
|
||||||
sortType: 'byDate',
|
|
||||||
modelAttributes: [],
|
|
||||||
backdropAttributes: [{
|
|
||||||
type: 'backdrop',
|
|
||||||
backdropId: giftAttributes.backdrop.backdropId,
|
|
||||||
}],
|
|
||||||
patternAttributes: [],
|
|
||||||
},
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
const handleModelClick = useLastCallback(() => {
|
|
||||||
if (!gift || !giftAttributes?.model) return;
|
|
||||||
|
|
||||||
openGiftInMarket({ gift });
|
|
||||||
updateResaleGiftsFilter({
|
|
||||||
filter: {
|
|
||||||
sortType: 'byDate',
|
|
||||||
modelAttributes: [{
|
|
||||||
type: 'model',
|
|
||||||
documentId: giftAttributes.model.sticker.id,
|
|
||||||
}],
|
|
||||||
backdropAttributes: [],
|
|
||||||
patternAttributes: [],
|
|
||||||
},
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
const isOpen = Boolean(modal);
|
const isOpen = Boolean(modal);
|
||||||
const renderingModal = useCurrentOrPrev(modal);
|
const renderingModal = useCurrentOrPrev(modal);
|
||||||
const renderingFromPeer = useCurrentOrPrev(fromPeer);
|
const renderingFromPeer = useCurrentOrPrev(fromPeer);
|
||||||
@ -303,6 +251,13 @@ const GiftInfoModal = ({
|
|||||||
return gift && getGiftAttributes(gift);
|
return gift && getGiftAttributes(gift);
|
||||||
}, [gift]);
|
}, [gift]);
|
||||||
|
|
||||||
|
const handleOpenPreviewModal = useLastCallback(() => {
|
||||||
|
if (!gift) return;
|
||||||
|
openGiftPreviewModal({
|
||||||
|
originGift: gift,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
const uniqueGiftTitle = useMemo(() => {
|
const uniqueGiftTitle = useMemo(() => {
|
||||||
if (!gift || gift.type !== 'starGiftUnique' || !giftAttributes?.backdrop) return undefined;
|
if (!gift || gift.type !== 'starGiftUnique' || !giftAttributes?.backdrop) return undefined;
|
||||||
|
|
||||||
@ -709,13 +664,8 @@ const GiftInfoModal = ({
|
|||||||
if (model) {
|
if (model) {
|
||||||
tableData.push([
|
tableData.push([
|
||||||
lang('GiftAttributeModel'),
|
lang('GiftAttributeModel'),
|
||||||
<span className={styles.uniqueAttribute}>
|
<span className={styles.uniqueAttribute} onClick={handleOpenPreviewModal}>
|
||||||
<span
|
<span className={styles.attributeName}>{model.name}</span>
|
||||||
className={styles.attributeName}
|
|
||||||
onClick={handleModelClick}
|
|
||||||
>
|
|
||||||
{model.name}
|
|
||||||
</span>
|
|
||||||
<GiftRarityBadge rarity={model.rarity} />
|
<GiftRarityBadge rarity={model.rarity} />
|
||||||
</span>,
|
</span>,
|
||||||
]);
|
]);
|
||||||
@ -724,14 +674,9 @@ const GiftInfoModal = ({
|
|||||||
if (backdrop) {
|
if (backdrop) {
|
||||||
tableData.push([
|
tableData.push([
|
||||||
lang('GiftAttributeBackdrop'),
|
lang('GiftAttributeBackdrop'),
|
||||||
<span className={styles.uniqueAttribute}>
|
<span className={styles.uniqueAttribute} onClick={handleOpenPreviewModal}>
|
||||||
<span
|
<span className={styles.attributeName}>{backdrop.name}</span>
|
||||||
className={styles.attributeName}
|
<GiftRarityBadge rarity={backdrop.rarity} />
|
||||||
onClick={handleBackdropClick}
|
|
||||||
>
|
|
||||||
{backdrop.name}
|
|
||||||
</span>
|
|
||||||
<BadgeButton>{getGiftRarityTitle(lang, backdrop.rarity)}</BadgeButton>
|
|
||||||
</span>,
|
</span>,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
@ -739,14 +684,9 @@ const GiftInfoModal = ({
|
|||||||
if (pattern) {
|
if (pattern) {
|
||||||
tableData.push([
|
tableData.push([
|
||||||
lang('GiftAttributeSymbol'),
|
lang('GiftAttributeSymbol'),
|
||||||
<span className={styles.uniqueAttribute}>
|
<span className={styles.uniqueAttribute} onClick={handleOpenPreviewModal}>
|
||||||
<span
|
<span className={styles.attributeName}>{pattern.name}</span>
|
||||||
className={styles.attributeName}
|
<GiftRarityBadge rarity={pattern.rarity} />
|
||||||
onClick={handleSymbolClick}
|
|
||||||
>
|
|
||||||
{pattern.name}
|
|
||||||
</span>
|
|
||||||
<BadgeButton>{getGiftRarityTitle(lang, pattern.rarity)}</BadgeButton>
|
|
||||||
</span>,
|
</span>,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
@ -874,7 +814,6 @@ const GiftInfoModal = ({
|
|||||||
gift, giftAttributes, renderFooterButton, isTargetChat,
|
gift, giftAttributes, renderFooterButton, isTargetChat,
|
||||||
isGiftUnique, saleDateInfo,
|
isGiftUnique, saleDateInfo,
|
||||||
canBuyGift, giftOwnerTitle, resellPrice, uniqueGiftTitle, uniqueGiftSubtitle, releasedByPeer,
|
canBuyGift, giftOwnerTitle, resellPrice, uniqueGiftTitle, uniqueGiftSubtitle, releasedByPeer,
|
||||||
handleSymbolClick, handleBackdropClick, handleModelClick,
|
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const moreMenuItems = typeGift && (
|
const moreMenuItems = typeGift && (
|
||||||
@ -896,8 +835,7 @@ const GiftInfoModal = ({
|
|||||||
hasBackdrop={isGiftUnique}
|
hasBackdrop={isGiftUnique}
|
||||||
tableData={modalData?.tableData}
|
tableData={modalData?.tableData}
|
||||||
footer={modalData?.footer}
|
footer={modalData?.footer}
|
||||||
className={styles.modal}
|
className={buildClassName(styles.modal, 'tall')}
|
||||||
contentClassName={styles.modalContent}
|
|
||||||
closeButtonColor={isGiftUnique ? 'translucent-white' : undefined}
|
closeButtonColor={isGiftUnique ? 'translucent-white' : undefined}
|
||||||
moreMenuItems={moreMenuItems}
|
moreMenuItems={moreMenuItems}
|
||||||
onClose={handleClose}
|
onClose={handleClose}
|
||||||
|
|||||||
@ -17,14 +17,14 @@ import {
|
|||||||
} from '../../../../util/localization/format';
|
} from '../../../../util/localization/format';
|
||||||
import { round } from '../../../../util/math';
|
import { round } from '../../../../util/math';
|
||||||
import { formatPercent } from '../../../../util/textFormat';
|
import { formatPercent } from '../../../../util/textFormat';
|
||||||
import { getGiftAttributes, getGiftRarityTitle } from '../../../common/helpers/gifts';
|
import { getGiftAttributes } from '../../../common/helpers/gifts';
|
||||||
|
|
||||||
import useCurrentOrPrev from '../../../../hooks/useCurrentOrPrev';
|
import useCurrentOrPrev from '../../../../hooks/useCurrentOrPrev';
|
||||||
import useLang from '../../../../hooks/useLang';
|
import useLang from '../../../../hooks/useLang';
|
||||||
import useLastCallback from '../../../../hooks/useLastCallback';
|
import useLastCallback from '../../../../hooks/useLastCallback';
|
||||||
|
|
||||||
import BadgeButton from '../../../common/BadgeButton';
|
|
||||||
import GiftTransferPreview from '../../../common/gift/GiftTransferPreview';
|
import GiftTransferPreview from '../../../common/gift/GiftTransferPreview';
|
||||||
|
import GiftRarityBadge from '../../../common/GiftRarityBadge';
|
||||||
import ConfirmDialog from '../../../ui/ConfirmDialog';
|
import ConfirmDialog from '../../../ui/ConfirmDialog';
|
||||||
import TableInfo, { type TableData } from '../../common/TableInfo';
|
import TableInfo, { type TableData } from '../../common/TableInfo';
|
||||||
|
|
||||||
@ -85,7 +85,7 @@ const GiftOfferAcceptModal = ({
|
|||||||
lang('GiftAttributeModel'),
|
lang('GiftAttributeModel'),
|
||||||
<span className={styles.attributeValue}>
|
<span className={styles.attributeValue}>
|
||||||
<span>{model.name}</span>
|
<span>{model.name}</span>
|
||||||
<BadgeButton>{getGiftRarityTitle(lang, model.rarity)}</BadgeButton>
|
<GiftRarityBadge rarity={model.rarity} />
|
||||||
</span>,
|
</span>,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
@ -95,7 +95,7 @@ const GiftOfferAcceptModal = ({
|
|||||||
lang('GiftAttributeBackdrop'),
|
lang('GiftAttributeBackdrop'),
|
||||||
<span className={styles.attributeValue}>
|
<span className={styles.attributeValue}>
|
||||||
<span>{backdrop.name}</span>
|
<span>{backdrop.name}</span>
|
||||||
<BadgeButton>{getGiftRarityTitle(lang, backdrop.rarity)}</BadgeButton>
|
<GiftRarityBadge rarity={backdrop.rarity} />
|
||||||
</span>,
|
</span>,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
@ -105,7 +105,7 @@ const GiftOfferAcceptModal = ({
|
|||||||
lang('GiftAttributeSymbol'),
|
lang('GiftAttributeSymbol'),
|
||||||
<span className={styles.attributeValue}>
|
<span className={styles.attributeValue}>
|
||||||
<span>{pattern.name}</span>
|
<span>{pattern.name}</span>
|
||||||
<BadgeButton>{getGiftRarityTitle(lang, pattern.rarity)}</BadgeButton>
|
<GiftRarityBadge rarity={pattern.rarity} />
|
||||||
</span>,
|
</span>,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,14 @@
|
|||||||
|
import type { OwnProps } from './GiftPreviewModal';
|
||||||
|
|
||||||
|
import { Bundles } from '../../../../util/moduleLoader';
|
||||||
|
|
||||||
|
import useModuleLoader from '../../../../hooks/useModuleLoader';
|
||||||
|
|
||||||
|
const GiftPreviewModalAsync = (props: OwnProps) => {
|
||||||
|
const { modal } = props;
|
||||||
|
const GiftPreviewModal = useModuleLoader(Bundles.Stars, 'GiftPreviewModal', !modal);
|
||||||
|
|
||||||
|
return GiftPreviewModal ? <GiftPreviewModal {...props} /> : undefined;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default GiftPreviewModalAsync;
|
||||||
110
src/components/modals/gift/preview/GiftPreviewModal.module.scss
Normal file
110
src/components/modals/gift/preview/GiftPreviewModal.module.scss
Normal file
@ -0,0 +1,110 @@
|
|||||||
|
@use "../../../../styles/mixins";
|
||||||
|
|
||||||
|
.content {
|
||||||
|
overflow: hidden;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
height: min(92vh, 46rem); // Required to keep Transition height
|
||||||
|
padding: 0 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tabs {
|
||||||
|
position: sticky;
|
||||||
|
z-index: 1;
|
||||||
|
top: 0;
|
||||||
|
|
||||||
|
padding-top: 0.5rem;
|
||||||
|
|
||||||
|
background-color: var(--color-background);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dialog,
|
||||||
|
.transition {
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.grid {
|
||||||
|
overflow-y: scroll;
|
||||||
|
display: grid;
|
||||||
|
grid-auto-rows: max-content;
|
||||||
|
grid-template-columns: repeat(3, 1fr);
|
||||||
|
gap: 0.5rem;
|
||||||
|
|
||||||
|
padding: 0.5rem;
|
||||||
|
|
||||||
|
@include mixins.adapt-padding-to-scrollbar(0.5rem, 0.125rem); // Prevent outline cutouts
|
||||||
|
}
|
||||||
|
|
||||||
|
.gridHeader {
|
||||||
|
display: flex;
|
||||||
|
grid-column: 1 / 4;
|
||||||
|
grid-row: 1;
|
||||||
|
flex-direction: column;
|
||||||
|
place-self: center;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
|
color: var(--color-text-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.item {
|
||||||
|
aspect-ratio: 1 / 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header {
|
||||||
|
--_height: 20rem;
|
||||||
|
|
||||||
|
justify-content: flex-end;
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.traitButtons {
|
||||||
|
z-index: 1;
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
gap: 0.375rem;
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
|
width: 100%;
|
||||||
|
margin-top: 0.25rem;
|
||||||
|
padding: 0 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.traitButton {
|
||||||
|
overflow: visible;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
flex-shrink: 1;
|
||||||
|
gap: 0.125rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.traitName {
|
||||||
|
font-size: 0.8125rem;
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.traitType {
|
||||||
|
font-size: 0.625rem;
|
||||||
|
color: var(--tinted-text-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.traitRarity {
|
||||||
|
--accent-background-active-color: var(--_badge-bg);
|
||||||
|
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
right: 0;
|
||||||
|
transform: translate(0.25rem, -50%);
|
||||||
|
|
||||||
|
color: white;
|
||||||
|
|
||||||
|
transition: background-color 150ms linear;
|
||||||
|
}
|
||||||
|
|
||||||
|
.playButton {
|
||||||
|
position: absolute;
|
||||||
|
z-index: 1;
|
||||||
|
top: 0.875rem;
|
||||||
|
right: 0.875rem;
|
||||||
|
}
|
||||||
490
src/components/modals/gift/preview/GiftPreviewModal.tsx
Normal file
490
src/components/modals/gift/preview/GiftPreviewModal.tsx
Normal file
@ -0,0 +1,490 @@
|
|||||||
|
import { memo, useEffect, useMemo, useRef, useState } from '../../../../lib/teact/teact';
|
||||||
|
import { getActions, withGlobal } from '../../../../global';
|
||||||
|
|
||||||
|
import type {
|
||||||
|
ApiStarGiftAttributeBackdrop,
|
||||||
|
ApiStarGiftAttributeModel,
|
||||||
|
ApiStarGiftAttributePattern,
|
||||||
|
} from '../../../../api/types';
|
||||||
|
import type { TabState } from '../../../../global/types';
|
||||||
|
import type { AnimationLevel } from '../../../../types';
|
||||||
|
|
||||||
|
import { selectAnimationLevel } from '../../../../global/selectors/sharedState';
|
||||||
|
import buildClassName from '../../../../util/buildClassName';
|
||||||
|
import { getNextArrowReplacement } from '../../../../util/localization/format';
|
||||||
|
import { resolveTransitionName } from '../../../../util/resolveTransitionName';
|
||||||
|
import { getGiftAttributes, getRandomGiftPreviewAttributes } from '../../../common/helpers/gifts';
|
||||||
|
|
||||||
|
import useInterval from '../../../../hooks/schedulers/useInterval';
|
||||||
|
import useCurrentOrPrev from '../../../../hooks/useCurrentOrPrev';
|
||||||
|
import useFlag from '../../../../hooks/useFlag';
|
||||||
|
import { useIntersectionObserver } from '../../../../hooks/useIntersectionObserver';
|
||||||
|
import useLang from '../../../../hooks/useLang';
|
||||||
|
import useLastCallback from '../../../../hooks/useLastCallback';
|
||||||
|
|
||||||
|
import GiftRarityBadge from '../../../common/GiftRarityBadge';
|
||||||
|
import Button from '../../../ui/Button';
|
||||||
|
import InfiniteScroll from '../../../ui/InfiniteScroll';
|
||||||
|
import Link from '../../../ui/Link';
|
||||||
|
import Modal from '../../../ui/Modal';
|
||||||
|
import TabList, { type TabWithProperties } from '../../../ui/TabList';
|
||||||
|
import Transition from '../../../ui/Transition';
|
||||||
|
import GiftAttributeItem from '../GiftAttributeItem';
|
||||||
|
import UniqueGiftHeader from '../UniqueGiftHeader';
|
||||||
|
|
||||||
|
import styles from './GiftPreviewModal.module.scss';
|
||||||
|
|
||||||
|
export type OwnProps = {
|
||||||
|
modal: TabState['giftPreviewModal'];
|
||||||
|
};
|
||||||
|
|
||||||
|
type StateProps = {
|
||||||
|
animationLevel: AnimationLevel;
|
||||||
|
};
|
||||||
|
|
||||||
|
const MODEL_STICKER_SIZE = 80;
|
||||||
|
const PATTERN_STICKER_SIZE = 60;
|
||||||
|
const INTERSECTION_THROTTLE = 200;
|
||||||
|
const PLAYBACK_INTERVAL = 5000;
|
||||||
|
|
||||||
|
enum AttributeTab {
|
||||||
|
Model,
|
||||||
|
Backdrop,
|
||||||
|
Pattern,
|
||||||
|
}
|
||||||
|
|
||||||
|
const GiftPreviewModal = ({ modal, animationLevel }: OwnProps & StateProps) => {
|
||||||
|
const {
|
||||||
|
closeGiftPreviewModal,
|
||||||
|
openGiftInMarket,
|
||||||
|
updateResaleGiftsFilter,
|
||||||
|
} = getActions();
|
||||||
|
const [isCraftableModelsMode, showCraftableModels, showRegularModels] = useFlag();
|
||||||
|
const [isPlayingRandomPreviews, playRandomPreviews, stopRandomPreviews] = useFlag(true);
|
||||||
|
|
||||||
|
const modelsContainerRef = useRef<HTMLDivElement>();
|
||||||
|
const patternsContainerRef = useRef<HTMLDivElement>();
|
||||||
|
const backdropsContainerRef = useRef<HTMLDivElement>();
|
||||||
|
|
||||||
|
const isOpen = Boolean(modal);
|
||||||
|
const renderingModal = useCurrentOrPrev(modal);
|
||||||
|
|
||||||
|
const originGift = renderingModal?.originGift;
|
||||||
|
const initialAttributes = useMemo(() => originGift && getGiftAttributes(originGift), [originGift]);
|
||||||
|
|
||||||
|
const lang = useLang();
|
||||||
|
|
||||||
|
const [selectedTabIndex, setSelectedTabIndex] = useState(AttributeTab.Model);
|
||||||
|
|
||||||
|
const { regularModels, craftableModels, patterns, backdrops } = useMemo(() => {
|
||||||
|
if (!renderingModal?.attributes) {
|
||||||
|
return { regularModels: [], craftableModels: [], patterns: [], backdrops: [] };
|
||||||
|
}
|
||||||
|
|
||||||
|
const result: {
|
||||||
|
regularModels: ApiStarGiftAttributeModel[];
|
||||||
|
craftableModels: ApiStarGiftAttributeModel[];
|
||||||
|
patterns: ApiStarGiftAttributePattern[];
|
||||||
|
backdrops: ApiStarGiftAttributeBackdrop[];
|
||||||
|
} = { regularModels: [], craftableModels: [], patterns: [], backdrops: [] };
|
||||||
|
|
||||||
|
for (const attr of renderingModal.attributes) {
|
||||||
|
if (attr.type === 'model') {
|
||||||
|
if (attr.rarity.type === 'regular') {
|
||||||
|
result.regularModels.push(attr);
|
||||||
|
} else {
|
||||||
|
result.craftableModels.push(attr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (attr.type === 'pattern') result.patterns.push(attr);
|
||||||
|
if (attr.type === 'backdrop') result.backdrops.push(attr);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}, [renderingModal?.attributes]);
|
||||||
|
|
||||||
|
const firstModel = regularModels[0];
|
||||||
|
const firstPattern = patterns[0];
|
||||||
|
const firstBackdrop = backdrops[0];
|
||||||
|
|
||||||
|
const [selectedModel, setSelectedModel] = useState<ApiStarGiftAttributeModel | undefined>(firstModel);
|
||||||
|
const [selectedPattern, setSelectedPattern] = useState<ApiStarGiftAttributePattern | undefined>(firstPattern);
|
||||||
|
const [selectedBackdrop, setSelectedBackdrop] = useState<ApiStarGiftAttributeBackdrop | undefined>(firstBackdrop);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (isOpen) return;
|
||||||
|
setSelectedTabIndex(AttributeTab.Model);
|
||||||
|
showRegularModels();
|
||||||
|
}, [isOpen]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const newModel = initialAttributes?.model || firstModel;
|
||||||
|
setSelectedModel(newModel);
|
||||||
|
setSelectedPattern(initialAttributes?.pattern || firstPattern);
|
||||||
|
setSelectedBackdrop(initialAttributes?.backdrop || firstBackdrop);
|
||||||
|
if (newModel && newModel.rarity.type !== 'regular') showCraftableModels();
|
||||||
|
}, [initialAttributes, firstModel, firstPattern, firstBackdrop]);
|
||||||
|
|
||||||
|
useInterval(() => {
|
||||||
|
if (!originGift || !selectedModel || !selectedPattern || !selectedBackdrop) return;
|
||||||
|
const newAttributes = getRandomGiftPreviewAttributes(renderingModal?.attributes, {
|
||||||
|
model: selectedModel,
|
||||||
|
pattern: selectedPattern,
|
||||||
|
backdrop: selectedBackdrop,
|
||||||
|
});
|
||||||
|
setSelectedModel(newAttributes.model);
|
||||||
|
setSelectedPattern(newAttributes.pattern);
|
||||||
|
setSelectedBackdrop(newAttributes.backdrop);
|
||||||
|
}, isPlayingRandomPreviews ? PLAYBACK_INTERVAL : undefined, true);
|
||||||
|
|
||||||
|
const {
|
||||||
|
observe: observeModelsIntersection,
|
||||||
|
} = useIntersectionObserver({
|
||||||
|
rootRef: modelsContainerRef,
|
||||||
|
throttleMs: INTERSECTION_THROTTLE,
|
||||||
|
isDisabled: selectedTabIndex !== AttributeTab.Model,
|
||||||
|
});
|
||||||
|
|
||||||
|
const {
|
||||||
|
observe: observePatternsIntersection,
|
||||||
|
} = useIntersectionObserver({
|
||||||
|
rootRef: patternsContainerRef,
|
||||||
|
throttleMs: INTERSECTION_THROTTLE,
|
||||||
|
isDisabled: selectedTabIndex !== AttributeTab.Pattern,
|
||||||
|
});
|
||||||
|
|
||||||
|
const {
|
||||||
|
observe: observeBackdropsIntersection,
|
||||||
|
} = useIntersectionObserver({
|
||||||
|
rootRef: backdropsContainerRef,
|
||||||
|
throttleMs: INTERSECTION_THROTTLE,
|
||||||
|
isDisabled: selectedTabIndex !== AttributeTab.Backdrop,
|
||||||
|
});
|
||||||
|
|
||||||
|
const tabs = useMemo<TabWithProperties[]>(() => [
|
||||||
|
{ title: lang('GiftAttributeModel') },
|
||||||
|
{ title: lang('GiftAttributeBackdrop') },
|
||||||
|
{ title: lang('GiftAttributeSymbol') },
|
||||||
|
], [lang]);
|
||||||
|
|
||||||
|
const handleClose = useLastCallback(() => closeGiftPreviewModal());
|
||||||
|
|
||||||
|
const handleSelectModel = useLastCallback((model: ApiStarGiftAttributeModel) => {
|
||||||
|
setSelectedModel(model);
|
||||||
|
stopRandomPreviews();
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleSelectPattern = useLastCallback((pattern: ApiStarGiftAttributePattern) => {
|
||||||
|
setSelectedPattern(pattern);
|
||||||
|
stopRandomPreviews();
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleSelectBackdrop = useLastCallback((backdrop: ApiStarGiftAttributeBackdrop) => {
|
||||||
|
setSelectedBackdrop(backdrop);
|
||||||
|
stopRandomPreviews();
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleSymbolClick = useLastCallback(() => {
|
||||||
|
if (!originGift || !selectedPattern) return;
|
||||||
|
|
||||||
|
openGiftInMarket({ gift: originGift });
|
||||||
|
updateResaleGiftsFilter({
|
||||||
|
filter: {
|
||||||
|
sortType: 'byDate',
|
||||||
|
modelAttributes: [],
|
||||||
|
backdropAttributes: [],
|
||||||
|
patternAttributes: [{
|
||||||
|
type: 'pattern',
|
||||||
|
documentId: selectedPattern.sticker.id,
|
||||||
|
}],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
handleClose();
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleBackdropClick = useLastCallback(() => {
|
||||||
|
if (!originGift || !selectedBackdrop) return;
|
||||||
|
|
||||||
|
openGiftInMarket({ gift: originGift });
|
||||||
|
updateResaleGiftsFilter({
|
||||||
|
filter: {
|
||||||
|
sortType: 'byDate',
|
||||||
|
modelAttributes: [],
|
||||||
|
backdropAttributes: [{
|
||||||
|
type: 'backdrop',
|
||||||
|
backdropId: selectedBackdrop.backdropId,
|
||||||
|
}],
|
||||||
|
patternAttributes: [],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
handleClose();
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleModelClick = useLastCallback(() => {
|
||||||
|
if (!originGift) return;
|
||||||
|
|
||||||
|
openGiftInMarket({ gift: originGift });
|
||||||
|
updateResaleGiftsFilter({
|
||||||
|
filter: {
|
||||||
|
sortType: 'byDate',
|
||||||
|
modelAttributes: [{
|
||||||
|
type: 'model',
|
||||||
|
documentId: selectedModel!.sticker.id,
|
||||||
|
}],
|
||||||
|
backdropAttributes: [],
|
||||||
|
patternAttributes: [],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
handleClose();
|
||||||
|
});
|
||||||
|
|
||||||
|
const modalHeader = useMemo(() => {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Button
|
||||||
|
className="modal-absolute-close-button"
|
||||||
|
round
|
||||||
|
color="translucent-white"
|
||||||
|
size="tiny"
|
||||||
|
iconName="close"
|
||||||
|
ariaLabel={lang('Close')}
|
||||||
|
onClick={handleClose}
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
className={styles.playButton}
|
||||||
|
round
|
||||||
|
color="translucent-white"
|
||||||
|
size="tiny"
|
||||||
|
iconName={isPlayingRandomPreviews ? 'pause' : 'play'}
|
||||||
|
ariaLabel={isPlayingRandomPreviews ? lang('AriaGiftPreviewStop') : lang('AriaGiftPreviewPlay')}
|
||||||
|
onClick={isPlayingRandomPreviews ? stopRandomPreviews : playRandomPreviews}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}, [lang, isPlayingRandomPreviews]);
|
||||||
|
|
||||||
|
function renderHeader() {
|
||||||
|
if (!selectedModel || !selectedPattern || !selectedBackdrop) return undefined;
|
||||||
|
return (
|
||||||
|
<UniqueGiftHeader
|
||||||
|
className={styles.header}
|
||||||
|
modelAttribute={selectedModel}
|
||||||
|
backdropAttribute={selectedBackdrop}
|
||||||
|
patternAttribute={selectedPattern}
|
||||||
|
title={originGift?.title}
|
||||||
|
subtitle={lang('GiftPreviewSelectedTraits')}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className={styles.traitButtons}
|
||||||
|
style={`--_badge-bg: ${selectedBackdrop.centerColor}`}
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
className={styles.traitButton}
|
||||||
|
color="transparentBlured"
|
||||||
|
onClick={handleModelClick}
|
||||||
|
>
|
||||||
|
<span className={styles.traitName}>{selectedModel.name}</span>
|
||||||
|
<span className={styles.traitType}>{lang('GiftAttributeModel')}</span>
|
||||||
|
<GiftRarityBadge
|
||||||
|
shouldInvertRare
|
||||||
|
rarity={selectedModel.rarity}
|
||||||
|
className={styles.traitRarity}
|
||||||
|
/>
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
className={styles.traitButton}
|
||||||
|
color="transparentBlured"
|
||||||
|
onClick={handleBackdropClick}
|
||||||
|
>
|
||||||
|
<span className={styles.traitName}>{selectedBackdrop.name}</span>
|
||||||
|
<span className={styles.traitType}>{lang('GiftAttributeBackdrop')}</span>
|
||||||
|
<GiftRarityBadge
|
||||||
|
shouldInvertRare
|
||||||
|
rarity={selectedBackdrop.rarity}
|
||||||
|
className={styles.traitRarity}
|
||||||
|
/>
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
className={styles.traitButton}
|
||||||
|
color="transparentBlured"
|
||||||
|
onClick={handleSymbolClick}
|
||||||
|
>
|
||||||
|
<span className={styles.traitName}>{selectedPattern.name}</span>
|
||||||
|
<span className={styles.traitType}>{lang('GiftAttributeSymbol')}</span>
|
||||||
|
<GiftRarityBadge
|
||||||
|
shouldInvertRare
|
||||||
|
rarity={selectedPattern.rarity}
|
||||||
|
className={styles.traitRarity}
|
||||||
|
/>
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</UniqueGiftHeader>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderTabContent() {
|
||||||
|
switch (selectedTabIndex) {
|
||||||
|
case AttributeTab.Model:
|
||||||
|
return (
|
||||||
|
<InfiniteScroll
|
||||||
|
ref={modelsContainerRef}
|
||||||
|
className={buildClassName(styles.grid, 'custom-scroll')}
|
||||||
|
beforeChildren={(
|
||||||
|
<div className={styles.gridHeader}>
|
||||||
|
<span className={styles.count}>
|
||||||
|
{lang(
|
||||||
|
isCraftableModelsMode ? 'GiftPreviewCountCraftableModels' : 'GiftPreviewCountModels',
|
||||||
|
{ count: isCraftableModelsMode ? craftableModels.length : regularModels.length }, {
|
||||||
|
pluralValue: isCraftableModelsMode ? craftableModels.length : regularModels.length,
|
||||||
|
withNodes: true,
|
||||||
|
withMarkdown: true,
|
||||||
|
})}
|
||||||
|
</span>
|
||||||
|
{Boolean(craftableModels?.length) && (
|
||||||
|
<Link
|
||||||
|
isPrimary
|
||||||
|
onClick={() => isCraftableModelsMode ? showRegularModels() : showCraftableModels()}
|
||||||
|
>
|
||||||
|
{lang(
|
||||||
|
isCraftableModelsMode ? 'GiftPreviewToggleRegularModels' : 'GiftPreviewToggleCraftableModels',
|
||||||
|
undefined,
|
||||||
|
{ withNodes: true, specialReplacement: getNextArrowReplacement() },
|
||||||
|
)}
|
||||||
|
</Link>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
items={isCraftableModelsMode ? craftableModels : regularModels}
|
||||||
|
noFastList
|
||||||
|
>
|
||||||
|
{(isCraftableModelsMode ? craftableModels : regularModels).map((model) => (
|
||||||
|
<GiftAttributeItem
|
||||||
|
className={styles.item}
|
||||||
|
key={model.name}
|
||||||
|
sticker={model.sticker}
|
||||||
|
stickerSize={MODEL_STICKER_SIZE}
|
||||||
|
rarity={model.rarity}
|
||||||
|
isSelected={selectedModel?.name === model.name}
|
||||||
|
clickArg={model}
|
||||||
|
onClick={handleSelectModel}
|
||||||
|
observeIntersection={observeModelsIntersection}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</InfiniteScroll>
|
||||||
|
);
|
||||||
|
case AttributeTab.Pattern:
|
||||||
|
return (
|
||||||
|
<InfiniteScroll
|
||||||
|
ref={patternsContainerRef}
|
||||||
|
className={buildClassName(styles.grid, 'custom-scroll')}
|
||||||
|
beforeChildren={(
|
||||||
|
<div className={styles.gridHeader}>
|
||||||
|
<span className={styles.count}>
|
||||||
|
{lang('GiftPreviewCountPatterns', { count: patterns.length }, {
|
||||||
|
pluralValue: patterns.length,
|
||||||
|
withNodes: true,
|
||||||
|
withMarkdown: true,
|
||||||
|
})}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
items={patterns}
|
||||||
|
noFastList
|
||||||
|
>
|
||||||
|
{patterns.map((pattern) => (
|
||||||
|
<GiftAttributeItem
|
||||||
|
key={pattern.name}
|
||||||
|
className={styles.item}
|
||||||
|
backdrop={selectedBackdrop}
|
||||||
|
sticker={pattern.sticker}
|
||||||
|
patternSticker={pattern.sticker}
|
||||||
|
stickerNoPlay
|
||||||
|
stickerSize={PATTERN_STICKER_SIZE}
|
||||||
|
rarity={pattern.rarity}
|
||||||
|
isSelected={selectedPattern?.name === pattern.name}
|
||||||
|
clickArg={pattern}
|
||||||
|
onClick={handleSelectPattern}
|
||||||
|
observeIntersection={observePatternsIntersection}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</InfiniteScroll>
|
||||||
|
);
|
||||||
|
case AttributeTab.Backdrop:
|
||||||
|
return (
|
||||||
|
<InfiniteScroll
|
||||||
|
ref={backdropsContainerRef}
|
||||||
|
className={buildClassName(styles.grid, 'custom-scroll')}
|
||||||
|
beforeChildren={(
|
||||||
|
<div className={styles.gridHeader}>
|
||||||
|
<span className={styles.count}>
|
||||||
|
{lang('GiftPreviewCountBackdrops', { count: backdrops.length }, {
|
||||||
|
pluralValue: backdrops.length,
|
||||||
|
withNodes: true,
|
||||||
|
withMarkdown: true,
|
||||||
|
})}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
items={backdrops}
|
||||||
|
noFastList
|
||||||
|
>
|
||||||
|
{backdrops.map((backdrop) => (
|
||||||
|
<GiftAttributeItem
|
||||||
|
key={backdrop.backdropId}
|
||||||
|
className={styles.item}
|
||||||
|
backdrop={backdrop}
|
||||||
|
sticker={selectedModel?.sticker}
|
||||||
|
stickerNoPlay
|
||||||
|
patternSticker={selectedPattern?.sticker}
|
||||||
|
rarity={backdrop.rarity}
|
||||||
|
isSelected={selectedBackdrop?.backdropId === backdrop.backdropId}
|
||||||
|
clickArg={backdrop}
|
||||||
|
onClick={handleSelectBackdrop}
|
||||||
|
observeIntersection={observeBackdropsIntersection}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</InfiniteScroll>
|
||||||
|
);
|
||||||
|
default:
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal
|
||||||
|
isOpen={isOpen}
|
||||||
|
className={buildClassName(styles.root, 'tall')}
|
||||||
|
hasAbsoluteCloseButton
|
||||||
|
absoluteCloseButtonColor="translucent-white"
|
||||||
|
dialogClassName={styles.dialog}
|
||||||
|
contentClassName={styles.content}
|
||||||
|
header={modalHeader}
|
||||||
|
isSlim
|
||||||
|
onClose={handleClose}
|
||||||
|
>
|
||||||
|
{renderHeader()}
|
||||||
|
<TabList
|
||||||
|
className={styles.tabs}
|
||||||
|
activeTab={selectedTabIndex}
|
||||||
|
tabs={tabs}
|
||||||
|
onSwitchTab={setSelectedTabIndex}
|
||||||
|
/>
|
||||||
|
<Transition
|
||||||
|
className={styles.transition}
|
||||||
|
name={resolveTransitionName('slideOptimized', animationLevel, undefined, lang.isRtl)}
|
||||||
|
activeKey={selectedTabIndex}
|
||||||
|
renderCount={tabs.length}
|
||||||
|
>
|
||||||
|
{renderTabContent()}
|
||||||
|
</Transition>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default memo(withGlobal<OwnProps>(
|
||||||
|
(global): Complete<StateProps> => {
|
||||||
|
return {
|
||||||
|
animationLevel: selectAnimationLevel(global),
|
||||||
|
};
|
||||||
|
},
|
||||||
|
)(GiftPreviewModal));
|
||||||
@ -1,3 +1,7 @@
|
|||||||
|
.header {
|
||||||
|
--_height: 17.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
.footer {
|
.footer {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
@ -19,3 +23,14 @@
|
|||||||
text-transform: none;
|
text-transform: none;
|
||||||
opacity: 0.6;
|
opacity: 0.6;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.subtitle {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.5rem;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.viewAllText {
|
||||||
|
margin-inline-start: 0.125rem;
|
||||||
|
}
|
||||||
|
|||||||
@ -3,7 +3,7 @@ import {
|
|||||||
} from '../../../../lib/teact/teact';
|
} from '../../../../lib/teact/teact';
|
||||||
import { getActions, withGlobal } from '../../../../global';
|
import { getActions, withGlobal } from '../../../../global';
|
||||||
|
|
||||||
import type { ApiPeer } from '../../../../api/types';
|
import type { ApiPeer, ApiStarGiftAttributeModel } from '../../../../api/types';
|
||||||
import type { TabState } from '../../../../global/types';
|
import type { TabState } from '../../../../global/types';
|
||||||
|
|
||||||
import { getPeerTitle } from '../../../../global/helpers/peers';
|
import { getPeerTitle } from '../../../../global/helpers/peers';
|
||||||
@ -19,6 +19,7 @@ import useLang from '../../../../hooks/useLang';
|
|||||||
import useLastCallback from '../../../../hooks/useLastCallback';
|
import useLastCallback from '../../../../hooks/useLastCallback';
|
||||||
|
|
||||||
import AnimatedCounter from '../../../common/AnimatedCounter';
|
import AnimatedCounter from '../../../common/AnimatedCounter';
|
||||||
|
import CustomEmoji from '../../../common/CustomEmoji';
|
||||||
import Icon from '../../../common/icons/Icon';
|
import Icon from '../../../common/icons/Icon';
|
||||||
import Button from '../../../ui/Button';
|
import Button from '../../../ui/Button';
|
||||||
import Checkbox from '../../../ui/Checkbox';
|
import Checkbox from '../../../ui/Checkbox';
|
||||||
@ -38,6 +39,7 @@ type StateProps = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const PREVIEW_UPDATE_INTERVAL = 3000;
|
const PREVIEW_UPDATE_INTERVAL = 3000;
|
||||||
|
const BUTTON_MODELS_COUNT = 3;
|
||||||
|
|
||||||
const GiftUpgradeModal = ({ modal, recipient }: OwnProps & StateProps) => {
|
const GiftUpgradeModal = ({ modal, recipient }: OwnProps & StateProps) => {
|
||||||
const {
|
const {
|
||||||
@ -47,6 +49,7 @@ const GiftUpgradeModal = ({ modal, recipient }: OwnProps & StateProps) => {
|
|||||||
upgradePrepaidGift,
|
upgradePrepaidGift,
|
||||||
openStarGiftPriceDecreaseInfoModal,
|
openStarGiftPriceDecreaseInfoModal,
|
||||||
shiftGiftUpgradeNextPrice,
|
shiftGiftUpgradeNextPrice,
|
||||||
|
openGiftPreviewModal,
|
||||||
} = getActions();
|
} = getActions();
|
||||||
const isOpen = Boolean(modal);
|
const isOpen = Boolean(modal);
|
||||||
|
|
||||||
@ -70,6 +73,12 @@ const GiftUpgradeModal = ({ modal, recipient }: OwnProps & StateProps) => {
|
|||||||
const nextPriceDate = nextPrice?.date;
|
const nextPriceDate = nextPrice?.date;
|
||||||
const upgradeStars = renderingModal?.currentUpgradeStars;
|
const upgradeStars = renderingModal?.currentUpgradeStars;
|
||||||
|
|
||||||
|
const previewModels = useMemo(() => {
|
||||||
|
return renderingModal?.sampleAttributes
|
||||||
|
?.filter((attr): attr is ApiStarGiftAttributeModel => attr.type === 'model')
|
||||||
|
.slice(0, BUTTON_MODELS_COUNT);
|
||||||
|
}, [renderingModal?.sampleAttributes]);
|
||||||
|
|
||||||
const handleUpgrade = useLastCallback(() => {
|
const handleUpgrade = useLastCallback(() => {
|
||||||
const gift = renderingModal?.gift;
|
const gift = renderingModal?.gift;
|
||||||
|
|
||||||
@ -103,6 +112,11 @@ const GiftUpgradeModal = ({ modal, recipient }: OwnProps & StateProps) => {
|
|||||||
setPreviewAttributes(getRandomGiftPreviewAttributes(renderingModal.sampleAttributes, previewAttributes));
|
setPreviewAttributes(getRandomGiftPreviewAttributes(renderingModal.sampleAttributes, previewAttributes));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const handleViewAllVariants = useLastCallback(() => {
|
||||||
|
if (!renderingModal?.gift) return;
|
||||||
|
openGiftPreviewModal({ originGift: renderingModal.gift.gift });
|
||||||
|
});
|
||||||
|
|
||||||
const handleOpenPriceInfo = useLastCallback(() => {
|
const handleOpenPriceInfo = useLastCallback(() => {
|
||||||
if (!renderingModal?.prices) return;
|
if (!renderingModal?.prices) return;
|
||||||
|
|
||||||
@ -155,16 +169,43 @@ const GiftUpgradeModal = ({ modal, recipient }: OwnProps & StateProps) => {
|
|||||||
['auction', lang('GiftUpgradeTradeableTitle'), lang('GiftUpgradeTradeableDescription')],
|
['auction', lang('GiftUpgradeTradeableTitle'), lang('GiftUpgradeTradeableDescription')],
|
||||||
]) satisfies TableAboutData;
|
]) satisfies TableAboutData;
|
||||||
|
|
||||||
const subtitle = renderingRecipient
|
const subtitleText = renderingRecipient
|
||||||
? lang('GiftPeerUpgradeText', { peer: getPeerTitle(lang, renderingRecipient) })
|
? lang('GiftPeerUpgradeText', { peer: getPeerTitle(lang, renderingRecipient) })
|
||||||
: lang('GiftUpgradeTextOwn');
|
: lang('GiftUpgradeTextOwn');
|
||||||
|
|
||||||
|
const subtitle = (
|
||||||
|
<div className={styles.subtitle}>
|
||||||
|
<span className={styles.subtitleText}>
|
||||||
|
{subtitleText}
|
||||||
|
</span>
|
||||||
|
<Button
|
||||||
|
size="tiny"
|
||||||
|
color="transparentBlured"
|
||||||
|
pill
|
||||||
|
fluid
|
||||||
|
className={styles.viewAllButton}
|
||||||
|
onClick={handleViewAllVariants}
|
||||||
|
>
|
||||||
|
{previewModels?.map((model) => (
|
||||||
|
<CustomEmoji
|
||||||
|
sticker={model.sticker}
|
||||||
|
noPlay
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
<span className={styles.viewAllText}>
|
||||||
|
{lang('GiftUpgradeViewAll', undefined, { withNodes: true, specialReplacement: getNextArrowReplacement() })}
|
||||||
|
</span>
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
const hasPriceDecreaseInfo = Boolean(nextPriceDate)
|
const hasPriceDecreaseInfo = Boolean(nextPriceDate)
|
||||||
&& Boolean(renderingModal?.prices?.length)
|
&& Boolean(renderingModal?.prices?.length)
|
||||||
&& !gift?.alreadyPaidUpgradeStars;
|
&& !gift?.alreadyPaidUpgradeStars;
|
||||||
|
|
||||||
const header = (
|
const header = (
|
||||||
<UniqueGiftHeader
|
<UniqueGiftHeader
|
||||||
|
className={styles.header}
|
||||||
modelAttribute={previewAttributes.model}
|
modelAttribute={previewAttributes.model}
|
||||||
backdropAttribute={previewAttributes.backdrop}
|
backdropAttribute={previewAttributes.backdrop}
|
||||||
patternAttribute={previewAttributes.pattern}
|
patternAttribute={previewAttributes.pattern}
|
||||||
@ -227,11 +268,10 @@ const GiftUpgradeModal = ({ modal, recipient }: OwnProps & StateProps) => {
|
|||||||
header,
|
header,
|
||||||
footer,
|
footer,
|
||||||
};
|
};
|
||||||
}, [previewAttributes, isOpen, lang,
|
}, [
|
||||||
renderingRecipient, renderingModal?.gift,
|
previewAttributes, isOpen, lang, renderingRecipient, renderingModal?.gift, shouldKeepOriginalDetails, isPrepaid,
|
||||||
shouldKeepOriginalDetails, isPrepaid,
|
renderingModal?.prices?.length, nextPriceDate, formattedPriceElement, previewModels,
|
||||||
renderingModal?.prices?.length,
|
]);
|
||||||
nextPriceDate, formattedPriceElement]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TableAboutModal
|
<TableAboutModal
|
||||||
|
|||||||
@ -1,6 +1,5 @@
|
|||||||
import type { MouseEvent as ReactMouseEvent } from 'react';
|
import type { MouseEvent as ReactMouseEvent } from 'react';
|
||||||
import type { ElementRef, FC } from '../../lib/teact/teact';
|
import type { ElementRef, TeactNode } from '../../lib/teact/teact';
|
||||||
import type React from '../../lib/teact/teact';
|
|
||||||
import { useRef, useState } from '../../lib/teact/teact';
|
import { useRef, useState } from '../../lib/teact/teact';
|
||||||
|
|
||||||
import type { IconName } from '../../types/icons';
|
import type { IconName } from '../../types/icons';
|
||||||
@ -22,7 +21,7 @@ import './Button.scss';
|
|||||||
export type OwnProps = {
|
export type OwnProps = {
|
||||||
ref?: ElementRef<HTMLButtonElement | HTMLAnchorElement>;
|
ref?: ElementRef<HTMLButtonElement | HTMLAnchorElement>;
|
||||||
type?: 'button' | 'submit' | 'reset';
|
type?: 'button' | 'submit' | 'reset';
|
||||||
children?: React.ReactNode;
|
children?: TeactNode;
|
||||||
size?: 'default' | 'smaller' | 'tiny';
|
size?: 'default' | 'smaller' | 'tiny';
|
||||||
color?: (
|
color?: (
|
||||||
'primary' | 'secondary' | 'gray' | 'danger' | 'translucent' | 'translucent-white' | 'translucent-black'
|
'primary' | 'secondary' | 'gray' | 'danger' | 'translucent' | 'translucent-white' | 'translucent-black'
|
||||||
@ -76,7 +75,7 @@ export type OwnProps = {
|
|||||||
// Longest animation duration;
|
// Longest animation duration;
|
||||||
const CLICKED_TIMEOUT = 400;
|
const CLICKED_TIMEOUT = 400;
|
||||||
|
|
||||||
const Button: FC<OwnProps> = ({
|
const Button = ({
|
||||||
ref,
|
ref,
|
||||||
type = 'button',
|
type = 'button',
|
||||||
id,
|
id,
|
||||||
@ -125,7 +124,7 @@ const Button: FC<OwnProps> = ({
|
|||||||
onMouseLeave,
|
onMouseLeave,
|
||||||
onFocus,
|
onFocus,
|
||||||
onTransitionEnd,
|
onTransitionEnd,
|
||||||
}) => {
|
}: OwnProps) => {
|
||||||
let elementRef = useRef<HTMLButtonElement | HTMLAnchorElement>();
|
let elementRef = useRef<HTMLButtonElement | HTMLAnchorElement>();
|
||||||
if (ref) {
|
if (ref) {
|
||||||
elementRef = ref;
|
elementRef = ref;
|
||||||
|
|||||||
@ -1,258 +1,266 @@
|
|||||||
.Modal {
|
@layer ui.modal {
|
||||||
position: relative;
|
.Modal {
|
||||||
z-index: var(--z-modal);
|
|
||||||
color: var(--color-text);
|
|
||||||
|
|
||||||
&.confirm,
|
|
||||||
&.pin {
|
|
||||||
z-index: var(--z-modal-confirm);
|
|
||||||
}
|
|
||||||
|
|
||||||
&.low-priority {
|
|
||||||
z-index: var(--z-modal-low-priority);
|
|
||||||
}
|
|
||||||
|
|
||||||
&.delete,
|
|
||||||
&.error,
|
|
||||||
&.confirm,
|
|
||||||
&.pin,
|
|
||||||
&.unpin-all {
|
|
||||||
.modal-dialog {
|
|
||||||
max-width: 24rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&.error {
|
|
||||||
.modal-content .dialog-buttons {
|
|
||||||
margin-top: 1rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&.narrow {
|
|
||||||
.modal-dialog {
|
|
||||||
max-width: 20rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&.slim {
|
|
||||||
.modal-dialog {
|
|
||||||
max-width: 26.25rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.modal-content {
|
|
||||||
max-height: min(92vh, 36rem);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.modal-container {
|
|
||||||
position: fixed;
|
|
||||||
top: 0;
|
|
||||||
right: 0;
|
|
||||||
bottom: 0;
|
|
||||||
left: 0;
|
|
||||||
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.with-balance-bar {
|
|
||||||
.modal-container {
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
|
|
||||||
.modal-dialog {
|
|
||||||
margin-top: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.modal-backdrop {
|
|
||||||
position: fixed;
|
|
||||||
z-index: -1;
|
|
||||||
top: 0;
|
|
||||||
right: 0;
|
|
||||||
bottom: 0;
|
|
||||||
left: 0;
|
|
||||||
|
|
||||||
background-color: rgba(0, 0, 0, 0.25);
|
|
||||||
}
|
|
||||||
|
|
||||||
&.transparent-backdrop .modal-backdrop {
|
|
||||||
background-color: transparent;
|
|
||||||
}
|
|
||||||
|
|
||||||
.modal-dialog {
|
|
||||||
position: relative;
|
position: relative;
|
||||||
transform: translate3d(0, -1rem, 0);
|
z-index: var(--z-modal);
|
||||||
|
color: var(--color-text);
|
||||||
|
|
||||||
display: inline-flex;
|
&.confirm,
|
||||||
flex-direction: column;
|
&.pin {
|
||||||
|
z-index: var(--z-modal-confirm);
|
||||||
width: 100%;
|
|
||||||
min-width: 17.5rem;
|
|
||||||
max-width: 35rem;
|
|
||||||
margin: 2rem auto;
|
|
||||||
border-radius: var(--border-radius-modal);
|
|
||||||
|
|
||||||
background-color: var(--color-background);
|
|
||||||
box-shadow: 0 0.25rem 0.5rem 0.125rem var(--color-default-shadow);
|
|
||||||
|
|
||||||
transition: transform 0.2s ease, opacity 0.2s ease;
|
|
||||||
|
|
||||||
body.no-page-transitions & {
|
|
||||||
transform: none !important;
|
|
||||||
transition: none;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 450px) {
|
&.low-priority {
|
||||||
max-width: calc(100vw - 3rem) !important;
|
z-index: var(--z-modal-low-priority);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
&.open .modal-dialog {
|
&.delete,
|
||||||
transform: translate3d(0, 0, 0);
|
&.error,
|
||||||
}
|
&.confirm,
|
||||||
|
&.pin,
|
||||||
&.closing .modal-dialog {
|
&.unpin-all {
|
||||||
transform: translate3d(0, 1rem, 0);
|
.modal-dialog {
|
||||||
}
|
max-width: 24rem;
|
||||||
|
}
|
||||||
.modal-header,
|
|
||||||
%modal-header {
|
|
||||||
display: flex;
|
|
||||||
flex-shrink: 0;
|
|
||||||
align-items: center;
|
|
||||||
padding: 1.3125rem 1.375rem 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.modal-title {
|
|
||||||
overflow: hidden;
|
|
||||||
flex: 1 1 auto;
|
|
||||||
|
|
||||||
font-size: 1.25rem;
|
|
||||||
font-weight: var(--font-weight-medium);
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
|
|
||||||
&:not(:only-child) {
|
|
||||||
margin: 0 1rem;
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
.modal-header-condensed {
|
&.error {
|
||||||
@extend %modal-header;
|
.modal-content .dialog-buttons {
|
||||||
|
margin-top: 1rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
min-height: 3.5rem;
|
&.narrow {
|
||||||
padding: 0.375rem 0.75rem !important;
|
.modal-dialog {
|
||||||
|
max-width: 20rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.modal-action-button {
|
&.slim {
|
||||||
width: auto;
|
.modal-dialog {
|
||||||
min-width: 5rem;
|
max-width: 26.25rem;
|
||||||
height: 2.25rem;
|
}
|
||||||
margin-left: auto;
|
|
||||||
padding-right: 1.25rem;
|
|
||||||
padding-left: 1.25rem;
|
|
||||||
|
|
||||||
font-size: 0.875rem;
|
.modal-content {
|
||||||
line-height: 1.625rem;
|
max-height: min(92vh, 36rem);
|
||||||
|
}
|
||||||
|
|
||||||
&.danger {
|
&.tall .modal-content {
|
||||||
color: var(--color-white);
|
max-height: min(92vh, 50rem);
|
||||||
background-color: var(--color-error);
|
}
|
||||||
|
}
|
||||||
|
|
||||||
&:hover,
|
.modal-container {
|
||||||
&:focus,
|
position: fixed;
|
||||||
&:active {
|
top: 0;
|
||||||
background-color: var(--color-error-shade);
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.with-balance-bar {
|
||||||
|
.modal-container {
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-dialog {
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-backdrop {
|
||||||
|
position: fixed;
|
||||||
|
z-index: -1;
|
||||||
|
top: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
|
||||||
|
background-color: rgba(0, 0, 0, 0.25);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.transparent-backdrop .modal-backdrop {
|
||||||
|
background-color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-dialog {
|
||||||
|
position: relative;
|
||||||
|
transform: translate3d(0, -1rem, 0);
|
||||||
|
|
||||||
|
display: inline-flex;
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
width: 100%;
|
||||||
|
min-width: 17.5rem;
|
||||||
|
max-width: 35rem;
|
||||||
|
margin: 2rem auto;
|
||||||
|
border-radius: var(--border-radius-modal);
|
||||||
|
|
||||||
|
background-color: var(--color-background);
|
||||||
|
box-shadow: 0 0.25rem 0.5rem 0.125rem var(--color-default-shadow);
|
||||||
|
|
||||||
|
transition:
|
||||||
|
transform 0.2s ease,
|
||||||
|
opacity 0.2s ease;
|
||||||
|
|
||||||
|
body.no-page-transitions & {
|
||||||
|
transform: none !important;
|
||||||
|
transition: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 450px) {
|
||||||
|
max-width: calc(100vw - 3rem) !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.open .modal-dialog {
|
||||||
|
transform: translate3d(0, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.closing .modal-dialog {
|
||||||
|
transform: translate3d(0, 1rem, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-header,
|
||||||
|
%modal-header {
|
||||||
|
display: flex;
|
||||||
|
flex-shrink: 0;
|
||||||
|
align-items: center;
|
||||||
|
padding: 1.3125rem 1.375rem 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-title {
|
||||||
|
overflow: hidden;
|
||||||
|
flex: 1 1 auto;
|
||||||
|
|
||||||
|
font-size: 1.25rem;
|
||||||
|
font-weight: var(--font-weight-medium);
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
|
||||||
|
&:not(:only-child) {
|
||||||
|
margin: 0 1rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-header-condensed {
|
||||||
|
@extend %modal-header;
|
||||||
|
|
||||||
|
min-height: 3.5rem;
|
||||||
|
padding: 0.375rem 0.75rem !important;
|
||||||
|
|
||||||
|
.modal-action-button {
|
||||||
|
width: auto;
|
||||||
|
min-width: 5rem;
|
||||||
|
height: 2.25rem;
|
||||||
|
margin-left: auto;
|
||||||
|
padding-right: 1.25rem;
|
||||||
|
padding-left: 1.25rem;
|
||||||
|
|
||||||
|
font-size: 0.875rem;
|
||||||
|
line-height: 1.625rem;
|
||||||
|
|
||||||
|
&.danger {
|
||||||
|
color: var(--color-white);
|
||||||
|
background-color: var(--color-error);
|
||||||
|
|
||||||
|
&:hover,
|
||||||
|
&:focus,
|
||||||
|
&:active {
|
||||||
|
background-color: var(--color-error-shade);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
.modal-content {
|
.modal-content {
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
|
|
||||||
width: 100%;
|
width: 100%;
|
||||||
max-height: 92vh;
|
max-height: min(92vh, 50rem);
|
||||||
padding: 1rem 1.5rem 1.1875rem;
|
padding: 1rem 1.5rem 1.1875rem;
|
||||||
|
|
||||||
b,
|
b,
|
||||||
strong {
|
strong {
|
||||||
overflow-wrap: anywhere;
|
overflow-wrap: anywhere;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-title,
|
||||||
|
.modal-content,
|
||||||
|
.modal-content > p {
|
||||||
|
unicode-bidi: plaintext;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-about {
|
||||||
|
overflow: hidden;
|
||||||
|
display: -webkit-box;
|
||||||
|
-webkit-box-orient: vertical;
|
||||||
|
-webkit-line-clamp: 5;
|
||||||
|
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-help {
|
||||||
|
font-size: 0.9375rem;
|
||||||
|
line-height: 1.3;
|
||||||
|
color: var(--color-text-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dialog-buttons {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row-reverse;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 0.5rem 1rem;
|
||||||
|
justify-content: flex-start;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dialog-buttons-column {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: flex-end;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dialog-buttons-centered {
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dialog-checkbox {
|
||||||
|
margin: 1rem -1.125rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dialog-checkbox-group {
|
||||||
|
margin: 0 -1.125rem 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.confirm-dialog-button {
|
||||||
|
width: auto;
|
||||||
|
height: auto;
|
||||||
|
|
||||||
|
font-weight: var(--font-weight-semibold);
|
||||||
|
text-align: right;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dialog-button-spacer {
|
||||||
|
flex-grow: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-absolute-close-button {
|
||||||
|
position: absolute;
|
||||||
|
z-index: 3;
|
||||||
|
top: 0.875rem;
|
||||||
|
left: 0.875rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-more-button {
|
||||||
|
position: absolute;
|
||||||
|
z-index: 3;
|
||||||
|
top: 0.875rem;
|
||||||
|
right: 0.875rem;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.modal-title,
|
|
||||||
.modal-content,
|
|
||||||
.modal-content > p {
|
|
||||||
unicode-bidi: plaintext;
|
|
||||||
}
|
|
||||||
|
|
||||||
.modal-about {
|
|
||||||
overflow: hidden;
|
|
||||||
display: -webkit-box;
|
|
||||||
-webkit-box-orient: vertical;
|
|
||||||
-webkit-line-clamp: 5;
|
|
||||||
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
}
|
|
||||||
|
|
||||||
.modal-help {
|
|
||||||
font-size: 0.9375rem;
|
|
||||||
line-height: 1.3;
|
|
||||||
color: var(--color-text-secondary);
|
|
||||||
}
|
|
||||||
|
|
||||||
.dialog-buttons {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row-reverse;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
gap: 0.5rem 1rem;
|
|
||||||
justify-content: flex-start;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dialog-buttons-column {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: flex-end;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dialog-buttons-centered {
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dialog-checkbox {
|
|
||||||
margin: 1rem -1.125rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dialog-checkbox-group {
|
|
||||||
margin: 0 -1.125rem 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.confirm-dialog-button {
|
|
||||||
width: auto;
|
|
||||||
height: auto;
|
|
||||||
|
|
||||||
font-weight: var(--font-weight-semibold);
|
|
||||||
text-align: right;
|
|
||||||
white-space: pre-wrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dialog-button-spacer {
|
|
||||||
flex-grow: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.modal-absolute-close-button {
|
|
||||||
position: absolute;
|
|
||||||
z-index: 3;
|
|
||||||
top: 0.875rem;
|
|
||||||
left: 0.875rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.modal-more-button {
|
|
||||||
position: absolute;
|
|
||||||
z-index: 3;
|
|
||||||
top: 0.875rem;
|
|
||||||
right: 0.875rem;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -47,13 +47,13 @@ export type OwnProps = {
|
|||||||
isLowStackPriority?: boolean;
|
isLowStackPriority?: boolean;
|
||||||
dialogContent?: React.ReactNode;
|
dialogContent?: React.ReactNode;
|
||||||
moreMenuItems?: TeactNode;
|
moreMenuItems?: TeactNode;
|
||||||
onClose: () => void;
|
|
||||||
onCloseAnimationEnd?: () => void;
|
|
||||||
onEnter?: () => void;
|
|
||||||
withBalanceBar?: boolean;
|
withBalanceBar?: boolean;
|
||||||
currencyInBalanceBar?: 'TON' | 'XTR';
|
currencyInBalanceBar?: 'TON' | 'XTR';
|
||||||
isCondensedHeader?: boolean;
|
isCondensedHeader?: boolean;
|
||||||
noFreezeOnClose?: boolean;
|
noFreezeOnClose?: boolean;
|
||||||
|
onClose: NoneToVoidFunction;
|
||||||
|
onCloseAnimationEnd?: NoneToVoidFunction;
|
||||||
|
onEnter?: NoneToVoidFunction;
|
||||||
};
|
};
|
||||||
|
|
||||||
const Modal = (props: OwnProps) => {
|
const Modal = (props: OwnProps) => {
|
||||||
@ -61,10 +61,10 @@ const Modal = (props: OwnProps) => {
|
|||||||
dialogRef,
|
dialogRef,
|
||||||
isOpen,
|
isOpen,
|
||||||
noBackdropClose,
|
noBackdropClose,
|
||||||
|
noFreezeOnClose,
|
||||||
onClose,
|
onClose,
|
||||||
onCloseAnimationEnd,
|
onCloseAnimationEnd,
|
||||||
onEnter,
|
onEnter,
|
||||||
noFreezeOnClose,
|
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
const {
|
const {
|
||||||
@ -72,8 +72,8 @@ const Modal = (props: OwnProps) => {
|
|||||||
shouldRender,
|
shouldRender,
|
||||||
} = useShowTransition({
|
} = useShowTransition({
|
||||||
isOpen,
|
isOpen,
|
||||||
onCloseAnimationEnd,
|
|
||||||
withShouldRender: true,
|
withShouldRender: true,
|
||||||
|
onCloseAnimationEnd,
|
||||||
});
|
});
|
||||||
|
|
||||||
const shouldFreeze = !noFreezeOnClose && !isOpen;
|
const shouldFreeze = !noFreezeOnClose && !isOpen;
|
||||||
|
|||||||
@ -752,3 +752,20 @@ addActionHandler('loadActiveGiftAuctions', async (global, actions, payload): Pro
|
|||||||
};
|
};
|
||||||
setGlobal(global);
|
setGlobal(global);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
addActionHandler('openGiftPreviewModal', async (global, _actions, payload): Promise<void> => {
|
||||||
|
const { originGift, tabId = getCurrentTabId() } = payload;
|
||||||
|
|
||||||
|
const giftId = originGift.type === 'starGiftUnique' ? originGift.regularGiftId : originGift.id;
|
||||||
|
const result = await callApi('fetchStarGiftUpgradeAttributes', { giftId });
|
||||||
|
if (!result) return;
|
||||||
|
|
||||||
|
global = getGlobal();
|
||||||
|
global = updateTabState(global, {
|
||||||
|
giftPreviewModal: {
|
||||||
|
originGift,
|
||||||
|
attributes: result.attributes,
|
||||||
|
},
|
||||||
|
}, tabId);
|
||||||
|
setGlobal(global);
|
||||||
|
});
|
||||||
|
|||||||
@ -422,6 +422,8 @@ addTabStateResetterAction('closeGiftResalePriceComposerModal', 'giftResalePriceC
|
|||||||
|
|
||||||
addTabStateResetterAction('closeGiftUpgradeModal', 'giftUpgradeModal');
|
addTabStateResetterAction('closeGiftUpgradeModal', 'giftUpgradeModal');
|
||||||
|
|
||||||
|
addTabStateResetterAction('closeGiftPreviewModal', 'giftPreviewModal');
|
||||||
|
|
||||||
addActionHandler('closeGiftAuctionModal', (global, _actions, payload): ActionReturnType => {
|
addActionHandler('closeGiftAuctionModal', (global, _actions, payload): ActionReturnType => {
|
||||||
const { shouldKeepAuction, tabId = getCurrentTabId() } = payload || {};
|
const { shouldKeepAuction, tabId = getCurrentTabId() } = payload || {};
|
||||||
const tabState = selectTabState(global, tabId);
|
const tabState = selectTabState(global, tabId);
|
||||||
|
|||||||
@ -2740,6 +2740,10 @@ export interface ActionPayloads {
|
|||||||
gift: ApiStarGiftUnique;
|
gift: ApiStarGiftUnique;
|
||||||
} & WithTabId;
|
} & WithTabId;
|
||||||
closeGiftInfoValueModal: WithTabId | undefined;
|
closeGiftInfoValueModal: WithTabId | undefined;
|
||||||
|
openGiftPreviewModal: {
|
||||||
|
originGift: ApiStarGift;
|
||||||
|
} & WithTabId;
|
||||||
|
closeGiftPreviewModal: WithTabId | undefined;
|
||||||
loadActiveGiftAuctions: undefined;
|
loadActiveGiftAuctions: undefined;
|
||||||
openActiveGiftAuctionsModal: WithTabId | undefined;
|
openActiveGiftAuctionsModal: WithTabId | undefined;
|
||||||
closeActiveGiftAuctionsModal: WithTabId | undefined;
|
closeActiveGiftAuctionsModal: WithTabId | undefined;
|
||||||
|
|||||||
@ -901,6 +901,11 @@ export type TabState = {
|
|||||||
emojiStatus: ApiEmojiStatusCollectible;
|
emojiStatus: ApiEmojiStatusCollectible;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
giftPreviewModal?: {
|
||||||
|
attributes: ApiStarGiftAttribute[];
|
||||||
|
originGift: ApiStarGift;
|
||||||
|
};
|
||||||
|
|
||||||
giftAuctionModal?: {
|
giftAuctionModal?: {
|
||||||
auctionGiftId: string;
|
auctionGiftId: string;
|
||||||
sampleAttributes?: ApiStarGiftAttribute[];
|
sampleAttributes?: ApiStarGiftAttribute[];
|
||||||
|
|||||||
@ -1904,6 +1904,7 @@ payments.getStarGiftAuctionState#5c9ff4d6 auction:InputStarGiftAuction version:i
|
|||||||
payments.getStarGiftAuctionAcquiredGifts#6ba2cbec gift_id:long = payments.StarGiftAuctionAcquiredGifts;
|
payments.getStarGiftAuctionAcquiredGifts#6ba2cbec gift_id:long = payments.StarGiftAuctionAcquiredGifts;
|
||||||
payments.getStarGiftActiveAuctions#a5d0514d hash:long = payments.StarGiftActiveAuctions;
|
payments.getStarGiftActiveAuctions#a5d0514d hash:long = payments.StarGiftActiveAuctions;
|
||||||
payments.resolveStarGiftOffer#e9ce781c flags:# decline:flags.0?true offer_msg_id:int = Updates;
|
payments.resolveStarGiftOffer#e9ce781c flags:# decline:flags.0?true offer_msg_id:int = Updates;
|
||||||
|
payments.getStarGiftUpgradeAttributes#6d038b58 gift_id:long = payments.StarGiftUpgradeAttributes;
|
||||||
phone.requestCall#42ff96ed flags:# video:flags.0?true user_id:InputUser random_id:int g_a_hash:bytes protocol:PhoneCallProtocol = phone.PhoneCall;
|
phone.requestCall#42ff96ed flags:# video:flags.0?true user_id:InputUser random_id:int g_a_hash:bytes protocol:PhoneCallProtocol = phone.PhoneCall;
|
||||||
phone.acceptCall#3bd2b4a0 peer:InputPhoneCall g_b:bytes protocol:PhoneCallProtocol = phone.PhoneCall;
|
phone.acceptCall#3bd2b4a0 peer:InputPhoneCall g_b:bytes protocol:PhoneCallProtocol = phone.PhoneCall;
|
||||||
phone.confirmCall#2efe1722 peer:InputPhoneCall g_a:bytes key_fingerprint:long protocol:PhoneCallProtocol = phone.PhoneCall;
|
phone.confirmCall#2efe1722 peer:InputPhoneCall g_a:bytes key_fingerprint:long protocol:PhoneCallProtocol = phone.PhoneCall;
|
||||||
|
|||||||
@ -359,6 +359,7 @@
|
|||||||
"payments.getStarGiftAuctionAcquiredGifts",
|
"payments.getStarGiftAuctionAcquiredGifts",
|
||||||
"payments.getStarGiftActiveAuctions",
|
"payments.getStarGiftActiveAuctions",
|
||||||
"payments.resolveStarGiftOffer",
|
"payments.resolveStarGiftOffer",
|
||||||
|
"payments.getStarGiftUpgradeAttributes",
|
||||||
"langpack.getLangPack",
|
"langpack.getLangPack",
|
||||||
"langpack.getStrings",
|
"langpack.getStrings",
|
||||||
"langpack.getLanguages",
|
"langpack.getLanguages",
|
||||||
|
|||||||
@ -5,12 +5,12 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@mixin adapt-padding-to-scrollbar($padding) {
|
@mixin adapt-padding-to-scrollbar($padding, $forceSpace: 0px) {
|
||||||
padding-inline-end: calc($padding - var(--scrollbar-width));
|
padding-inline-end: calc(max($padding - var(--scrollbar-width), $forceSpace));
|
||||||
}
|
}
|
||||||
|
|
||||||
@mixin adapt-margin-to-scrollbar($margin) {
|
@mixin adapt-margin-to-scrollbar($margin, $forceSpace: 0px) {
|
||||||
margin-inline-end: calc($margin - var(--scrollbar-width));
|
margin-inline-end: calc(max($margin - var(--scrollbar-width), $forceSpace));
|
||||||
}
|
}
|
||||||
|
|
||||||
@mixin filter-outline($width: 0.125rem, $color) {
|
@mixin filter-outline($width: 0.125rem, $color) {
|
||||||
|
|||||||
18
src/types/language.d.ts
vendored
18
src/types/language.d.ts
vendored
@ -1955,6 +1955,12 @@ export interface LangPair {
|
|||||||
'ChatListAuctionOutbid': undefined;
|
'ChatListAuctionOutbid': undefined;
|
||||||
'ChatListAuctionView': undefined;
|
'ChatListAuctionView': undefined;
|
||||||
'BotAuthSuccessTitle': undefined;
|
'BotAuthSuccessTitle': undefined;
|
||||||
|
'GiftPreviewSelectedTraits': undefined;
|
||||||
|
'GiftUpgradeViewAll': undefined;
|
||||||
|
'GiftPreviewToggleCraftableModels': undefined;
|
||||||
|
'GiftPreviewToggleRegularModels': undefined;
|
||||||
|
'AriaGiftPreviewPlay': undefined;
|
||||||
|
'AriaGiftPreviewStop': undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface LangPairWithVariables<V = LangVariable> {
|
export interface LangPairWithVariables<V = LangVariable> {
|
||||||
@ -3892,6 +3898,18 @@ export interface LangPairPluralWithVariables<V = LangVariable> {
|
|||||||
'ChatListAuctionTitle': {
|
'ChatListAuctionTitle': {
|
||||||
'count': V;
|
'count': V;
|
||||||
};
|
};
|
||||||
|
'GiftPreviewCountModels': {
|
||||||
|
'count': V;
|
||||||
|
};
|
||||||
|
'GiftPreviewCountCraftableModels': {
|
||||||
|
'count': V;
|
||||||
|
};
|
||||||
|
'GiftPreviewCountPatterns': {
|
||||||
|
'count': V;
|
||||||
|
};
|
||||||
|
'GiftPreviewCountBackdrops': {
|
||||||
|
'count': V;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
export type RegularLangKey = keyof LangPair;
|
export type RegularLangKey = keyof LangPair;
|
||||||
export type RegularLangKeyWithVariables = keyof LangPairWithVariables;
|
export type RegularLangKeyWithVariables = keyof LangPairWithVariables;
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user