Translation: Add Cocoon info (#6646)
This commit is contained in:
parent
6605a90606
commit
baff436709
6
src/@types/global.d.ts
vendored
6
src/@types/global.d.ts
vendored
@ -84,6 +84,12 @@ declare module '*.jpg' {
|
||||
const url: string;
|
||||
export default url;
|
||||
}
|
||||
|
||||
declare module '*.webp' {
|
||||
const url: string;
|
||||
export default url;
|
||||
}
|
||||
|
||||
declare module '*.svg' {
|
||||
const url: string;
|
||||
export default url;
|
||||
|
||||
BIN
src/assets/cocoon.webp
Normal file
BIN
src/assets/cocoon.webp
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 47 KiB |
@ -2608,6 +2608,23 @@
|
||||
"SettingsDataClearMediaCache" = "Clear Media Cache";
|
||||
"SettingsDataClearMediaCacheDescription" = "Deletes locally cached media for this account";
|
||||
"SettingsDataClearMediaDone" = "Media cache cleared";
|
||||
"TranslateMenuCocoon" = "Translations are powered by 🥚 **Cocoon**. {link}";
|
||||
"TranslateMenuCocoonLinkText" = "How does it work?";
|
||||
"CocoonTitle" = "Cocoon";
|
||||
"CocoonDescription" = "Cocoon (**Co**nfidential **Co**mpute **O**pen **N**etwork)\nhandles AI tasks safely and efficiently.";
|
||||
"CocoonFeature1Title" = "Private";
|
||||
"CocoonFeature1Text" = "No third party can access any data, such as translations, inside **{username}**.";
|
||||
"CocoonFeature1Username" = "@cocoon";
|
||||
"CocoonFeature1UsernameLink" = "https://t.me/cocoon";
|
||||
"CocoonFeature2Title" = "Efficient";
|
||||
"CocoonFeature2Text" = "Cocoon has allowed Telegram to reduce translation cost by 6x.";
|
||||
"CocoonFeature3Title" = "For Everyone";
|
||||
"CocoonFeature3Text" = "Any developer can use Cocoon for AI features. Learn more at **{link}**.";
|
||||
"CocoonFeature3LinkText" = "cocoon.org";
|
||||
"CocoonFeature3Link" = "https://cocoon.org";
|
||||
"CocoonFooterText" = "Want to integrate Cocoon into your projects?\nReach out **{link}**";
|
||||
"CocoonFooterLinkText" = "directly to @cocoon";
|
||||
"CocoonFooterLink" = "https://t.me/cocoon?direct";
|
||||
"ChatListAuctionTitle_one" = "Active Auction";
|
||||
"ChatListAuctionTitle_other" = "{count} Active Auctions";
|
||||
"ChatListAuctionWinning" = "You are winning";
|
||||
|
||||
@ -108,3 +108,4 @@ export { default as ProfileRatingModal } from '../components/modals/profileRatin
|
||||
export { default as QuickPreviewModal } from '../components/modals/quickPreview/QuickPreviewModal';
|
||||
export { default as StealthModeModal } from '../components/modals/storyStealthMode/StealthModeModal';
|
||||
export { default as QuickChatPickerModal } from '../components/modals/quickChatPicker/QuickChatPickerModal';
|
||||
export { default as CocoonModal } from '../components/modals/cocoon/CocoonModal';
|
||||
|
||||
@ -17,6 +17,8 @@ const DEFAULT_PARTICLE_PARAMS = {
|
||||
centerShift: [0, -36] as const,
|
||||
};
|
||||
|
||||
const MIN_BURST_INTERVAL = 8;
|
||||
|
||||
const InteractiveSparkles = ({
|
||||
color = 'purple',
|
||||
centerShift = DEFAULT_PARTICLE_PARAMS.centerShift,
|
||||
@ -25,6 +27,7 @@ const InteractiveSparkles = ({
|
||||
onRequestAnimation,
|
||||
}: OwnProps) => {
|
||||
const canvasRef = useRef<HTMLCanvasElement>();
|
||||
const lastBurstTimeRef = useRef<number>(0);
|
||||
|
||||
useLayoutEffect(() => {
|
||||
if (isDisabled) return undefined;
|
||||
@ -41,6 +44,11 @@ const InteractiveSparkles = ({
|
||||
const animate = () => {
|
||||
if (isDisabled) return;
|
||||
|
||||
const now = Date.now();
|
||||
if (now - lastBurstTimeRef.current < MIN_BURST_INTERVAL) return;
|
||||
|
||||
lastBurstTimeRef.current = now;
|
||||
|
||||
setupParticles(canvasRef.current!, {
|
||||
color: PARTICLE_COLORS[`${color}Gradient`],
|
||||
centerShift,
|
||||
|
||||
@ -8,6 +8,7 @@ import type { IAnchorPosition, MessageListType, ThreadId } from '../../types';
|
||||
import { MAIN_THREAD_ID } from '../../api/types';
|
||||
import { ManagementScreens } from '../../types';
|
||||
|
||||
import { COCOON_EMOJI_ID } from '../../config';
|
||||
import { requestMeasure, requestNextMutation } from '../../lib/fasterdom/fasterdom';
|
||||
import {
|
||||
getHasAdminRight,
|
||||
@ -38,11 +39,14 @@ import { isUserId } from '../../util/entities/ids';
|
||||
import focusNoScroll from '../../util/focusNoScroll';
|
||||
|
||||
import { useHotkeys } from '../../hooks/useHotkeys';
|
||||
import useLang from '../../hooks/useLang';
|
||||
import useLastCallback from '../../hooks/useLastCallback';
|
||||
import useOldLang from '../../hooks/useOldLang';
|
||||
|
||||
import CustomEmoji from '../common/CustomEmoji';
|
||||
import Button from '../ui/Button';
|
||||
import DropdownMenu from '../ui/DropdownMenu';
|
||||
import Link from '../ui/Link';
|
||||
import MenuItem from '../ui/MenuItem';
|
||||
import MenuSeparator from '../ui/MenuSeparator';
|
||||
import HeaderMenuContainer from './HeaderMenuContainer.async';
|
||||
@ -142,9 +146,12 @@ const HeaderActions: FC<OwnProps & StateProps> = ({
|
||||
unblockUser,
|
||||
setViewForumAsMessages,
|
||||
openFrozenAccountModal,
|
||||
openCocoonModal,
|
||||
} = getActions();
|
||||
const menuButtonRef = useRef<HTMLButtonElement>();
|
||||
const lang = useOldLang();
|
||||
const oldLang = useOldLang();
|
||||
const lang = useLang();
|
||||
|
||||
const [isMenuOpen, setIsMenuOpen] = useState(false);
|
||||
const [menuAnchor, setMenuAnchor] = useState<IAnchorPosition | undefined>(undefined);
|
||||
|
||||
@ -166,7 +173,8 @@ const HeaderActions: FC<OwnProps & StateProps> = ({
|
||||
joinChannel({ chatId });
|
||||
if (shouldSendJoinRequest) {
|
||||
showNotification({
|
||||
message: isChannel ? lang('RequestToJoinChannelSentDescription') : lang('RequestToJoinGroupSentDescription'),
|
||||
message: isChannel ? oldLang('RequestToJoinChannelSentDescription')
|
||||
: oldLang('RequestToJoinGroupSentDescription'),
|
||||
});
|
||||
}
|
||||
});
|
||||
@ -239,21 +247,21 @@ const HeaderActions: FC<OwnProps & StateProps> = ({
|
||||
const getTextWithLanguage = useCallback((langKey: string, langCode: string) => {
|
||||
const simplified = langCode.split('-')[0];
|
||||
const translationKey = `TranslateLanguage${simplified.toUpperCase()}`;
|
||||
const name = lang(translationKey);
|
||||
const name = oldLang(translationKey);
|
||||
if (name !== translationKey) {
|
||||
return lang(langKey, name);
|
||||
return oldLang(langKey, name);
|
||||
}
|
||||
|
||||
const translatedNames = new Intl.DisplayNames([language], { type: 'language' });
|
||||
const translatedName = translatedNames.of(langCode)!;
|
||||
return lang(`${langKey}Other`, translatedName);
|
||||
}, [language, lang]);
|
||||
return oldLang(`${langKey}Other`, translatedName);
|
||||
}, [language, oldLang]);
|
||||
|
||||
const buttonText = useMemo(() => {
|
||||
if (isTranslating) return lang('ShowOriginalButton');
|
||||
if (isTranslating) return oldLang('ShowOriginalButton');
|
||||
|
||||
return getTextWithLanguage('TranslateToButton', translationLanguage);
|
||||
}, [translationLanguage, getTextWithLanguage, isTranslating, lang]);
|
||||
}, [translationLanguage, getTextWithLanguage, isTranslating, oldLang]);
|
||||
|
||||
const doNotTranslateText = useMemo(() => {
|
||||
if (!detectedChatLanguage) return undefined;
|
||||
@ -270,6 +278,10 @@ const HeaderActions: FC<OwnProps & StateProps> = ({
|
||||
openChatLanguageModal({ chatId });
|
||||
});
|
||||
|
||||
const handleCocoonClick = useLastCallback(() => {
|
||||
openCocoonModal();
|
||||
});
|
||||
|
||||
const handleDoNotTranslate = useLastCallback(() => {
|
||||
if (!detectedChatLanguage) return;
|
||||
|
||||
@ -294,11 +306,11 @@ const HeaderActions: FC<OwnProps & StateProps> = ({
|
||||
size="smaller"
|
||||
className={isOpen ? 'active' : ''}
|
||||
onClick={onTrigger}
|
||||
ariaLabel={lang('TranslateMessage')}
|
||||
ariaLabel={oldLang('TranslateMessage')}
|
||||
iconName="language"
|
||||
/>
|
||||
);
|
||||
}, [isRightColumnShown, lang]);
|
||||
}, [isRightColumnShown, oldLang]);
|
||||
|
||||
return (
|
||||
<div className="HeaderActions">
|
||||
@ -312,12 +324,28 @@ const HeaderActions: FC<OwnProps & StateProps> = ({
|
||||
{buttonText}
|
||||
</MenuItem>
|
||||
<MenuItem icon="replace" onClick={handleChangeLanguage}>
|
||||
{lang('Chat.Translate.Menu.To')}
|
||||
{oldLang('Chat.Translate.Menu.To')}
|
||||
</MenuItem>
|
||||
<MenuSeparator />
|
||||
{detectedChatLanguage
|
||||
&& <MenuItem icon="hand-stop" onClick={handleDoNotTranslate}>{doNotTranslateText}</MenuItem>}
|
||||
<MenuItem icon="close-circle" onClick={handleHide}>{lang('Hide')}</MenuItem>
|
||||
<MenuItem icon="close-circle" onClick={handleHide}>{oldLang('Hide')}</MenuItem>
|
||||
<MenuSeparator />
|
||||
<MenuItem withWrap onClick={handleCocoonClick}>
|
||||
{lang('TranslateMenuCocoon', {
|
||||
link: (
|
||||
<Link isPrimary onClick={(e) => e.preventDefault()}>
|
||||
{lang('TranslateMenuCocoonLinkText')}
|
||||
</Link>
|
||||
),
|
||||
}, {
|
||||
withNodes: true,
|
||||
withMarkdown: true,
|
||||
specialReplacement: {
|
||||
'🥚': <CustomEmoji documentId={COCOON_EMOJI_ID} />,
|
||||
},
|
||||
})}
|
||||
</MenuItem>
|
||||
</DropdownMenu>
|
||||
)}
|
||||
{!isMobile && (
|
||||
@ -329,7 +357,7 @@ const HeaderActions: FC<OwnProps & StateProps> = ({
|
||||
fluid
|
||||
onClick={handleSubscribeClick}
|
||||
>
|
||||
{lang(isChannel ? 'ProfileJoinChannel' : 'ProfileJoinGroup')}
|
||||
{oldLang(isChannel ? 'ProfileJoinChannel' : 'ProfileJoinGroup')}
|
||||
</Button>
|
||||
)}
|
||||
{canExpandActions && shouldSendJoinRequest && (
|
||||
@ -339,7 +367,7 @@ const HeaderActions: FC<OwnProps & StateProps> = ({
|
||||
fluid
|
||||
onClick={handleSubscribeClick}
|
||||
>
|
||||
{lang('ChannelJoinRequest')}
|
||||
{oldLang('ChannelJoinRequest')}
|
||||
</Button>
|
||||
)}
|
||||
{canExpandActions && canStartBot && (
|
||||
@ -349,7 +377,7 @@ const HeaderActions: FC<OwnProps & StateProps> = ({
|
||||
fluid
|
||||
onClick={handleStartBot}
|
||||
>
|
||||
{lang('BotStart')}
|
||||
{oldLang('BotStart')}
|
||||
</Button>
|
||||
)}
|
||||
{canExpandActions && canRestartBot && (
|
||||
@ -359,7 +387,7 @@ const HeaderActions: FC<OwnProps & StateProps> = ({
|
||||
fluid
|
||||
onClick={handleRestartBot}
|
||||
>
|
||||
{lang('BotRestart')}
|
||||
{oldLang('BotRestart')}
|
||||
</Button>
|
||||
)}
|
||||
{canExpandActions && canUnblock && (
|
||||
@ -369,7 +397,7 @@ const HeaderActions: FC<OwnProps & StateProps> = ({
|
||||
fluid
|
||||
onClick={handleUnblock}
|
||||
>
|
||||
{lang('Unblock')}
|
||||
{oldLang('Unblock')}
|
||||
</Button>
|
||||
)}
|
||||
{canSearch && (
|
||||
@ -379,7 +407,7 @@ const HeaderActions: FC<OwnProps & StateProps> = ({
|
||||
color="translucent"
|
||||
size="smaller"
|
||||
onClick={handleSearchClick}
|
||||
ariaLabel={lang('Conversation.SearchPlaceholder')}
|
||||
ariaLabel={oldLang('Conversation.SearchPlaceholder')}
|
||||
iconName="search"
|
||||
/>
|
||||
)}
|
||||
@ -404,7 +432,7 @@ const HeaderActions: FC<OwnProps & StateProps> = ({
|
||||
size="smaller"
|
||||
iconName="user"
|
||||
onClick={handleJoinRequestsClick}
|
||||
ariaLabel={isChannel ? lang('SubscribeRequests') : lang('MemberRequests')}
|
||||
ariaLabel={isChannel ? oldLang('SubscribeRequests') : oldLang('MemberRequests')}
|
||||
>
|
||||
<div className="badge">{pendingJoinRequests}</div>
|
||||
</Button>
|
||||
|
||||
@ -583,13 +583,13 @@
|
||||
|
||||
&:not(.custom-shape) .text-content {
|
||||
.emoji {
|
||||
width: calc(1.25 * var(--message-text-size, 1rem));
|
||||
height: calc(1.25 * var(--message-text-size, 1rem));
|
||||
background-size: calc(1.25 * var(--message-text-size, 1rem));
|
||||
width: 1.25em;
|
||||
height: 1.25em;
|
||||
background-size: 1.25em;
|
||||
}
|
||||
|
||||
.custom-emoji {
|
||||
--custom-emoji-size: max(calc(1.25 * var(--message-text-size, 1rem)), 20px);
|
||||
--custom-emoji-size: max(1.25em, 20px);
|
||||
|
||||
width: var(--custom-emoji-size);
|
||||
height: var(--custom-emoji-size);
|
||||
|
||||
@ -15,6 +15,7 @@ import BirthdaySetupModal from './birthday/BirthdaySetupModal.async';
|
||||
import BoostModal from './boost/BoostModal.async';
|
||||
import ChatInviteModal from './chatInvite/ChatInviteModal.async';
|
||||
import ChatlistModal from './chatlist/ChatlistModal.async';
|
||||
import CocoonModal from './cocoon/CocoonModal.async';
|
||||
import CollectibleInfoModal from './collectible/CollectibleInfoModal.async';
|
||||
import DeleteAccountModal from './deleteAccount/DeleteAccountModal.async';
|
||||
import EmojiStatusAccessModal from './emojiStatusAccess/EmojiStatusAccessModal.async';
|
||||
@ -130,7 +131,8 @@ type ModalKey = keyof Pick<TabState,
|
||||
'storyStealthModal' |
|
||||
'isPasskeyModalOpen' |
|
||||
'birthdaySetupModal' |
|
||||
'isQuickChatPickerOpen'
|
||||
'isQuickChatPickerOpen' |
|
||||
'isCocoonModalOpen'
|
||||
>;
|
||||
|
||||
type StateProps = {
|
||||
@ -207,6 +209,7 @@ const MODALS: ModalRegistry = {
|
||||
isPasskeyModalOpen: PasskeyModal,
|
||||
birthdaySetupModal: BirthdaySetupModal,
|
||||
isQuickChatPickerOpen: QuickChatPickerModal,
|
||||
isCocoonModalOpen: CocoonModal,
|
||||
};
|
||||
const MODAL_KEYS = Object.keys(MODALS) as ModalKey[];
|
||||
const MODAL_ENTRIES = Object.entries(MODALS) as Entries<ModalRegistry>;
|
||||
|
||||
14
src/components/modals/cocoon/CocoonModal.async.tsx
Normal file
14
src/components/modals/cocoon/CocoonModal.async.tsx
Normal file
@ -0,0 +1,14 @@
|
||||
import type { OwnProps } from './CocoonModal';
|
||||
|
||||
import { Bundles } from '../../../util/moduleLoader';
|
||||
|
||||
import useModuleLoader from '../../../hooks/useModuleLoader';
|
||||
|
||||
const CocoonModalAsync = (props: OwnProps) => {
|
||||
const { modal } = props;
|
||||
const CocoonModal = useModuleLoader(Bundles.Extra, 'CocoonModal', !modal);
|
||||
|
||||
return CocoonModal ? <CocoonModal {...props} /> : undefined;
|
||||
};
|
||||
|
||||
export default CocoonModalAsync;
|
||||
26
src/components/modals/cocoon/CocoonModal.module.scss
Normal file
26
src/components/modals/cocoon/CocoonModal.module.scss
Normal file
@ -0,0 +1,26 @@
|
||||
.header {
|
||||
position: relative;
|
||||
margin-top: -1rem;
|
||||
margin-inline: -1rem;
|
||||
}
|
||||
|
||||
.footer {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.75rem;
|
||||
}
|
||||
|
||||
.footerText {
|
||||
font-size: 0.9375rem;
|
||||
color: var(--color-text-secondary);
|
||||
text-align: center;
|
||||
text-wrap: balance;
|
||||
}
|
||||
|
||||
.egg {
|
||||
transition: transform 0.25s ease-out;
|
||||
|
||||
&:hover {
|
||||
transform: scale(1.1);
|
||||
}
|
||||
}
|
||||
108
src/components/modals/cocoon/CocoonModal.tsx
Normal file
108
src/components/modals/cocoon/CocoonModal.tsx
Normal file
@ -0,0 +1,108 @@
|
||||
import { memo, useMemo } from '@teact';
|
||||
import { getActions } from '../../../global';
|
||||
|
||||
import type { TabState } from '../../../global/types';
|
||||
|
||||
import useLang from '../../../hooks/useLang';
|
||||
import useLastCallback from '../../../hooks/useLastCallback';
|
||||
|
||||
import SafeLink from '../../common/SafeLink';
|
||||
import Button from '../../ui/Button';
|
||||
import Link from '../../ui/Link';
|
||||
import ParticlesHeader from '../common/ParticlesHeader';
|
||||
import TableAboutModal, { type TableAboutData } from '../common/TableAboutModal';
|
||||
|
||||
import styles from './CocoonModal.module.scss';
|
||||
|
||||
export type OwnProps = {
|
||||
modal: TabState['isCocoonModalOpen'];
|
||||
};
|
||||
|
||||
const CocoonModal = ({ modal }: OwnProps) => {
|
||||
const { closeCocoonModal, openTelegramLink } = getActions();
|
||||
const isOpen = Boolean(modal);
|
||||
|
||||
const lang = useLang();
|
||||
|
||||
const handleClose = useLastCallback(() => {
|
||||
closeCocoonModal();
|
||||
});
|
||||
|
||||
const openLinkAndClose = useLastCallback((url: string) => {
|
||||
openTelegramLink({ url });
|
||||
handleClose();
|
||||
});
|
||||
|
||||
const listItemData = useMemo(() => {
|
||||
const feature1Description = lang('CocoonFeature1Text', {
|
||||
username: (
|
||||
<Link
|
||||
isPrimary
|
||||
onClick={() => {
|
||||
openLinkAndClose(lang('CocoonFeature1UsernameLink'));
|
||||
}}
|
||||
>
|
||||
{lang('CocoonFeature1Username')}
|
||||
</Link>
|
||||
),
|
||||
}, { withNodes: true, withMarkdown: true });
|
||||
|
||||
const feature3Description = lang('CocoonFeature3Text', {
|
||||
link: <SafeLink url={lang('CocoonFeature3Link')} text={lang('CocoonFeature3LinkText')} />,
|
||||
}, {
|
||||
withNodes: true,
|
||||
withMarkdown: true,
|
||||
});
|
||||
|
||||
return [
|
||||
['lock', lang('CocoonFeature1Title'), feature1Description],
|
||||
['stats', lang('CocoonFeature2Title'), lang('CocoonFeature2Text')],
|
||||
['gift', lang('CocoonFeature3Title'), feature3Description],
|
||||
] satisfies TableAboutData;
|
||||
}, [lang]);
|
||||
|
||||
const header = useMemo(() => {
|
||||
return (
|
||||
<ParticlesHeader
|
||||
className={styles.header}
|
||||
modelClassName={styles.egg}
|
||||
model="ai-egg"
|
||||
color="purple"
|
||||
title={lang('CocoonTitle')}
|
||||
description={lang('CocoonDescription', undefined, { withNodes: true, withMarkdown: true })}
|
||||
/>
|
||||
);
|
||||
}, [lang]);
|
||||
|
||||
const footer = useMemo(() => {
|
||||
return (
|
||||
<div className={styles.footer}>
|
||||
<span className={styles.footerText}>
|
||||
{lang('CocoonFooterText', {
|
||||
link: (
|
||||
<Link isPrimary onClick={() => openLinkAndClose(lang('CocoonFooterLink'))}>
|
||||
{lang('CocoonFooterLinkText')}
|
||||
</Link>
|
||||
),
|
||||
}, { withNodes: true, withMarkdown: true })}
|
||||
</span>
|
||||
<Button iconName="understood" onClick={handleClose}>
|
||||
{lang('ButtonUnderstood')}
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
}, [lang]);
|
||||
|
||||
return (
|
||||
<TableAboutModal
|
||||
isOpen={isOpen}
|
||||
onClose={closeCocoonModal}
|
||||
listItemData={listItemData}
|
||||
withSeparator
|
||||
header={header}
|
||||
footer={footer}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(CocoonModal);
|
||||
@ -5,6 +5,29 @@
|
||||
padding-top: 9rem;
|
||||
}
|
||||
|
||||
.ai-egg {
|
||||
padding-top: 10rem;
|
||||
background-color: #061029;
|
||||
background-image: radial-gradient(circle at center, #FFFFFF08 0%, #00000000 50%);
|
||||
|
||||
.title {
|
||||
margin-bottom: 0;
|
||||
|
||||
font-size: 2.5rem;
|
||||
color: transparent;
|
||||
|
||||
background-image: linear-gradient(90deg, #67c9ff 0%, #cb56ff 100%);
|
||||
background-clip: text;
|
||||
}
|
||||
|
||||
.description {
|
||||
color: #b8c9ef;
|
||||
:global(b) {
|
||||
color: white;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.particles {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
@ -41,3 +64,10 @@
|
||||
transform: scale(1.1);
|
||||
}
|
||||
}
|
||||
|
||||
.cocoon {
|
||||
position: absolute;
|
||||
z-index: 1;
|
||||
top: 1.75rem;
|
||||
height: 8.125rem;
|
||||
}
|
||||
|
||||
@ -15,14 +15,17 @@ import SwayingStar from './SwayingStar.tsx';
|
||||
|
||||
import styles from './ParticlesHeader.module.scss';
|
||||
|
||||
import Cocoon from '../../../assets/cocoon.webp';
|
||||
|
||||
interface OwnProps {
|
||||
model: 'swaying-star' | 'speeding-diamond' | 'sticker';
|
||||
model: 'swaying-star' | 'speeding-diamond' | 'ai-egg' | 'sticker';
|
||||
sticker?: ApiSticker;
|
||||
color: 'purple' | 'gold' | 'blue';
|
||||
title: TeactNode;
|
||||
description: TeactNode;
|
||||
isDisabled?: boolean;
|
||||
className?: string;
|
||||
modelClassName?: string;
|
||||
}
|
||||
|
||||
const GIFT_STICKER_SIZE = 8 * REM;
|
||||
@ -39,6 +42,7 @@ function ParticlesHeader({
|
||||
description,
|
||||
isDisabled,
|
||||
className,
|
||||
modelClassName,
|
||||
}: OwnProps) {
|
||||
const stickerRef = useRef<HTMLDivElement>();
|
||||
const triggerSparklesRef = useRef<(() => void) | undefined>();
|
||||
@ -52,7 +56,7 @@ function ParticlesHeader({
|
||||
});
|
||||
|
||||
return (
|
||||
<div className={buildClassName(styles.root, className)}>
|
||||
<div className={buildClassName(styles.root, styles[model], className)}>
|
||||
<InteractiveSparkles
|
||||
color={color}
|
||||
centerShift={PARTICLE_PARAMS.centerShift}
|
||||
@ -63,16 +67,27 @@ function ParticlesHeader({
|
||||
|
||||
{model === 'swaying-star' ? (
|
||||
<SwayingStar
|
||||
className={modelClassName}
|
||||
color={color as 'purple' | 'gold'}
|
||||
centerShift={PARTICLE_PARAMS.centerShift}
|
||||
onMouseMove={handleMouseMove}
|
||||
/>
|
||||
) : model === 'ai-egg' ? (
|
||||
<img
|
||||
src={Cocoon}
|
||||
alt=""
|
||||
role="presentation"
|
||||
aria-hidden="true"
|
||||
className={buildClassName(styles.cocoon, modelClassName)}
|
||||
draggable={false}
|
||||
onMouseMove={handleMouseMove}
|
||||
/>
|
||||
) : model === 'speeding-diamond' ? (
|
||||
<SpeedingDiamond onMouseMove={handleMouseMove} />
|
||||
<SpeedingDiamond className={modelClassName} onMouseMove={handleMouseMove} />
|
||||
) : model === 'sticker' && sticker && (
|
||||
<div
|
||||
ref={stickerRef}
|
||||
className={styles.stickerWrapper}
|
||||
className={buildClassName(styles.stickerWrapper, modelClassName)}
|
||||
style={`width: ${GIFT_STICKER_SIZE}px; height: ${GIFT_STICKER_SIZE}px`}
|
||||
onMouseMove={handleMouseMove}
|
||||
>
|
||||
|
||||
@ -2,6 +2,7 @@ import { memo, useState } from '@teact';
|
||||
|
||||
import { requestMutation } from '../../../lib/fasterdom/fasterdom.ts';
|
||||
import { animateSingle } from '../../../util/animation.ts';
|
||||
import buildClassName from '../../../util/buildClassName';
|
||||
import { LOCAL_TGS_URLS } from '../../common/helpers/animatedAssets.ts';
|
||||
|
||||
import useLastCallback from '../../../hooks/useLastCallback.ts';
|
||||
@ -13,6 +14,7 @@ import styles from './SpeedingDiamond.module.scss';
|
||||
import diamondPreviewUrl from '../../../assets/diamond.png';
|
||||
|
||||
interface OwnProps {
|
||||
className?: string;
|
||||
onMouseMove: NoneToVoidFunction;
|
||||
}
|
||||
|
||||
@ -24,7 +26,7 @@ const SLOWDOWN_DURATION = 1500;
|
||||
let slowdownTimeout: number | undefined;
|
||||
let isAnimating = true;
|
||||
|
||||
function SpeedingDiamond({ onMouseMove }: OwnProps) {
|
||||
function SpeedingDiamond({ className, onMouseMove }: OwnProps) {
|
||||
const [speed, setSpeed] = useState(MIN_SPEED);
|
||||
|
||||
const handleMouseMove = useLastCallback(() => {
|
||||
@ -58,7 +60,7 @@ function SpeedingDiamond({ onMouseMove }: OwnProps) {
|
||||
});
|
||||
|
||||
return (
|
||||
<div className={styles.root}>
|
||||
<div className={buildClassName(styles.root, className)}>
|
||||
<div
|
||||
className={styles.diamond}
|
||||
onMouseMove={handleMouseMove}
|
||||
|
||||
@ -8,6 +8,7 @@ import useLastCallback from '../../../hooks/useLastCallback.ts';
|
||||
import styles from './SwayingStar.module.scss';
|
||||
|
||||
interface OwnProps {
|
||||
className?: string;
|
||||
color: 'purple' | 'gold';
|
||||
centerShift: readonly [number, number];
|
||||
onMouseMove: NoneToVoidFunction;
|
||||
@ -16,6 +17,7 @@ interface OwnProps {
|
||||
const INTERACTIVE_RADIUS = 50;
|
||||
|
||||
function SwayingStar({
|
||||
className,
|
||||
color,
|
||||
centerShift,
|
||||
onMouseMove,
|
||||
@ -48,7 +50,7 @@ function SwayingStar({
|
||||
|
||||
return (
|
||||
<div
|
||||
className={styles.root}
|
||||
className={buildClassName(styles.root, className)}
|
||||
onMouseMove={handleMouseMove}
|
||||
onMouseLeave={handleMouseLeave}
|
||||
>
|
||||
|
||||
@ -75,8 +75,10 @@
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.listItem {
|
||||
padding-bottom: 0.5rem;
|
||||
.listItems {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.listItemTitle {
|
||||
@ -86,5 +88,5 @@
|
||||
|
||||
.separator {
|
||||
width: calc(100% + 2rem); // Hack to cover modal paddings
|
||||
margin-block: 1rem;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
@ -63,7 +63,7 @@ const TableAboutModal = ({
|
||||
</div>
|
||||
)}
|
||||
{header}
|
||||
<div>
|
||||
<div className={styles.listItems}>
|
||||
{listItemData?.map(([icon, title, subtitle]) => {
|
||||
return (
|
||||
<ListItem
|
||||
|
||||
@ -1,6 +1,3 @@
|
||||
import type { FC } from '../../lib/teact/teact';
|
||||
import type React from '../../lib/teact/teact';
|
||||
|
||||
import buildClassName from '../../util/buildClassName';
|
||||
|
||||
import useLastCallback from '../../hooks/useLastCallback';
|
||||
@ -16,9 +13,9 @@ type OwnProps = {
|
||||
onClick?: (e: React.MouseEvent<HTMLAnchorElement>) => void;
|
||||
};
|
||||
|
||||
const Link: FC<OwnProps> = ({
|
||||
const Link = ({
|
||||
children, isPrimary, className, isRtl, withMultilineFix, onClick,
|
||||
}) => {
|
||||
}: OwnProps) => {
|
||||
const handleClick = useLastCallback((e: React.MouseEvent<HTMLAnchorElement>) => {
|
||||
e.preventDefault();
|
||||
onClick!(e);
|
||||
|
||||
@ -161,11 +161,20 @@
|
||||
font-weight: var(--font-weight-semibold);
|
||||
}
|
||||
|
||||
a:hover {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
&.wrap {
|
||||
display: block;
|
||||
white-space: normal;
|
||||
}
|
||||
|
||||
&.text-only {
|
||||
padding-inline: 0.5rem;
|
||||
line-height: 1.375;
|
||||
}
|
||||
|
||||
&.menu-custom-emoji-sets {
|
||||
margin: 0 0.25rem;
|
||||
padding: 0.5rem 0.75rem;
|
||||
|
||||
@ -91,6 +91,7 @@ const MenuItem = (props: MenuItemProps) => {
|
||||
destructive && 'destructive',
|
||||
!isTouchScreen && 'compact',
|
||||
withWrap && 'wrap',
|
||||
!icon && !customIcon && 'text-only',
|
||||
);
|
||||
|
||||
const content = (
|
||||
|
||||
@ -345,6 +345,7 @@ export const REPLIES_USER_ID = '1271266957'; // TODO For Test connection ID must
|
||||
export const VERIFICATION_CODES_USER_ID = '489000';
|
||||
export const ANONYMOUS_USER_ID = '2666000';
|
||||
export const RESTRICTED_EMOJI_SET_ID = '7173162320003080';
|
||||
export const COCOON_EMOJI_ID = '5197252827247841976';
|
||||
export const LOCAL_MESSAGES_LIMIT = 1e6; // 1M
|
||||
export const CHANNEL_ID_BASE = 10n ** 12n;
|
||||
export const DEFAULT_GIF_SEARCH_BOT_USERNAME = 'gif';
|
||||
|
||||
@ -961,3 +961,12 @@ addActionHandler('openQuickChatPicker', (global, actions, payload): ActionReturn
|
||||
});
|
||||
|
||||
addTabStateResetterAction('closeQuickChatPicker', 'isQuickChatPickerOpen');
|
||||
|
||||
addActionHandler('openCocoonModal', (global, actions, payload): ActionReturnType => {
|
||||
const { tabId = getCurrentTabId() } = payload || {};
|
||||
return updateTabState(global, {
|
||||
isCocoonModalOpen: true,
|
||||
}, tabId);
|
||||
});
|
||||
|
||||
addTabStateResetterAction('closeCocoonModal', 'isCocoonModalOpen');
|
||||
|
||||
@ -2996,6 +2996,9 @@ export interface ActionPayloads {
|
||||
chatId: string;
|
||||
messageId: number;
|
||||
};
|
||||
|
||||
openCocoonModal: WithTabId | undefined;
|
||||
closeCocoonModal: WithTabId | undefined;
|
||||
}
|
||||
|
||||
export interface RequiredActionPayloads {
|
||||
|
||||
@ -975,4 +975,6 @@ export type TabState = {
|
||||
isWaitingForStarGiftTransfer?: true;
|
||||
insertingPeerIdMention?: string;
|
||||
shouldSaveAttachmentsCompression?: boolean;
|
||||
|
||||
isCocoonModalOpen?: boolean;
|
||||
};
|
||||
|
||||
@ -1872,6 +1872,7 @@ payments.getStarsStatus#4ea9b3bf flags:# ton:flags.0?true peer:InputPeer = payme
|
||||
payments.getStarsTransactions#69da4557 flags:# inbound:flags.0?true outbound:flags.1?true ascending:flags.2?true ton:flags.4?true subscription_id:flags.3?string peer:InputPeer offset:string limit:int = payments.StarsStatus;
|
||||
payments.sendStarsForm#7998c914 form_id:long invoice:InputInvoice = payments.PaymentResult;
|
||||
payments.refundStarsCharge#25ae8f4a user_id:InputUser charge_id:string = Updates;
|
||||
payments.getStarsRevenueStats#d91ffad6 flags:# dark:flags.0?true ton:flags.1?true peer:InputPeer = payments.StarsRevenueStats;
|
||||
payments.getStarsTransactionsByID#2dca16b8 flags:# ton:flags.0?true peer:InputPeer id:Vector<InputStarsTransaction> = payments.StarsStatus;
|
||||
payments.getStarsGiftOptions#d3c96bc8 flags:# user_id:flags.0?InputUser = Vector<StarsGiftOption>;
|
||||
payments.getStarsSubscriptions#32512c5 flags:# missing_balance:flags.0?true peer:InputPeer offset:string = payments.StarsStatus;
|
||||
|
||||
@ -331,6 +331,7 @@
|
||||
"payments.getStarsTransactions",
|
||||
"payments.getStarsTransactionsByID",
|
||||
"payments.getStarsSubscriptions",
|
||||
"payments.getStarsRevenueStats",
|
||||
"payments.changeStarsSubscription",
|
||||
"payments.fulfillStarsSubscription",
|
||||
"payments.sendStarsForm",
|
||||
|
||||
25
src/types/language.d.ts
vendored
25
src/types/language.d.ts
vendored
@ -1916,6 +1916,19 @@ export interface LangPair {
|
||||
'SettingsDataClearMediaCache': undefined;
|
||||
'SettingsDataClearMediaCacheDescription': undefined;
|
||||
'SettingsDataClearMediaDone': undefined;
|
||||
'TranslateMenuCocoonLinkText': undefined;
|
||||
'CocoonTitle': undefined;
|
||||
'CocoonDescription': undefined;
|
||||
'CocoonFeature1Title': undefined;
|
||||
'CocoonFeature1Username': undefined;
|
||||
'CocoonFeature1UsernameLink': undefined;
|
||||
'CocoonFeature2Title': undefined;
|
||||
'CocoonFeature2Text': undefined;
|
||||
'CocoonFeature3Title': undefined;
|
||||
'CocoonFeature3LinkText': undefined;
|
||||
'CocoonFeature3Link': undefined;
|
||||
'CocoonFooterLinkText': undefined;
|
||||
'CocoonFooterLink': undefined;
|
||||
'ChatListAuctionWinning': undefined;
|
||||
'ChatListAuctionOutbid': undefined;
|
||||
'ChatListAuctionView': undefined;
|
||||
@ -3360,6 +3373,18 @@ export interface LangPairWithVariables<V = LangVariable> {
|
||||
'status': V;
|
||||
'onlineCount': V;
|
||||
};
|
||||
'TranslateMenuCocoon': {
|
||||
'link': V;
|
||||
};
|
||||
'CocoonFeature1Text': {
|
||||
'username': V;
|
||||
};
|
||||
'CocoonFeature3Text': {
|
||||
'link': V;
|
||||
};
|
||||
'CocoonFooterText': {
|
||||
'link': V;
|
||||
};
|
||||
'ChatListAuctionMixed': {
|
||||
'winCount': V;
|
||||
'outbidCount': V;
|
||||
|
||||
@ -1,5 +1,3 @@
|
||||
import type React from '../lib/teact/teact';
|
||||
|
||||
const stopEvent = (e: React.UIEvent | Event | React.FormEvent) => {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
|
||||
@ -164,7 +164,7 @@ export default function createConfig(
|
||||
],
|
||||
},
|
||||
{
|
||||
test: /\.(woff(2)?|ttf|eot|svg|png|jpg|tgs)(\?v=\d+\.\d+\.\d+)?$/,
|
||||
test: /\.(woff(2)?|ttf|eot|svg|png|jpg|tgs|webp)(\?v=\d+\.\d+\.\d+)?$/,
|
||||
type: 'asset/resource',
|
||||
},
|
||||
{
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user