Star Gift: Support craft (#6713)
107
CLAUDE.md
@ -420,3 +420,110 @@ lang('MarkdownKey', undefined, { withNodes: true, withMarkdown: true });
|
||||
|
||||
**7. Beyond React**
|
||||
Use `getTranslationFn()` to grab the same `lang` function in non-component code. Discouraged, use object syntax.
|
||||
|
||||
# ⚠️ IMPORTANT: Fasterdom & Rendering Phases
|
||||
|
||||
## Rendering Cycle
|
||||
|
||||
```
|
||||
--- frame start ---
|
||||
1. effects
|
||||
2. requested measures (DOM reads)
|
||||
3. render JSX → DOM
|
||||
4. layout effects
|
||||
5. requested mutations (DOM writes)
|
||||
6. forced reflow measure (avoid!)
|
||||
7. forced reflow mutate (avoid!)
|
||||
--- frame end ---
|
||||
```
|
||||
|
||||
## Phase Rules
|
||||
|
||||
| Hook/Context | Can READ (measure) | Can WRITE (mutate) |
|
||||
|--------------|-------------------|-------------------|
|
||||
| `useLayoutEffect` | ❌ NO | ✅ YES |
|
||||
| `useLayout` (deprecated) | ✅ YES | ❌ NO |
|
||||
| Event handlers (default) | ✅ YES | ❌ NO (use `requestMutation`) |
|
||||
| `requestMeasure` callback | ✅ YES | ❌ NO |
|
||||
| `requestMutation` callback | ❌ NO | ✅ YES |
|
||||
|
||||
## Usage Patterns
|
||||
|
||||
```typescript
|
||||
// ✅ CORRECT: Read in measure phase, write in mutation phase
|
||||
requestMeasure(() => {
|
||||
const width = element.offsetWidth; // READ
|
||||
|
||||
requestMutation(() => {
|
||||
element.style.width = `${width * 2}px`; // WRITE
|
||||
});
|
||||
});
|
||||
|
||||
// ❌ WRONG: Alternating reads/writes causes layout thrashing
|
||||
const width = element.offsetWidth; // READ → reflow
|
||||
element.style.width = `${width * 2}px`; // WRITE → reflow
|
||||
const height = element.offsetHeight; // READ → reflow again!
|
||||
```
|
||||
|
||||
## Signals: State Without Re-renders
|
||||
|
||||
Signals deliver updates **without causing component renders**. Use for frequently-updated values.
|
||||
|
||||
```typescript
|
||||
// Create signal
|
||||
const [getValue, setValue] = createSignal(initialValue);
|
||||
|
||||
// Get value
|
||||
getValue();
|
||||
|
||||
// Set value (notifies subscribers, NO re-render)
|
||||
setValue(newValue);
|
||||
|
||||
// Subscribe to changes
|
||||
getValue.subscribe(() => { /* react to change */ });
|
||||
```
|
||||
|
||||
**Signal Hooks:**
|
||||
- `useSignal()` – Create signal tied to component
|
||||
- `useDerivedSignal()` – Derive new signal from other signals/variables
|
||||
- `useDerivedState()` – Convert signal to render variable (triggers re-render)
|
||||
- `useStateRef()` – Access current value without it being a dependency
|
||||
|
||||
**When to use signals:**
|
||||
- Typing text, caret position
|
||||
- Animation state tracking
|
||||
- Values that change frequently but don't need re-render
|
||||
- Cross-component communication without prop drilling
|
||||
|
||||
## Key Optimization Hooks
|
||||
|
||||
| Hook | Purpose |
|
||||
|------|---------|
|
||||
| `useLastCallback` | Stable callback reference, always latest scope |
|
||||
| `useStateRef` | Access state without triggering effects |
|
||||
| `useLayoutEffectWithPrevDeps` | Synchronous effect with previous values |
|
||||
| `useSyncEffect` | Effect that runs during render (not RAF) |
|
||||
| `useResizeObserver` | Efficient element size observation |
|
||||
| `useIntersectionObserver` | Viewport visibility tracking |
|
||||
|
||||
## Heavy Animation Handling
|
||||
|
||||
```typescript
|
||||
// Mark animation start (pauses non-critical updates)
|
||||
const endAnimation = beginHeavyAnimation(duration);
|
||||
|
||||
// Run code only when fully idle (no animations + browser idle)
|
||||
onFullyIdle(() => {
|
||||
// Safe for heavy computations
|
||||
});
|
||||
```
|
||||
|
||||
## Performance Checklist
|
||||
|
||||
1. **Animations first** – Evaluate if code negatively impacts animations
|
||||
2. **Simplify algorithms** – Move complex ones to `onFullyIdle`
|
||||
3. **No loops in selectors** – Avoid iterations in `withGlobal` selectors
|
||||
4. **Minimize re-renders** – Especially in `Message`, `Chat`, `Sticker`, etc.
|
||||
5. **Understand effect timing** – `useEffect` vs `useLayoutEffect`
|
||||
6. **Prefer signals** – When you need effects only, not renders
|
||||
7. **Use `requestForcedReflow`** – Only as last resort for sync measure+mutate
|
||||
|
||||
@ -100,6 +100,7 @@ export interface GramJsAppConfig extends LimitsConfig {
|
||||
stars_stargift_resale_amount_max?: number;
|
||||
stars_stargift_resale_amount_min?: number;
|
||||
stars_stargift_resale_commission_permille?: number;
|
||||
stargifts_craft_attribute_permilles?: number[];
|
||||
ton_stargift_resale_amount_min?: number;
|
||||
ton_stargift_resale_amount_max?: number;
|
||||
ton_stargift_resale_commission_permille?: number;
|
||||
@ -239,6 +240,7 @@ export function buildAppConfig(json: GramJs.TypeJSONValue, hash: number): ApiApp
|
||||
starsStargiftResaleAmountMin: appConfig.stars_stargift_resale_amount_min,
|
||||
starsStargiftResaleAmountMax: appConfig.stars_stargift_resale_amount_max,
|
||||
starsStargiftResaleCommissionPermille: appConfig.stars_stargift_resale_commission_permille,
|
||||
stargiftsCraftAttributePermilles: appConfig.stargifts_craft_attribute_permilles,
|
||||
tonStargiftResaleAmountMin: appConfig.ton_stargift_resale_amount_min,
|
||||
tonStargiftResaleAmountMax: appConfig.ton_stargift_resale_amount_max,
|
||||
tonStargiftResaleCommissionPermille: appConfig.ton_stargift_resale_commission_permille,
|
||||
|
||||
@ -36,7 +36,7 @@ export function buildApiStarGift(starGift: GramJs.TypeStarGift): ApiStarGift {
|
||||
const {
|
||||
id, num, ownerId, ownerName, title, attributes, availabilityIssued, availabilityTotal, slug, ownerAddress,
|
||||
giftAddress, resellAmount, releasedBy, resaleTonOnly, requirePremium, valueCurrency, valueAmount, giftId,
|
||||
valueUsdAmount, burned, crafted,
|
||||
valueUsdAmount, burned, crafted, craftChancePermille,
|
||||
} = starGift;
|
||||
|
||||
return {
|
||||
@ -63,6 +63,7 @@ export function buildApiStarGift(starGift: GramJs.TypeStarGift): ApiStarGift {
|
||||
offerMinStars: starGift.offerMinStars,
|
||||
isBurned: burned,
|
||||
isCrafted: crafted,
|
||||
craftChancePermille,
|
||||
};
|
||||
}
|
||||
|
||||
@ -202,7 +203,7 @@ export function buildApiSavedStarGift(userStarGift: GramJs.SavedStarGift, peerId
|
||||
const {
|
||||
gift, date, convertStars, fromId, message, msgId, nameHidden, unsaved, refunded, upgradeStars, transferStars,
|
||||
canUpgrade, savedId, canExportAt, pinnedToTop, canResellAt, canTransferAt, prepaidUpgradeHash,
|
||||
dropOriginalDetailsStars,
|
||||
dropOriginalDetailsStars, canCraftAt,
|
||||
} = userStarGift;
|
||||
|
||||
const inputGift: ApiInputSavedStarGift | undefined = savedId && peerId
|
||||
@ -230,6 +231,7 @@ export function buildApiSavedStarGift(userStarGift: GramJs.SavedStarGift, peerId
|
||||
isPinned: pinnedToTop,
|
||||
dropOriginalDetailsStars: dropOriginalDetailsStars !== undefined ? toJSNumber(dropOriginalDetailsStars) : undefined,
|
||||
prepaidUpgradeHash,
|
||||
canCraftAt,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@ -426,7 +426,7 @@ export function buildApiMessageAction(action: GramJs.TypeMessageAction): ApiMess
|
||||
if (action instanceof GramJs.MessageActionStarGiftUnique) {
|
||||
const {
|
||||
upgrade, transferred, saved, refunded, gift, canExportAt, transferStars, fromId, peer, savedId,
|
||||
resaleAmount, prepaidUpgrade, dropOriginalDetailsStars, fromOffer,
|
||||
resaleAmount, prepaidUpgrade, dropOriginalDetailsStars, fromOffer, canCraftAt,
|
||||
} = action;
|
||||
|
||||
const starGift = buildApiStarGift(gift);
|
||||
@ -451,6 +451,7 @@ export function buildApiMessageAction(action: GramJs.TypeMessageAction): ApiMess
|
||||
dropOriginalDetailsStars: dropOriginalDetailsStars !== undefined
|
||||
? toJSNumber(dropOriginalDetailsStars)
|
||||
: undefined,
|
||||
canCraftAt,
|
||||
};
|
||||
}
|
||||
if (action instanceof GramJs.MessageActionPaidMessagesPrice) {
|
||||
|
||||
@ -105,12 +105,14 @@ export async function fetchResaleGifts({
|
||||
limit = DEFAULT_PRIMITIVES.INT,
|
||||
attributesHash,
|
||||
filter,
|
||||
forCraft,
|
||||
}: {
|
||||
giftId: string;
|
||||
offset?: string;
|
||||
limit?: number;
|
||||
attributesHash?: string;
|
||||
filter?: ResaleGiftsFilterOptions;
|
||||
forCraft?: boolean;
|
||||
}) {
|
||||
type GetResaleStarGifts = ConstructorParameters<typeof GramJs.payments.GetResaleStarGifts>[0];
|
||||
|
||||
@ -126,6 +128,7 @@ export async function fetchResaleGifts({
|
||||
limit,
|
||||
attributesHash: attributesHash ? BigInt(attributesHash) : DEFAULT_PRIMITIVES.BIGINT,
|
||||
attributes: buildInputResaleGiftsAttributes(attributes),
|
||||
forCraft: forCraft || undefined,
|
||||
...(filter && {
|
||||
sortByPrice: filter.sortType === 'byPrice' || undefined,
|
||||
sortByNum: filter.sortType === 'byNumber' || undefined,
|
||||
@ -631,6 +634,54 @@ export function resolveStarGiftOffer({
|
||||
});
|
||||
}
|
||||
|
||||
export async function fetchCraftStarGifts({
|
||||
giftId,
|
||||
peerId,
|
||||
offset = DEFAULT_PRIMITIVES.STRING,
|
||||
limit = DEFAULT_PRIMITIVES.INT,
|
||||
}: {
|
||||
giftId: string;
|
||||
peerId: string;
|
||||
offset?: string;
|
||||
limit?: number;
|
||||
}) {
|
||||
const result = await invokeRequest(new GramJs.payments.GetCraftStarGifts({
|
||||
giftId: BigInt(giftId),
|
||||
offset,
|
||||
limit,
|
||||
}));
|
||||
|
||||
if (!result) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return {
|
||||
gifts: result.gifts.map((g) => buildApiSavedStarGift(g, peerId)),
|
||||
nextOffset: result.nextOffset,
|
||||
count: result.count,
|
||||
};
|
||||
}
|
||||
|
||||
export async function craftStarGift({
|
||||
inputSavedGifts,
|
||||
}: {
|
||||
inputSavedGifts: ApiRequestInputSavedStarGift[];
|
||||
}) {
|
||||
try {
|
||||
await invokeRequest(new GramJs.payments.CraftStarGift({
|
||||
stargift: inputSavedGifts.map(buildInputSavedStarGift),
|
||||
}), {
|
||||
shouldThrow: true,
|
||||
});
|
||||
return undefined;
|
||||
} catch (err) {
|
||||
if (err instanceof RPCError) {
|
||||
return { error: err.errorMessage };
|
||||
}
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
export async function fetchStarGiftUpgradeAttributes({
|
||||
giftId,
|
||||
}: {
|
||||
|
||||
@ -1136,6 +1136,10 @@ export function updater(update: Update) {
|
||||
giftId: update.giftId.toString(),
|
||||
userState: buildApiStarGiftAuctionUserState(update.userState),
|
||||
});
|
||||
} else if (update instanceof GramJs.UpdateStarGiftCraftFail) {
|
||||
sendApiUpdate({
|
||||
'@type': 'updateStarGiftCraftFail',
|
||||
});
|
||||
} else if (update instanceof GramJs.UpdatePaidReactionPrivacy) {
|
||||
sendApiUpdate({
|
||||
'@type': 'updatePaidReactionPrivacy',
|
||||
|
||||
@ -274,6 +274,7 @@ export interface ApiMessageActionStarGiftUnique extends ActionMediaType {
|
||||
savedId?: string;
|
||||
resaleAmount?: ApiTypeCurrencyAmount;
|
||||
dropOriginalDetailsStars?: number;
|
||||
canCraftAt?: number;
|
||||
}
|
||||
|
||||
export interface ApiMessageActionChannelJoined extends ActionMediaType {
|
||||
|
||||
@ -265,6 +265,7 @@ export interface ApiAppConfig {
|
||||
starsStargiftResaleAmountMin?: number;
|
||||
starsStargiftResaleAmountMax?: number;
|
||||
starsStargiftResaleCommissionPermille?: number;
|
||||
stargiftsCraftAttributePermilles?: number[];
|
||||
starsSuggestedPostAmountMax: number;
|
||||
starsSuggestedPostAmountMin: number;
|
||||
starsSuggestedPostCommissionPermille: number;
|
||||
|
||||
@ -63,6 +63,7 @@ export interface ApiStarGiftUnique {
|
||||
offerMinStars?: number;
|
||||
isBurned?: true;
|
||||
isCrafted?: true;
|
||||
craftChancePermille?: number;
|
||||
}
|
||||
|
||||
export type ApiStarGift = ApiStarGiftRegular | ApiStarGiftUnique;
|
||||
@ -149,6 +150,7 @@ export interface ApiSavedStarGift {
|
||||
upgradeMsgId?: number; // Local field, used for Action Message
|
||||
localTag?: number; // Local field, used for key in list
|
||||
dropOriginalDetailsStars?: number;
|
||||
canCraftAt?: number;
|
||||
}
|
||||
|
||||
export type StarGiftAttributeIdModel = {
|
||||
|
||||
@ -861,6 +861,10 @@ export type ApiUpdateStarGiftAuctionUserState = {
|
||||
userState: ApiStarGiftAuctionUserState;
|
||||
};
|
||||
|
||||
export type ApiUpdateStarGiftCraftFail = {
|
||||
'@type': 'updateStarGiftCraftFail';
|
||||
};
|
||||
|
||||
export type ApiUpdateDeleteProfilePhoto = {
|
||||
'@type': 'updateDeleteProfilePhoto';
|
||||
peerId: string;
|
||||
@ -943,7 +947,8 @@ export type ApiUpdate = (
|
||||
ApiUpdateStealthMode | ApiUpdateAttachMenuBots | ApiUpdateNewAuthorization | ApiUpdateGroupInvitePrivacyForbidden |
|
||||
ApiUpdateViewForumAsMessages | ApiUpdateSavedDialogPinned | ApiUpdatePinnedSavedDialogIds | ApiUpdateChatLastMessage |
|
||||
ApiUpdateDeleteSavedHistory | ApiUpdatePremiumFloodWait | ApiUpdateStarsBalance | ApiUpdateStarGiftAuctionState
|
||||
| ApiUpdateStarGiftAuctionUserState | ApiUpdateBotCommands | ApiUpdateQuickReplyMessage | ApiUpdateQuickReplies
|
||||
| ApiUpdateStarGiftAuctionUserState | ApiUpdateStarGiftCraftFail | ApiUpdateBotCommands
|
||||
| ApiUpdateQuickReplyMessage | ApiUpdateQuickReplies
|
||||
| ApiDeleteQuickReply | ApiUpdateDeleteQuickReplyMessages | ApiUpdateDeleteProfilePhoto | ApiUpdateNewProfilePhoto
|
||||
| ApiUpdateEntities | ApiUpdatePaidReactionPrivacy | ApiUpdateLangPackTooLong | ApiUpdateLangPack
|
||||
| ApiUpdateNotSupportedInFrozenAccountError
|
||||
|
||||
1
src/assets/attribute-mask.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="29" fill="none"><defs><filter id="a" width="120%" height="120%" x="-10%" y="-10%"><feGaussianBlur in="SourceGraphic" stdDeviation=".3"/></filter></defs><path fill="#fff" d="M32 16c0 4.302-1.698 8.208-4.46 11.083-1.299 1.352-3.249 1.733-5.105 1.48-1.97-.27-4.55-.563-6.435-.563-1.886 0-4.466.293-6.434.562-1.857.254-3.807-.127-5.106-1.479A15.95 15.95 0 0 1 0 16C0 7.163 7.163 0 16 0s16 7.163 16 16" filter="url(#a)"/></svg>
|
||||
|
After Width: | Height: | Size: 481 B |
1
src/assets/broken-gift.svg
Normal file
|
After Width: | Height: | Size: 7.7 KiB |
1
src/assets/craft-progress.svg
Normal file
|
After Width: | Height: | Size: 7.5 KiB |
1
src/assets/font-icons/add-filled.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" viewBox="0 0 32 32"><path d="M16 0C7.163 0 0 7.163 0 16s7.163 16 16 16 16-7.163 16-16S24.837 0 16 0m7.5 17.5h-6v6a1.5 1.5 0 0 1-3 0v-6h-6a1.5 1.5 0 0 1 0-3h6v-6a1.5 1.5 0 0 1 3 0v6h6a1.5 1.5 0 0 1 0 3"/></svg>
|
||||
|
After Width: | Height: | Size: 270 B |
1
src/assets/font-icons/boost-craft-chance.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32"><path d="M12.36 25.174c-1.506 0-2.732-1.226-2.732-2.732s1.226-2.731 2.732-2.731 2.731 1.226 2.731 2.731-1.225 2.732-2.731 2.732m0-3.463a.732.732 0 1 0 .002 1.464.732.732 0 0 0-.002-1.464M19.76 31.458c-1.507 0-2.732-1.226-2.732-2.731s1.225-2.732 2.731-2.732 2.732 1.226 2.732 2.732-1.226 2.731-2.732 2.731m0-3.463a.732.732 0 1 0 0 1.465.732.732 0 0 0 0-1.465M11.334 31.31a1 1 0 0 1-.707-1.707l9.452-9.452a1 1 0 1 1 1.414 1.414l-9.452 9.453a1 1 0 0 1-.707.293M22.315 17.215a1 1 0 0 1-.495-.132l-5.607-3.205-5.606 3.205a1 1 0 0 1-.992-1.736l6.598-3.772 6.599 3.772a1 1 0 0 1-.497 1.868"/><path d="M22.315 11.88a1 1 0 0 1-.495-.132l-5.607-3.204-5.606 3.204a1 1 0 1 1-.992-1.736l6.598-3.77 6.599 3.77a1 1 0 0 1-.497 1.868"/><path d="M24.819 27.818a1 1 0 0 1-.623-1.783c3.17-2.52 4.99-6.278 4.99-10.308 0-7.27-5.916-13.185-13.185-13.185-7.271 0-13.186 5.915-13.186 13.185 0 4.03 1.818 7.788 4.99 10.308a1 1 0 0 1-1.245 1.566C2.91 24.698.815 20.37.815 15.727.815 7.354 7.627.542 16 .542s15.185 6.812 15.185 15.185c0 4.643-2.094 8.971-5.745 11.874a1 1 0 0 1-.621.217"/></svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
1
src/assets/font-icons/combine-craft.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32"><path d="M22.155.843H9.845C4.882.843.844 4.881.844 9.844v12.312c0 4.962 4.038 9 9 9h12.311c4.964 0 9.002-4.038 9.002-9V9.844c0-4.963-4.038-9.001-9.002-9.001m7.002 9.001V15h-2.404c-.437-1.552-1.864-2.692-3.554-2.692s-3.118 1.14-3.555 2.692H17v-4.507h-1a1.694 1.694 0 0 1-1.692-1.692c0-.933.76-1.691 1.692-1.691h1V2.843h5.155c3.861 0 7.002 3.14 7.002 7.001M9.845 2.843H15v2.404c-1.55.437-2.692 1.865-2.692 3.554S13.45 11.918 15 12.356V15h-4.507v1c0 .933-.759 1.692-1.692 1.692A1.693 1.693 0 0 1 7.109 16v-1H2.844V9.844c0-3.86 3.14-7.001 7-7.001M2.844 22.156V17h2.403c.436 1.551 1.864 2.692 3.554 2.692S11.918 18.55 12.355 17H15v4.507h1c.933 0 1.692.759 1.692 1.691 0 .934-.759 1.693-1.692 1.693h-1v4.265H9.845a7.01 7.01 0 0 1-7.001-7m19.311 7H17v-2.403c1.552-.436 2.692-1.865 2.692-3.555s-1.14-3.117-2.692-3.553V17h4.507v-1c0-.933.759-1.692 1.692-1.692s1.692.759 1.692 1.692v1h4.266v5.155c0 3.86-3.14 7.001-7.002 7.001" /></svg>
|
||||
|
After Width: | Height: | Size: 986 B |
1
src/assets/font-icons/craft.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32"><path d="M11.945 19.473V15.37a.49.49 0 0 0-.484-.5H3.598a.494.494 0 0 0-.489.5c0 .113.04.223.106.309q1.553 2.016 3.41 3.02c1.285.695 2.504 1.167 4.45 1.417l.144.016c.36.047.68-.215.722-.582l.004-.04zM13.723 14.871h12.902a.49.49 0 0 1 .484.5v1.2a.497.497 0 0 1-.437.5q-1.745.17-2.969 1.109c-1.375 1.058-1.82 1.949-1.848 3.53-.015.962.426 1.892 1.325 2.782l.062.063a.66.66 0 0 1 .203.484v1.336c0 .367-.289.668-.648.668h-8.91c-.36 0-.649-.3-.649-.668v-1.21c0-.243.13-.466.336-.583q1.113-.634 1.114-1.953 0-1.354-1.176-1.942a.5.5 0 0 1-.274-.449v-4.867c0-.277.219-.5.485-.5M12.996 9.402l-.262-1.117a.664.664 0 0 1 .48-.805l.024-.007L25.2 5.07a1.224 1.224 0 0 1 1.426.934c.145.621-.227 1.246-.832 1.398l-.02.004-12.02 2.492a.643.643 0 0 1-.75-.464zM9.355 13.441l2.13-.55a1.17 1.17 0 0 0 .828-1.395l-.688-3.066a2.7 2.7 0 0 0-.375-.89l-.102-.157q-1.85-2.78-2.683-2.547-.846.239-1.211 3.707l-.008.094a2.7 2.7 0 0 0 .055.879l.687 3.054c.14.625.754 1.02 1.367.871m0 0"/></svg>
|
||||
|
After Width: | Height: | Size: 1.0 KiB |
@ -1576,6 +1576,7 @@
|
||||
"GiftInfoCollectible" = "Collectible #{number}";
|
||||
"GiftInfoUniqueTitle" = "{name} {number}";
|
||||
"GiftSavedNumber" = "#{number}";
|
||||
"GiftInfoModelCrafted" = "{model} (crafted)";
|
||||
"GiftAttributeModel" = "Model";
|
||||
"GiftAttributeBackdrop" = "Backdrop";
|
||||
"GiftAttributeSymbol" = "Symbol";
|
||||
@ -1593,6 +1594,32 @@
|
||||
"GiftInfoUpgradeBadge" = "upgrade";
|
||||
"GiftInfoUpgradeForFree" = "Upgrade For Free";
|
||||
"GiftInfoUpgrade" = "Upgrade";
|
||||
"GiftInfoCraft" = "Craft";
|
||||
"GiftCraftTitle" = "Craft Gift";
|
||||
"GiftCraftButton" = "CRAFT {giftName}";
|
||||
"GiftCraftSuccessChance" = "{percent} Success Chance";
|
||||
"GiftCraftEmptyHint" = "Tap {button} to add your first gift";
|
||||
"GiftCraftNewGift" = "Craft New Gift";
|
||||
"GiftCraftSelectTitle" = "Select Gifts";
|
||||
"GiftCraftSelectYourGifts" = "Your Gifts";
|
||||
"GiftCraftSelectMarketGifts_one" = "{count} Suitable Gift on Sale";
|
||||
"GiftCraftSelectMarketGifts_other" = "{count} Suitable Gifts on Sale";
|
||||
"GiftCraftDescription" = "Add up to **4 gifts** to craft new {giftLine}";
|
||||
"GiftCraftWarning" = "If crafting fails, all selected gifts will be consumed.";
|
||||
"GiftCraftingTitle" = "Crafting";
|
||||
"GiftCraftFailedTitle" = "Crafting Failed";
|
||||
"GiftCraftFailedDescription_one" = "This crafting attempt was unsuccessful.\n**{count} gift** was lost.";
|
||||
"GiftCraftFailedDescription_other" = "This crafting attempt was unsuccessful.\n**{count} gifts** were lost.";
|
||||
"GiftCraftInfoTitle" = "Gift Crafting";
|
||||
"GiftCraftInfoSubtitle" = "Turn your gifts into rare, epic, uncommon and legendary versions.";
|
||||
"GiftCraftInfoCraftTitle" = "Get Rare Models";
|
||||
"GiftCraftInfoCraftDescription" = "Select up to 4 gifts to craft a new exclusive model.";
|
||||
"GiftCraftInfoChanceTitle" = "Maximize Chances";
|
||||
"GiftCraftInfoChanceDescription" = "Combine more gifts to increase your odds of success.";
|
||||
"GiftCraftInfoRiskTitle" = "Affect the Result";
|
||||
"GiftCraftInfoRiskDescription" = "Use gifts with the same attribute to boost its chance.";
|
||||
"GiftCraftHelp" = "Help";
|
||||
"GiftCraftViewAll" = "View all craftable models >";
|
||||
"GiftInfoWithdraw" = "Withdraw";
|
||||
"GiftInfoWear" = "Wear";
|
||||
"GiftInfoTakeOff" = "Take Off";
|
||||
|
||||
BIN
src/assets/tgs/BrokenGift.tgs
Normal file
BIN
src/assets/tgs/CraftProgress.tgs
Normal file
@ -13,6 +13,9 @@ export { default as GiftInfoValueModal } from '../components/modals/gift/value/G
|
||||
export { default as GiftLockedModal } from '../components/modals/gift/locked/GiftLockedModal';
|
||||
export { default as GiftResalePriceComposerModal } from '../components/modals/gift/resale/GiftResalePriceComposerModal';
|
||||
export { default as GiftUpgradeModal } from '../components/modals/gift/upgrade/GiftUpgradeModal';
|
||||
export { default as GiftCraftModal } from '../components/modals/gift/craft/GiftCraftModal';
|
||||
export { default as GiftCraftSelectModal } from '../components/modals/gift/craft/GiftCraftSelectModal';
|
||||
export { default as GiftCraftInfoModal } from '../components/modals/gift/craft/GiftCraftInfoModal';
|
||||
export { default as GiftPreviewModal } from '../components/modals/gift/preview/GiftPreviewModal';
|
||||
export { default as GiftAuctionModal } from '../components/modals/gift/auction/GiftAuctionModal';
|
||||
export { default as GiftAuctionBidModal } from '../components/modals/gift/auction/GiftAuctionBidModal';
|
||||
|
||||
@ -1,10 +1,21 @@
|
||||
import { memo, useRef } from '../../lib/teact/teact';
|
||||
import { memo } from '../../lib/teact/teact';
|
||||
|
||||
import type { GlobalState } from '../../global/types';
|
||||
|
||||
import { selectTabState } from '../../global/selectors';
|
||||
import buildClassName from '../../util/buildClassName';
|
||||
import buildStyle from '../../util/buildStyle';
|
||||
|
||||
import useSelector from '../../hooks/data/useSelector';
|
||||
import useShowTransition from '../../hooks/useShowTransition';
|
||||
|
||||
import styles from './Sparkles.module.scss';
|
||||
|
||||
function selectIsHeavyModalOpen(global: GlobalState) {
|
||||
const tabState = selectTabState(global);
|
||||
return Boolean(tabState.giftCraftModal || tabState.giftCraftSelectModal);
|
||||
}
|
||||
|
||||
type ButtonParameters = {
|
||||
preset: 'button';
|
||||
};
|
||||
@ -89,7 +100,15 @@ const Sparkles = ({
|
||||
noAnimation,
|
||||
...presetSettings
|
||||
}: OwnProps) => {
|
||||
const ref = useRef<HTMLDivElement>();
|
||||
const isHeavyModalOpen = useSelector(selectIsHeavyModalOpen);
|
||||
|
||||
const { ref, shouldRender } = useShowTransition<HTMLDivElement>({
|
||||
isOpen: !isHeavyModalOpen,
|
||||
withShouldRender: true,
|
||||
noMountTransition: true,
|
||||
});
|
||||
|
||||
if (!shouldRender) return undefined;
|
||||
|
||||
if (presetSettings.preset === 'button') {
|
||||
return (
|
||||
@ -124,7 +143,11 @@ const Sparkles = ({
|
||||
|
||||
if (presetSettings.preset === 'progress') {
|
||||
return (
|
||||
<div ref={ref} className={buildClassName(styles.root, styles.progress, className)} style={style}>
|
||||
<div
|
||||
ref={ref}
|
||||
className={buildClassName(styles.root, styles.progress, className)}
|
||||
style={style}
|
||||
>
|
||||
{PROGRESS_POSITIONS.map((position) => {
|
||||
return (
|
||||
<div
|
||||
|
||||
@ -2,16 +2,12 @@
|
||||
position: absolute;
|
||||
top: -0.125rem;
|
||||
right: -0.125rem;
|
||||
|
||||
width: 3.5rem;
|
||||
height: 3.5rem;
|
||||
}
|
||||
|
||||
.text {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%) translate(6px, -6px) rotate(45deg);
|
||||
|
||||
font-size: 0.625rem;
|
||||
font-weight: var(--font-weight-semibold);
|
||||
|
||||
@ -22,10 +22,15 @@ type ColorKey = keyof typeof COLORS;
|
||||
const COLOR_KEYS = new Set(Object.keys(COLORS) as ColorKey[]);
|
||||
type GradientColor = readonly [string, string];
|
||||
|
||||
const DEFAULT_SIZE = 56;
|
||||
const TEXT_OFFSET_RATIO = 6 / DEFAULT_SIZE;
|
||||
|
||||
type OwnProps = {
|
||||
color: ColorKey | GradientColor | (string & {});
|
||||
text: string;
|
||||
size?: number;
|
||||
className?: string;
|
||||
textClassName?: string;
|
||||
};
|
||||
|
||||
type StateProps = {
|
||||
@ -33,7 +38,7 @@ type StateProps = {
|
||||
};
|
||||
|
||||
const GiftRibbon = ({
|
||||
text, color, className, theme,
|
||||
text, color, size = DEFAULT_SIZE, className, textClassName, theme,
|
||||
}: OwnProps & StateProps) => {
|
||||
const randomId = useUniqueId();
|
||||
const validSvgRandomId = `svg-${randomId}`; // ID must start with a letter
|
||||
@ -52,9 +57,11 @@ const GiftRibbon = ({
|
||||
const startColor = gradientColor ? gradientColor[0] : color;
|
||||
const endColor = gradientColor ? gradientColor[1] : color;
|
||||
|
||||
const textOffset = Math.round(size * TEXT_OFFSET_RATIO);
|
||||
|
||||
return (
|
||||
<div className={buildClassName(styles.root, className)}>
|
||||
<svg className={styles.ribbon} width="56" height="56" viewBox="0 0 56 56" fill="none">
|
||||
<div className={buildClassName(styles.root, className)} style={`width: ${size}px; height: ${size}px`}>
|
||||
<svg className={styles.ribbon} width={size} height={size} viewBox="0 0 56 56" fill="none" aria-hidden="true">
|
||||
<path d="M52.4851 26.4853L29.5145 3.51472C27.2641 1.26428 24.2119 0 21.0293 0H2.82824C1.04643 0 0.154103 2.15429 1.41403 3.41422L52.5856 54.5858C53.8455 55.8457 55.9998 54.9534 55.9998 53.1716V34.9706C55.9998 31.788 54.7355 28.7357 52.4851 26.4853Z" fill={`url(#${validSvgRandomId})`} />
|
||||
<defs>
|
||||
<linearGradient id={validSvgRandomId} x1="27.9998" y1="1" x2="27.9998" y2="55" gradientUnits="userSpaceOnUse">
|
||||
@ -63,7 +70,12 @@ const GiftRibbon = ({
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
||||
<div className={styles.text}>{text}</div>
|
||||
<div
|
||||
className={buildClassName(styles.text, textClassName)}
|
||||
style={`transform: translate(-50%, -50%) translate(${textOffset}px, ${-textOffset}px) rotate(45deg)`}
|
||||
>
|
||||
{text}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@ -1,5 +1,7 @@
|
||||
import BrokenGiftPreview from '../../../assets/broken-gift.svg';
|
||||
import QrPlane from '../../../assets/tgs/auth/QrPlane.tgs';
|
||||
import BannedDuck from '../../../assets/tgs/BannedDuck.tgs';
|
||||
import BrokenGift from '../../../assets/tgs/BrokenGift.tgs';
|
||||
import CameraFlip from '../../../assets/tgs/calls/CameraFlip.tgs';
|
||||
import HandFilled from '../../../assets/tgs/calls/HandFilled.tgs';
|
||||
import HandOutline from '../../../assets/tgs/calls/HandOutline.tgs';
|
||||
@ -8,6 +10,7 @@ import VoiceAllowTalk from '../../../assets/tgs/calls/VoiceAllowTalk.tgs';
|
||||
import VoiceMini from '../../../assets/tgs/calls/VoiceMini.tgs';
|
||||
import VoiceMuted from '../../../assets/tgs/calls/VoiceMuted.tgs';
|
||||
import VoiceOutlined from '../../../assets/tgs/calls/VoiceOutlined.tgs';
|
||||
import CraftProgress from '../../../assets/tgs/CraftProgress.tgs';
|
||||
import Diamond from '../../../assets/tgs/Diamond.tgs';
|
||||
import DuckNothingFound from '../../../assets/tgs/DuckNothingFound.tgs';
|
||||
import Flame from '../../../assets/tgs/general/Flame.tgs';
|
||||
@ -42,6 +45,7 @@ import SearchPreview from '../../../assets/tgs-previews/Search.svg';
|
||||
import PasskeysPreview from '../../../assets/tgs-previews/settings/Passkeys.svg';
|
||||
|
||||
export const LOCAL_TGS_PREVIEW_URLS = {
|
||||
BrokenGift: BrokenGiftPreview,
|
||||
DuckNothingFound: DuckNothingFoundPreview,
|
||||
Search: SearchPreview,
|
||||
Passkeys: PasskeysPreview,
|
||||
@ -82,6 +86,8 @@ export const LOCAL_TGS_URLS = {
|
||||
Report,
|
||||
SearchingDuck,
|
||||
BannedDuck,
|
||||
BrokenGift,
|
||||
CraftProgress,
|
||||
Diamond,
|
||||
Search,
|
||||
DuckNothingFound,
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { memo, useEffect, useLayoutEffect, useMemo, useRef, useSignal, useState } from '../../../lib/teact/teact';
|
||||
import { memo, useEffect, useLayoutEffect, useMemo, useRef, useSignal } from '../../../lib/teact/teact';
|
||||
|
||||
import type { ApiSticker } from '../../../api/types';
|
||||
|
||||
@ -10,6 +10,7 @@ import { adjustHsv, getColorLuma, hex2rgb } from '../../../util/colors.ts';
|
||||
import { preloadImage } from '../../../util/files';
|
||||
import { REM } from '../helpers/mediaDimensions';
|
||||
|
||||
import useAsync from '../../../hooks/useAsync';
|
||||
import useLastCallback from '../../../hooks/useLastCallback';
|
||||
import useMedia from '../../../hooks/useMedia';
|
||||
import useResizeObserver from '../../../hooks/useResizeObserver';
|
||||
@ -20,7 +21,8 @@ import styles from './RadialPatternBackground.module.scss';
|
||||
type OwnProps = {
|
||||
backgroundColors?: string[];
|
||||
patternIcon?: ApiSticker;
|
||||
patternColor?: number;
|
||||
patternUrl?: string;
|
||||
patternColor?: string;
|
||||
patternSize?: number;
|
||||
maxRadius?: number;
|
||||
centerEmptiness?: number;
|
||||
@ -48,6 +50,8 @@ const DEFAULT_OVAL_FACTOR = 1.4;
|
||||
const RadialPatternBackground = ({
|
||||
backgroundColors,
|
||||
patternIcon,
|
||||
patternUrl,
|
||||
patternColor,
|
||||
patternSize = DEFAULT_PATTERN_SIZE,
|
||||
centerEmptiness = DEFAULT_CENTER_EMPTINESS,
|
||||
ringsCount = DEFAULT_RINGS_COUNT,
|
||||
@ -67,15 +71,15 @@ const RadialPatternBackground = ({
|
||||
|
||||
const dpr = useDevicePixelRatio();
|
||||
|
||||
const [emojiImage, setEmojiImage] = useState<HTMLImageElement | undefined>();
|
||||
|
||||
const previewMediaHash = patternIcon && getStickerMediaHash(patternIcon, 'preview');
|
||||
const previewUrl = useMedia(previewMediaHash);
|
||||
|
||||
useEffect(() => {
|
||||
if (!previewUrl) return;
|
||||
preloadImage(previewUrl).then(setEmojiImage);
|
||||
}, [previewUrl]);
|
||||
const imageUrl = previewUrl || patternUrl;
|
||||
|
||||
const { result: emojiImage } = useAsync(
|
||||
() => (imageUrl ? preloadImage(imageUrl) : Promise.resolve(undefined)),
|
||||
[imageUrl],
|
||||
);
|
||||
|
||||
const patternPositions = useMemo(() => {
|
||||
const coordinates: { x: number; y: number; sizeFactor: number }[] = [];
|
||||
@ -146,9 +150,13 @@ const RadialPatternBackground = ({
|
||||
ctx.drawImage(emojiImage, renderX - size / 2, renderY - size / 2, size, size);
|
||||
});
|
||||
|
||||
const patternColor = backgroundColors?.[1] ?? backgroundColors?.[0] ?? '#000000';
|
||||
const isDark = getColorLuma(hex2rgb(patternColor)) < DARK_LUMA_THRESHOLD;
|
||||
ctx.fillStyle = adjustHsv(patternColor, 0.5, isDark ? 0.28 : -0.28);
|
||||
if (patternColor) {
|
||||
ctx.fillStyle = patternColor;
|
||||
} else {
|
||||
const baseColor = backgroundColors?.[1] ?? backgroundColors?.[0] ?? '#000000';
|
||||
const isDark = getColorLuma(hex2rgb(baseColor)) < DARK_LUMA_THRESHOLD;
|
||||
ctx.fillStyle = adjustHsv(baseColor, 0.5, isDark ? 0.28 : -0.28);
|
||||
}
|
||||
ctx.globalCompositeOperation = 'source-in';
|
||||
ctx.fillRect(0, 0, width, height);
|
||||
|
||||
@ -171,7 +179,7 @@ const RadialPatternBackground = ({
|
||||
|
||||
useEffect(() => {
|
||||
draw();
|
||||
}, [emojiImage, patternPositions, yPosition, ovalFactor]);
|
||||
}, [emojiImage, patternPositions, yPosition, ovalFactor, patternColor]);
|
||||
|
||||
useLayoutEffect(() => {
|
||||
const { width, height } = getContainerSize();
|
||||
|
||||
@ -702,6 +702,17 @@ const ActionMessageText = ({
|
||||
if (isSavedMessages) {
|
||||
if (isUpgrade) return lang('ActionStarGiftUpgradedSelf');
|
||||
if (isTransferred) return lang('ActionStarGiftTransferredSelf');
|
||||
if (resaleAmount) {
|
||||
const amountText = formatCurrencyAmountAsText(lang, resaleAmount);
|
||||
return lang(
|
||||
'ApiMessageMessageActionResaleStarGiftUniqueOutgoing',
|
||||
{
|
||||
gift: lang('GiftUnique', { title: gift.title, number: gift.number }),
|
||||
stars: asPreview ? amountText : renderStrong(amountText),
|
||||
},
|
||||
{ withNodes: true },
|
||||
);
|
||||
}
|
||||
if (gift.isCrafted) return lang('ActionStarGiftCraftedSelf');
|
||||
}
|
||||
|
||||
|
||||
@ -27,6 +27,9 @@ import GiftAuctionBidModal from './gift/auction/GiftAuctionBidModal.async';
|
||||
import GiftAuctionChangeRecipientModal from './gift/auction/GiftAuctionChangeRecipientModal.async';
|
||||
import GiftAuctionInfoModal from './gift/auction/GiftAuctionInfoModal.async';
|
||||
import GiftAuctionModal from './gift/auction/GiftAuctionModal.async';
|
||||
import GiftCraftInfoModal from './gift/craft/GiftCraftInfoModal.async';
|
||||
import GiftCraftModal from './gift/craft/GiftCraftModal.async';
|
||||
import GiftCraftSelectModal from './gift/craft/GiftCraftSelectModal.async';
|
||||
import PremiumGiftModal from './gift/GiftModal.async';
|
||||
import GiftInfoModal from './gift/info/GiftInfoModal.async';
|
||||
import GiftLockedModal from './gift/locked/GiftLockedModal.async';
|
||||
@ -108,6 +111,9 @@ type ModalKey = keyof Pick<TabState,
|
||||
'aboutAdsModal' |
|
||||
'giftPreviewModal' |
|
||||
'giftUpgradeModal' |
|
||||
'giftCraftModal' |
|
||||
'giftCraftSelectModal' |
|
||||
'giftCraftInfoModal' |
|
||||
'giftAuctionModal' |
|
||||
'giftAuctionBidModal' |
|
||||
'giftAuctionInfoModal' |
|
||||
@ -188,6 +194,9 @@ const MODALS: ModalRegistry = {
|
||||
aboutAdsModal: AboutAdsModal,
|
||||
giftPreviewModal: GiftPreviewModal,
|
||||
giftUpgradeModal: GiftUpgradeModal,
|
||||
giftCraftModal: GiftCraftModal,
|
||||
giftCraftSelectModal: GiftCraftSelectModal,
|
||||
giftCraftInfoModal: GiftCraftInfoModal,
|
||||
giftAuctionModal: GiftAuctionModal,
|
||||
giftAuctionBidModal: GiftAuctionBidModal,
|
||||
giftAuctionInfoModal: GiftAuctionInfoModal,
|
||||
|
||||
@ -32,6 +32,7 @@ type OwnProps = {
|
||||
hasBackdrop?: boolean;
|
||||
closeButtonColor?: 'translucent' | 'translucent-white';
|
||||
moreMenuItems?: TeactNode;
|
||||
headerRightToolBar?: TeactNode;
|
||||
onClose: NoneToVoidFunction;
|
||||
onButtonClick?: NoneToVoidFunction;
|
||||
withBalanceBar?: boolean;
|
||||
@ -54,6 +55,7 @@ const TableInfoModal = ({
|
||||
hasBackdrop,
|
||||
closeButtonColor,
|
||||
moreMenuItems,
|
||||
headerRightToolBar,
|
||||
onClose,
|
||||
onButtonClick,
|
||||
withBalanceBar,
|
||||
@ -79,6 +81,7 @@ const TableInfoModal = ({
|
||||
className={className}
|
||||
contentClassName={buildClassName(styles.content, contentClassName)}
|
||||
moreMenuItems={moreMenuItems}
|
||||
headerRightToolBar={headerRightToolBar}
|
||||
onClose={onClose}
|
||||
withBalanceBar={withBalanceBar}
|
||||
currencyInBalanceBar={currencyInBalanceBar}
|
||||
|
||||
41
src/components/modals/gift/GiftEmptyState.tsx
Normal file
@ -0,0 +1,41 @@
|
||||
import type { TeactNode } from '../../../lib/teact/teact';
|
||||
import { memo } from '../../../lib/teact/teact';
|
||||
|
||||
import { LOCAL_TGS_URLS } from '../../common/helpers/animatedAssets';
|
||||
|
||||
import AnimatedIconWithPreview from '../../common/AnimatedIconWithPreview';
|
||||
import Link from '../../ui/Link';
|
||||
|
||||
import styles from './GiftEmptyState.module.scss';
|
||||
|
||||
type OwnProps = {
|
||||
description: TeactNode;
|
||||
linkText?: TeactNode;
|
||||
onLinkClick?: NoneToVoidFunction;
|
||||
};
|
||||
|
||||
const GiftEmptyState = ({ description, linkText, onLinkClick }: OwnProps) => {
|
||||
return (
|
||||
<div className={styles.root}>
|
||||
<AnimatedIconWithPreview
|
||||
size={160}
|
||||
tgsUrl={LOCAL_TGS_URLS.SearchingDuck}
|
||||
nonInteractive
|
||||
noLoop
|
||||
/>
|
||||
<div className={styles.description}>
|
||||
{description}
|
||||
</div>
|
||||
{Boolean(linkText && onLinkClick) && (
|
||||
<Link
|
||||
className={styles.link}
|
||||
onClick={onLinkClick}
|
||||
>
|
||||
{linkText}
|
||||
</Link>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(GiftEmptyState);
|
||||
@ -14,18 +14,16 @@ import { selectTabState,
|
||||
} from '../../../global/selectors';
|
||||
import buildClassName from '../../../util/buildClassName';
|
||||
import { RESALE_GIFTS_LIMIT } from '../../../limits';
|
||||
import { LOCAL_TGS_URLS } from '../../common/helpers/animatedAssets';
|
||||
|
||||
import useInfiniteScroll from '../../../hooks/useInfiniteScroll';
|
||||
import { useIntersectionObserver } from '../../../hooks/useIntersectionObserver';
|
||||
import useLang from '../../../hooks/useLang';
|
||||
import useLastCallback from '../../../hooks/useLastCallback';
|
||||
|
||||
import AnimatedIconWithPreview from '../../common/AnimatedIconWithPreview';
|
||||
import InfiniteScroll from '../../ui/InfiniteScroll';
|
||||
import Link from '../../ui/Link';
|
||||
import Transition from '../../ui/Transition';
|
||||
import GiftItemStar from './GiftItemStar';
|
||||
import ResaleGiftsNotFound from './ResaleGiftsNotFound';
|
||||
|
||||
import styles from './GiftModal.module.scss';
|
||||
|
||||
@ -96,37 +94,19 @@ const GiftModalResaleScreen: FC<OwnProps & StateProps> = ({
|
||||
} });
|
||||
});
|
||||
|
||||
function renderNothingFoundGiftsWithFilter() {
|
||||
return (
|
||||
<div className={styles.notFoundGiftsRoot}>
|
||||
<AnimatedIconWithPreview
|
||||
size={160}
|
||||
tgsUrl={LOCAL_TGS_URLS.SearchingDuck}
|
||||
nonInteractive
|
||||
noLoop
|
||||
/>
|
||||
<div className={styles.notFoundGiftsDescription}>
|
||||
{lang('ResellGiftsNoFound')}
|
||||
</div>
|
||||
{hasFilter && (
|
||||
<Link
|
||||
className={styles.notFoundGiftsLink}
|
||||
onClick={handleResetGiftsFilter}
|
||||
>
|
||||
{lang('ResellGiftsClearFilters')}
|
||||
</Link>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div ref={scrollerRef} className={buildClassName(styles.resaleScreenRoot, 'custom-scroll')}>
|
||||
<Transition
|
||||
name="zoomFade"
|
||||
activeKey={updateIteration}
|
||||
>
|
||||
{isGiftsEmpty && areGiftsAllLoaded && renderNothingFoundGiftsWithFilter()}
|
||||
{isGiftsEmpty && areGiftsAllLoaded && (
|
||||
<ResaleGiftsNotFound
|
||||
description={lang('ResellGiftsNoFound')}
|
||||
linkText={hasFilter ? lang('ResellGiftsClearFilters') : undefined}
|
||||
onLinkClick={hasFilter ? handleResetGiftsFilter : undefined}
|
||||
/>
|
||||
)}
|
||||
<InfiniteScroll
|
||||
className={buildClassName(styles.resaleStarGiftsContainer)}
|
||||
items={viewportIds}
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { type MouseEvent as ReactMouseEvent } from 'react';
|
||||
import type { ElementRef, FC } from '../../../lib/teact/teact';
|
||||
import type { ElementRef } from '../../../lib/teact/teact';
|
||||
import type React from '../../../lib/teact/teact';
|
||||
import {
|
||||
memo,
|
||||
@ -39,8 +39,12 @@ import ResaleGiftMenuAttributeSticker from './ResaleGiftMenuAttributeSticker';
|
||||
|
||||
import styles from './GiftResaleFilters.module.scss';
|
||||
|
||||
type FilterType = 'resale' | 'craft';
|
||||
|
||||
type OwnProps = {
|
||||
dialogRef: ElementRef<HTMLDivElement>;
|
||||
className?: string;
|
||||
filterType?: FilterType;
|
||||
};
|
||||
type StateProps = {
|
||||
filter: ResaleGiftsFilterOptions;
|
||||
@ -48,15 +52,20 @@ type StateProps = {
|
||||
counters?: ApiStarGiftAttributeCounter[];
|
||||
};
|
||||
|
||||
const GiftResaleFilters: FC<StateProps & OwnProps> = ({
|
||||
const DEFAULT_CRAFT_FILTER: ResaleGiftsFilterOptions = { sortType: 'byPrice' };
|
||||
|
||||
const GiftResaleFilters = ({
|
||||
attributes,
|
||||
counters,
|
||||
filter,
|
||||
dialogRef,
|
||||
}) => {
|
||||
className,
|
||||
filterType = 'resale',
|
||||
}: OwnProps & StateProps) => {
|
||||
const lang = useLang();
|
||||
const {
|
||||
updateResaleGiftsFilter,
|
||||
updateCraftGiftsFilter,
|
||||
} = getActions();
|
||||
|
||||
const [searchModelQuery, setSearchModelQuery] = useState('');
|
||||
@ -193,100 +202,114 @@ const GiftResaleFilters: FC<StateProps & OwnProps> = ({
|
||||
} = useContextMenuHandlers(dialogRef);
|
||||
const getPatternMenuElement = useLastCallback(() => patternMenuRef.current!);
|
||||
|
||||
const SortMenuButton: FC<{ onTrigger: (e: ReactMouseEvent<HTMLDivElement, MouseEvent>) => void; isOpen?: boolean }>
|
||||
= useMemo(() => {
|
||||
const sortType = filter.sortType;
|
||||
const iconName = sortType === 'byDate' ? 'sort-by-date'
|
||||
: sortType === 'byNumber' ? 'sort-by-number'
|
||||
: 'sort-by-price';
|
||||
return ({ onTrigger, isOpen: isMenuOpen }) => (
|
||||
<div
|
||||
className={styles.item}
|
||||
onClick={onTrigger}
|
||||
>
|
||||
<Icon
|
||||
name={iconName}
|
||||
className={styles.itemIcon}
|
||||
/>
|
||||
{sortType === 'byDate' && lang('ValueGiftSortByDate')}
|
||||
{sortType === 'byNumber' && lang('ValueGiftSortByNumber')}
|
||||
{sortType === 'byPrice' && lang('ValueGiftSortByPrice')}
|
||||
</div>
|
||||
);
|
||||
}, [lang, filter]);
|
||||
const SortMenuButton = useMemo(() => {
|
||||
const sortType = filter.sortType;
|
||||
const iconName = sortType === 'byDate' ? 'sort-by-date'
|
||||
: sortType === 'byNumber' ? 'sort-by-number'
|
||||
: 'sort-by-price';
|
||||
return ({ onTrigger, isOpen: isMenuOpen }: {
|
||||
onTrigger: (e: ReactMouseEvent<HTMLDivElement, MouseEvent>) => void;
|
||||
isOpen?: boolean;
|
||||
}) => (
|
||||
<div
|
||||
className={styles.item}
|
||||
onClick={onTrigger}
|
||||
>
|
||||
<Icon
|
||||
name={iconName}
|
||||
className={styles.itemIcon}
|
||||
/>
|
||||
{sortType === 'byDate' && lang('ValueGiftSortByDate')}
|
||||
{sortType === 'byNumber' && lang('ValueGiftSortByNumber')}
|
||||
{sortType === 'byPrice' && lang('ValueGiftSortByPrice')}
|
||||
</div>
|
||||
);
|
||||
}, [lang, filter]);
|
||||
|
||||
const ModelMenuButton:
|
||||
FC<{ onTrigger: (e: ReactMouseEvent<HTMLDivElement, MouseEvent>) => void; isOpen?: boolean }>
|
||||
= useMemo(() => {
|
||||
const attributesCount = filter?.modelAttributes?.length || 0;
|
||||
return ({ onTrigger, isOpen: isMenuOpen }) => (
|
||||
<div
|
||||
className={styles.item}
|
||||
onClick={onTrigger}
|
||||
>
|
||||
{attributesCount === 0 && lang('GiftAttributeModel')}
|
||||
{attributesCount > 0
|
||||
&& lang('GiftAttributeModelPlural', { count: attributesCount }, { pluralValue: attributesCount })}
|
||||
{renderDropdownArrows(isMenuOpen)}
|
||||
</div>
|
||||
);
|
||||
}, [lang, filter]);
|
||||
const BackdropMenuButton:
|
||||
FC<{ onTrigger: (e: ReactMouseEvent<HTMLDivElement, MouseEvent>) => void; isOpen?: boolean }>
|
||||
= useMemo(() => {
|
||||
const attributesCount = filter?.backdropAttributes?.length || 0;
|
||||
return ({ onTrigger, isOpen: isMenuOpen }) => (
|
||||
<div
|
||||
className={styles.item}
|
||||
onClick={onTrigger}
|
||||
>
|
||||
{attributesCount === 0 && lang('GiftAttributeBackdrop')}
|
||||
{attributesCount > 0
|
||||
&& lang('GiftAttributeBackdropPlural', { count: attributesCount }, { pluralValue: attributesCount })}
|
||||
{renderDropdownArrows(isMenuOpen)}
|
||||
</div>
|
||||
);
|
||||
}, [lang, filter]);
|
||||
const PatternMenuButton: FC<{ onTrigger: (e: ReactMouseEvent<HTMLDivElement, MouseEvent>) => void; isOpen?: boolean }>
|
||||
= useMemo(() => {
|
||||
const attributesCount = filter?.patternAttributes?.length || 0;
|
||||
return ({ onTrigger, isOpen: isMenuOpen }) => (
|
||||
<div
|
||||
className={styles.item}
|
||||
onClick={onTrigger}
|
||||
>
|
||||
{attributesCount === 0 && lang('GiftAttributeSymbol')}
|
||||
{attributesCount > 0
|
||||
&& lang('GiftAttributeSymbolPlural', { count: attributesCount }, { pluralValue: attributesCount })}
|
||||
{renderDropdownArrows(isMenuOpen)}
|
||||
</div>
|
||||
);
|
||||
}, [lang, filter]);
|
||||
const ModelMenuButton = useMemo(() => {
|
||||
const attributesCount = filter?.modelAttributes?.length || 0;
|
||||
return ({ onTrigger, isOpen: isMenuOpen }: {
|
||||
onTrigger: (e: ReactMouseEvent<HTMLDivElement, MouseEvent>) => void;
|
||||
isOpen?: boolean;
|
||||
}) => (
|
||||
<div
|
||||
className={styles.item}
|
||||
onClick={onTrigger}
|
||||
>
|
||||
{attributesCount === 0 && lang('GiftAttributeModel')}
|
||||
{attributesCount > 0
|
||||
&& lang('GiftAttributeModelPlural', { count: attributesCount }, { pluralValue: attributesCount })}
|
||||
{renderDropdownArrows(isMenuOpen)}
|
||||
</div>
|
||||
);
|
||||
}, [lang, filter]);
|
||||
const BackdropMenuButton = useMemo(() => {
|
||||
const attributesCount = filter?.backdropAttributes?.length || 0;
|
||||
return ({ onTrigger, isOpen: isMenuOpen }: {
|
||||
onTrigger: (e: ReactMouseEvent<HTMLDivElement, MouseEvent>) => void;
|
||||
isOpen?: boolean;
|
||||
}) => (
|
||||
<div
|
||||
className={styles.item}
|
||||
onClick={onTrigger}
|
||||
>
|
||||
{attributesCount === 0 && lang('GiftAttributeBackdrop')}
|
||||
{attributesCount > 0
|
||||
&& lang('GiftAttributeBackdropPlural', { count: attributesCount }, { pluralValue: attributesCount })}
|
||||
{renderDropdownArrows(isMenuOpen)}
|
||||
</div>
|
||||
);
|
||||
}, [lang, filter]);
|
||||
const PatternMenuButton = useMemo(() => {
|
||||
const attributesCount = filter?.patternAttributes?.length || 0;
|
||||
return ({ onTrigger, isOpen: isMenuOpen }: {
|
||||
onTrigger: (e: ReactMouseEvent<HTMLDivElement, MouseEvent>) => void;
|
||||
isOpen?: boolean;
|
||||
}) => (
|
||||
<div
|
||||
className={styles.item}
|
||||
onClick={onTrigger}
|
||||
>
|
||||
{attributesCount === 0 && lang('GiftAttributeSymbol')}
|
||||
{attributesCount > 0
|
||||
&& lang('GiftAttributeSymbolPlural', { count: attributesCount }, { pluralValue: attributesCount })}
|
||||
{renderDropdownArrows(isMenuOpen)}
|
||||
</div>
|
||||
);
|
||||
}, [lang, filter]);
|
||||
|
||||
const handleFilterUpdate = useLastCallback((newFilter: ResaleGiftsFilterOptions) => {
|
||||
if (filterType === 'craft') {
|
||||
updateCraftGiftsFilter({ filter: newFilter });
|
||||
} else {
|
||||
updateResaleGiftsFilter({ filter: newFilter });
|
||||
}
|
||||
});
|
||||
|
||||
const handleSortMenuItemClick = useLastCallback((type: ResaleGiftsSortType) => {
|
||||
updateResaleGiftsFilter({ filter: {
|
||||
handleFilterUpdate({
|
||||
...filter,
|
||||
sortType: type,
|
||||
} });
|
||||
});
|
||||
});
|
||||
|
||||
const handleSelectedAllModelsClick = useLastCallback(() => {
|
||||
updateResaleGiftsFilter({ filter: {
|
||||
handleFilterUpdate({
|
||||
...filter,
|
||||
modelAttributes: [],
|
||||
} });
|
||||
});
|
||||
});
|
||||
const handleSelectedAllPatternsClick = useLastCallback(() => {
|
||||
updateResaleGiftsFilter({ filter: {
|
||||
handleFilterUpdate({
|
||||
...filter,
|
||||
patternAttributes: [],
|
||||
} });
|
||||
});
|
||||
});
|
||||
const handleSelectedAllBackdropsClick = useLastCallback(() => {
|
||||
updateResaleGiftsFilter({ filter: {
|
||||
handleFilterUpdate({
|
||||
...filter,
|
||||
backdropAttributes: [],
|
||||
} });
|
||||
});
|
||||
});
|
||||
|
||||
const handleModelMenuItemClick = useLastCallback((attribute: ApiStarGiftAttributeModel) => {
|
||||
@ -303,10 +326,10 @@ const GiftResaleFilters: FC<StateProps & OwnProps> = ({
|
||||
const updatedAttributes = isActive
|
||||
? modelAttributes.filter((item) => item.documentId !== modelAttribute.documentId)
|
||||
: [...modelAttributes, modelAttribute];
|
||||
updateResaleGiftsFilter({ filter: {
|
||||
handleFilterUpdate({
|
||||
...filter,
|
||||
modelAttributes: updatedAttributes,
|
||||
} });
|
||||
});
|
||||
});
|
||||
|
||||
const handlePatternMenuItemClick = useLastCallback((attribute: ApiStarGiftAttributePattern) => {
|
||||
@ -323,10 +346,10 @@ const GiftResaleFilters: FC<StateProps & OwnProps> = ({
|
||||
const updatedAttributes = isActive
|
||||
? patternAttributes.filter((item) => item.documentId !== patternAttribute.documentId)
|
||||
: [...patternAttributes, patternAttribute];
|
||||
updateResaleGiftsFilter({ filter: {
|
||||
handleFilterUpdate({
|
||||
...filter,
|
||||
patternAttributes: updatedAttributes,
|
||||
} });
|
||||
});
|
||||
});
|
||||
|
||||
const handleBackdropMenuItemClick = useLastCallback((attribute: ApiStarGiftAttributeBackdrop) => {
|
||||
@ -343,10 +366,10 @@ const GiftResaleFilters: FC<StateProps & OwnProps> = ({
|
||||
const updatedAttributes = isActive
|
||||
? backdropAttributes.filter((item) => item.backdropId !== backdropAttribute.backdropId)
|
||||
: [...backdropAttributes, backdropAttribute];
|
||||
updateResaleGiftsFilter({ filter: {
|
||||
handleFilterUpdate({
|
||||
...filter,
|
||||
backdropAttributes: updatedAttributes,
|
||||
} });
|
||||
});
|
||||
});
|
||||
|
||||
function renderDropdownArrows(isOpen?: boolean) {
|
||||
@ -648,7 +671,7 @@ const GiftResaleFilters: FC<StateProps & OwnProps> = ({
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={styles.root}>
|
||||
<div className={buildClassName(styles.root, className)}>
|
||||
{Boolean(sortContextMenuAnchor) && renderSortMenu()}
|
||||
{Boolean(modelContextMenuAnchor) && renderModelMenu()}
|
||||
{Boolean(backdropContextMenuAnchor) && renderBackdropMenu()}
|
||||
@ -675,16 +698,22 @@ const GiftResaleFilters: FC<StateProps & OwnProps> = ({
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(withGlobal((global): Complete<StateProps> => {
|
||||
const { resaleGifts } = selectTabState(global);
|
||||
export default memo(withGlobal<OwnProps>((global, { filterType }): Complete<StateProps> => {
|
||||
const tabState = selectTabState(global);
|
||||
|
||||
const attributes = resaleGifts.attributes;
|
||||
const counters = resaleGifts.counters;
|
||||
const filter = resaleGifts.filter;
|
||||
if (filterType === 'craft') {
|
||||
const craftModal = tabState.giftCraftModal;
|
||||
return {
|
||||
filter: craftModal?.marketFilter || DEFAULT_CRAFT_FILTER,
|
||||
attributes: craftModal?.marketAttributes,
|
||||
counters: craftModal?.marketCounters,
|
||||
};
|
||||
}
|
||||
|
||||
const { resaleGifts } = tabState;
|
||||
return {
|
||||
attributes,
|
||||
counters,
|
||||
filter,
|
||||
filter: resaleGifts.filter,
|
||||
attributes: resaleGifts.attributes,
|
||||
counters: resaleGifts.counters,
|
||||
};
|
||||
})(GiftResaleFilters));
|
||||
|
||||
30
src/components/modals/gift/ResaleGiftsNotFound.module.scss
Normal file
@ -0,0 +1,30 @@
|
||||
.root {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
padding-top: 5rem;
|
||||
}
|
||||
|
||||
.description {
|
||||
margin-block: 1rem;
|
||||
|
||||
font-size: 1rem;
|
||||
font-weight: var(--font-weight-medium);
|
||||
color: var(--color-text-secondary);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.link {
|
||||
font-weight: var(--font-weight-medium);
|
||||
color: var(--color-links);
|
||||
transition: opacity 0.15s ease-in;
|
||||
|
||||
&:active,
|
||||
&:hover {
|
||||
color: var(--color-links);
|
||||
text-decoration: none;
|
||||
opacity: 0.85;
|
||||
}
|
||||
}
|
||||
43
src/components/modals/gift/ResaleGiftsNotFound.tsx
Normal file
@ -0,0 +1,43 @@
|
||||
import type { TeactNode } from '../../../lib/teact/teact';
|
||||
import { memo } from '../../../lib/teact/teact';
|
||||
|
||||
import buildClassName from '../../../util/buildClassName';
|
||||
import { LOCAL_TGS_URLS } from '../../common/helpers/animatedAssets';
|
||||
|
||||
import AnimatedIconWithPreview from '../../common/AnimatedIconWithPreview';
|
||||
import Link from '../../ui/Link';
|
||||
|
||||
import styles from './ResaleGiftsNotFound.module.scss';
|
||||
|
||||
type OwnProps = {
|
||||
className?: string;
|
||||
description: TeactNode;
|
||||
linkText?: TeactNode;
|
||||
onLinkClick?: NoneToVoidFunction;
|
||||
};
|
||||
|
||||
const ResaleGiftsNotFound = ({ className, description, linkText, onLinkClick }: OwnProps) => {
|
||||
return (
|
||||
<div className={buildClassName(styles.root, className)}>
|
||||
<AnimatedIconWithPreview
|
||||
size={160}
|
||||
tgsUrl={LOCAL_TGS_URLS.SearchingDuck}
|
||||
nonInteractive
|
||||
noLoop
|
||||
/>
|
||||
<div className={styles.description}>
|
||||
{description}
|
||||
</div>
|
||||
{Boolean(linkText && onLinkClick) && (
|
||||
<Link
|
||||
className={styles.link}
|
||||
onClick={onLinkClick}
|
||||
>
|
||||
{linkText}
|
||||
</Link>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(ResaleGiftsNotFound);
|
||||
@ -0,0 +1,16 @@
|
||||
import { memo } from '../../../../lib/teact/teact';
|
||||
|
||||
import type { OwnProps } from './GiftCraftInfoModal';
|
||||
|
||||
import { Bundles } from '../../../../util/moduleLoader';
|
||||
|
||||
import useModuleLoader from '../../../../hooks/useModuleLoader';
|
||||
|
||||
const GiftCraftInfoModalAsync = (props: OwnProps) => {
|
||||
const { modal } = props;
|
||||
const GiftCraftInfoModal = useModuleLoader(Bundles.Stars, 'GiftCraftInfoModal', !modal);
|
||||
|
||||
return GiftCraftInfoModal ? <GiftCraftInfoModal {...props} /> : undefined;
|
||||
};
|
||||
|
||||
export default memo(GiftCraftInfoModalAsync);
|
||||
@ -0,0 +1,18 @@
|
||||
.header {
|
||||
--_height: 16rem;
|
||||
}
|
||||
|
||||
.footer {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-self: stretch;
|
||||
margin-top: 0.5rem;
|
||||
}
|
||||
|
||||
.understoodIcon {
|
||||
font-size: 1.1875rem;
|
||||
}
|
||||
|
||||
.content {
|
||||
gap: 1.5rem;
|
||||
}
|
||||
91
src/components/modals/gift/craft/GiftCraftInfoModal.tsx
Normal file
@ -0,0 +1,91 @@
|
||||
import { memo, useMemo } from '../../../../lib/teact/teact';
|
||||
import { getActions } from '../../../../global';
|
||||
|
||||
import type { TabState } from '../../../../global/types';
|
||||
|
||||
import { getGiftAttributes } from '../../../common/helpers/gifts';
|
||||
|
||||
import useCurrentOrPrev from '../../../../hooks/useCurrentOrPrev';
|
||||
import useLang from '../../../../hooks/useLang';
|
||||
import useLastCallback from '../../../../hooks/useLastCallback';
|
||||
|
||||
import Button from '../../../ui/Button';
|
||||
import TableAboutModal, { type TableAboutData } from '../../common/TableAboutModal';
|
||||
import UniqueGiftHeader from '../UniqueGiftHeader';
|
||||
|
||||
import styles from './GiftCraftInfoModal.module.scss';
|
||||
|
||||
export type OwnProps = {
|
||||
modal: TabState['giftCraftInfoModal'];
|
||||
};
|
||||
|
||||
const GiftCraftInfoModal = ({
|
||||
modal,
|
||||
}: OwnProps) => {
|
||||
const { closeGiftCraftInfoModal } = getActions();
|
||||
const lang = useLang();
|
||||
|
||||
const isOpen = Boolean(modal);
|
||||
const renderingModal = useCurrentOrPrev(modal);
|
||||
const gift = renderingModal?.gift;
|
||||
|
||||
const handleClose = useLastCallback(() => {
|
||||
closeGiftCraftInfoModal();
|
||||
});
|
||||
|
||||
const giftAttributes = useMemo(() => {
|
||||
return gift ? getGiftAttributes(gift) : undefined;
|
||||
}, [gift]);
|
||||
|
||||
const header = useMemo(() => {
|
||||
if (!giftAttributes) return undefined;
|
||||
|
||||
return (
|
||||
<UniqueGiftHeader
|
||||
className={styles.header}
|
||||
modelAttribute={giftAttributes.model!}
|
||||
backdropAttribute={giftAttributes.backdrop!}
|
||||
patternAttribute={giftAttributes.pattern!}
|
||||
title={lang('GiftCraftInfoTitle')}
|
||||
subtitle={lang('GiftCraftInfoSubtitle')}
|
||||
/>
|
||||
);
|
||||
}, [giftAttributes, lang]);
|
||||
|
||||
const footer = useMemo(() => {
|
||||
if (!isOpen) return undefined;
|
||||
return (
|
||||
<div className={styles.footer}>
|
||||
<Button
|
||||
iconName="understood"
|
||||
iconClassName={styles.understoodIcon}
|
||||
onClick={handleClose}
|
||||
>
|
||||
{lang('ButtonUnderstood')}
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
}, [lang, isOpen, handleClose]);
|
||||
|
||||
const listItemData = useMemo(() => {
|
||||
return [
|
||||
['radial-badge', lang('GiftCraftInfoCraftTitle'), lang('GiftCraftInfoCraftDescription')],
|
||||
['combine-craft', lang('GiftCraftInfoChanceTitle'), lang('GiftCraftInfoChanceDescription')],
|
||||
['boost-craft-chance', lang('GiftCraftInfoRiskTitle'), lang('GiftCraftInfoRiskDescription')],
|
||||
] satisfies TableAboutData;
|
||||
}, [lang]);
|
||||
|
||||
return (
|
||||
<TableAboutModal
|
||||
isOpen={isOpen}
|
||||
header={header}
|
||||
listItemData={listItemData}
|
||||
footer={footer}
|
||||
hasBackdrop
|
||||
contentClassName={styles.content}
|
||||
onClose={handleClose}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(GiftCraftInfoModal);
|
||||
16
src/components/modals/gift/craft/GiftCraftModal.async.tsx
Normal file
@ -0,0 +1,16 @@
|
||||
import { memo } from '../../../../lib/teact/teact';
|
||||
|
||||
import type { OwnProps } from './GiftCraftModal';
|
||||
|
||||
import { Bundles } from '../../../../util/moduleLoader';
|
||||
|
||||
import useModuleLoader from '../../../../hooks/useModuleLoader';
|
||||
|
||||
const GiftCraftModalAsync = (props: OwnProps) => {
|
||||
const { modal } = props;
|
||||
const GiftCraftModal = useModuleLoader(Bundles.Stars, 'GiftCraftModal', !modal);
|
||||
|
||||
return GiftCraftModal ? <GiftCraftModal {...props} /> : undefined;
|
||||
};
|
||||
|
||||
export default memo(GiftCraftModalAsync);
|
||||
722
src/components/modals/gift/craft/GiftCraftModal.module.scss
Normal file
@ -0,0 +1,722 @@
|
||||
@use "../../../../styles/mixins";
|
||||
|
||||
.modal {
|
||||
:global(.modal-dialog) {
|
||||
overflow: clip;
|
||||
background: #232E3F;
|
||||
}
|
||||
|
||||
:global(.modal-content) {
|
||||
max-height: 92vh !important;
|
||||
}
|
||||
}
|
||||
|
||||
.patternOverlay {
|
||||
position: absolute;
|
||||
z-index: 0;
|
||||
inset: 0;
|
||||
border-radius: var(--border-radius-modal);
|
||||
}
|
||||
|
||||
.patternSlide,
|
||||
.patternBackground {
|
||||
position: absolute !important;
|
||||
inset: 0 !important;
|
||||
}
|
||||
|
||||
.content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding: 0 !important;
|
||||
}
|
||||
|
||||
.title {
|
||||
z-index: 1;
|
||||
|
||||
align-self: flex-start;
|
||||
|
||||
margin: 0;
|
||||
margin-left: 4.3125rem;
|
||||
padding: 0.75rem;
|
||||
|
||||
font-size: 1.25rem;
|
||||
font-weight: var(--font-weight-medium);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.helpButton {
|
||||
position: absolute;
|
||||
z-index: 3;
|
||||
top: 0.875rem;
|
||||
right: 0.875rem;
|
||||
}
|
||||
|
||||
.header {
|
||||
position: relative;
|
||||
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/* 3D Cube Animation */
|
||||
.cube {
|
||||
pointer-events: none;
|
||||
|
||||
position: relative;
|
||||
transform-style: preserve-3d;
|
||||
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.corners {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.face {
|
||||
position: absolute;
|
||||
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
box-sizing: border-box;
|
||||
width: var(--cube-size);
|
||||
height: var(--cube-size);
|
||||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||
border-radius: 1.875rem;
|
||||
|
||||
backface-visibility: hidden;
|
||||
background: #4F5B71;
|
||||
|
||||
transition: opacity 1s;
|
||||
|
||||
&.faceHidden {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
&[data-face="front"] {
|
||||
transform: translateZ(var(--cube-half));
|
||||
}
|
||||
|
||||
&[data-face="back"] {
|
||||
transform: translateZ(calc(-1 * var(--cube-half))) rotateY(180deg);
|
||||
}
|
||||
|
||||
&[data-face="left"] {
|
||||
transform: translateX(calc(-1 * var(--cube-half))) rotateY(-90deg);
|
||||
}
|
||||
|
||||
&[data-face="right"] {
|
||||
transform: translateX(var(--cube-half)) rotateY(90deg);
|
||||
}
|
||||
|
||||
&[data-face="top"] {
|
||||
transform: translateY(calc(-1 * var(--cube-half))) rotateX(90deg);
|
||||
}
|
||||
|
||||
&[data-face="bottom"] {
|
||||
transform: translateY(var(--cube-half)) rotateX(-90deg);
|
||||
}
|
||||
}
|
||||
|
||||
.faceHighChance {
|
||||
background: #36595F;
|
||||
}
|
||||
|
||||
.faceFailed {
|
||||
border-color: #653B31;
|
||||
}
|
||||
|
||||
.faceIcon {
|
||||
font-size: 2.5rem;
|
||||
color: rgba(255, 255, 255, 0.5);
|
||||
}
|
||||
|
||||
.craftedGiftFace {
|
||||
position: relative;
|
||||
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
opacity: 0;
|
||||
|
||||
animation: fadeInContent 0.25s ease-out 0.25s both;
|
||||
}
|
||||
|
||||
.slotBackdrop,
|
||||
.craftedGiftBackdrop,
|
||||
.attributeRing,
|
||||
.burnedGiftBackdrop {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
}
|
||||
|
||||
.slotSticker,
|
||||
.craftedGiftSticker,
|
||||
.patternAttribute,
|
||||
.burnedGift,
|
||||
.burnedGiftSticker {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.failedGiftFace {
|
||||
position: relative;
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
opacity: 0;
|
||||
background: linear-gradient(180deg, #683E34 0%, #51291F 100%);
|
||||
|
||||
animation: fadeInContent 0.2s ease-out 0.2s both;
|
||||
}
|
||||
|
||||
@keyframes fadeInContent {
|
||||
to {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.burnedCount {
|
||||
position: absolute;
|
||||
bottom: 0.5rem;
|
||||
|
||||
margin-top: 0.25rem;
|
||||
|
||||
font-size: 0.875rem;
|
||||
font-weight: var(--font-weight-semibold);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.slotsGrid {
|
||||
--perspective: 600px;
|
||||
--cube-size: 7.5rem;
|
||||
--cube-half: 3.75rem;
|
||||
--slot-size: 4.5rem;
|
||||
|
||||
position: relative;
|
||||
|
||||
display: grid;
|
||||
grid-template-columns: var(--slot-size) var(--cube-size) var(--slot-size);
|
||||
grid-template-rows: var(--slot-size) var(--slot-size);
|
||||
gap: 1.5rem;
|
||||
row-gap: 17px;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
justify-items: center;
|
||||
|
||||
padding: 1.125rem;
|
||||
|
||||
perspective: var(--perspective);
|
||||
|
||||
&.activated {
|
||||
.craftSlot:not(.craftSlotFilled) {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.slotChance,
|
||||
.slotClear {
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.slotWrapper {
|
||||
z-index: 1;
|
||||
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
width: var(--slot-size);
|
||||
height: var(--slot-size);
|
||||
|
||||
&.bottomRow {
|
||||
align-self: start;
|
||||
}
|
||||
|
||||
&.used {
|
||||
pointer-events: none;
|
||||
}
|
||||
}
|
||||
|
||||
.cubeWrapper {
|
||||
position: relative;
|
||||
transform-style: preserve-3d;
|
||||
transform: scale(0.913);
|
||||
|
||||
display: flex;
|
||||
grid-column: 2;
|
||||
grid-row: 1 / 3;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
width: var(--cube-size);
|
||||
height: var(--cube-size);
|
||||
}
|
||||
|
||||
.craftSlot {
|
||||
pointer-events: auto;
|
||||
cursor: pointer;
|
||||
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
width: var(--slot-size);
|
||||
height: var(--slot-size);
|
||||
border-radius: 1.125rem;
|
||||
|
||||
background: rgba(255, 255, 255, 0.08);
|
||||
|
||||
transition: transform 0.15s, background-color 0.15s, opacity 0.5s;
|
||||
|
||||
&:hover {
|
||||
background: rgba(255, 255, 255, 0.12);
|
||||
}
|
||||
|
||||
&.animating {
|
||||
will-change: transform;
|
||||
z-index: 100;
|
||||
|
||||
.slotChance,
|
||||
.slotClear {
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.slotIcon {
|
||||
font-size: 1.875rem;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.craftSlotFilled {
|
||||
position: relative;
|
||||
background: transparent;
|
||||
|
||||
&:hover {
|
||||
background: transparent;
|
||||
}
|
||||
}
|
||||
|
||||
.craftSlotHidden {
|
||||
opacity: 0;
|
||||
transition: opacity 0.5s !important;
|
||||
}
|
||||
|
||||
.removing {
|
||||
transform: scale(0.8);
|
||||
opacity: 0;
|
||||
transition: opacity 0.15s, transform 0.15s ease-out;
|
||||
}
|
||||
|
||||
.slotInner {
|
||||
position: relative;
|
||||
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-radius: 1.125rem;
|
||||
}
|
||||
|
||||
.slotChance {
|
||||
position: absolute;
|
||||
top: -0.3125rem;
|
||||
left: -0.3125rem;
|
||||
|
||||
padding: 0.0625rem 0.25rem;
|
||||
border-radius: 0.625rem;
|
||||
|
||||
font-size: 0.75rem;
|
||||
font-weight: var(--font-weight-semibold);
|
||||
color: white;
|
||||
|
||||
opacity: 0.9;
|
||||
background-color: #374354B2;
|
||||
backdrop-filter: blur(25px);
|
||||
|
||||
transition: opacity 0.15s;
|
||||
}
|
||||
|
||||
.slotClear {
|
||||
cursor: pointer;
|
||||
|
||||
position: absolute;
|
||||
top: -0.3125rem;
|
||||
right: -0.3125rem;
|
||||
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
width: 1.25rem;
|
||||
height: 1.25rem;
|
||||
padding: 0;
|
||||
border: none;
|
||||
border-radius: 50%;
|
||||
|
||||
font-size: 0.75rem;
|
||||
color: white;
|
||||
|
||||
opacity: 0.9;
|
||||
background-color: #374354B2;
|
||||
backdrop-filter: blur(25px);
|
||||
outline: none !important;
|
||||
|
||||
transition: opacity 0.15s;
|
||||
|
||||
&:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.centerAnvil {
|
||||
position: relative;
|
||||
|
||||
display: flex;
|
||||
grid-column: 2;
|
||||
grid-row: 1 / 3;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
box-sizing: border-box;
|
||||
width: var(--cube-size);
|
||||
height: var(--cube-size);
|
||||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||
border-radius: 1.875rem;
|
||||
|
||||
background: #4F5B71;
|
||||
|
||||
&.centerAnvilHighChance {
|
||||
background: #36595F;
|
||||
}
|
||||
}
|
||||
|
||||
.progressRing {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
.anvilIcon {
|
||||
font-size: 3.125rem;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.percentage {
|
||||
position: absolute;
|
||||
bottom: 0.75rem;
|
||||
|
||||
font-size: 1.1875rem;
|
||||
font-weight: var(--font-weight-semibold);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.infoTransition {
|
||||
z-index: 1;
|
||||
min-height: 20.5rem;
|
||||
}
|
||||
|
||||
.infoSection,
|
||||
.craftingSection,
|
||||
.failedSection {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
|
||||
padding: 0.75rem 1.5rem 1.5rem;
|
||||
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.craftingTitle {
|
||||
margin: 0 0 0.25rem;
|
||||
font-size: 1.25rem;
|
||||
font-weight: var(--font-weight-semibold);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.craftingGiftName {
|
||||
margin: 0 0 1rem;
|
||||
font-size: 1rem;
|
||||
font-weight: var(--font-weight-medium);
|
||||
color: #8D979B;
|
||||
}
|
||||
|
||||
.craftingWarning,
|
||||
.failedDescription {
|
||||
margin: 0.25rem 0 1.5rem;
|
||||
font-size: 1rem;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.craftingWarning {
|
||||
max-width: 15rem;
|
||||
margin: 3.5rem 0 1.5rem;
|
||||
color: #8D979B;
|
||||
}
|
||||
|
||||
.failedDescription {
|
||||
text-wrap: balance;
|
||||
}
|
||||
|
||||
.infoDescription {
|
||||
max-width: 18rem;
|
||||
min-height: 2.625rem;
|
||||
margin: 0 0 0.5rem;
|
||||
|
||||
font-size: 1rem;
|
||||
line-height: 1.25;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.giftLine {
|
||||
display: flex;
|
||||
gap: 0.25rem;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.giftIcon {
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.infoWarning {
|
||||
max-width: 18rem;
|
||||
margin: 0.5rem 0 0.5rem;
|
||||
|
||||
font-size: 1rem;
|
||||
line-height: 1.25;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.attributeCircles {
|
||||
--circle-attribute-size: 4rem;
|
||||
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.25rem;
|
||||
align-items: center;
|
||||
align-items: flex-start;
|
||||
align-self: center;
|
||||
justify-content: center;
|
||||
|
||||
max-width: 17rem;
|
||||
padding-top: 0.4375rem;
|
||||
}
|
||||
|
||||
.viewAllButton {
|
||||
gap: 0.1875rem;
|
||||
margin-top: 0.625rem;
|
||||
margin-bottom: 0.5rem;
|
||||
filter: none;
|
||||
}
|
||||
|
||||
.viewAllText {
|
||||
margin-inline-start: 0.125rem;
|
||||
}
|
||||
|
||||
.attributeCircle {
|
||||
position: relative;
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
|
||||
width: var(--circle-attribute-size);
|
||||
|
||||
@include mixins.with-vt-type('craftAttributes');
|
||||
}
|
||||
|
||||
.attributeContent {
|
||||
position: relative;
|
||||
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
width: var(--circle-attribute-size);
|
||||
height: var(--circle-attribute-size);
|
||||
}
|
||||
|
||||
.colorAttribute {
|
||||
width: 2rem;
|
||||
height: 1.8125rem;
|
||||
margin-top: -0.1875rem;
|
||||
|
||||
/* stylelint-disable-next-line plugin/use-baseline */
|
||||
mask: url('../../../../assets/attribute-mask.svg') center / 100% 100% no-repeat;
|
||||
}
|
||||
|
||||
.patternAttributeThumb,
|
||||
.burnedGiftBadge {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
}
|
||||
|
||||
.burnedGiftBadgeText {
|
||||
font-size: 0.5rem;
|
||||
}
|
||||
|
||||
.attributePercent {
|
||||
position: absolute;
|
||||
bottom: 0.25rem;
|
||||
|
||||
font-size: 0.6875rem;
|
||||
font-weight: var(--font-weight-semibold);
|
||||
color: rgba(255, 255, 255, 0.85);
|
||||
}
|
||||
|
||||
.footer {
|
||||
z-index: 1;
|
||||
width: 100%;
|
||||
padding: 1rem;
|
||||
padding-top: 0;
|
||||
}
|
||||
|
||||
.emptyHintIcon {
|
||||
display: inline-block;
|
||||
font-size: 0.75rem;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.craftButton {
|
||||
width: 100%;
|
||||
min-height: 3.5rem;
|
||||
padding: 0.75rem 1rem;
|
||||
|
||||
&:global(.danger) {
|
||||
color: var(--color-white);
|
||||
background-color: var(--color-error);
|
||||
|
||||
&:not(.disabled):not(:disabled) {
|
||||
&:active,
|
||||
&:global(.active),
|
||||
&:focus {
|
||||
opacity: 0.85 !important;
|
||||
}
|
||||
|
||||
@media (hover: hover) {
|
||||
&:hover {
|
||||
opacity: 0.85 !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.craftButtonCrafting {
|
||||
opacity: 1 !important;
|
||||
background-color: rgba(255, 255, 255, 0.08) !important;
|
||||
}
|
||||
|
||||
.craftButtonHighChance {
|
||||
background: linear-gradient(90deg, #0A8A90, #34C351) !important;
|
||||
}
|
||||
|
||||
.craftButtonContent {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.125rem;
|
||||
justify-content: center;
|
||||
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.craftButtonTitle {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
height: 100%;
|
||||
|
||||
font-size: 1rem;
|
||||
font-weight: var(--font-weight-semibold);
|
||||
}
|
||||
|
||||
.craftButtonSubtitle {
|
||||
font-size: 0.8125rem;
|
||||
font-weight: var(--font-weight-medium);
|
||||
}
|
||||
|
||||
.failedTitle {
|
||||
margin: 0 0 0.5rem;
|
||||
font-size: 1.25rem;
|
||||
font-weight: var(--font-weight-semibold);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.burnedGifts {
|
||||
display: flex;
|
||||
gap: 0.75rem;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.burnedGiftInner {
|
||||
position: relative;
|
||||
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
width: 4.25rem;
|
||||
height: 4.25rem;
|
||||
border-radius: 0.625rem;
|
||||
}
|
||||
|
||||
@include mixins.on-active-vt('craftAttributes') {
|
||||
&::view-transition-group(.craftAttribute) {
|
||||
animation-duration: 250ms;
|
||||
animation-timing-function: ease-out;
|
||||
}
|
||||
|
||||
&::view-transition-old(.craftAttribute) {
|
||||
:local {
|
||||
animation-name: craftAttributeFadeOut;
|
||||
}
|
||||
|
||||
@keyframes craftAttributeFadeOut {
|
||||
to {
|
||||
transform: scale(0.8);
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&::view-transition-new(.craftAttribute) {
|
||||
:local {
|
||||
animation-name: craftAttributeFadeIn;
|
||||
}
|
||||
|
||||
@keyframes craftAttributeFadeIn {
|
||||
from {
|
||||
transform: scale(0.8);
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
1644
src/components/modals/gift/craft/GiftCraftModal.tsx
Normal file
@ -0,0 +1,16 @@
|
||||
import { memo } from '../../../../lib/teact/teact';
|
||||
|
||||
import type { OwnProps } from './GiftCraftSelectModal';
|
||||
|
||||
import { Bundles } from '../../../../util/moduleLoader';
|
||||
|
||||
import useModuleLoader from '../../../../hooks/useModuleLoader';
|
||||
|
||||
const GiftCraftSelectModalAsync = (props: OwnProps) => {
|
||||
const { modal } = props;
|
||||
const GiftCraftSelectModal = useModuleLoader(Bundles.Stars, 'GiftCraftSelectModal', !modal);
|
||||
|
||||
return GiftCraftSelectModal ? <GiftCraftSelectModal {...props} /> : undefined;
|
||||
};
|
||||
|
||||
export default memo(GiftCraftSelectModalAsync);
|
||||
@ -0,0 +1,144 @@
|
||||
.modal {
|
||||
:global(.modal-dialog) {
|
||||
overflow: clip;
|
||||
}
|
||||
|
||||
:global(.modal-content) {
|
||||
height: min(92vh, 39rem);
|
||||
}
|
||||
}
|
||||
|
||||
.content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 0 !important;
|
||||
}
|
||||
|
||||
.title {
|
||||
margin: 0;
|
||||
padding: 1rem 1rem 0.5rem;
|
||||
border-bottom: 0.0625rem solid transparent;
|
||||
|
||||
font-size: 1.25rem;
|
||||
font-weight: var(--font-weight-semibold);
|
||||
text-align: center;
|
||||
|
||||
transition: border-bottom-color 0.15s ease;
|
||||
|
||||
&.titleWithBorder {
|
||||
border-bottom-color: var(--color-borders);
|
||||
}
|
||||
}
|
||||
|
||||
.wrapper {
|
||||
position: relative;
|
||||
|
||||
display: flex;
|
||||
flex: 1;
|
||||
flex-direction: column;
|
||||
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.scrollContainer {
|
||||
overflow-y: auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.5rem;
|
||||
|
||||
height: 100%;
|
||||
padding: 0 1rem 1rem;
|
||||
|
||||
opacity: 1;
|
||||
|
||||
transition: opacity 0.2s;
|
||||
}
|
||||
|
||||
.section {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.sectionTitle {
|
||||
margin: 0;
|
||||
padding: 0.25rem 0;
|
||||
|
||||
font-size: 0.875rem;
|
||||
font-weight: var(--font-weight-medium);
|
||||
color: var(--color-primary);
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.filters {
|
||||
position: sticky;
|
||||
z-index: 2;
|
||||
top: 0;
|
||||
|
||||
width: 100%;
|
||||
margin-top: 0;
|
||||
margin-bottom: 0.5rem;
|
||||
padding-block: 0.5rem;
|
||||
border-bottom: 0.0625rem solid transparent;
|
||||
|
||||
background-color: var(--color-background);
|
||||
|
||||
transition: border-bottom-color 0.15s ease;
|
||||
|
||||
&.stuck {
|
||||
border-bottom-color: var(--color-borders);
|
||||
}
|
||||
}
|
||||
|
||||
.transitionWrapper {
|
||||
z-index: 0;
|
||||
min-height: 28rem;
|
||||
}
|
||||
|
||||
.giftsGrid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
gap: 0.5rem;
|
||||
align-content: start;
|
||||
|
||||
min-height: 28rem;
|
||||
}
|
||||
|
||||
.loading {
|
||||
pointer-events: none;
|
||||
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
|
||||
opacity: 1;
|
||||
|
||||
transition: opacity 0.2s;
|
||||
}
|
||||
|
||||
.hidden {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.giftWrapper {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.giftChance {
|
||||
position: absolute;
|
||||
top: 0.25rem;
|
||||
left: 0.25rem;
|
||||
|
||||
padding: 0.0625rem 0.25rem;
|
||||
border-radius: 0.625rem;
|
||||
|
||||
font-size: 0.75rem;
|
||||
font-weight: var(--font-weight-semibold);
|
||||
color: white;
|
||||
|
||||
background-color: rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
.notFound {
|
||||
padding-top: 5rem;
|
||||
padding-bottom: 12rem;
|
||||
}
|
||||
367
src/components/modals/gift/craft/GiftCraftSelectModal.tsx
Normal file
@ -0,0 +1,367 @@
|
||||
import {
|
||||
memo, useMemo, useRef, useState,
|
||||
} from '../../../../lib/teact/teact';
|
||||
import { getActions, withGlobal } from '../../../../global';
|
||||
|
||||
import type { ApiSavedStarGift, ApiStarGift, ApiStarGiftUnique } from '../../../../api/types';
|
||||
import type { TabState } from '../../../../global/types';
|
||||
import type { ObserveFn } from '../../../../hooks/useIntersectionObserver';
|
||||
|
||||
import { getSavedGiftKey } from '../../../../global/helpers/stars';
|
||||
import { selectTabState } from '../../../../global/selectors';
|
||||
import buildClassName from '../../../../util/buildClassName';
|
||||
import { throttle } from '../../../../util/schedulers';
|
||||
import { formatPercent } from '../../../../util/textFormat';
|
||||
import { getGiftAttributes } from '../../../common/helpers/gifts';
|
||||
|
||||
import useCurrentOrPrev from '../../../../hooks/useCurrentOrPrev';
|
||||
import useInfiniteScroll from '../../../../hooks/useInfiniteScroll';
|
||||
import { useIntersectionObserver } from '../../../../hooks/useIntersectionObserver';
|
||||
import useLang from '../../../../hooks/useLang';
|
||||
import useLastCallback from '../../../../hooks/useLastCallback';
|
||||
import usePrevious from '../../../../hooks/usePrevious';
|
||||
|
||||
import InfiniteScroll from '../../../ui/InfiniteScroll';
|
||||
import Loading from '../../../ui/Loading';
|
||||
import Modal from '../../../ui/Modal';
|
||||
import Transition from '../../../ui/Transition';
|
||||
import GiftItemStar from '../GiftItemStar';
|
||||
import GiftResaleFilters from '../GiftResaleFilters';
|
||||
import ResaleGiftsNotFound from '../ResaleGiftsNotFound';
|
||||
|
||||
import styles from './GiftCraftSelectModal.module.scss';
|
||||
|
||||
export type OwnProps = {
|
||||
modal: TabState['giftCraftSelectModal'];
|
||||
};
|
||||
|
||||
type StateProps = {
|
||||
craftModal?: TabState['giftCraftModal'];
|
||||
};
|
||||
|
||||
const CRAFT_GIFTS_LIMIT = 50;
|
||||
const INTERSECTION_THROTTLE = 200;
|
||||
const SCROLL_THROTTLE = 200;
|
||||
|
||||
const runThrottledForScroll = throttle((cb: NoneToVoidFunction) => cb(), SCROLL_THROTTLE, true);
|
||||
|
||||
type CraftGiftItemProps = {
|
||||
gift: ApiStarGift;
|
||||
chancePercent?: number;
|
||||
chanceColor?: string;
|
||||
showPrice?: boolean;
|
||||
observe?: ObserveFn;
|
||||
onClick: (gift: ApiStarGift) => void;
|
||||
};
|
||||
|
||||
const CraftGiftItem = memo(({ gift, chancePercent, chanceColor, showPrice, observe, onClick }: CraftGiftItemProps) => {
|
||||
return (
|
||||
<div className={styles.giftWrapper}>
|
||||
<GiftItemStar
|
||||
gift={gift}
|
||||
observeIntersection={observe}
|
||||
hideBadge={!showPrice}
|
||||
onClick={onClick}
|
||||
/>
|
||||
{Boolean(chancePercent && chancePercent > 0) && (
|
||||
<span
|
||||
className={styles.giftChance}
|
||||
style={chanceColor ? `background-color: ${chanceColor}` : undefined}
|
||||
>
|
||||
+
|
||||
{formatPercent(chancePercent!, 0)}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
const GiftCraftSelectModal = ({ modal, craftModal }: OwnProps & StateProps) => {
|
||||
const {
|
||||
closeGiftCraftSelectModal, selectGiftForCraft,
|
||||
loadMoreCraftableGifts, loadMoreMarketCraftableGifts,
|
||||
updateCraftGiftsFilter, openGiftInfoModal,
|
||||
} = getActions();
|
||||
|
||||
const scrollerRef = useRef<HTMLDivElement>();
|
||||
const dialogRef = useRef<HTMLDivElement>();
|
||||
const filtersSeparatorRef = useRef<HTMLDivElement>();
|
||||
|
||||
const [isScrolled, setIsScrolled] = useState(false);
|
||||
const [isFiltersSeparatorAbove, setIsFiltersSeparatorAbove] = useState(false);
|
||||
|
||||
const lang = useLang();
|
||||
const isOpen = Boolean(modal);
|
||||
const renderingModal = useCurrentOrPrev(modal);
|
||||
const renderingCraftModal = useCurrentOrPrev(craftModal);
|
||||
|
||||
const {
|
||||
gift1,
|
||||
gift2,
|
||||
gift3,
|
||||
gift4,
|
||||
myCraftableGifts,
|
||||
marketCraftableGifts,
|
||||
marketFilter,
|
||||
marketUpdateIteration = 0,
|
||||
marketCraftableGiftsCount,
|
||||
myCraftableGiftsNextOffset,
|
||||
marketCraftableGiftsNextOffset,
|
||||
isMarketLoading,
|
||||
} = renderingCraftModal || {};
|
||||
|
||||
const { isLoading } = renderingModal || {};
|
||||
|
||||
const hasMoreMyGifts = Boolean(myCraftableGiftsNextOffset);
|
||||
const hasMoreMarketGifts = Boolean(marketCraftableGiftsNextOffset);
|
||||
|
||||
const { observe } = useIntersectionObserver({
|
||||
rootRef: scrollerRef,
|
||||
throttleMs: INTERSECTION_THROTTLE,
|
||||
isDisabled: !isOpen,
|
||||
});
|
||||
|
||||
const hasMarketGiftsData = Boolean(marketCraftableGifts?.length);
|
||||
const isMyDataLoaded = myCraftableGifts !== undefined;
|
||||
const isMarketDataLoaded = marketCraftableGifts !== undefined;
|
||||
const isDataLoaded = isMyDataLoaded && isMarketDataLoaded && !isLoading;
|
||||
const prevMarketIteration = usePrevious(marketUpdateIteration);
|
||||
const isMarketJustUpdated = prevMarketIteration !== undefined && prevMarketIteration !== marketUpdateIteration;
|
||||
const isFiltersStuck = isScrolled && isFiltersSeparatorAbove;
|
||||
const shouldShowTitleBorder = isScrolled && !isFiltersStuck;
|
||||
|
||||
const handleScroll = useLastCallback((e: { currentTarget: HTMLDivElement }) => {
|
||||
const scroller = e.currentTarget;
|
||||
|
||||
runThrottledForScroll(() => {
|
||||
setIsScrolled(scroller.scrollTop > 0);
|
||||
|
||||
const separator = filtersSeparatorRef.current;
|
||||
if (separator && hasMarketGiftsData) {
|
||||
const scrollerRect = scroller.getBoundingClientRect();
|
||||
const separatorRect = separator.getBoundingClientRect();
|
||||
setIsFiltersSeparatorAbove(separatorRect.top <= scrollerRect.top);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
const selectedIds = useMemo(() => {
|
||||
return new Set(
|
||||
[gift1, gift2, gift3, gift4]
|
||||
.filter((g): g is ApiSavedStarGift => Boolean(g))
|
||||
.map((g) => getSavedGiftKey(g)),
|
||||
);
|
||||
}, [gift1, gift2, gift3, gift4]);
|
||||
|
||||
const selectedUniqueIds = useMemo(() => {
|
||||
return new Set(
|
||||
[gift1, gift2, gift3, gift4]
|
||||
.filter((g): g is ApiSavedStarGift => Boolean(g) && g.gift.type === 'starGiftUnique')
|
||||
.map((g) => (g.gift as ApiStarGiftUnique).id),
|
||||
);
|
||||
}, [gift1, gift2, gift3, gift4]);
|
||||
|
||||
const availableMyGifts = useMemo(() => {
|
||||
if (!myCraftableGifts) return [];
|
||||
return myCraftableGifts.filter((g) => {
|
||||
if (g.gift.type === 'starGiftUnique' && selectedUniqueIds.has(g.gift.id)) {
|
||||
return false;
|
||||
}
|
||||
return !selectedIds.has(getSavedGiftKey(g));
|
||||
});
|
||||
}, [myCraftableGifts, selectedIds, selectedUniqueIds]);
|
||||
|
||||
const availableMarketGifts = useMemo(() => {
|
||||
if (!marketCraftableGifts) return [];
|
||||
return marketCraftableGifts.filter((g) => !selectedUniqueIds.has(g.id));
|
||||
}, [marketCraftableGifts, selectedUniqueIds]);
|
||||
|
||||
const myGiftByIdMap = useMemo(() => {
|
||||
const map = new Map<string, ApiSavedStarGift>();
|
||||
availableMyGifts.forEach((g) => {
|
||||
if (g.gift.type === 'starGiftUnique') {
|
||||
map.set(g.gift.id, g);
|
||||
}
|
||||
});
|
||||
return map;
|
||||
}, [availableMyGifts]);
|
||||
|
||||
const handleLoadMore = useLastCallback(() => {
|
||||
if (hasMoreMyGifts) {
|
||||
loadMoreCraftableGifts();
|
||||
} else if (hasMoreMarketGifts) {
|
||||
loadMoreMarketCraftableGifts();
|
||||
}
|
||||
});
|
||||
|
||||
const allItemIds = useMemo(() => {
|
||||
const myIds = availableMyGifts.map((g) => `my-${getSavedGiftKey(g)}`);
|
||||
const marketIds = availableMarketGifts.map((g) => `market-${g.id}`);
|
||||
return [...myIds, ...marketIds];
|
||||
}, [availableMyGifts, availableMarketGifts]);
|
||||
|
||||
const [viewportIds, getMore] = useInfiniteScroll(
|
||||
handleLoadMore,
|
||||
allItemIds,
|
||||
!isOpen,
|
||||
CRAFT_GIFTS_LIMIT,
|
||||
);
|
||||
|
||||
const handleClose = useLastCallback(() => {
|
||||
closeGiftCraftSelectModal();
|
||||
});
|
||||
|
||||
const handleMyGiftClick = useLastCallback((gift: ApiStarGift) => {
|
||||
if (gift.type !== 'starGiftUnique') return;
|
||||
const savedGift = myGiftByIdMap.get(gift.id);
|
||||
const slotIndex = renderingModal?.slotIndex;
|
||||
if (savedGift && slotIndex !== undefined) {
|
||||
selectGiftForCraft({ gift: savedGift, slotIndex });
|
||||
}
|
||||
});
|
||||
|
||||
const handleMarketGiftClick = useLastCallback((gift: ApiStarGift) => {
|
||||
const slotIndex = renderingModal?.slotIndex;
|
||||
if (slotIndex === undefined) return;
|
||||
|
||||
openGiftInfoModal({ gift, craftSlotIndex: slotIndex });
|
||||
});
|
||||
|
||||
const handleResetMarketFilter = useLastCallback(() => {
|
||||
updateCraftGiftsFilter({
|
||||
filter: {
|
||||
sortType: marketFilter?.sortType || 'byPrice',
|
||||
modelAttributes: [],
|
||||
backdropAttributes: [],
|
||||
patternAttributes: [],
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
const hasMyGifts = availableMyGifts.length > 0;
|
||||
const hasMarketGifts = availableMarketGifts.length > 0;
|
||||
const isMarketGiftsEmpty = !hasMarketGifts && Boolean(marketCraftableGifts);
|
||||
const hasMarketFilter = Boolean(
|
||||
marketFilter?.modelAttributes?.length
|
||||
|| marketFilter?.patternAttributes?.length
|
||||
|| marketFilter?.backdropAttributes?.length,
|
||||
);
|
||||
const shouldShowMarketSection = isMarketDataLoaded && (hasMarketGifts || hasMarketFilter || isMarketLoading);
|
||||
|
||||
return (
|
||||
<Modal
|
||||
dialogRef={dialogRef}
|
||||
isOpen={isOpen}
|
||||
onClose={handleClose}
|
||||
className={styles.modal}
|
||||
contentClassName={styles.content}
|
||||
hasAbsoluteCloseButton
|
||||
isSlim
|
||||
isLowStackPriority
|
||||
>
|
||||
<h3 className={buildClassName(styles.title, shouldShowTitleBorder && styles.titleWithBorder)}>
|
||||
{lang('GiftCraftSelectTitle')}
|
||||
</h3>
|
||||
<div className={styles.wrapper}>
|
||||
<Loading className={buildClassName(styles.loading, !isLoading && styles.hidden)} />
|
||||
<InfiniteScroll
|
||||
ref={scrollerRef}
|
||||
className={buildClassName(styles.scrollContainer, isLoading && styles.hidden, 'custom-scroll')}
|
||||
items={viewportIds}
|
||||
onLoadMore={getMore}
|
||||
onScroll={handleScroll}
|
||||
itemSelector=".starGiftItem"
|
||||
noFastList
|
||||
noScrollRestore={isMarketJustUpdated}
|
||||
preloadBackwards={CRAFT_GIFTS_LIMIT}
|
||||
>
|
||||
{isOpen && !hasMyGifts && !shouldShowMarketSection && isDataLoaded && (
|
||||
<ResaleGiftsNotFound
|
||||
className={styles.notFound}
|
||||
description={lang('ResellGiftsNoFound')}
|
||||
/>
|
||||
)}
|
||||
{isOpen && hasMyGifts && (
|
||||
<div className={styles.section}>
|
||||
<p className={styles.sectionTitle}>{lang('GiftCraftSelectYourGifts')}</p>
|
||||
<div className={styles.giftsGrid}>
|
||||
{availableMyGifts.map((savedGift) => {
|
||||
const chancePercent = savedGift.gift.type === 'starGiftUnique'
|
||||
? (savedGift.gift.craftChancePermille || 0) / 10
|
||||
: 0;
|
||||
const { backdrop } = getGiftAttributes(savedGift.gift) || {};
|
||||
return (
|
||||
<CraftGiftItem
|
||||
key={`my-${getSavedGiftKey(savedGift)}`}
|
||||
gift={savedGift.gift}
|
||||
chancePercent={chancePercent}
|
||||
chanceColor={backdrop?.centerColor}
|
||||
observe={observe}
|
||||
onClick={handleMyGiftClick}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{isOpen && shouldShowMarketSection && (
|
||||
<div className={styles.section}>
|
||||
<p className={styles.sectionTitle}>
|
||||
{lang('GiftCraftSelectMarketGifts', {
|
||||
count: marketCraftableGiftsCount || 0 },
|
||||
{ pluralValue: marketCraftableGiftsCount || 0 })}
|
||||
</p>
|
||||
<div ref={filtersSeparatorRef} />
|
||||
<GiftResaleFilters
|
||||
dialogRef={dialogRef}
|
||||
className={buildClassName(styles.filters, isFiltersStuck && styles.stuck)}
|
||||
filterType="craft"
|
||||
/>
|
||||
<Transition
|
||||
className={styles.transitionWrapper}
|
||||
name="semiFade"
|
||||
activeKey={marketUpdateIteration}
|
||||
>
|
||||
{isMarketGiftsEmpty && (
|
||||
<ResaleGiftsNotFound
|
||||
className={styles.notFound}
|
||||
description={lang('ResellGiftsNoFound')}
|
||||
linkText={hasMarketFilter ? lang('ResellGiftsClearFilters') : undefined}
|
||||
onLinkClick={hasMarketFilter ? handleResetMarketFilter : undefined}
|
||||
/>
|
||||
)}
|
||||
{hasMarketGifts && (
|
||||
<div className={styles.giftsGrid}>
|
||||
{availableMarketGifts.map((marketGift) => {
|
||||
const chancePercent = (marketGift.craftChancePermille || 0) / 10;
|
||||
const { backdrop } = getGiftAttributes(marketGift) || {};
|
||||
return (
|
||||
<CraftGiftItem
|
||||
key={`market-${marketGift.id}`}
|
||||
gift={marketGift}
|
||||
chancePercent={chancePercent}
|
||||
chanceColor={backdrop?.centerColor}
|
||||
showPrice
|
||||
observe={observe}
|
||||
onClick={handleMarketGiftClick}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
</Transition>
|
||||
</div>
|
||||
)}
|
||||
</InfiniteScroll>
|
||||
</div>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(withGlobal<OwnProps>((global): Complete<StateProps> => {
|
||||
const tabState = selectTabState(global);
|
||||
|
||||
return {
|
||||
craftModal: tabState.giftCraftModal,
|
||||
};
|
||||
})(GiftCraftSelectModal));
|
||||
16
src/components/modals/gift/craft/RadialProgress.module.scss
Normal file
@ -0,0 +1,16 @@
|
||||
.root {
|
||||
transform: rotate(135deg);
|
||||
}
|
||||
|
||||
.background {
|
||||
stroke: rgba(255, 255, 255, 0.15);
|
||||
stroke-linecap: round;
|
||||
stroke-width: 0.375rem;
|
||||
}
|
||||
|
||||
.fill {
|
||||
stroke: white;
|
||||
stroke-linecap: round;
|
||||
stroke-width: 0.375rem;
|
||||
transition: stroke-dashoffset 0.3s ease;
|
||||
}
|
||||
61
src/components/modals/gift/craft/RadialProgress.tsx
Normal file
@ -0,0 +1,61 @@
|
||||
import { memo } from '../../../../lib/teact/teact';
|
||||
|
||||
import buildClassName from '../../../../util/buildClassName';
|
||||
import { clamp } from '../../../../util/math';
|
||||
import { REM } from '../../../common/helpers/mediaDimensions';
|
||||
|
||||
import styles from './RadialProgress.module.scss';
|
||||
|
||||
type OwnProps = {
|
||||
progress: number;
|
||||
size?: number;
|
||||
className?: string;
|
||||
};
|
||||
|
||||
const VIEWBOX_SIZE = 100;
|
||||
const RADIUS_RATIO = 0.35; // 35% of viewbox size
|
||||
const STROKE_START = 0.125;
|
||||
const STROKE_END = 0.875;
|
||||
const ARC_RANGE = STROKE_END - STROKE_START; // 0.75 = 270 degrees
|
||||
|
||||
export const DEFAULT_RING_SIZE = 7.5 * REM;
|
||||
|
||||
const RadialProgress = ({ progress, size = DEFAULT_RING_SIZE, className }: OwnProps) => {
|
||||
const center = VIEWBOX_SIZE / 2;
|
||||
const radius = VIEWBOX_SIZE * RADIUS_RATIO;
|
||||
|
||||
const clampedProgress = clamp(progress, 0, 100);
|
||||
const progressStrokeEnd = STROKE_START + ARC_RANGE * (clampedProgress / 100);
|
||||
|
||||
const bgDashoffset = 1 - ARC_RANGE;
|
||||
const fillDashoffset = 1 - (progressStrokeEnd - STROKE_START);
|
||||
|
||||
return (
|
||||
<svg
|
||||
className={buildClassName(styles.root, className)}
|
||||
viewBox={`0 0 ${VIEWBOX_SIZE} ${VIEWBOX_SIZE}`}
|
||||
style={`width: ${size}px; height: ${size}px`}
|
||||
>
|
||||
<circle
|
||||
className={styles.background}
|
||||
cx={center}
|
||||
cy={center}
|
||||
r={radius}
|
||||
fill="none"
|
||||
pathLength="1"
|
||||
style={`stroke-dasharray: 1; stroke-dashoffset: ${bgDashoffset}`}
|
||||
/>
|
||||
<circle
|
||||
className={styles.fill}
|
||||
cx={center}
|
||||
cy={center}
|
||||
r={radius}
|
||||
fill="none"
|
||||
pathLength="1"
|
||||
style={`stroke-dasharray: 1; stroke-dashoffset: ${fillDashoffset}`}
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(RadialProgress);
|
||||
@ -2,6 +2,21 @@
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.headerRightButtons {
|
||||
position: absolute;
|
||||
z-index: 3;
|
||||
top: 0.875rem;
|
||||
right: 3rem;
|
||||
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.craftButton {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.uniqueTitleNumber {
|
||||
&.small {
|
||||
font-size: 0.75em;
|
||||
@ -73,9 +88,7 @@
|
||||
.giftResalePriceContainer {
|
||||
pointer-events: auto;
|
||||
|
||||
position: absolute;
|
||||
top: 0.875rem;
|
||||
right: 3.25rem;
|
||||
position: relative;
|
||||
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
@ -16,7 +16,7 @@ import { selectPeer, selectUser } from '../../../../global/selectors';
|
||||
import buildClassName from '../../../../util/buildClassName';
|
||||
import { copyTextToClipboard } from '../../../../util/clipboard';
|
||||
import { formatDateTimeToString } from '../../../../util/dates/dateFormat';
|
||||
import { formatCurrencyAsString } from '../../../../util/formatCurrency';
|
||||
import { formatCurrency, formatCurrencyAsString } from '../../../../util/formatCurrency';
|
||||
import {
|
||||
formatStarsAsIcon, formatStarsAsText, formatTonAsIcon, formatTonAsText,
|
||||
getNextArrowReplacement,
|
||||
@ -91,6 +91,7 @@ const GiftInfoModal = ({
|
||||
openChatWithInfo,
|
||||
focusMessage,
|
||||
openGiftUpgradeModal,
|
||||
openGiftCraftModal,
|
||||
showNotification,
|
||||
buyStarGift,
|
||||
closeGiftModal,
|
||||
@ -247,6 +248,12 @@ const GiftInfoModal = ({
|
||||
});
|
||||
});
|
||||
|
||||
const handleOpenCraftModal = useLastCallback(() => {
|
||||
if (!savedGift || savedGift.gift.type !== 'starGiftUnique') return;
|
||||
handleClose();
|
||||
openGiftCraftModal({ gift: savedGift });
|
||||
});
|
||||
|
||||
const giftAttributes = useMemo(() => {
|
||||
return gift && getGiftAttributes(gift);
|
||||
}, [gift]);
|
||||
@ -262,6 +269,7 @@ const GiftInfoModal = ({
|
||||
if (!gift || gift.type !== 'starGiftUnique' || !giftAttributes?.backdrop) return undefined;
|
||||
|
||||
const numberColor = giftAttributes.backdrop.textColor;
|
||||
|
||||
const digitCount = String(gift.number).length;
|
||||
const numberSizeClass = digitCount >= 6 ? styles.small : styles.regular;
|
||||
const styledNumber = (
|
||||
@ -300,9 +308,7 @@ const GiftInfoModal = ({
|
||||
<Button className={styles.buyButton} onClick={handleBuyGift}>
|
||||
<div>
|
||||
{lang('ButtonBuyGift', {
|
||||
stars: resellPrice?.currency === TON_CURRENCY_CODE
|
||||
? formatTonAsIcon(lang, resellPrice.amount, { shouldConvertFromNanos: true })
|
||||
: formatStarsAsIcon(lang, resellPrice?.amount, { asFont: true }),
|
||||
stars: formatCurrency(lang, resellPrice.amount, resellPrice.currency, { asFontIcon: true }),
|
||||
}, { withNodes: true })}
|
||||
</div>
|
||||
{resellPrice?.currency === TON_CURRENCY_CODE && Boolean(resellPriceInStars) && (
|
||||
@ -389,6 +395,14 @@ const GiftInfoModal = ({
|
||||
return text;
|
||||
}, [gift, lang]);
|
||||
|
||||
// ToDo
|
||||
// const canCraft = Boolean(
|
||||
// canManage && savedGift?.canCraftAt && getServerTime() >= savedGift.canCraftAt,
|
||||
// );
|
||||
|
||||
// Mock for Tests
|
||||
const canCraft = Boolean(canManage && savedGift?.canCraftAt);
|
||||
|
||||
const modalData = useMemo(() => {
|
||||
if (!typeGift || !gift) {
|
||||
return undefined;
|
||||
@ -487,10 +501,7 @@ const GiftInfoModal = ({
|
||||
}
|
||||
|
||||
const uniqueGiftModalHeader = (
|
||||
<div
|
||||
className={styles.modalHeader}
|
||||
>
|
||||
|
||||
<div className={styles.modalHeader}>
|
||||
<Button
|
||||
className={styles.closeButton}
|
||||
round
|
||||
@ -500,23 +511,34 @@ const GiftInfoModal = ({
|
||||
ariaLabel={lang('Close')}
|
||||
onClick={handleClose}
|
||||
/>
|
||||
|
||||
{Boolean(resellPrice?.amount) && (
|
||||
<div className={styles.giftResalePriceContainer}>
|
||||
{resellPrice.currency === TON_CURRENCY_CODE
|
||||
? formatTonAsIcon(lang, resellPrice.amount, {
|
||||
className: styles.giftResalePriceStar,
|
||||
shouldConvertFromNanos: true,
|
||||
})
|
||||
: formatStarsAsIcon(lang, resellPrice.amount, {
|
||||
asFont: true,
|
||||
className: styles.giftResalePriceStar,
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
|
||||
const headerRightToolBar = (Boolean(resellPrice?.amount) || canCraft) ? (
|
||||
<div className={styles.headerRightButtons}>
|
||||
{Boolean(resellPrice?.amount) && (
|
||||
<div className={styles.giftResalePriceContainer}>
|
||||
{formatCurrency(lang, resellPrice.amount, resellPrice.currency, {
|
||||
asFontIcon: true,
|
||||
iconClassName: styles.giftResalePriceStar,
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
{canCraft && (
|
||||
<Button
|
||||
className={styles.craftButton}
|
||||
round
|
||||
color="translucent-white"
|
||||
size="tiny"
|
||||
ariaLabel={lang('GiftInfoCraft')}
|
||||
onClick={handleOpenCraftModal}
|
||||
>
|
||||
<Icon name="craft" />
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
) : undefined;
|
||||
|
||||
const uniqueGiftHeader = isGiftUnique && (
|
||||
<div ref={uniqueGiftHeaderRef} className={buildClassName(styles.header, styles.uniqueGift)}>
|
||||
<UniqueGiftHeader
|
||||
@ -804,6 +826,7 @@ const GiftInfoModal = ({
|
||||
|
||||
return {
|
||||
modalHeader: isGiftUnique ? uniqueGiftModalHeader : undefined,
|
||||
headerRightToolBar: isGiftUnique ? headerRightToolBar : undefined,
|
||||
header: isGiftUnique ? uniqueGiftHeader : regularHeader,
|
||||
tableData,
|
||||
footer,
|
||||
@ -812,7 +835,7 @@ const GiftInfoModal = ({
|
||||
typeGift, savedGift, renderingTargetPeer, giftSticker, lang,
|
||||
canManage, hasConvertOption, isSender, oldLang, tonExplorerUrl,
|
||||
gift, giftAttributes, renderFooterButton, isTargetChat,
|
||||
isGiftUnique, saleDateInfo,
|
||||
isGiftUnique, saleDateInfo, canCraft, handleOpenCraftModal,
|
||||
canBuyGift, giftOwnerTitle, resellPrice, uniqueGiftTitle, uniqueGiftSubtitle, releasedByPeer,
|
||||
]);
|
||||
|
||||
@ -831,6 +854,7 @@ const GiftInfoModal = ({
|
||||
<TableInfoModal
|
||||
isOpen={isOpen}
|
||||
modalHeader={modalData?.modalHeader}
|
||||
headerRightToolBar={modalData?.headerRightToolBar}
|
||||
header={modalData?.header}
|
||||
hasBackdrop={isGiftUnique}
|
||||
tableData={modalData?.tableData}
|
||||
@ -842,7 +866,7 @@ const GiftInfoModal = ({
|
||||
onClose={handleClose}
|
||||
withBalanceBar={Boolean(canBuyGift)}
|
||||
currencyInBalanceBar={confirmPrice?.currency}
|
||||
isLowStackPriority
|
||||
isLowStackPriority={renderingModal?.craftSlotIndex !== undefined ? true : undefined}
|
||||
/>
|
||||
{uniqueGift && currentUser && Boolean(confirmPrice) && (
|
||||
<ConfirmDialog
|
||||
|
||||
@ -123,11 +123,20 @@ const GiftPreviewModal = ({ modal, animationLevel }: OwnProps & StateProps) => {
|
||||
if (newModel && newModel.rarity.type !== 'regular') showCraftableModels();
|
||||
}, [initialAttributes, firstModel, firstPattern, firstBackdrop]);
|
||||
|
||||
useEffect(() => {
|
||||
if (renderingModal?.shouldShowCraftableOnStart) {
|
||||
showCraftableModels();
|
||||
}
|
||||
}, [renderingModal?.shouldShowCraftableOnStart]);
|
||||
|
||||
const handleStickerAnimationEnded = useLastCallback((modelName: string) => {
|
||||
if (modelName !== selectedModel?.name || !isPlayingRandomPreviews) return;
|
||||
|
||||
if (!originGift || !selectedModel || !selectedPattern || !selectedBackdrop) return;
|
||||
const newAttributes = getRandomGiftPreviewAttributes(renderingModal?.attributes, {
|
||||
const attributesToUse = renderingModal?.shouldShowCraftableOnStart && isCraftableModelsMode
|
||||
? renderingModal.attributes.filter((attr) => attr.type !== 'model' || attr.rarity.type !== 'regular')
|
||||
: renderingModal?.attributes;
|
||||
const newAttributes = getRandomGiftPreviewAttributes(attributesToUse, {
|
||||
model: selectedModel,
|
||||
pattern: selectedPattern,
|
||||
backdrop: selectedBackdrop,
|
||||
|
||||
@ -47,6 +47,7 @@ export type OwnProps = {
|
||||
isLowStackPriority?: boolean;
|
||||
dialogContent?: React.ReactNode;
|
||||
moreMenuItems?: TeactNode;
|
||||
headerRightToolBar?: TeactNode;
|
||||
withBalanceBar?: boolean;
|
||||
currencyInBalanceBar?: 'TON' | 'XTR';
|
||||
isCondensedHeader?: boolean;
|
||||
@ -95,6 +96,7 @@ const Modal = (props: OwnProps) => {
|
||||
dialogStyle,
|
||||
dialogContent,
|
||||
moreMenuItems,
|
||||
headerRightToolBar: headerToolBar,
|
||||
withBalanceBar,
|
||||
isCondensedHeader,
|
||||
currencyInBalanceBar = 'XTR',
|
||||
@ -226,6 +228,7 @@ const Modal = (props: OwnProps) => {
|
||||
)}
|
||||
<div className={modalDialogClassName} ref={actualDialogRef} style={dialogStyle}>
|
||||
{renderHeader()}
|
||||
{headerToolBar}
|
||||
{Boolean(moreMenuItems) && (
|
||||
<>
|
||||
<Button
|
||||
|
||||
@ -1,4 +1,10 @@
|
||||
import type { ApiSavedStarGift, ApiStarGiftUnique } from '../../../api/types';
|
||||
import type {
|
||||
ApiInputSavedStarGift,
|
||||
ApiRequestInputSavedStarGift,
|
||||
ApiSavedStarGift,
|
||||
ApiStarGiftAttribute,
|
||||
ApiStarGiftUnique,
|
||||
} from '../../../api/types';
|
||||
import type { ActionReturnType } from '../../types';
|
||||
|
||||
import {
|
||||
@ -10,6 +16,7 @@ import { getCurrentTabId } from '../../../util/establishMultitabRole';
|
||||
import { buildCollectionByCallback, buildCollectionByKey } from '../../../util/iteratees';
|
||||
import { getServerTime } from '../../../util/serverTime';
|
||||
import { callApi } from '../../../api/gramjs';
|
||||
import { preloadGiftAttributeStickers } from '../../../components/common/helpers/gifts';
|
||||
import { RESALE_GIFTS_LIMIT } from '../../../limits';
|
||||
import { areInputSavedGiftsEqual, getRequestInputSavedStarGift } from '../../helpers/payments';
|
||||
import { addActionHandler, getGlobal, getPromiseActions, setGlobal } from '../../index';
|
||||
@ -27,6 +34,8 @@ import {
|
||||
import { updateTabState } from '../../reducers/tabs';
|
||||
import {
|
||||
selectActiveGiftsCollectionId,
|
||||
selectChat,
|
||||
selectChatMessage,
|
||||
selectGiftProfileFilter,
|
||||
selectPeer,
|
||||
selectPeerCollectionSavedGifts,
|
||||
@ -753,8 +762,389 @@ addActionHandler('loadActiveGiftAuctions', async (global, actions, payload): Pro
|
||||
setGlobal(global);
|
||||
});
|
||||
|
||||
addActionHandler('openGiftInfoModalFromMessage', async (global, actions, payload): Promise<void> => {
|
||||
const {
|
||||
chatId, messageId, tabId = getCurrentTabId(),
|
||||
} = payload;
|
||||
|
||||
const chat = selectChat(global, chatId);
|
||||
if (!chat) return;
|
||||
|
||||
await getPromiseActions().loadMessage({ chatId, messageId });
|
||||
|
||||
global = getGlobal();
|
||||
const message = selectChatMessage(global, chatId, messageId);
|
||||
|
||||
if (!message || !message.content.action) return;
|
||||
|
||||
const action = message.content.action;
|
||||
if (action.type !== 'starGift' && action.type !== 'starGiftUnique') return;
|
||||
|
||||
const starGift = action.type === 'starGift' ? action : undefined;
|
||||
const uniqueGift = action.type === 'starGiftUnique' ? action : undefined;
|
||||
const giftMsgId = starGift?.giftMsgId;
|
||||
|
||||
const giftReceiverId = action.peerId || (message.isOutgoing ? message.chatId : global.currentUserId!);
|
||||
|
||||
const inputGift: ApiInputSavedStarGift = (() => {
|
||||
if (giftMsgId) {
|
||||
return { type: 'user', messageId: giftMsgId };
|
||||
}
|
||||
if (action.savedId) {
|
||||
return { type: 'chat', chatId, savedId: action.savedId };
|
||||
}
|
||||
return { type: 'user', messageId };
|
||||
})();
|
||||
|
||||
const fromId = action.fromId || (message.isOutgoing ? global.currentUserId! : message.chatId);
|
||||
|
||||
const gift: ApiSavedStarGift = {
|
||||
date: message.date,
|
||||
gift: action.gift,
|
||||
message: starGift?.message,
|
||||
starsToConvert: starGift?.starsToConvert,
|
||||
isNameHidden: starGift?.isNameHidden,
|
||||
isUnsaved: !action.isSaved,
|
||||
fromId,
|
||||
messageId: message.id,
|
||||
isConverted: starGift?.isConverted,
|
||||
upgradeMsgId: starGift?.upgradeMsgId,
|
||||
canUpgrade: starGift?.canUpgrade,
|
||||
alreadyPaidUpgradeStars: starGift?.alreadyPaidUpgradeStars,
|
||||
inputGift,
|
||||
canExportAt: uniqueGift?.canExportAt,
|
||||
savedId: action.savedId,
|
||||
transferStars: uniqueGift?.transferStars,
|
||||
dropOriginalDetailsStars: uniqueGift?.dropOriginalDetailsStars,
|
||||
prepaidUpgradeHash: starGift?.prepaidUpgradeHash,
|
||||
canCraftAt: uniqueGift?.canCraftAt,
|
||||
};
|
||||
|
||||
actions.openGiftInfoModal({ peerId: giftReceiverId, gift, tabId });
|
||||
});
|
||||
|
||||
addActionHandler('openGiftInfoValueModal', async (global, actions, payload): Promise<void> => {
|
||||
const { gift, tabId = getCurrentTabId() } = payload;
|
||||
|
||||
const result = await callApi('fetchUniqueStarGiftValueInfo', { slug: gift.slug });
|
||||
if (!result) return;
|
||||
|
||||
global = getGlobal();
|
||||
global = updateTabState(global, {
|
||||
giftInfoValueModal: {
|
||||
valueInfo: result,
|
||||
gift,
|
||||
},
|
||||
}, tabId);
|
||||
setGlobal(global);
|
||||
});
|
||||
|
||||
addActionHandler('openGiftCraftModal', async (global, _actions, payload): Promise<void> => {
|
||||
const { gift, tabId = getCurrentTabId() } = payload;
|
||||
|
||||
const uniqueGift = gift?.gift.type === 'starGiftUnique' ? gift.gift : undefined;
|
||||
const regularGiftId = uniqueGift?.regularGiftId;
|
||||
|
||||
let previewAttributes: ApiStarGiftAttribute[] | undefined;
|
||||
|
||||
if (regularGiftId) {
|
||||
const result = await callApi('fetchStarGiftUpgradeAttributes', { giftId: regularGiftId });
|
||||
if (result) {
|
||||
const craftableModels = result.attributes.filter(
|
||||
(attr) => attr.type === 'model' && attr.rarity.type !== 'regular',
|
||||
);
|
||||
preloadGiftAttributeStickers(craftableModels);
|
||||
previewAttributes = craftableModels;
|
||||
}
|
||||
}
|
||||
|
||||
global = getGlobal();
|
||||
global = updateTabState(global, {
|
||||
giftCraftModal: {
|
||||
regularGiftId,
|
||||
regularGiftTitle: uniqueGift?.title,
|
||||
gift1: gift,
|
||||
marketFilter: { sortType: 'byPrice' },
|
||||
marketUpdateIteration: 0,
|
||||
previewAttributes,
|
||||
},
|
||||
}, tabId);
|
||||
setGlobal(global);
|
||||
});
|
||||
|
||||
addActionHandler('openGiftCraftSelectModal', async (global, actions, payload): Promise<void> => {
|
||||
const { slotIndex, tabId = getCurrentTabId() } = payload;
|
||||
|
||||
const tabState = selectTabState(global, tabId);
|
||||
const craftModal = tabState.giftCraftModal;
|
||||
if (!craftModal?.regularGiftId) return;
|
||||
|
||||
const shouldLoadMyGifts = !craftModal.myCraftableGifts || craftModal.shouldRefreshMyCraftableGifts;
|
||||
const shouldLoadMarketGifts = !craftModal.marketCraftableGifts;
|
||||
|
||||
if (!shouldLoadMyGifts && !shouldLoadMarketGifts) {
|
||||
global = updateTabState(global, {
|
||||
giftCraftSelectModal: { slotIndex },
|
||||
}, tabId);
|
||||
setGlobal(global);
|
||||
return;
|
||||
}
|
||||
|
||||
global = updateTabState(global, {
|
||||
giftCraftSelectModal: { slotIndex, isLoading: true },
|
||||
}, tabId);
|
||||
setGlobal(global);
|
||||
|
||||
const [myGiftsResult, marketGiftsResult] = await Promise.all([
|
||||
shouldLoadMyGifts
|
||||
? callApi('fetchCraftStarGifts', { giftId: craftModal.regularGiftId, peerId: global.currentUserId! })
|
||||
: undefined,
|
||||
shouldLoadMarketGifts
|
||||
? callApi('fetchResaleGifts', {
|
||||
giftId: craftModal.regularGiftId,
|
||||
filter: craftModal.marketFilter,
|
||||
forCraft: true,
|
||||
})
|
||||
: undefined,
|
||||
]);
|
||||
|
||||
global = getGlobal();
|
||||
const currentCraftModal = selectTabState(global, tabId).giftCraftModal;
|
||||
const currentSelectModal = selectTabState(global, tabId).giftCraftSelectModal;
|
||||
if (!currentCraftModal || !currentSelectModal) return;
|
||||
|
||||
// Filter to only unique gifts
|
||||
const savedGifts = myGiftsResult?.gifts.filter((g) => g.gift.type === 'starGiftUnique');
|
||||
const marketGifts = marketGiftsResult?.gifts.filter(
|
||||
(g): g is ApiStarGiftUnique => g.type === 'starGiftUnique',
|
||||
);
|
||||
|
||||
const didLoadMyGifts = shouldLoadMyGifts && myGiftsResult;
|
||||
const didLoadMarketGifts = shouldLoadMarketGifts && marketGiftsResult;
|
||||
|
||||
global = updateTabState(global, {
|
||||
giftCraftModal: {
|
||||
...currentCraftModal,
|
||||
myCraftableGifts: didLoadMyGifts ? savedGifts : currentCraftModal.myCraftableGifts,
|
||||
myCraftableGiftsNextOffset: didLoadMyGifts
|
||||
? myGiftsResult.nextOffset : currentCraftModal.myCraftableGiftsNextOffset,
|
||||
shouldRefreshMyCraftableGifts: shouldLoadMyGifts ? !myGiftsResult :
|
||||
currentCraftModal.shouldRefreshMyCraftableGifts,
|
||||
marketCraftableGifts: didLoadMarketGifts ? marketGifts : currentCraftModal.marketCraftableGifts,
|
||||
marketCraftableGiftsNextOffset: didLoadMarketGifts
|
||||
? marketGiftsResult.nextOffset : currentCraftModal.marketCraftableGiftsNextOffset,
|
||||
marketCraftableGiftsCount: didLoadMarketGifts
|
||||
? marketGiftsResult.count : currentCraftModal.marketCraftableGiftsCount,
|
||||
marketAttributes: didLoadMarketGifts ? marketGiftsResult.attributes : currentCraftModal.marketAttributes,
|
||||
marketCounters: didLoadMarketGifts ? marketGiftsResult.counters : currentCraftModal.marketCounters,
|
||||
marketAttributesHash: didLoadMarketGifts
|
||||
? marketGiftsResult.attributesHash : currentCraftModal.marketAttributesHash,
|
||||
},
|
||||
giftCraftSelectModal: {
|
||||
...currentSelectModal,
|
||||
isLoading: false,
|
||||
},
|
||||
}, tabId);
|
||||
setGlobal(global);
|
||||
});
|
||||
|
||||
addActionHandler('loadMoreCraftableGifts', async (global, actions, payload): Promise<void> => {
|
||||
const { tabId = getCurrentTabId() } = payload || {};
|
||||
|
||||
const tabState = selectTabState(global, tabId);
|
||||
const craftModal = tabState.giftCraftModal;
|
||||
if (!craftModal?.myCraftableGiftsNextOffset) return;
|
||||
|
||||
const gift1Unique = craftModal.gift1?.gift.type === 'starGiftUnique' ? craftModal.gift1.gift : undefined;
|
||||
if (!gift1Unique?.regularGiftId) return;
|
||||
|
||||
const result = await callApi('fetchCraftStarGifts', {
|
||||
giftId: gift1Unique.regularGiftId,
|
||||
peerId: global.currentUserId!,
|
||||
offset: craftModal.myCraftableGiftsNextOffset,
|
||||
});
|
||||
|
||||
if (!result) return;
|
||||
|
||||
global = getGlobal();
|
||||
const currentCraftModal = selectTabState(global, tabId).giftCraftModal;
|
||||
if (!currentCraftModal) return;
|
||||
|
||||
// Filter to only unique gifts
|
||||
const newSavedGifts = result.gifts.filter((g) => g.gift.type === 'starGiftUnique');
|
||||
|
||||
global = updateTabState(global, {
|
||||
giftCraftModal: {
|
||||
...currentCraftModal,
|
||||
myCraftableGifts: [...(currentCraftModal.myCraftableGifts || []), ...newSavedGifts],
|
||||
myCraftableGiftsNextOffset: result.nextOffset,
|
||||
},
|
||||
}, tabId);
|
||||
setGlobal(global);
|
||||
});
|
||||
|
||||
addActionHandler('loadMoreMarketCraftableGifts', async (global, actions, payload): Promise<void> => {
|
||||
const { tabId = getCurrentTabId() } = payload || {};
|
||||
|
||||
const tabState = selectTabState(global, tabId);
|
||||
const craftModal = tabState.giftCraftModal;
|
||||
if (!craftModal?.regularGiftId) return;
|
||||
|
||||
if (craftModal.isMarketLoading) return;
|
||||
if (craftModal.marketCraftableGifts && !craftModal.marketCraftableGiftsNextOffset) return;
|
||||
|
||||
global = updateTabState(global, {
|
||||
giftCraftModal: {
|
||||
...craftModal,
|
||||
isMarketLoading: true,
|
||||
},
|
||||
}, tabId);
|
||||
setGlobal(global);
|
||||
|
||||
const result = await callApi('fetchResaleGifts', {
|
||||
giftId: craftModal.regularGiftId,
|
||||
offset: craftModal.marketCraftableGiftsNextOffset,
|
||||
filter: craftModal.marketFilter,
|
||||
forCraft: true,
|
||||
});
|
||||
|
||||
global = getGlobal();
|
||||
const currentCraftModal = selectTabState(global, tabId).giftCraftModal;
|
||||
if (!currentCraftModal) return;
|
||||
|
||||
if (!result) {
|
||||
global = updateTabState(global, {
|
||||
giftCraftModal: { ...currentCraftModal, isMarketLoading: false },
|
||||
}, tabId);
|
||||
setGlobal(global);
|
||||
return;
|
||||
}
|
||||
|
||||
const newGifts = result.gifts.filter((g): g is ApiStarGiftUnique => g.type === 'starGiftUnique');
|
||||
|
||||
global = updateTabState(global, {
|
||||
giftCraftModal: {
|
||||
...currentCraftModal,
|
||||
marketCraftableGifts: [...(currentCraftModal.marketCraftableGifts || []), ...newGifts],
|
||||
marketCraftableGiftsNextOffset: result.nextOffset,
|
||||
isMarketLoading: false,
|
||||
},
|
||||
}, tabId);
|
||||
setGlobal(global);
|
||||
});
|
||||
|
||||
addActionHandler('updateCraftGiftsFilter', async (global, actions, payload): Promise<void> => {
|
||||
const { filter, tabId = getCurrentTabId() } = payload;
|
||||
|
||||
const tabState = selectTabState(global, tabId);
|
||||
const modal = tabState.giftCraftModal;
|
||||
if (!modal?.regularGiftId) return;
|
||||
|
||||
global = updateTabState(global, {
|
||||
giftCraftModal: {
|
||||
...modal,
|
||||
marketFilter: filter,
|
||||
isMarketLoading: true,
|
||||
},
|
||||
}, tabId);
|
||||
setGlobal(global);
|
||||
|
||||
const result = await callApi('fetchResaleGifts', {
|
||||
giftId: modal.regularGiftId,
|
||||
filter,
|
||||
forCraft: true,
|
||||
});
|
||||
|
||||
global = getGlobal();
|
||||
const currentModal = selectTabState(global, tabId).giftCraftModal;
|
||||
if (!currentModal) return;
|
||||
|
||||
if (!result) {
|
||||
global = updateTabState(global, {
|
||||
giftCraftModal: { ...currentModal, isMarketLoading: false },
|
||||
}, tabId);
|
||||
setGlobal(global);
|
||||
return;
|
||||
}
|
||||
|
||||
const newGifts = result.gifts.filter((g): g is ApiStarGiftUnique => g.type === 'starGiftUnique');
|
||||
|
||||
global = updateTabState(global, {
|
||||
giftCraftModal: {
|
||||
...currentModal,
|
||||
marketCraftableGifts: newGifts,
|
||||
marketCraftableGiftsNextOffset: result.nextOffset,
|
||||
marketCraftableGiftsCount: result.count,
|
||||
marketCounters: result.counters,
|
||||
marketUpdateIteration: currentModal.marketUpdateIteration + 1,
|
||||
isMarketLoading: false,
|
||||
},
|
||||
}, tabId);
|
||||
setGlobal(global);
|
||||
});
|
||||
|
||||
addActionHandler('craftStarGift', async (global, _actions, payload): Promise<void> => {
|
||||
const { tabId = getCurrentTabId() } = payload || {};
|
||||
|
||||
const tabState = selectTabState(global, tabId);
|
||||
const modal = tabState.giftCraftModal;
|
||||
|
||||
if (!modal?.regularGiftId) return;
|
||||
|
||||
const savedGifts = [modal.gift1, modal.gift2, modal.gift3, modal.gift4].filter(
|
||||
(g): g is ApiSavedStarGift => Boolean(g),
|
||||
);
|
||||
if (savedGifts.length === 0) return;
|
||||
|
||||
const inputSavedGifts = savedGifts
|
||||
.map((g) => g.inputGift && getRequestInputSavedStarGift(global, g.inputGift))
|
||||
.filter((g): g is ApiRequestInputSavedStarGift => Boolean(g));
|
||||
|
||||
if (inputSavedGifts.length === 0) return;
|
||||
|
||||
const result = await callApi('craftStarGift', { inputSavedGifts });
|
||||
|
||||
if (result?.error) {
|
||||
global = getGlobal();
|
||||
const currentModal = selectTabState(global, tabId).giftCraftModal;
|
||||
if (!currentModal) return;
|
||||
|
||||
global = updateTabState(global, {
|
||||
giftCraftModal: {
|
||||
...currentModal,
|
||||
craftResult: { success: false, isError: true },
|
||||
},
|
||||
}, tabId);
|
||||
setGlobal(global);
|
||||
}
|
||||
});
|
||||
|
||||
addActionHandler('openAboutStarGiftModal', async (global, actions, payload): Promise<void> => {
|
||||
const { tabId = getCurrentTabId() } = payload || {};
|
||||
|
||||
const result = await callApi('fetchPremiumPromo');
|
||||
|
||||
let videoId: string | undefined;
|
||||
let videoThumbnail;
|
||||
|
||||
if (result?.promo) {
|
||||
const giftsIndex = result.promo.videoSections.indexOf('gifts');
|
||||
if (giftsIndex !== -1 && giftsIndex < result.promo.videos.length) {
|
||||
const video = result.promo.videos[giftsIndex];
|
||||
videoId = video.id;
|
||||
videoThumbnail = video.thumbnail;
|
||||
}
|
||||
}
|
||||
|
||||
global = getGlobal();
|
||||
global = updateTabState(global, {
|
||||
aboutStarGiftModal: { videoId, videoThumbnail },
|
||||
}, tabId);
|
||||
setGlobal(global);
|
||||
});
|
||||
|
||||
addActionHandler('openGiftPreviewModal', async (global, _actions, payload): Promise<void> => {
|
||||
const { originGift, tabId = getCurrentTabId() } = payload;
|
||||
const { originGift, shouldShowCraftableOnStart, tabId = getCurrentTabId() } = payload;
|
||||
|
||||
const giftId = originGift.type === 'starGiftUnique' ? originGift.regularGiftId : originGift.id;
|
||||
const result = await callApi('fetchStarGiftUpgradeAttributes', { giftId });
|
||||
@ -765,6 +1155,7 @@ addActionHandler('openGiftPreviewModal', async (global, _actions, payload): Prom
|
||||
giftPreviewModal: {
|
||||
originGift,
|
||||
attributes: result.attributes,
|
||||
shouldShowCraftableOnStart,
|
||||
},
|
||||
}, tabId);
|
||||
setGlobal(global);
|
||||
|
||||
@ -339,6 +339,15 @@ addActionHandler('apiUpdate', (global, actions, update): ActionReturnType => {
|
||||
|
||||
actions.reloadPeerSavedGifts({ peerId: global.currentUserId! });
|
||||
}
|
||||
|
||||
if (tabState.giftCraftModal && actionStarGift.isCrafted) {
|
||||
global = updateTabState(global, {
|
||||
giftCraftModal: {
|
||||
...tabState.giftCraftModal,
|
||||
craftResult: { success: true, gift: actionStarGift },
|
||||
},
|
||||
}, tabId);
|
||||
}
|
||||
});
|
||||
|
||||
setGlobal(global);
|
||||
|
||||
@ -162,6 +162,10 @@ addActionHandler('apiUpdate', (global, actions, update): ActionReturnType => {
|
||||
const starGiftModalState = selectTabState(global, tabId).giftInfoModal;
|
||||
|
||||
if (starGiftModalState) {
|
||||
const { craftSlotIndex, gift } = starGiftModalState;
|
||||
const actualGift = 'gift' in gift ? gift.gift : gift;
|
||||
const giftId = actualGift.type === 'starGiftUnique' ? actualGift.id : undefined;
|
||||
|
||||
actions.showNotification({
|
||||
message: {
|
||||
key: 'StarsGiftBought',
|
||||
@ -173,6 +177,11 @@ addActionHandler('apiUpdate', (global, actions, update): ActionReturnType => {
|
||||
}
|
||||
actions.reloadPeerSavedGifts({ peerId: inputInvoice.peerId });
|
||||
actions.requestConfetti({ withStars: true, tabId });
|
||||
|
||||
if (craftSlotIndex !== undefined && giftId) {
|
||||
actions.selectPurchasedGiftForCraft({ giftId, slotIndex: craftSlotIndex, tabId });
|
||||
}
|
||||
|
||||
actions.closeGiftInfoModal({ tabId });
|
||||
}
|
||||
}
|
||||
@ -270,5 +279,21 @@ addActionHandler('apiUpdate', (global, actions, update): ActionReturnType => {
|
||||
setGlobal(global);
|
||||
break;
|
||||
}
|
||||
|
||||
case 'updateStarGiftCraftFail': {
|
||||
Object.values(global.byTabId).forEach(({ id: tabId }) => {
|
||||
const modal = selectTabState(global, tabId).giftCraftModal;
|
||||
if (modal) {
|
||||
global = updateTabState(global, {
|
||||
giftCraftModal: {
|
||||
...modal,
|
||||
craftResult: { success: false },
|
||||
},
|
||||
}, tabId);
|
||||
}
|
||||
});
|
||||
setGlobal(global);
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@ -1,16 +1,12 @@
|
||||
import { getPromiseActions } from '../../../global';
|
||||
|
||||
import type { ApiInputSavedStarGift, ApiSavedStarGift } from '../../../api/types';
|
||||
import type { ApiSavedStarGift, ApiStarGiftUnique } from '../../../api/types';
|
||||
import type { ActionReturnType } from '../../types';
|
||||
|
||||
import { STARS_CURRENCY_CODE } from '../../../config';
|
||||
import { selectChat } from '../../../global/selectors';
|
||||
import { getCurrentTabId } from '../../../util/establishMultitabRole';
|
||||
import * as langProvider from '../../../util/oldLangProvider';
|
||||
import { callApi } from '../../../api/gramjs';
|
||||
import { addTabStateResetterAction } from '../../helpers/meta';
|
||||
import { getPrizeStarsTransactionFromGiveaway, getStarsTransactionFromGift } from '../../helpers/payments';
|
||||
import { addActionHandler, getGlobal, setGlobal } from '../../index';
|
||||
import { addActionHandler, setGlobal } from '../../index';
|
||||
import { clearStarPayment, openStarsTransactionModal } from '../../reducers';
|
||||
import { removeGiftAuction } from '../../reducers/gifts';
|
||||
import { updateTabState } from '../../reducers/tabs';
|
||||
@ -18,6 +14,14 @@ import {
|
||||
selectChatMessage, selectIsCurrentUserFrozen, selectShouldRemoveGiftAuction, selectStarsPayment, selectTabState,
|
||||
} from '../../selectors';
|
||||
|
||||
function buildShortSavedGift(gift: ApiStarGiftUnique, fromId?: string): ApiSavedStarGift {
|
||||
return {
|
||||
gift,
|
||||
date: Math.floor(Date.now() / 1000),
|
||||
fromId,
|
||||
};
|
||||
}
|
||||
|
||||
addActionHandler('processOriginStarsPayment', (global, actions, payload): ActionReturnType => {
|
||||
const { originData, status, tabId = getCurrentTabId() } = payload;
|
||||
const {
|
||||
@ -248,69 +252,9 @@ addActionHandler('closeStarsGiftModal', (global, actions, payload): ActionReturn
|
||||
}, tabId);
|
||||
});
|
||||
|
||||
addActionHandler('openGiftInfoModalFromMessage', async (global, actions, payload): Promise<void> => {
|
||||
const {
|
||||
chatId, messageId, tabId = getCurrentTabId(),
|
||||
} = payload;
|
||||
|
||||
const chat = selectChat(global, chatId);
|
||||
if (!chat) return;
|
||||
|
||||
await getPromiseActions().loadMessage({ chatId, messageId });
|
||||
|
||||
global = getGlobal();
|
||||
const message = selectChatMessage(global, chatId, messageId);
|
||||
|
||||
if (!message || !message.content.action) return;
|
||||
|
||||
const action = message.content.action;
|
||||
if (action.type !== 'starGift' && action.type !== 'starGiftUnique') return;
|
||||
|
||||
const starGift = action.type === 'starGift' ? action : undefined;
|
||||
const uniqueGift = action.type === 'starGiftUnique' ? action : undefined;
|
||||
const giftMsgId = starGift?.giftMsgId;
|
||||
|
||||
const giftReceiverId = action.peerId || (message.isOutgoing ? message.chatId : global.currentUserId!);
|
||||
|
||||
const inputGift: ApiInputSavedStarGift = (() => {
|
||||
if (giftMsgId) {
|
||||
return { type: 'user', messageId: giftMsgId };
|
||||
}
|
||||
if (action.savedId) {
|
||||
return { type: 'chat', chatId, savedId: action.savedId };
|
||||
}
|
||||
return { type: 'user', messageId };
|
||||
})();
|
||||
|
||||
const fromId = action.fromId || (message.isOutgoing ? global.currentUserId! : message.chatId);
|
||||
|
||||
const gift: ApiSavedStarGift = {
|
||||
date: message.date,
|
||||
gift: action.gift,
|
||||
message: starGift?.message,
|
||||
starsToConvert: starGift?.starsToConvert,
|
||||
isNameHidden: starGift?.isNameHidden,
|
||||
isUnsaved: !action.isSaved,
|
||||
fromId,
|
||||
messageId: message.id,
|
||||
isConverted: starGift?.isConverted,
|
||||
upgradeMsgId: starGift?.upgradeMsgId,
|
||||
canUpgrade: starGift?.canUpgrade,
|
||||
alreadyPaidUpgradeStars: starGift?.alreadyPaidUpgradeStars,
|
||||
inputGift,
|
||||
canExportAt: uniqueGift?.canExportAt,
|
||||
savedId: action.savedId,
|
||||
transferStars: uniqueGift?.transferStars,
|
||||
dropOriginalDetailsStars: uniqueGift?.dropOriginalDetailsStars,
|
||||
prepaidUpgradeHash: starGift?.prepaidUpgradeHash,
|
||||
};
|
||||
|
||||
actions.openGiftInfoModal({ peerId: giftReceiverId, gift, tabId });
|
||||
});
|
||||
|
||||
addActionHandler('openGiftInfoModal', (global, actions, payload): ActionReturnType => {
|
||||
const {
|
||||
gift, tabId = getCurrentTabId(),
|
||||
gift, craftSlotIndex, tabId = getCurrentTabId(),
|
||||
} = payload;
|
||||
|
||||
const peerId = 'peerId' in payload ? payload.peerId : undefined;
|
||||
@ -321,6 +265,7 @@ addActionHandler('openGiftInfoModal', (global, actions, payload): ActionReturnTy
|
||||
peerId,
|
||||
gift,
|
||||
recipientId,
|
||||
craftSlotIndex,
|
||||
},
|
||||
}, tabId);
|
||||
});
|
||||
@ -398,22 +343,6 @@ addActionHandler('closeResaleGiftsMarket', (global, actions, payload): ActionRet
|
||||
return global;
|
||||
});
|
||||
|
||||
addActionHandler('openGiftInfoValueModal', async (global, actions, payload): Promise<void> => {
|
||||
const { gift, tabId = getCurrentTabId() } = payload;
|
||||
|
||||
const result = await callApi('fetchUniqueStarGiftValueInfo', { slug: gift.slug });
|
||||
if (!result) return;
|
||||
|
||||
global = getGlobal();
|
||||
global = updateTabState(global, {
|
||||
giftInfoValueModal: {
|
||||
valueInfo: result,
|
||||
gift,
|
||||
},
|
||||
}, tabId);
|
||||
setGlobal(global);
|
||||
});
|
||||
|
||||
addTabStateResetterAction('closeGiftInfoModal', 'giftInfoModal');
|
||||
|
||||
addTabStateResetterAction('closeGiftInfoValueModal', 'giftInfoValueModal');
|
||||
@ -422,6 +351,95 @@ addTabStateResetterAction('closeGiftResalePriceComposerModal', 'giftResalePriceC
|
||||
|
||||
addTabStateResetterAction('closeGiftUpgradeModal', 'giftUpgradeModal');
|
||||
|
||||
addTabStateResetterAction('closeGiftCraftModal', 'giftCraftModal');
|
||||
|
||||
addTabStateResetterAction('closeGiftCraftSelectModal', 'giftCraftSelectModal');
|
||||
|
||||
addActionHandler('openGiftCraftInfoModal', (global, _actions, payload): ActionReturnType => {
|
||||
const { gift, tabId = getCurrentTabId() } = payload;
|
||||
|
||||
return updateTabState(global, {
|
||||
giftCraftInfoModal: { gift },
|
||||
}, tabId);
|
||||
});
|
||||
|
||||
addTabStateResetterAction('closeGiftCraftInfoModal', 'giftCraftInfoModal');
|
||||
|
||||
addActionHandler('selectGiftForCraft', (global, _actions, payload): ActionReturnType => {
|
||||
const { gift, slotIndex, tabId = getCurrentTabId() } = payload;
|
||||
|
||||
const tabState = selectTabState(global, tabId);
|
||||
const modal = tabState.giftCraftModal;
|
||||
|
||||
if (!modal) return undefined;
|
||||
|
||||
const slots = [modal.gift1, modal.gift2, modal.gift3, modal.gift4];
|
||||
slots[slotIndex] = gift;
|
||||
|
||||
return updateTabState(global, {
|
||||
giftCraftModal: {
|
||||
...modal,
|
||||
gift1: slots[0],
|
||||
gift2: slots[1],
|
||||
gift3: slots[2],
|
||||
gift4: slots[3],
|
||||
},
|
||||
giftCraftSelectModal: gift ? undefined : tabState.giftCraftSelectModal,
|
||||
}, tabId);
|
||||
});
|
||||
|
||||
addActionHandler('selectPurchasedGiftForCraft', (global, actions, payload): ActionReturnType => {
|
||||
const { giftId, slotIndex, tabId = getCurrentTabId() } = payload;
|
||||
|
||||
const tabState = selectTabState(global, tabId);
|
||||
const craftModal = tabState.giftCraftModal;
|
||||
const giftInfoModal = tabState.giftInfoModal;
|
||||
if (!craftModal) return undefined;
|
||||
|
||||
const giftFromModal = giftInfoModal?.gift;
|
||||
const actualGift = giftFromModal && 'gift' in giftFromModal ? giftFromModal.gift : giftFromModal;
|
||||
|
||||
if (!actualGift || actualGift.type !== 'starGiftUnique' || actualGift.id !== giftId) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const shortSavedGift = buildShortSavedGift(actualGift, global.currentUserId);
|
||||
|
||||
const slots = [craftModal.gift1, craftModal.gift2, craftModal.gift3, craftModal.gift4];
|
||||
slots[slotIndex] = shortSavedGift;
|
||||
|
||||
global = updateTabState(global, {
|
||||
giftCraftModal: {
|
||||
...craftModal,
|
||||
gift1: slots[0],
|
||||
gift2: slots[1],
|
||||
gift3: slots[2],
|
||||
gift4: slots[3],
|
||||
shouldRefreshMyCraftableGifts: true,
|
||||
},
|
||||
}, tabId);
|
||||
|
||||
actions.closeGiftCraftSelectModal({ tabId });
|
||||
|
||||
return global;
|
||||
});
|
||||
|
||||
addActionHandler('resetGiftCraftResult', (global, _actions, payload): ActionReturnType => {
|
||||
const { tabId = getCurrentTabId() } = payload || {};
|
||||
const modal = selectTabState(global, tabId).giftCraftModal;
|
||||
if (!modal) return undefined;
|
||||
|
||||
return updateTabState(global, {
|
||||
giftCraftModal: {
|
||||
...modal,
|
||||
craftResult: undefined,
|
||||
gift1: undefined,
|
||||
gift2: undefined,
|
||||
gift3: undefined,
|
||||
gift4: undefined,
|
||||
},
|
||||
}, tabId);
|
||||
});
|
||||
addTabStateResetterAction('closeGiftPreviewModal', 'giftPreviewModal');
|
||||
|
||||
addActionHandler('closeGiftAuctionModal', (global, _actions, payload): ActionReturnType => {
|
||||
@ -476,30 +494,6 @@ addActionHandler('closeGiftAuctionInfoModal', (global, _actions, payload): Actio
|
||||
return global;
|
||||
});
|
||||
|
||||
addActionHandler('openAboutStarGiftModal', async (global, _actions, payload): Promise<void> => {
|
||||
const { tabId = getCurrentTabId() } = payload || {};
|
||||
|
||||
const result = await callApi('fetchPremiumPromo');
|
||||
|
||||
let videoId: string | undefined;
|
||||
let videoThumbnail;
|
||||
|
||||
if (result?.promo) {
|
||||
const giftsIndex = result.promo.videoSections.indexOf('gifts');
|
||||
if (giftsIndex !== -1 && giftsIndex < result.promo.videos.length) {
|
||||
const video = result.promo.videos[giftsIndex];
|
||||
videoId = video.id;
|
||||
videoThumbnail = video.thumbnail;
|
||||
}
|
||||
}
|
||||
|
||||
global = getGlobal();
|
||||
global = updateTabState(global, {
|
||||
aboutStarGiftModal: { videoId, videoThumbnail },
|
||||
}, tabId);
|
||||
setGlobal(global);
|
||||
});
|
||||
|
||||
addTabStateResetterAction('closeAboutStarGiftModal', 'aboutStarGiftModal');
|
||||
|
||||
addActionHandler('openGiftAuctionChangeRecipientModal', (global, _actions, payload): ActionReturnType => {
|
||||
|
||||
@ -2640,6 +2640,9 @@ export interface ActionPayloads {
|
||||
updateResaleGiftsFilter: {
|
||||
filter: ResaleGiftsFilterOptions;
|
||||
} & WithTabId;
|
||||
updateCraftGiftsFilter: {
|
||||
filter: ResaleGiftsFilterOptions;
|
||||
} & WithTabId;
|
||||
loadResaleGifts: {
|
||||
giftId: string;
|
||||
shouldRefresh?: boolean;
|
||||
@ -2678,8 +2681,10 @@ export interface ActionPayloads {
|
||||
peerId: string;
|
||||
recipientId?: string;
|
||||
gift: ApiSavedStarGift;
|
||||
craftSlotIndex?: number;
|
||||
} | {
|
||||
gift: ApiStarGift;
|
||||
craftSlotIndex?: number;
|
||||
}) & WithTabId;
|
||||
openLockedGiftModalInfo: {
|
||||
untilDate?: number;
|
||||
@ -2709,6 +2714,31 @@ export interface ActionPayloads {
|
||||
closeGiftUpgradeModal: WithTabId | undefined;
|
||||
shiftGiftUpgradeNextPrice: WithTabId | undefined;
|
||||
|
||||
openGiftCraftModal: {
|
||||
gift: ApiSavedStarGift;
|
||||
} & WithTabId;
|
||||
closeGiftCraftModal: WithTabId | undefined;
|
||||
resetGiftCraftResult: WithTabId | undefined;
|
||||
openGiftCraftSelectModal: {
|
||||
slotIndex: number;
|
||||
} & WithTabId;
|
||||
closeGiftCraftSelectModal: WithTabId | undefined;
|
||||
openGiftCraftInfoModal: {
|
||||
gift: ApiStarGiftUnique;
|
||||
} & WithTabId;
|
||||
closeGiftCraftInfoModal: WithTabId | undefined;
|
||||
selectGiftForCraft: {
|
||||
gift?: ApiSavedStarGift;
|
||||
slotIndex: number;
|
||||
} & WithTabId;
|
||||
selectPurchasedGiftForCraft: {
|
||||
giftId: string;
|
||||
slotIndex: number;
|
||||
} & WithTabId;
|
||||
loadMoreCraftableGifts: WithTabId | undefined;
|
||||
loadMoreMarketCraftableGifts: WithTabId | undefined;
|
||||
craftStarGift: WithTabId | undefined;
|
||||
|
||||
openStarGiftPriceDecreaseInfoModal: {
|
||||
prices: ApiStarGiftUpgradePrice[];
|
||||
currentPrice: number;
|
||||
@ -2742,6 +2772,7 @@ export interface ActionPayloads {
|
||||
closeGiftInfoValueModal: WithTabId | undefined;
|
||||
openGiftPreviewModal: {
|
||||
originGift: ApiStarGift;
|
||||
shouldShowCraftableOnStart?: boolean;
|
||||
} & WithTabId;
|
||||
closeGiftPreviewModal: WithTabId | undefined;
|
||||
loadActiveGiftAuctions: undefined;
|
||||
|
||||
@ -841,6 +841,7 @@ export type TabState = {
|
||||
peerId?: string;
|
||||
recipientId?: string;
|
||||
gift: ApiSavedStarGift | ApiStarGift;
|
||||
craftSlotIndex?: number;
|
||||
};
|
||||
|
||||
giftInfoValueModal?: {
|
||||
@ -891,6 +892,44 @@ export type TabState = {
|
||||
maxPrice?: number;
|
||||
};
|
||||
|
||||
giftCraftModal?: {
|
||||
regularGiftId?: string;
|
||||
regularGiftTitle?: string;
|
||||
gift1?: ApiSavedStarGift;
|
||||
gift2?: ApiSavedStarGift;
|
||||
gift3?: ApiSavedStarGift;
|
||||
gift4?: ApiSavedStarGift;
|
||||
previewAttributes?: ApiStarGiftAttribute[];
|
||||
myCraftableGifts?: ApiSavedStarGift[];
|
||||
myCraftableGiftsNextOffset?: string;
|
||||
shouldRefreshMyCraftableGifts?: boolean;
|
||||
marketCraftableGifts?: ApiStarGiftUnique[];
|
||||
marketCraftableGiftsNextOffset?: string;
|
||||
marketCraftableGiftsCount?: number;
|
||||
isMarketLoading?: boolean;
|
||||
marketFilter: ResaleGiftsFilterOptions;
|
||||
marketAttributes?: ApiStarGiftAttribute[];
|
||||
marketCounters?: ApiStarGiftAttributeCounter[];
|
||||
marketAttributesHash?: string;
|
||||
marketUpdateIteration: number;
|
||||
craftResult?: {
|
||||
success: true;
|
||||
gift: ApiStarGiftUnique;
|
||||
} | {
|
||||
success: false;
|
||||
isError?: true;
|
||||
};
|
||||
};
|
||||
|
||||
giftCraftSelectModal?: {
|
||||
slotIndex: number;
|
||||
isLoading?: boolean;
|
||||
};
|
||||
|
||||
giftCraftInfoModal?: {
|
||||
gift: ApiStarGiftUnique;
|
||||
};
|
||||
|
||||
giftWithdrawModal?: {
|
||||
gift: ApiSavedStarGift;
|
||||
isLoading?: boolean;
|
||||
@ -904,6 +943,7 @@ export type TabState = {
|
||||
giftPreviewModal?: {
|
||||
attributes: ApiStarGiftAttribute[];
|
||||
originGift: ApiStarGift;
|
||||
shouldShowCraftableOnStart?: boolean;
|
||||
};
|
||||
|
||||
giftAuctionModal?: {
|
||||
|
||||
@ -1905,6 +1905,8 @@ payments.getStarGiftAuctionAcquiredGifts#6ba2cbec gift_id:long = payments.StarGi
|
||||
payments.getStarGiftActiveAuctions#a5d0514d hash:long = payments.StarGiftActiveAuctions;
|
||||
payments.resolveStarGiftOffer#e9ce781c flags:# decline:flags.0?true offer_msg_id:int = Updates;
|
||||
payments.getStarGiftUpgradeAttributes#6d038b58 gift_id:long = payments.StarGiftUpgradeAttributes;
|
||||
payments.getCraftStarGifts#fd05dd00 gift_id:long offset:string limit:int = payments.SavedStarGifts;
|
||||
payments.craftStarGift#b0f9684f stargift:Vector<InputSavedStarGift> = Updates;
|
||||
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.confirmCall#2efe1722 peer:InputPhoneCall g_a:bytes key_fingerprint:long protocol:PhoneCallProtocol = phone.PhoneCall;
|
||||
|
||||
@ -359,6 +359,8 @@
|
||||
"payments.getStarGiftAuctionAcquiredGifts",
|
||||
"payments.getStarGiftActiveAuctions",
|
||||
"payments.resolveStarGiftOffer",
|
||||
"payments.getCraftStarGifts",
|
||||
"payments.craftStarGift",
|
||||
"payments.getStarGiftUpgradeAttributes",
|
||||
"langpack.getLangPack",
|
||||
"langpack.getStrings",
|
||||
|
||||
@ -16,315 +16,319 @@
|
||||
}
|
||||
|
||||
$icons-map: (
|
||||
"active-sessions": "\f101",
|
||||
"add-one-badge": "\f102",
|
||||
"add-user-filled": "\f103",
|
||||
"add-user": "\f104",
|
||||
"add": "\f105",
|
||||
"admin": "\f106",
|
||||
"allow-speak": "\f107",
|
||||
"animals": "\f108",
|
||||
"animations": "\f109",
|
||||
"archive-filled": "\f10a",
|
||||
"archive-from-main": "\f10b",
|
||||
"archive-to-main": "\f10c",
|
||||
"archive": "\f10d",
|
||||
"arrow-down-circle": "\f10e",
|
||||
"arrow-down": "\f10f",
|
||||
"arrow-left": "\f110",
|
||||
"arrow-right": "\f111",
|
||||
"ask-support": "\f112",
|
||||
"attach": "\f113",
|
||||
"auction-drop": "\f114",
|
||||
"auction-filled": "\f115",
|
||||
"auction-next-round": "\f116",
|
||||
"auction": "\f117",
|
||||
"author-hidden": "\f118",
|
||||
"avatar-archived-chats": "\f119",
|
||||
"avatar-deleted-account": "\f11a",
|
||||
"avatar-saved-messages": "\f11b",
|
||||
"bold": "\f11c",
|
||||
"boost-outline": "\f11d",
|
||||
"boost": "\f11e",
|
||||
"boostcircle": "\f11f",
|
||||
"boosts": "\f120",
|
||||
"bot-command": "\f121",
|
||||
"bot-commands-filled": "\f122",
|
||||
"bots": "\f123",
|
||||
"bug": "\f124",
|
||||
"calendar-filter": "\f125",
|
||||
"calendar": "\f126",
|
||||
"camera-add": "\f127",
|
||||
"camera": "\f128",
|
||||
"car": "\f129",
|
||||
"card": "\f12a",
|
||||
"cash-circle": "\f12b",
|
||||
"channel-filled": "\f12c",
|
||||
"channel": "\f12d",
|
||||
"channelviews": "\f12e",
|
||||
"chat-badge": "\f12f",
|
||||
"chats-badge": "\f130",
|
||||
"check": "\f131",
|
||||
"clock-edit": "\f132",
|
||||
"clock": "\f133",
|
||||
"close-circle": "\f134",
|
||||
"close-topic": "\f135",
|
||||
"close": "\f136",
|
||||
"closed-gift": "\f137",
|
||||
"cloud-download": "\f138",
|
||||
"collapse-modal": "\f139",
|
||||
"collapse": "\f13a",
|
||||
"colorize": "\f13b",
|
||||
"comments-sticker": "\f13c",
|
||||
"comments": "\f13d",
|
||||
"copy-media": "\f13e",
|
||||
"copy": "\f13f",
|
||||
"crown-take-off-outline": "\f140",
|
||||
"crown-take-off": "\f141",
|
||||
"crown-wear-outline": "\f142",
|
||||
"crown-wear": "\f143",
|
||||
"darkmode": "\f144",
|
||||
"data": "\f145",
|
||||
"delete-filled": "\f146",
|
||||
"delete-left": "\f147",
|
||||
"delete-user": "\f148",
|
||||
"delete": "\f149",
|
||||
"diamond": "\f14a",
|
||||
"document": "\f14b",
|
||||
"double-badge": "\f14c",
|
||||
"down": "\f14d",
|
||||
"download": "\f14e",
|
||||
"dropdown-arrows": "\f14f",
|
||||
"eats": "\f150",
|
||||
"edit": "\f151",
|
||||
"email": "\f152",
|
||||
"enter": "\f153",
|
||||
"expand-modal": "\f154",
|
||||
"expand": "\f155",
|
||||
"eye-crossed-outline": "\f156",
|
||||
"eye-crossed": "\f157",
|
||||
"eye-outline": "\f158",
|
||||
"eye": "\f159",
|
||||
"favorite-filled": "\f15a",
|
||||
"favorite": "\f15b",
|
||||
"file-badge": "\f15c",
|
||||
"flag": "\f15d",
|
||||
"folder-badge": "\f15e",
|
||||
"folder-tabs-bot": "\f15f",
|
||||
"folder-tabs-channel": "\f160",
|
||||
"folder-tabs-chat": "\f161",
|
||||
"folder-tabs-chats": "\f162",
|
||||
"folder-tabs-folder": "\f163",
|
||||
"folder-tabs-group": "\f164",
|
||||
"folder-tabs-star": "\f165",
|
||||
"folder-tabs-user": "\f166",
|
||||
"folder": "\f167",
|
||||
"fontsize": "\f168",
|
||||
"forums": "\f169",
|
||||
"forward": "\f16a",
|
||||
"fragment": "\f16b",
|
||||
"frozen-time": "\f16c",
|
||||
"fullscreen": "\f16d",
|
||||
"gifs": "\f16e",
|
||||
"gift-transfer-inline": "\f16f",
|
||||
"gift": "\f170",
|
||||
"group-filled": "\f171",
|
||||
"group": "\f172",
|
||||
"grouped-disable": "\f173",
|
||||
"grouped": "\f174",
|
||||
"hand-stop": "\f175",
|
||||
"hashtag": "\f176",
|
||||
"hd-photo": "\f177",
|
||||
"heart-outline": "\f178",
|
||||
"heart": "\f179",
|
||||
"help": "\f17a",
|
||||
"info-filled": "\f17b",
|
||||
"info": "\f17c",
|
||||
"install": "\f17d",
|
||||
"italic": "\f17e",
|
||||
"key": "\f17f",
|
||||
"keyboard": "\f180",
|
||||
"lamp": "\f181",
|
||||
"language": "\f182",
|
||||
"large-pause": "\f183",
|
||||
"large-play": "\f184",
|
||||
"link-badge": "\f185",
|
||||
"link-broken": "\f186",
|
||||
"link": "\f187",
|
||||
"location": "\f188",
|
||||
"lock-badge": "\f189",
|
||||
"lock": "\f18a",
|
||||
"logout": "\f18b",
|
||||
"loop": "\f18c",
|
||||
"mention": "\f18d",
|
||||
"menu": "\f18e",
|
||||
"message-failed": "\f18f",
|
||||
"message-pending": "\f190",
|
||||
"message-read": "\f191",
|
||||
"message-succeeded": "\f192",
|
||||
"message": "\f193",
|
||||
"microphone-alt": "\f194",
|
||||
"microphone": "\f195",
|
||||
"monospace": "\f196",
|
||||
"more-circle": "\f197",
|
||||
"more": "\f198",
|
||||
"move-caption-down": "\f199",
|
||||
"move-caption-up": "\f19a",
|
||||
"mute": "\f19b",
|
||||
"muted": "\f19c",
|
||||
"my-notes": "\f19d",
|
||||
"new-chat-filled": "\f19e",
|
||||
"next-link": "\f19f",
|
||||
"next": "\f1a0",
|
||||
"nochannel": "\f1a1",
|
||||
"noise-suppression": "\f1a2",
|
||||
"non-contacts": "\f1a3",
|
||||
"note": "\f1a4",
|
||||
"one-filled": "\f1a5",
|
||||
"open-in-new-tab": "\f1a6",
|
||||
"password-off": "\f1a7",
|
||||
"pause": "\f1a8",
|
||||
"permissions": "\f1a9",
|
||||
"phone-discard-outline": "\f1aa",
|
||||
"phone-discard": "\f1ab",
|
||||
"phone": "\f1ac",
|
||||
"photo": "\f1ad",
|
||||
"pin-badge": "\f1ae",
|
||||
"pin-list": "\f1af",
|
||||
"pin": "\f1b0",
|
||||
"pinned-chat": "\f1b1",
|
||||
"pinned-message": "\f1b2",
|
||||
"pip": "\f1b3",
|
||||
"play-story": "\f1b4",
|
||||
"play": "\f1b5",
|
||||
"poll": "\f1b6",
|
||||
"previous": "\f1b7",
|
||||
"privacy-policy": "\f1b8",
|
||||
"proof-of-ownership": "\f1b9",
|
||||
"quote-text": "\f1ba",
|
||||
"quote": "\f1bb",
|
||||
"radial-badge": "\f1bc",
|
||||
"rating-icons-level1": "\f1bd",
|
||||
"rating-icons-level10": "\f1be",
|
||||
"rating-icons-level2": "\f1bf",
|
||||
"rating-icons-level20": "\f1c0",
|
||||
"rating-icons-level3": "\f1c1",
|
||||
"rating-icons-level30": "\f1c2",
|
||||
"rating-icons-level4": "\f1c3",
|
||||
"rating-icons-level40": "\f1c4",
|
||||
"rating-icons-level5": "\f1c5",
|
||||
"rating-icons-level50": "\f1c6",
|
||||
"rating-icons-level6": "\f1c7",
|
||||
"rating-icons-level60": "\f1c8",
|
||||
"rating-icons-level7": "\f1c9",
|
||||
"rating-icons-level70": "\f1ca",
|
||||
"rating-icons-level8": "\f1cb",
|
||||
"rating-icons-level80": "\f1cc",
|
||||
"rating-icons-level9": "\f1cd",
|
||||
"rating-icons-level90": "\f1ce",
|
||||
"rating-icons-negative": "\f1cf",
|
||||
"readchats": "\f1d0",
|
||||
"recent": "\f1d1",
|
||||
"refund": "\f1d2",
|
||||
"reload": "\f1d3",
|
||||
"remove-quote": "\f1d4",
|
||||
"remove": "\f1d5",
|
||||
"reopen-topic": "\f1d6",
|
||||
"reorder-tabs": "\f1d7",
|
||||
"replace": "\f1d8",
|
||||
"replies": "\f1d9",
|
||||
"reply-filled": "\f1da",
|
||||
"reply": "\f1db",
|
||||
"revenue-split": "\f1dc",
|
||||
"revote": "\f1dd",
|
||||
"save-story": "\f1de",
|
||||
"saved-messages": "\f1df",
|
||||
"schedule": "\f1e0",
|
||||
"scheduled": "\f1e1",
|
||||
"sd-photo": "\f1e2",
|
||||
"search": "\f1e3",
|
||||
"select": "\f1e4",
|
||||
"sell-outline": "\f1e5",
|
||||
"sell": "\f1e6",
|
||||
"send-outline": "\f1e7",
|
||||
"send": "\f1e8",
|
||||
"settings-filled": "\f1e9",
|
||||
"settings": "\f1ea",
|
||||
"share-filled": "\f1eb",
|
||||
"share-screen-outlined": "\f1ec",
|
||||
"share-screen-stop": "\f1ed",
|
||||
"share-screen": "\f1ee",
|
||||
"show-message": "\f1ef",
|
||||
"sidebar": "\f1f0",
|
||||
"skip-next": "\f1f1",
|
||||
"skip-previous": "\f1f2",
|
||||
"smallscreen": "\f1f3",
|
||||
"smile": "\f1f4",
|
||||
"sort-by-date": "\f1f5",
|
||||
"sort-by-number": "\f1f6",
|
||||
"sort-by-price": "\f1f7",
|
||||
"sort": "\f1f8",
|
||||
"speaker-muted-story": "\f1f9",
|
||||
"speaker-outline": "\f1fa",
|
||||
"speaker-story": "\f1fb",
|
||||
"speaker": "\f1fc",
|
||||
"spoiler-disable": "\f1fd",
|
||||
"spoiler": "\f1fe",
|
||||
"sport": "\f1ff",
|
||||
"star": "\f200",
|
||||
"stars-lock": "\f201",
|
||||
"stars-refund": "\f202",
|
||||
"stats": "\f203",
|
||||
"stealth-future": "\f204",
|
||||
"stealth-past": "\f205",
|
||||
"stickers": "\f206",
|
||||
"stop-raising-hand": "\f207",
|
||||
"stop": "\f208",
|
||||
"story-caption": "\f209",
|
||||
"story-expired": "\f20a",
|
||||
"story-priority": "\f20b",
|
||||
"story-reply": "\f20c",
|
||||
"strikethrough": "\f20d",
|
||||
"tag-add": "\f20e",
|
||||
"tag-crossed": "\f20f",
|
||||
"tag-filter": "\f210",
|
||||
"tag-name": "\f211",
|
||||
"tag": "\f212",
|
||||
"timer": "\f213",
|
||||
"toncoin": "\f214",
|
||||
"tools": "\f215",
|
||||
"topic-new": "\f216",
|
||||
"trade": "\f217",
|
||||
"transcribe": "\f218",
|
||||
"truck": "\f219",
|
||||
"unarchive": "\f21a",
|
||||
"underlined": "\f21b",
|
||||
"understood": "\f21c",
|
||||
"unique-profile": "\f21d",
|
||||
"unlist-outline": "\f21e",
|
||||
"unlist": "\f21f",
|
||||
"unlock-badge": "\f220",
|
||||
"unlock": "\f221",
|
||||
"unmute": "\f222",
|
||||
"unpin": "\f223",
|
||||
"unread": "\f224",
|
||||
"up": "\f225",
|
||||
"user-filled": "\f226",
|
||||
"user-online": "\f227",
|
||||
"user-stars": "\f228",
|
||||
"user": "\f229",
|
||||
"video-outlined": "\f22a",
|
||||
"video-stop": "\f22b",
|
||||
"video": "\f22c",
|
||||
"view-once": "\f22d",
|
||||
"voice-chat": "\f22e",
|
||||
"volume-1": "\f22f",
|
||||
"volume-2": "\f230",
|
||||
"volume-3": "\f231",
|
||||
"warning": "\f232",
|
||||
"web": "\f233",
|
||||
"webapp": "\f234",
|
||||
"word-wrap": "\f235",
|
||||
"zoom-in": "\f236",
|
||||
"zoom-out": "\f237",
|
||||
"zoom-out": "\f101",
|
||||
"zoom-in": "\f102",
|
||||
"word-wrap": "\f103",
|
||||
"webapp": "\f104",
|
||||
"web": "\f105",
|
||||
"warning": "\f106",
|
||||
"volume-3": "\f107",
|
||||
"volume-2": "\f108",
|
||||
"volume-1": "\f109",
|
||||
"voice-chat": "\f10a",
|
||||
"view-once": "\f10b",
|
||||
"video": "\f10c",
|
||||
"video-stop": "\f10d",
|
||||
"video-outlined": "\f10e",
|
||||
"user": "\f10f",
|
||||
"user-stars": "\f110",
|
||||
"user-online": "\f111",
|
||||
"user-filled": "\f112",
|
||||
"up": "\f113",
|
||||
"unread": "\f114",
|
||||
"unpin": "\f115",
|
||||
"unmute": "\f116",
|
||||
"unlock": "\f117",
|
||||
"unlock-badge": "\f118",
|
||||
"unlist": "\f119",
|
||||
"unlist-outline": "\f11a",
|
||||
"unique-profile": "\f11b",
|
||||
"understood": "\f11c",
|
||||
"underlined": "\f11d",
|
||||
"unarchive": "\f11e",
|
||||
"truck": "\f11f",
|
||||
"transcribe": "\f120",
|
||||
"trade": "\f121",
|
||||
"topic-new": "\f122",
|
||||
"tools": "\f123",
|
||||
"toncoin": "\f124",
|
||||
"timer": "\f125",
|
||||
"tag": "\f126",
|
||||
"tag-name": "\f127",
|
||||
"tag-filter": "\f128",
|
||||
"tag-crossed": "\f129",
|
||||
"tag-add": "\f12a",
|
||||
"strikethrough": "\f12b",
|
||||
"story-reply": "\f12c",
|
||||
"story-priority": "\f12d",
|
||||
"story-expired": "\f12e",
|
||||
"story-caption": "\f12f",
|
||||
"stop": "\f130",
|
||||
"stop-raising-hand": "\f131",
|
||||
"stickers": "\f132",
|
||||
"stealth-past": "\f133",
|
||||
"stealth-future": "\f134",
|
||||
"stats": "\f135",
|
||||
"stars-refund": "\f136",
|
||||
"stars-lock": "\f137",
|
||||
"star": "\f138",
|
||||
"sport": "\f139",
|
||||
"spoiler": "\f13a",
|
||||
"spoiler-disable": "\f13b",
|
||||
"speaker": "\f13c",
|
||||
"speaker-story": "\f13d",
|
||||
"speaker-outline": "\f13e",
|
||||
"speaker-muted-story": "\f13f",
|
||||
"sort": "\f140",
|
||||
"sort-by-price": "\f141",
|
||||
"sort-by-number": "\f142",
|
||||
"sort-by-date": "\f143",
|
||||
"smile": "\f144",
|
||||
"smallscreen": "\f145",
|
||||
"skip-previous": "\f146",
|
||||
"skip-next": "\f147",
|
||||
"sidebar": "\f148",
|
||||
"show-message": "\f149",
|
||||
"share-screen": "\f14a",
|
||||
"share-screen-stop": "\f14b",
|
||||
"share-screen-outlined": "\f14c",
|
||||
"share-filled": "\f14d",
|
||||
"settings": "\f14e",
|
||||
"settings-filled": "\f14f",
|
||||
"send": "\f150",
|
||||
"send-outline": "\f151",
|
||||
"sell": "\f152",
|
||||
"sell-outline": "\f153",
|
||||
"select": "\f154",
|
||||
"search": "\f155",
|
||||
"sd-photo": "\f156",
|
||||
"scheduled": "\f157",
|
||||
"schedule": "\f158",
|
||||
"saved-messages": "\f159",
|
||||
"save-story": "\f15a",
|
||||
"revote": "\f15b",
|
||||
"revenue-split": "\f15c",
|
||||
"reply": "\f15d",
|
||||
"reply-filled": "\f15e",
|
||||
"replies": "\f15f",
|
||||
"replace": "\f160",
|
||||
"reorder-tabs": "\f161",
|
||||
"reopen-topic": "\f162",
|
||||
"remove": "\f163",
|
||||
"remove-quote": "\f164",
|
||||
"reload": "\f165",
|
||||
"refund": "\f166",
|
||||
"recent": "\f167",
|
||||
"readchats": "\f168",
|
||||
"radial-badge": "\f169",
|
||||
"quote": "\f16a",
|
||||
"quote-text": "\f16b",
|
||||
"proof-of-ownership": "\f16c",
|
||||
"privacy-policy": "\f16d",
|
||||
"previous": "\f16e",
|
||||
"poll": "\f16f",
|
||||
"play": "\f170",
|
||||
"play-story": "\f171",
|
||||
"pip": "\f172",
|
||||
"pinned-message": "\f173",
|
||||
"pinned-chat": "\f174",
|
||||
"pin": "\f175",
|
||||
"pin-list": "\f176",
|
||||
"pin-badge": "\f177",
|
||||
"photo": "\f178",
|
||||
"phone": "\f179",
|
||||
"phone-discard": "\f17a",
|
||||
"phone-discard-outline": "\f17b",
|
||||
"permissions": "\f17c",
|
||||
"pause": "\f17d",
|
||||
"password-off": "\f17e",
|
||||
"open-in-new-tab": "\f17f",
|
||||
"one-filled": "\f180",
|
||||
"note": "\f181",
|
||||
"non-contacts": "\f182",
|
||||
"noise-suppression": "\f183",
|
||||
"nochannel": "\f184",
|
||||
"next": "\f185",
|
||||
"next-link": "\f186",
|
||||
"new-chat-filled": "\f187",
|
||||
"my-notes": "\f188",
|
||||
"muted": "\f189",
|
||||
"mute": "\f18a",
|
||||
"move-caption-up": "\f18b",
|
||||
"move-caption-down": "\f18c",
|
||||
"more": "\f18d",
|
||||
"more-circle": "\f18e",
|
||||
"monospace": "\f18f",
|
||||
"microphone": "\f190",
|
||||
"microphone-alt": "\f191",
|
||||
"message": "\f192",
|
||||
"message-succeeded": "\f193",
|
||||
"message-read": "\f194",
|
||||
"message-pending": "\f195",
|
||||
"message-failed": "\f196",
|
||||
"menu": "\f197",
|
||||
"mention": "\f198",
|
||||
"loop": "\f199",
|
||||
"logout": "\f19a",
|
||||
"lock": "\f19b",
|
||||
"lock-badge": "\f19c",
|
||||
"location": "\f19d",
|
||||
"link": "\f19e",
|
||||
"link-broken": "\f19f",
|
||||
"link-badge": "\f1a0",
|
||||
"large-play": "\f1a1",
|
||||
"large-pause": "\f1a2",
|
||||
"language": "\f1a3",
|
||||
"lamp": "\f1a4",
|
||||
"keyboard": "\f1a5",
|
||||
"key": "\f1a6",
|
||||
"italic": "\f1a7",
|
||||
"install": "\f1a8",
|
||||
"info": "\f1a9",
|
||||
"info-filled": "\f1aa",
|
||||
"help": "\f1ab",
|
||||
"heart": "\f1ac",
|
||||
"heart-outline": "\f1ad",
|
||||
"hd-photo": "\f1ae",
|
||||
"hashtag": "\f1af",
|
||||
"hand-stop": "\f1b0",
|
||||
"grouped": "\f1b1",
|
||||
"grouped-disable": "\f1b2",
|
||||
"group": "\f1b3",
|
||||
"group-filled": "\f1b4",
|
||||
"gift": "\f1b5",
|
||||
"gift-transfer-inline": "\f1b6",
|
||||
"gifs": "\f1b7",
|
||||
"fullscreen": "\f1b8",
|
||||
"frozen-time": "\f1b9",
|
||||
"fragment": "\f1ba",
|
||||
"forward": "\f1bb",
|
||||
"forums": "\f1bc",
|
||||
"fontsize": "\f1bd",
|
||||
"folder": "\f1be",
|
||||
"folder-badge": "\f1bf",
|
||||
"flag": "\f1c0",
|
||||
"file-badge": "\f1c1",
|
||||
"favorite": "\f1c2",
|
||||
"favorite-filled": "\f1c3",
|
||||
"eye": "\f1c4",
|
||||
"eye-outline": "\f1c5",
|
||||
"eye-crossed": "\f1c6",
|
||||
"eye-crossed-outline": "\f1c7",
|
||||
"expand": "\f1c8",
|
||||
"expand-modal": "\f1c9",
|
||||
"enter": "\f1ca",
|
||||
"email": "\f1cb",
|
||||
"edit": "\f1cc",
|
||||
"eats": "\f1cd",
|
||||
"dropdown-arrows": "\f1ce",
|
||||
"download": "\f1cf",
|
||||
"down": "\f1d0",
|
||||
"double-badge": "\f1d1",
|
||||
"document": "\f1d2",
|
||||
"diamond": "\f1d3",
|
||||
"delete": "\f1d4",
|
||||
"delete-user": "\f1d5",
|
||||
"delete-left": "\f1d6",
|
||||
"delete-filled": "\f1d7",
|
||||
"data": "\f1d8",
|
||||
"darkmode": "\f1d9",
|
||||
"crown-wear": "\f1da",
|
||||
"crown-wear-outline": "\f1db",
|
||||
"crown-take-off": "\f1dc",
|
||||
"crown-take-off-outline": "\f1dd",
|
||||
"craft": "\f1de",
|
||||
"copy": "\f1df",
|
||||
"copy-media": "\f1e0",
|
||||
"comments": "\f1e1",
|
||||
"comments-sticker": "\f1e2",
|
||||
"combine-craft": "\f1e3",
|
||||
"colorize": "\f1e4",
|
||||
"collapse": "\f1e5",
|
||||
"collapse-modal": "\f1e6",
|
||||
"cloud-download": "\f1e7",
|
||||
"closed-gift": "\f1e8",
|
||||
"close": "\f1e9",
|
||||
"close-topic": "\f1ea",
|
||||
"close-circle": "\f1eb",
|
||||
"clock": "\f1ec",
|
||||
"clock-edit": "\f1ed",
|
||||
"check": "\f1ee",
|
||||
"chats-badge": "\f1ef",
|
||||
"chat-badge": "\f1f0",
|
||||
"channelviews": "\f1f1",
|
||||
"channel": "\f1f2",
|
||||
"channel-filled": "\f1f3",
|
||||
"cash-circle": "\f1f4",
|
||||
"card": "\f1f5",
|
||||
"car": "\f1f6",
|
||||
"camera": "\f1f7",
|
||||
"camera-add": "\f1f8",
|
||||
"calendar": "\f1f9",
|
||||
"calendar-filter": "\f1fa",
|
||||
"bug": "\f1fb",
|
||||
"bots": "\f1fc",
|
||||
"bot-commands-filled": "\f1fd",
|
||||
"bot-command": "\f1fe",
|
||||
"boosts": "\f1ff",
|
||||
"boostcircle": "\f200",
|
||||
"boost": "\f201",
|
||||
"boost-outline": "\f202",
|
||||
"boost-craft-chance": "\f203",
|
||||
"bold": "\f204",
|
||||
"avatar-saved-messages": "\f205",
|
||||
"avatar-deleted-account": "\f206",
|
||||
"avatar-archived-chats": "\f207",
|
||||
"author-hidden": "\f208",
|
||||
"auction": "\f209",
|
||||
"auction-next-round": "\f20a",
|
||||
"auction-filled": "\f20b",
|
||||
"auction-drop": "\f20c",
|
||||
"attach": "\f20d",
|
||||
"ask-support": "\f20e",
|
||||
"arrow-right": "\f20f",
|
||||
"arrow-left": "\f210",
|
||||
"arrow-down": "\f211",
|
||||
"arrow-down-circle": "\f212",
|
||||
"archive": "\f213",
|
||||
"archive-to-main": "\f214",
|
||||
"archive-from-main": "\f215",
|
||||
"archive-filled": "\f216",
|
||||
"animations": "\f217",
|
||||
"animals": "\f218",
|
||||
"allow-speak": "\f219",
|
||||
"admin": "\f21a",
|
||||
"add": "\f21b",
|
||||
"add-user": "\f21c",
|
||||
"add-user-filled": "\f21d",
|
||||
"add-one-badge": "\f21e",
|
||||
"add-filled": "\f21f",
|
||||
"active-sessions": "\f220",
|
||||
"rating-icons-negative": "\f221",
|
||||
"rating-icons-level90": "\f222",
|
||||
"rating-icons-level9": "\f223",
|
||||
"rating-icons-level80": "\f224",
|
||||
"rating-icons-level8": "\f225",
|
||||
"rating-icons-level70": "\f226",
|
||||
"rating-icons-level7": "\f227",
|
||||
"rating-icons-level60": "\f228",
|
||||
"rating-icons-level6": "\f229",
|
||||
"rating-icons-level50": "\f22a",
|
||||
"rating-icons-level5": "\f22b",
|
||||
"rating-icons-level40": "\f22c",
|
||||
"rating-icons-level4": "\f22d",
|
||||
"rating-icons-level30": "\f22e",
|
||||
"rating-icons-level3": "\f22f",
|
||||
"rating-icons-level20": "\f230",
|
||||
"rating-icons-level2": "\f231",
|
||||
"rating-icons-level10": "\f232",
|
||||
"rating-icons-level1": "\f233",
|
||||
"folder-tabs-user": "\f234",
|
||||
"folder-tabs-star": "\f235",
|
||||
"folder-tabs-group": "\f236",
|
||||
"folder-tabs-folder": "\f237",
|
||||
"folder-tabs-chats": "\f238",
|
||||
"folder-tabs-chat": "\f239",
|
||||
"folder-tabs-channel": "\f23a",
|
||||
"folder-tabs-bot": "\f23b",
|
||||
);
|
||||
|
||||
@ -1,312 +1,316 @@
|
||||
export type FontIconName =
|
||||
| 'active-sessions'
|
||||
| 'add-one-badge'
|
||||
| 'add-user-filled'
|
||||
| 'add-user'
|
||||
| 'add'
|
||||
| 'admin'
|
||||
| 'allow-speak'
|
||||
| 'animals'
|
||||
| 'animations'
|
||||
| 'archive-filled'
|
||||
| 'archive-from-main'
|
||||
| 'archive-to-main'
|
||||
| 'archive'
|
||||
| 'arrow-down-circle'
|
||||
| 'arrow-down'
|
||||
| 'arrow-left'
|
||||
| 'arrow-right'
|
||||
| 'ask-support'
|
||||
| 'attach'
|
||||
| 'auction-drop'
|
||||
| 'auction-filled'
|
||||
| 'auction-next-round'
|
||||
| 'auction'
|
||||
| 'author-hidden'
|
||||
| 'avatar-archived-chats'
|
||||
| 'avatar-deleted-account'
|
||||
| 'avatar-saved-messages'
|
||||
| 'bold'
|
||||
| 'boost-outline'
|
||||
| 'boost'
|
||||
| 'boostcircle'
|
||||
| 'boosts'
|
||||
| 'bot-command'
|
||||
| 'bot-commands-filled'
|
||||
| 'bots'
|
||||
| 'bug'
|
||||
| 'calendar-filter'
|
||||
| 'calendar'
|
||||
| 'camera-add'
|
||||
| 'camera'
|
||||
| 'car'
|
||||
| 'card'
|
||||
| 'cash-circle'
|
||||
| 'channel-filled'
|
||||
| 'channel'
|
||||
| 'channelviews'
|
||||
| 'chat-badge'
|
||||
| 'chats-badge'
|
||||
| 'check'
|
||||
| 'clock-edit'
|
||||
| 'clock'
|
||||
| 'close-circle'
|
||||
| 'close-topic'
|
||||
| 'close'
|
||||
| 'closed-gift'
|
||||
| 'cloud-download'
|
||||
| 'collapse-modal'
|
||||
| 'collapse'
|
||||
| 'colorize'
|
||||
| 'comments-sticker'
|
||||
| 'comments'
|
||||
| 'copy-media'
|
||||
| 'copy'
|
||||
| 'crown-take-off-outline'
|
||||
| 'crown-take-off'
|
||||
| 'crown-wear-outline'
|
||||
| 'crown-wear'
|
||||
| 'darkmode'
|
||||
| 'data'
|
||||
| 'delete-filled'
|
||||
| 'delete-left'
|
||||
| 'delete-user'
|
||||
| 'delete'
|
||||
| 'diamond'
|
||||
| 'document'
|
||||
| 'double-badge'
|
||||
| 'down'
|
||||
| 'download'
|
||||
| 'dropdown-arrows'
|
||||
| 'eats'
|
||||
| 'edit'
|
||||
| 'email'
|
||||
| 'enter'
|
||||
| 'expand-modal'
|
||||
| 'expand'
|
||||
| 'eye-crossed-outline'
|
||||
| 'eye-crossed'
|
||||
| 'eye-outline'
|
||||
| 'eye'
|
||||
| 'favorite-filled'
|
||||
| 'favorite'
|
||||
| 'file-badge'
|
||||
| 'flag'
|
||||
| 'folder-badge'
|
||||
| 'folder-tabs-bot'
|
||||
| 'folder-tabs-channel'
|
||||
| 'folder-tabs-chat'
|
||||
| 'folder-tabs-chats'
|
||||
| 'folder-tabs-folder'
|
||||
| 'folder-tabs-group'
|
||||
| 'folder-tabs-star'
|
||||
| 'folder-tabs-user'
|
||||
| 'folder'
|
||||
| 'fontsize'
|
||||
| 'forums'
|
||||
| 'forward'
|
||||
| 'fragment'
|
||||
| 'frozen-time'
|
||||
| 'fullscreen'
|
||||
| 'gifs'
|
||||
| 'gift-transfer-inline'
|
||||
| 'gift'
|
||||
| 'group-filled'
|
||||
| 'group'
|
||||
| 'grouped-disable'
|
||||
| 'grouped'
|
||||
| 'hand-stop'
|
||||
| 'hashtag'
|
||||
| 'hd-photo'
|
||||
| 'heart-outline'
|
||||
| 'heart'
|
||||
| 'help'
|
||||
| 'info-filled'
|
||||
| 'info'
|
||||
| 'install'
|
||||
| 'italic'
|
||||
| 'key'
|
||||
| 'keyboard'
|
||||
| 'lamp'
|
||||
| 'language'
|
||||
| 'large-pause'
|
||||
| 'large-play'
|
||||
| 'link-badge'
|
||||
| 'link-broken'
|
||||
| 'link'
|
||||
| 'location'
|
||||
| 'lock-badge'
|
||||
| 'lock'
|
||||
| 'logout'
|
||||
| 'loop'
|
||||
| 'mention'
|
||||
| 'menu'
|
||||
| 'message-failed'
|
||||
| 'message-pending'
|
||||
| 'message-read'
|
||||
| 'message-succeeded'
|
||||
| 'message'
|
||||
| 'microphone-alt'
|
||||
| 'microphone'
|
||||
| 'monospace'
|
||||
| 'more-circle'
|
||||
| 'more'
|
||||
| 'move-caption-down'
|
||||
| 'move-caption-up'
|
||||
| 'mute'
|
||||
| 'muted'
|
||||
| 'my-notes'
|
||||
| 'new-chat-filled'
|
||||
| 'next-link'
|
||||
| 'next'
|
||||
| 'nochannel'
|
||||
| 'noise-suppression'
|
||||
| 'non-contacts'
|
||||
| 'note'
|
||||
| 'one-filled'
|
||||
| 'open-in-new-tab'
|
||||
| 'password-off'
|
||||
| 'pause'
|
||||
| 'permissions'
|
||||
| 'phone-discard-outline'
|
||||
| 'phone-discard'
|
||||
| 'phone'
|
||||
| 'photo'
|
||||
| 'pin-badge'
|
||||
| 'pin-list'
|
||||
| 'pin'
|
||||
| 'pinned-chat'
|
||||
| 'pinned-message'
|
||||
| 'pip'
|
||||
| 'play-story'
|
||||
| 'play'
|
||||
| 'poll'
|
||||
| 'previous'
|
||||
| 'privacy-policy'
|
||||
| 'proof-of-ownership'
|
||||
| 'quote-text'
|
||||
| 'quote'
|
||||
| 'radial-badge'
|
||||
| 'rating-icons-level1'
|
||||
| 'rating-icons-level10'
|
||||
| 'rating-icons-level2'
|
||||
| 'rating-icons-level20'
|
||||
| 'rating-icons-level3'
|
||||
| 'rating-icons-level30'
|
||||
| 'rating-icons-level4'
|
||||
| 'rating-icons-level40'
|
||||
| 'rating-icons-level5'
|
||||
| 'rating-icons-level50'
|
||||
| 'rating-icons-level6'
|
||||
| 'rating-icons-level60'
|
||||
| 'rating-icons-level7'
|
||||
| 'rating-icons-level70'
|
||||
| 'rating-icons-level8'
|
||||
| 'rating-icons-level80'
|
||||
| 'rating-icons-level9'
|
||||
| 'rating-icons-level90'
|
||||
| 'rating-icons-negative'
|
||||
| 'readchats'
|
||||
| 'recent'
|
||||
| 'refund'
|
||||
| 'reload'
|
||||
| 'remove-quote'
|
||||
| 'remove'
|
||||
| 'reopen-topic'
|
||||
| 'reorder-tabs'
|
||||
| 'replace'
|
||||
| 'replies'
|
||||
| 'reply-filled'
|
||||
| 'reply'
|
||||
| 'revenue-split'
|
||||
| 'revote'
|
||||
| 'save-story'
|
||||
| 'saved-messages'
|
||||
| 'schedule'
|
||||
| 'scheduled'
|
||||
| 'sd-photo'
|
||||
| 'search'
|
||||
| 'select'
|
||||
| 'sell-outline'
|
||||
| 'sell'
|
||||
| 'send-outline'
|
||||
| 'send'
|
||||
| 'settings-filled'
|
||||
| 'settings'
|
||||
| 'share-filled'
|
||||
| 'share-screen-outlined'
|
||||
| 'share-screen-stop'
|
||||
| 'share-screen'
|
||||
| 'show-message'
|
||||
| 'sidebar'
|
||||
| 'skip-next'
|
||||
| 'skip-previous'
|
||||
| 'smallscreen'
|
||||
| 'smile'
|
||||
| 'sort-by-date'
|
||||
| 'sort-by-number'
|
||||
| 'sort-by-price'
|
||||
| 'sort'
|
||||
| 'speaker-muted-story'
|
||||
| 'speaker-outline'
|
||||
| 'speaker-story'
|
||||
| 'speaker'
|
||||
| 'spoiler-disable'
|
||||
| 'spoiler'
|
||||
| 'sport'
|
||||
| 'star'
|
||||
| 'stars-lock'
|
||||
| 'stars-refund'
|
||||
| 'stats'
|
||||
| 'stealth-future'
|
||||
| 'stealth-past'
|
||||
| 'stickers'
|
||||
| 'stop-raising-hand'
|
||||
| 'stop'
|
||||
| 'story-caption'
|
||||
| 'story-expired'
|
||||
| 'story-priority'
|
||||
| 'story-reply'
|
||||
| 'strikethrough'
|
||||
| 'tag-add'
|
||||
| 'tag-crossed'
|
||||
| 'tag-filter'
|
||||
| 'tag-name'
|
||||
| 'tag'
|
||||
| 'timer'
|
||||
| 'toncoin'
|
||||
| 'tools'
|
||||
| 'topic-new'
|
||||
| 'trade'
|
||||
| 'transcribe'
|
||||
| 'truck'
|
||||
| 'unarchive'
|
||||
| 'underlined'
|
||||
| 'understood'
|
||||
| 'unique-profile'
|
||||
| 'unlist-outline'
|
||||
| 'unlist'
|
||||
| 'unlock-badge'
|
||||
| 'unlock'
|
||||
| 'unmute'
|
||||
| 'unpin'
|
||||
| 'unread'
|
||||
| 'up'
|
||||
| 'user-filled'
|
||||
| 'user-online'
|
||||
| 'user-stars'
|
||||
| 'user'
|
||||
| 'video-outlined'
|
||||
| 'video-stop'
|
||||
| 'video'
|
||||
| 'view-once'
|
||||
| 'voice-chat'
|
||||
| 'volume-1'
|
||||
| 'volume-2'
|
||||
| 'volume-3'
|
||||
| 'warning'
|
||||
| 'web'
|
||||
| 'webapp'
|
||||
| 'word-wrap'
|
||||
| 'zoom-out'
|
||||
| 'zoom-in'
|
||||
| 'zoom-out';
|
||||
| 'word-wrap'
|
||||
| 'webapp'
|
||||
| 'web'
|
||||
| 'warning'
|
||||
| 'volume-3'
|
||||
| 'volume-2'
|
||||
| 'volume-1'
|
||||
| 'voice-chat'
|
||||
| 'view-once'
|
||||
| 'video'
|
||||
| 'video-stop'
|
||||
| 'video-outlined'
|
||||
| 'user'
|
||||
| 'user-stars'
|
||||
| 'user-online'
|
||||
| 'user-filled'
|
||||
| 'up'
|
||||
| 'unread'
|
||||
| 'unpin'
|
||||
| 'unmute'
|
||||
| 'unlock'
|
||||
| 'unlock-badge'
|
||||
| 'unlist'
|
||||
| 'unlist-outline'
|
||||
| 'unique-profile'
|
||||
| 'understood'
|
||||
| 'underlined'
|
||||
| 'unarchive'
|
||||
| 'truck'
|
||||
| 'transcribe'
|
||||
| 'trade'
|
||||
| 'topic-new'
|
||||
| 'tools'
|
||||
| 'toncoin'
|
||||
| 'timer'
|
||||
| 'tag'
|
||||
| 'tag-name'
|
||||
| 'tag-filter'
|
||||
| 'tag-crossed'
|
||||
| 'tag-add'
|
||||
| 'strikethrough'
|
||||
| 'story-reply'
|
||||
| 'story-priority'
|
||||
| 'story-expired'
|
||||
| 'story-caption'
|
||||
| 'stop'
|
||||
| 'stop-raising-hand'
|
||||
| 'stickers'
|
||||
| 'stealth-past'
|
||||
| 'stealth-future'
|
||||
| 'stats'
|
||||
| 'stars-refund'
|
||||
| 'stars-lock'
|
||||
| 'star'
|
||||
| 'sport'
|
||||
| 'spoiler'
|
||||
| 'spoiler-disable'
|
||||
| 'speaker'
|
||||
| 'speaker-story'
|
||||
| 'speaker-outline'
|
||||
| 'speaker-muted-story'
|
||||
| 'sort'
|
||||
| 'sort-by-price'
|
||||
| 'sort-by-number'
|
||||
| 'sort-by-date'
|
||||
| 'smile'
|
||||
| 'smallscreen'
|
||||
| 'skip-previous'
|
||||
| 'skip-next'
|
||||
| 'sidebar'
|
||||
| 'show-message'
|
||||
| 'share-screen'
|
||||
| 'share-screen-stop'
|
||||
| 'share-screen-outlined'
|
||||
| 'share-filled'
|
||||
| 'settings'
|
||||
| 'settings-filled'
|
||||
| 'send'
|
||||
| 'send-outline'
|
||||
| 'sell'
|
||||
| 'sell-outline'
|
||||
| 'select'
|
||||
| 'search'
|
||||
| 'sd-photo'
|
||||
| 'scheduled'
|
||||
| 'schedule'
|
||||
| 'saved-messages'
|
||||
| 'save-story'
|
||||
| 'revote'
|
||||
| 'revenue-split'
|
||||
| 'reply'
|
||||
| 'reply-filled'
|
||||
| 'replies'
|
||||
| 'replace'
|
||||
| 'reorder-tabs'
|
||||
| 'reopen-topic'
|
||||
| 'remove'
|
||||
| 'remove-quote'
|
||||
| 'reload'
|
||||
| 'refund'
|
||||
| 'recent'
|
||||
| 'readchats'
|
||||
| 'radial-badge'
|
||||
| 'quote'
|
||||
| 'quote-text'
|
||||
| 'proof-of-ownership'
|
||||
| 'privacy-policy'
|
||||
| 'previous'
|
||||
| 'poll'
|
||||
| 'play'
|
||||
| 'play-story'
|
||||
| 'pip'
|
||||
| 'pinned-message'
|
||||
| 'pinned-chat'
|
||||
| 'pin'
|
||||
| 'pin-list'
|
||||
| 'pin-badge'
|
||||
| 'photo'
|
||||
| 'phone'
|
||||
| 'phone-discard'
|
||||
| 'phone-discard-outline'
|
||||
| 'permissions'
|
||||
| 'pause'
|
||||
| 'password-off'
|
||||
| 'open-in-new-tab'
|
||||
| 'one-filled'
|
||||
| 'note'
|
||||
| 'non-contacts'
|
||||
| 'noise-suppression'
|
||||
| 'nochannel'
|
||||
| 'next'
|
||||
| 'next-link'
|
||||
| 'new-chat-filled'
|
||||
| 'my-notes'
|
||||
| 'muted'
|
||||
| 'mute'
|
||||
| 'move-caption-up'
|
||||
| 'move-caption-down'
|
||||
| 'more'
|
||||
| 'more-circle'
|
||||
| 'monospace'
|
||||
| 'microphone'
|
||||
| 'microphone-alt'
|
||||
| 'message'
|
||||
| 'message-succeeded'
|
||||
| 'message-read'
|
||||
| 'message-pending'
|
||||
| 'message-failed'
|
||||
| 'menu'
|
||||
| 'mention'
|
||||
| 'loop'
|
||||
| 'logout'
|
||||
| 'lock'
|
||||
| 'lock-badge'
|
||||
| 'location'
|
||||
| 'link'
|
||||
| 'link-broken'
|
||||
| 'link-badge'
|
||||
| 'large-play'
|
||||
| 'large-pause'
|
||||
| 'language'
|
||||
| 'lamp'
|
||||
| 'keyboard'
|
||||
| 'key'
|
||||
| 'italic'
|
||||
| 'install'
|
||||
| 'info'
|
||||
| 'info-filled'
|
||||
| 'help'
|
||||
| 'heart'
|
||||
| 'heart-outline'
|
||||
| 'hd-photo'
|
||||
| 'hashtag'
|
||||
| 'hand-stop'
|
||||
| 'grouped'
|
||||
| 'grouped-disable'
|
||||
| 'group'
|
||||
| 'group-filled'
|
||||
| 'gift'
|
||||
| 'gift-transfer-inline'
|
||||
| 'gifs'
|
||||
| 'fullscreen'
|
||||
| 'frozen-time'
|
||||
| 'fragment'
|
||||
| 'forward'
|
||||
| 'forums'
|
||||
| 'fontsize'
|
||||
| 'folder'
|
||||
| 'folder-badge'
|
||||
| 'flag'
|
||||
| 'file-badge'
|
||||
| 'favorite'
|
||||
| 'favorite-filled'
|
||||
| 'eye'
|
||||
| 'eye-outline'
|
||||
| 'eye-crossed'
|
||||
| 'eye-crossed-outline'
|
||||
| 'expand'
|
||||
| 'expand-modal'
|
||||
| 'enter'
|
||||
| 'email'
|
||||
| 'edit'
|
||||
| 'eats'
|
||||
| 'dropdown-arrows'
|
||||
| 'download'
|
||||
| 'down'
|
||||
| 'double-badge'
|
||||
| 'document'
|
||||
| 'diamond'
|
||||
| 'delete'
|
||||
| 'delete-user'
|
||||
| 'delete-left'
|
||||
| 'delete-filled'
|
||||
| 'data'
|
||||
| 'darkmode'
|
||||
| 'crown-wear'
|
||||
| 'crown-wear-outline'
|
||||
| 'crown-take-off'
|
||||
| 'crown-take-off-outline'
|
||||
| 'craft'
|
||||
| 'copy'
|
||||
| 'copy-media'
|
||||
| 'comments'
|
||||
| 'comments-sticker'
|
||||
| 'combine-craft'
|
||||
| 'colorize'
|
||||
| 'collapse'
|
||||
| 'collapse-modal'
|
||||
| 'cloud-download'
|
||||
| 'closed-gift'
|
||||
| 'close'
|
||||
| 'close-topic'
|
||||
| 'close-circle'
|
||||
| 'clock'
|
||||
| 'clock-edit'
|
||||
| 'check'
|
||||
| 'chats-badge'
|
||||
| 'chat-badge'
|
||||
| 'channelviews'
|
||||
| 'channel'
|
||||
| 'channel-filled'
|
||||
| 'cash-circle'
|
||||
| 'card'
|
||||
| 'car'
|
||||
| 'camera'
|
||||
| 'camera-add'
|
||||
| 'calendar'
|
||||
| 'calendar-filter'
|
||||
| 'bug'
|
||||
| 'bots'
|
||||
| 'bot-commands-filled'
|
||||
| 'bot-command'
|
||||
| 'boosts'
|
||||
| 'boostcircle'
|
||||
| 'boost'
|
||||
| 'boost-outline'
|
||||
| 'boost-craft-chance'
|
||||
| 'bold'
|
||||
| 'avatar-saved-messages'
|
||||
| 'avatar-deleted-account'
|
||||
| 'avatar-archived-chats'
|
||||
| 'author-hidden'
|
||||
| 'auction'
|
||||
| 'auction-next-round'
|
||||
| 'auction-filled'
|
||||
| 'auction-drop'
|
||||
| 'attach'
|
||||
| 'ask-support'
|
||||
| 'arrow-right'
|
||||
| 'arrow-left'
|
||||
| 'arrow-down'
|
||||
| 'arrow-down-circle'
|
||||
| 'archive'
|
||||
| 'archive-to-main'
|
||||
| 'archive-from-main'
|
||||
| 'archive-filled'
|
||||
| 'animations'
|
||||
| 'animals'
|
||||
| 'allow-speak'
|
||||
| 'admin'
|
||||
| 'add'
|
||||
| 'add-user'
|
||||
| 'add-user-filled'
|
||||
| 'add-one-badge'
|
||||
| 'add-filled'
|
||||
| 'active-sessions'
|
||||
| 'rating-icons-negative'
|
||||
| 'rating-icons-level90'
|
||||
| 'rating-icons-level9'
|
||||
| 'rating-icons-level80'
|
||||
| 'rating-icons-level8'
|
||||
| 'rating-icons-level70'
|
||||
| 'rating-icons-level7'
|
||||
| 'rating-icons-level60'
|
||||
| 'rating-icons-level6'
|
||||
| 'rating-icons-level50'
|
||||
| 'rating-icons-level5'
|
||||
| 'rating-icons-level40'
|
||||
| 'rating-icons-level4'
|
||||
| 'rating-icons-level30'
|
||||
| 'rating-icons-level3'
|
||||
| 'rating-icons-level20'
|
||||
| 'rating-icons-level2'
|
||||
| 'rating-icons-level10'
|
||||
| 'rating-icons-level1'
|
||||
| 'folder-tabs-user'
|
||||
| 'folder-tabs-star'
|
||||
| 'folder-tabs-group'
|
||||
| 'folder-tabs-folder'
|
||||
| 'folder-tabs-chats'
|
||||
| 'folder-tabs-chat'
|
||||
| 'folder-tabs-channel'
|
||||
| 'folder-tabs-bot';
|
||||
|
||||
39
src/types/language.d.ts
vendored
@ -1320,6 +1320,24 @@ export interface LangPair {
|
||||
'GiftInfoUpgradeBadge': undefined;
|
||||
'GiftInfoUpgradeForFree': undefined;
|
||||
'GiftInfoUpgrade': undefined;
|
||||
'GiftInfoCraft': undefined;
|
||||
'GiftCraftTitle': undefined;
|
||||
'GiftCraftNewGift': undefined;
|
||||
'GiftCraftSelectTitle': undefined;
|
||||
'GiftCraftSelectYourGifts': undefined;
|
||||
'GiftCraftWarning': undefined;
|
||||
'GiftCraftingTitle': undefined;
|
||||
'GiftCraftFailedTitle': undefined;
|
||||
'GiftCraftInfoTitle': undefined;
|
||||
'GiftCraftInfoSubtitle': undefined;
|
||||
'GiftCraftInfoCraftTitle': undefined;
|
||||
'GiftCraftInfoCraftDescription': undefined;
|
||||
'GiftCraftInfoChanceTitle': undefined;
|
||||
'GiftCraftInfoChanceDescription': undefined;
|
||||
'GiftCraftInfoRiskTitle': undefined;
|
||||
'GiftCraftInfoRiskDescription': undefined;
|
||||
'GiftCraftHelp': undefined;
|
||||
'GiftCraftViewAll': undefined;
|
||||
'GiftInfoWithdraw': undefined;
|
||||
'GiftInfoWear': undefined;
|
||||
'GiftInfoTakeOff': undefined;
|
||||
@ -2396,6 +2414,9 @@ export interface LangPairWithVariables<V = LangVariable> {
|
||||
'GiftSavedNumber': {
|
||||
'number': V;
|
||||
};
|
||||
'GiftInfoModelCrafted': {
|
||||
'model': V;
|
||||
};
|
||||
'GiftInfoPeerOriginalInfo': {
|
||||
'peer': V;
|
||||
'date': V;
|
||||
@ -2416,6 +2437,18 @@ export interface LangPairWithVariables<V = LangVariable> {
|
||||
'date': V;
|
||||
'text': V;
|
||||
};
|
||||
'GiftCraftButton': {
|
||||
'giftName': V;
|
||||
};
|
||||
'GiftCraftSuccessChance': {
|
||||
'percent': V;
|
||||
};
|
||||
'GiftCraftEmptyHint': {
|
||||
'button': V;
|
||||
};
|
||||
'GiftCraftDescription': {
|
||||
'giftLine': V;
|
||||
};
|
||||
'GiftTransferTONBlocked': {
|
||||
'time': V;
|
||||
};
|
||||
@ -3665,6 +3698,12 @@ export interface LangPairPluralWithVariables<V = LangVariable> {
|
||||
'count': V;
|
||||
'total': V;
|
||||
};
|
||||
'GiftCraftSelectMarketGifts': {
|
||||
'count': V;
|
||||
};
|
||||
'GiftCraftFailedDescription': {
|
||||
'count': V;
|
||||
};
|
||||
'GiftWithdrawWait': {
|
||||
'days': V;
|
||||
};
|
||||
|
||||
@ -31,3 +31,5 @@ export const VTT_PROFILE_BUSINESS_HOURS_COLLAPSE = VTT_PROFILE_BUSINESS_HOURS.wi
|
||||
export const VTT_PROFILE_NOTE = VTT_CHAT_EXTRA.with('profileNote');
|
||||
export const VTT_PROFILE_NOTE_EXPAND = VTT_PROFILE_NOTE.with('profileNoteExpand');
|
||||
export const VTT_PROFILE_NOTE_COLLAPSE = VTT_PROFILE_NOTE.with('profileNoteCollapse');
|
||||
|
||||
export const VTT_CRAFT_ATTRIBUTES = new VTTypes(['craftAttributes']);
|
||||
|
||||