MiniApps: More apps tab (#5137)

This commit is contained in:
Alexander Zinchuk 2024-11-09 15:40:37 +04:00
parent c0be156229
commit 92ebef4f49
16 changed files with 468 additions and 30 deletions

View File

@ -1370,6 +1370,10 @@
"StarsSubscribeInfoLinkText" = "Terms of Service"; "StarsSubscribeInfoLinkText" = "Terms of Service";
"StarsSubscribeInfoLink" = "https://telegram.org/tos/stars"; "StarsSubscribeInfoLink" = "https://telegram.org/tos/stars";
"StarsPerMonth" = "⭐️{amount}/month"; "StarsPerMonth" = "⭐️{amount}/month";
"OpenApp" = "Open App";
"PopularApps" = "Popular Apps";
"SearchApps" = "Search Apps";
"Apps" = "Apps";
"AreYouSureCloseMiniApps" = "Are you sure you want to close all Mini Apps?"; "AreYouSureCloseMiniApps" = "Are you sure you want to close all Mini Apps?";
"CloseMiniApps" = "Close Mini Apps"; "CloseMiniApps" = "Close Mini Apps";
"DoNotAskAgain" = "Don't ask again"; "DoNotAskAgain" = "Don't ask again";

View File

@ -18,6 +18,8 @@ type OwnProps = {
badgeIcon?: IconName; badgeIcon?: IconName;
className?: string; className?: string;
badgeClassName?: string; badgeClassName?: string;
badgeIconClassName?: string;
textClassName?: string;
onClick?: NoneToVoidFunction; onClick?: NoneToVoidFunction;
}; };
@ -28,6 +30,8 @@ const PeerBadge = ({
badgeIcon, badgeIcon,
className, className,
badgeClassName, badgeClassName,
badgeIconClassName,
textClassName,
onClick, onClick,
}: OwnProps) => { }: OwnProps) => {
return ( return (
@ -39,12 +43,12 @@ const PeerBadge = ({
<Avatar size="large" peer={peer} /> <Avatar size="large" peer={peer} />
{badgeText && ( {badgeText && (
<div className={buildClassName(styles.badge, badgeClassName)}> <div className={buildClassName(styles.badge, badgeClassName)}>
{badgeIcon && <Icon name={badgeIcon} />} {badgeIcon && <Icon name={badgeIcon} className={badgeIconClassName} />}
{badgeText} {badgeText}
</div> </div>
)} )}
</div> </div>
{text && <p className={styles.text}>{text}</p>} {text && <p className={buildClassName(styles.text, textClassName)}>{text}</p>}
</div> </div>
); );
}; };

View File

@ -207,12 +207,14 @@
.Link { .Link {
float: right; float: right;
color: var(--color-links); color: var(--color-links);
font-weight: 400; font-weight: 500;
margin-right: 1.5rem; margin-right: 1.5rem;
transition: opacity 0.15s ease-in;
&:focus, &:active,
&:hover { &:hover {
text-decoration: underline; text-decoration: none;
opacity: 0.85;
} }
} }

View File

@ -67,7 +67,7 @@ const MinimizedWebAppModal = ({
if (!isMinimizedState) return undefined; if (!isMinimizedState) return undefined;
function renderTitle() { function renderTitle() {
const activeTabName = activeTabBot?.firstName; const activeTabName = peers.length > 0 && peers[0]?.firstName;
const title = openedTabsCount && activeTabName && openedTabsCount > 1 const title = openedTabsCount && activeTabName && openedTabsCount > 1
? `${lang('MiniAppsMoreTabs', ? `${lang('MiniAppsMoreTabs',
{ {

View File

@ -0,0 +1,49 @@
@use "../../../styles/mixins";
.root {
height: 100%;
width: 100%;
display: flex;
flex-direction: column;
z-index: 0;
padding-inline: 1.25rem;
padding-top: 1.25rem;
overflow-y: scroll;
@include mixins.adapt-padding-to-scrollbar(1rem);
}
.search {
margin-bottom: 1rem;
}
.section-title {
font-size: 1rem;
font-weight: 500;
padding: 0.5rem;
padding-block: 0.25rem;
justify-content: space-between;
display: flex;
padding-bottom: 0.5rem;
}
.section-content {
display: grid;
justify-content: center;
align-items: center;
gap: 0.125rem;
grid-template-columns: repeat(5, 1fr);
padding: 0.125rem;
padding-bottom: 1.25em;
}
.showMoreLink {
color: var(--color-links);
cursor: pointer;
transition: opacity 0.2s ease-in;
&:hover, &:active {
background-color: transparent !important;
opacity: 0.85;
}
}

View File

@ -0,0 +1,143 @@
import type { FC } from '../../../lib/teact/teact';
import React, {
memo,
useCallback,
useMemo,
useState,
} from '../../../lib/teact/teact';
import { getActions, getGlobal, withGlobal } from '../../../global';
import { LoadMoreDirection } from '../../../types';
import { filterUsersByName } from '../../../global/helpers';
import { selectTabState } from '../../../global/selectors';
import buildClassName from '../../../util/buildClassName';
import { throttle } from '../../../util/schedulers';
import useLang from '../../../hooks/useLang';
import useLastCallback from '../../../hooks/useLastCallback';
import useOldLang from '../../../hooks/useOldLang';
import InfiniteScroll from '../../ui/InfiniteScroll';
import SearchInput from '../../ui/SearchInput';
import WebAppGridItem from './WebAppGridtem';
import styles from './MoreAppsTabContent.module.scss';
const POPULAR_APPS_SLICE = 30;
export type OwnProps = {
};
type StateProps = {
isLoading?: boolean;
foundIds?: string[];
recentBotIds?: string[];
};
const LESS_GRID_ITEMS_AMOUNT = 5;
const runThrottled = throttle((cb) => cb(), 500, true);
const MoreAppsTabContent: FC<OwnProps & StateProps> = ({
foundIds,
recentBotIds,
}) => {
const oldLang = useOldLang();
const lang = useLang();
const [shouldShowMoreMine, setShouldShowMoreMine] = useState<boolean>(false);
const {
searchPopularBotApps,
} = getActions();
const handleToggleShowMoreMine = useLastCallback(() => {
setShouldShowMoreMine((prev) => !prev);
});
const [searchQuery, setSearchQuery] = useState('');
const filteredFoundIds = useMemo(() => {
if (!foundIds) return [];
const usersById = getGlobal().users.byId;
return filterUsersByName(foundIds, usersById, searchQuery);
}, [foundIds, searchQuery]);
const handleLoadMore = useCallback(({ direction }: { direction: LoadMoreDirection }) => {
if (direction === LoadMoreDirection.Backwards) {
runThrottled(() => {
searchPopularBotApps();
});
}
}, []);
const handleSearchInputReset = useCallback(() => {
setSearchQuery('');
}, []);
return (
<InfiniteScroll
className={buildClassName(styles.root, 'custom-scroll')}
items={filteredFoundIds}
onLoadMore={handleLoadMore}
itemSelector=".PopularAppGridItem"
noFastList
preloadBackwards={POPULAR_APPS_SLICE}
>
<SearchInput
className={styles.search}
value={searchQuery}
onChange={setSearchQuery}
onReset={handleSearchInputReset}
placeholder={lang('SearchApps')}
/>
{recentBotIds && !searchQuery && (
<div className={styles.section}>
<div className={styles.sectionTitle}>
<span>{oldLang('SearchAppsMine')}</span>
<span className={styles.showMoreLink} onClick={handleToggleShowMoreMine}>
{oldLang(shouldShowMoreMine ? 'ChatList.Search.ShowLess' : 'ChatList.Search.ShowMore')}
</span>
</div>
<div className={styles.sectionContent}>
{recentBotIds.map((id, index) => {
if (!shouldShowMoreMine && index >= LESS_GRID_ITEMS_AMOUNT) {
return undefined;
}
return (
<WebAppGridItem
chatId={id}
/>
);
})}
</div>
</div>
)}
<div className={styles.section}>
<div className={styles.sectionTitle}>
{searchQuery ? lang('Apps') : lang('PopularApps')}
</div>
<div className={styles.sectionContent}>
{filteredFoundIds && filteredFoundIds.map((id) => {
return (
<WebAppGridItem
chatId={id}
isPopularApp={!searchQuery}
/>
);
})}
</div>
</div>
</InfiniteScroll>
);
};
export default memo(withGlobal<OwnProps>((global) => {
const globalSearch = selectTabState(global).globalSearch;
const foundIds = globalSearch.popularBotApps?.peerIds;
return {
isLoading: !foundIds && globalSearch.fetchingStatus?.botApps,
foundIds,
recentBotIds: global.topBotApps.userIds,
};
})(MoreAppsTabContent));

View File

@ -0,0 +1,39 @@
.container {
display: flex;
flex-direction: column;
align-items: center;
padding: 0.375rem;
border-radius: 0.625rem;
cursor: var(--custom-cursor, pointer);
&:hover {
background-color: var(--color-background-secondary);
}
}
.user-count-badge {
font-family: var(--font-family-condensed);
font-weight: 600 !important;
font-size: 0.5rem !important;
border-width: 1px !important;
bottom: 0 !important;
padding-block: 0.1875rem !important;
background: rgba(0, 0, 0, 0.2) !important;
backdrop-filter: blur(50px);
}
.user-badge-icon {
font-size: 0.4375rem !important;
}
.name {
white-space: inherit !important;
max-width: 100% !important;
width: 4rem !important;
font-size: 0.625rem !important;
height: 1.625rem;
font-weight: 500;
line-height: 0.75rem;
}

View File

@ -0,0 +1,87 @@
import React, { memo } from '../../../lib/teact/teact';
import { getActions, withGlobal } from '../../../global';
import type {
ApiUser,
} from '../../../api/types';
import {
selectUser,
} from '../../../global/selectors';
import buildClassName from '../../../util/buildClassName';
import { formatIntegerCompact } from '../../../util/textFormat';
import { extractCurrentThemeParams } from '../../../util/themeStyle';
import useLastCallback from '../../../hooks/useLastCallback';
import PeerBadge from '../../common/PeerBadge';
import styles from './WebAppGridItem.module.scss';
export type OwnProps = {
// eslint-disable-next-line react/no-unused-prop-types
chatId: string;
isPopularApp?: boolean;
};
export type StateProps = {
user?: ApiUser;
};
function WebAppGridItem({ user, isPopularApp }: OwnProps & StateProps) {
const {
requestMainWebView,
} = getActions();
const handleClick = useLastCallback(() => {
if (!user) {
return;
}
const botId = user?.id;
if (!botId) {
return;
}
const theme = extractCurrentThemeParams();
requestMainWebView({
botId,
peerId: botId,
theme,
});
});
if (!user) return undefined;
// eslint-disable-next-line no-null/no-null
const title = user?.firstName;
const activeUserCount = user?.botActiveUsers;
const badgeText = activeUserCount && isPopularApp ? formatIntegerCompact(activeUserCount) : undefined;
return (
<div
className={styles.container}
onClick={handleClick}
>
<PeerBadge
className={buildClassName(styles.avatarContainer, isPopularApp && 'PopularAppGridItem')}
textClassName={styles.name}
badgeClassName={styles.userCountBadge}
badgeIconClassName={styles.userBadgeIcon}
peer={user}
text={title}
badgeText={badgeText}
badgeIcon="user-filled"
/>
</div>
);
}
export default memo(withGlobal<OwnProps>(
(global, { chatId }): StateProps => {
const user = selectUser(global, chatId);
return {
user,
};
},
)(WebAppGridItem));

View File

@ -161,7 +161,7 @@
padding-left: 1rem; padding-left: 1rem;
padding-right: 1rem; padding-right: 1rem;
font-size: 0.875rem; font-size: 1rem;
font-weight: 500; font-weight: 500;
text-overflow: ellipsis; text-overflow: ellipsis;
@ -220,6 +220,7 @@
transform: scaleX(-1); transform: scaleX(-1);
} }
.more-apps-tab-icon,
.avatar-container { .avatar-container {
position: relative; position: relative;
display: flex; display: flex;
@ -227,6 +228,11 @@
margin-right: 0.5rem; margin-right: 0.5rem;
} }
.more-apps-tab-icon {
font-size: 1.5rem;
color: var(--color-text-secondary);
}
.web-app-tab-more-menu { .web-app-tab-more-menu {
z-index: 1; z-index: 1;
position: absolute; position: absolute;
@ -255,6 +261,7 @@
transform: translate(-50%, -50%); transform: translate(-50%, -50%);
} }
.more-apps-button,
.window-state-button, .window-state-button,
.header-button { .header-button {
width: 1.75rem !important; width: 1.75rem !important;

View File

@ -26,6 +26,7 @@ import useAppLayout from '../../../hooks/useAppLayout';
import useContextMenuHandlers from '../../../hooks/useContextMenuHandlers'; import useContextMenuHandlers from '../../../hooks/useContextMenuHandlers';
import useDraggable from '../../../hooks/useDraggable'; import useDraggable from '../../../hooks/useDraggable';
import useHorizontalScroll from '../../../hooks/useHorizontalScroll'; import useHorizontalScroll from '../../../hooks/useHorizontalScroll';
import useLang from '../../../hooks/useLang';
import useLastCallback from '../../../hooks/useLastCallback'; import useLastCallback from '../../../hooks/useLastCallback';
import useOldLang from '../../../hooks/useOldLang'; import useOldLang from '../../../hooks/useOldLang';
@ -37,6 +38,7 @@ import Menu from '../../ui/Menu';
import MenuItem from '../../ui/MenuItem'; import MenuItem from '../../ui/MenuItem';
import Modal from '../../ui/Modal'; import Modal from '../../ui/Modal';
import MinimizedWebAppModal from './MinimizedWebAppModal'; import MinimizedWebAppModal from './MinimizedWebAppModal';
import MoreAppsTabContent from './MoreAppsTabContent';
import WebAppModalTabContent from './WebAppModalTabContent'; import WebAppModalTabContent from './WebAppModalTabContent';
import styles from './WebAppModal.module.scss'; import styles from './WebAppModal.module.scss';
@ -77,6 +79,8 @@ const WebAppModal: FC<OwnProps & StateProps> = ({
changeWebAppModalState, changeWebAppModalState,
openWebAppTab, openWebAppTab,
updateWebApp, updateWebApp,
openMoreAppsTab,
closeMoreAppsTab,
} = getActions(); } = getActions();
const maximizedStateSize = useMemo(() => { const maximizedStateSize = useMemo(() => {
@ -94,7 +98,7 @@ const WebAppModal: FC<OwnProps & StateProps> = ({
} }
const { const {
openedWebApps, activeWebApp, openedOrderedKeys, sessionKeys, openedWebApps, activeWebApp, openedOrderedKeys, sessionKeys, isMoreAppsTabActive,
} = modal || {}; } = modal || {};
const { const {
isBackButtonVisible, headerColor, backgroundColor, isSettingsButtonVisible, isBackButtonVisible, headerColor, backgroundColor, isSettingsButtonVisible,
@ -168,7 +172,8 @@ const WebAppModal: FC<OwnProps & StateProps> = ({
} }
}, [currentWidth, currentHeight, isMaximizedState, minimizedStateSize, setFrameSize]); }, [currentWidth, currentHeight, isMaximizedState, minimizedStateSize, setFrameSize]);
const lang = useOldLang(); const oldLang = useOldLang();
const lang = useLang();
const { const {
queryId, queryId,
} = activeWebApp || {}; } = activeWebApp || {};
@ -229,6 +234,10 @@ const WebAppModal: FC<OwnProps & StateProps> = ({
closeWebAppModal(); closeWebAppModal();
}); });
const handleCloseMoreAppsTab = useLastCallback(() => {
closeMoreAppsTab();
});
const handleTabClose = useLastCallback(() => { const handleTabClose = useLastCallback(() => {
if (openTabsCount > 1) { if (openTabsCount > 1) {
closeActiveWebApp(); closeActiveWebApp();
@ -268,6 +277,10 @@ const WebAppModal: FC<OwnProps & StateProps> = ({
changeWebAppModalState(); changeWebAppModalState();
}); });
const handleOpenMoreAppsTabClick = useLastCallback(() => {
openMoreAppsTab();
});
const handleTabClick = useLastCallback((tab: WebAppModalTab) => { const handleTabClick = useLastCallback((tab: WebAppModalTab) => {
openWebAppTab({ webApp: tab.webApp }); openWebAppTab({ webApp: tab.webApp });
}); });
@ -304,12 +317,12 @@ const WebAppModal: FC<OwnProps & StateProps> = ({
return ( return (
<> <>
{chat && bot && chat.id !== bot.id && ( {chat && bot && chat.id !== bot.id && (
<MenuItem icon="bots" onClick={openBotChat}>{lang('BotWebViewOpenBot')}</MenuItem> <MenuItem icon="bots" onClick={openBotChat}>{oldLang('BotWebViewOpenBot')}</MenuItem>
)} )}
<MenuItem icon="reload" onClick={handleRefreshClick}>{lang('WebApp.ReloadPage')}</MenuItem> <MenuItem icon="reload" onClick={handleRefreshClick}>{oldLang('WebApp.ReloadPage')}</MenuItem>
{isSettingsButtonVisible && ( {isSettingsButtonVisible && (
<MenuItem icon="settings" onClick={handleSettingsButtonClick}> <MenuItem icon="settings" onClick={handleSettingsButtonClick}>
{lang('Settings')} {oldLang('Settings')}
</MenuItem> </MenuItem>
)} )}
{bot?.isAttachBot && ( {bot?.isAttachBot && (
@ -318,7 +331,7 @@ const WebAppModal: FC<OwnProps & StateProps> = ({
onClick={handleToggleClick} onClick={handleToggleClick}
destructive={Boolean(attachBot)} destructive={Boolean(attachBot)}
> >
{lang(attachBot ? 'WebApp.RemoveBot' : 'WebApp.AddToAttachmentAdd')} {oldLang(attachBot ? 'WebApp.RemoveBot' : 'WebApp.AddToAttachmentAdd')}
</MenuItem> </MenuItem>
)} )}
</> </>
@ -368,12 +381,13 @@ const WebAppModal: FC<OwnProps & StateProps> = ({
); );
const headerTextVar = useMemo(() => { const headerTextVar = useMemo(() => {
if (isMoreAppsTabActive) return 'color-text';
if (!headerColor) return undefined; if (!headerColor) return undefined;
const { r, g, b } = hexToRgb(headerColor); const { r, g, b } = hexToRgb(headerColor);
const luma = getColorLuma([r, g, b]); const luma = getColorLuma([r, g, b]);
const adaptedLuma = theme === 'dark' ? 255 - luma : luma; const adaptedLuma = theme === 'dark' ? 255 - luma : luma;
return adaptedLuma > LUMA_THRESHOLD ? 'color-text' : 'color-background'; return adaptedLuma > LUMA_THRESHOLD ? 'color-text' : 'color-background';
}, [headerColor, theme]); }, [headerColor, theme, isMoreAppsTabActive]);
function renderTabCurveBorder(className: string) { function renderTabCurveBorder(className: string) {
return ( return (
@ -422,7 +436,7 @@ const WebAppModal: FC<OwnProps & StateProps> = ({
round round
color="translucent" color="translucent"
size="tiny" size="tiny"
ariaLabel={lang('Close')} ariaLabel={oldLang('Close')}
onClick={handleTabClose} onClick={handleTabClose}
> >
<Icon className={styles.tabCloseIcon} name="close" /> <Icon className={styles.tabCloseIcon} name="close" />
@ -433,6 +447,53 @@ const WebAppModal: FC<OwnProps & StateProps> = ({
); );
} }
function renderMoreAppsTab() {
return (
<div
className={styles.tabButtonWrapper}
>
{renderTabCurveBorder(styles.tabButtonLeftCurve)}
<div
className={styles.tabButton}
>
<div className={styles.moreAppsTabIcon}>
<Icon className={styles.icon} name="add" />
</div>
{lang('OpenApp')}
<div className={styles.tabRightMask} />
<Button
className={styles.tabCloseButton}
round
color="translucent"
size="tiny"
ariaLabel={oldLang('Close')}
onClick={handleCloseMoreAppsTab}
>
<Icon className={styles.tabCloseIcon} name="close" />
</Button>
</div>
{renderTabCurveBorder(styles.tabButtonRightCurve)}
</div>
);
}
function renderMoreAppsButton() {
return (
<Button
className={buildClassName(
styles.moreAppsButton,
'no-drag',
)}
round
color="translucent"
size="tiny"
onClick={handleOpenMoreAppsTabClick}
>
<Icon className={styles.icon} name="add" />
</Button>
);
}
// eslint-disable-next-line no-null/no-null // eslint-disable-next-line no-null/no-null
const containerRef = useRef<HTMLDivElement>(null); const containerRef = useRef<HTMLDivElement>(null);
useHorizontalScroll(containerRef, !isOpen || !isMaximizedState || !(containerRef.current)); useHorizontalScroll(containerRef, !isOpen || !isMaximizedState || !(containerRef.current));
@ -456,6 +517,8 @@ const WebAppModal: FC<OwnProps & StateProps> = ({
/> />
) )
))} ))}
{isMoreAppsTabActive && renderMoreAppsTab()}
{!isMoreAppsTabActive && renderMoreAppsButton()}
</div> </div>
); );
} }
@ -488,7 +551,7 @@ const WebAppModal: FC<OwnProps & StateProps> = ({
round round
color="translucent" color="translucent"
size="tiny" size="tiny"
ariaLabel={lang(isBackButtonVisible ? 'Back' : 'Close')} ariaLabel={oldLang(isBackButtonVisible ? 'Back' : 'Close')}
onClick={handleBackClick} onClick={handleBackClick}
> >
<div className={backButtonClassName} /> <div className={backButtonClassName} />
@ -496,15 +559,6 @@ const WebAppModal: FC<OwnProps & StateProps> = ({
{renderTabs()} {renderTabs()}
{renderMoreMenu()} {renderMoreMenu()}
{/* <Button
round
color="translucent"
size="tiny"
>
<Icon className={styles.icon} name="add" />
</Button>
*/}
<Button <Button
className={buildClassName( className={buildClassName(
styles.windowStateButton, styles.windowStateButton,
@ -534,13 +588,13 @@ const WebAppModal: FC<OwnProps & StateProps> = ({
round round
color="translucent" color="translucent"
size="smaller" size="smaller"
ariaLabel={lang(isBackButtonVisible ? 'Back' : 'Close')} ariaLabel={oldLang(isBackButtonVisible ? 'Back' : 'Close')}
onClick={handleBackClick} onClick={handleBackClick}
> >
<div className={backButtonClassName} /> <div className={backButtonClassName} />
</Button> </Button>
<div className="modal-title">{attachBot?.shortName ?? bot?.firstName}</div> <div className="modal-title">{attachBot?.shortName ?? bot?.firstName}</div>
{renderDropdownMoreMenu()} {!isMoreAppsTabActive && renderDropdownMoreMenu()}
</div> </div>
); );
} }
@ -574,6 +628,7 @@ const WebAppModal: FC<OwnProps & StateProps> = ({
isMultiTabSupported={supportMultiTabMode} isMultiTabSupported={supportMultiTabMode}
/> />
))} ))}
{ isMoreAppsTabActive && (<MoreAppsTabContent />)}
</Modal> </Modal>
); );
}; };

View File

@ -888,6 +888,43 @@ addActionHandler('closeActiveWebApp', (global, actions, payload): ActionReturnTy
return global; return global;
}); });
addActionHandler('openMoreAppsTab', (global, actions, payload): ActionReturnType => {
const { tabId = getCurrentTabId() } = payload || {};
const tabState = selectTabState(global, tabId);
global = updateTabState(global, {
webApps: {
...tabState.webApps,
activeWebApp: undefined,
isMoreAppsTabActive: true,
},
}, tabId);
return global;
});
addActionHandler('closeMoreAppsTab', (global, actions, payload): ActionReturnType => {
const { tabId = getCurrentTabId() } = payload || {};
const tabState = selectTabState(global, tabId);
const openedWebApps = tabState.webApps.openedWebApps;
const openedWebAppsValues = Object.values(openedWebApps);
const openedWebAppsCount = openedWebAppsValues.length;
global = updateTabState(global, {
webApps: {
...tabState.webApps,
isMoreAppsTabActive: false,
activeWebApp: openedWebAppsCount ? openedWebAppsValues[openedWebAppsCount - 1] : undefined,
isModalOpen: openedWebAppsCount > 0,
},
}, tabId);
return global;
});
addActionHandler('closeWebApp', (global, actions, payload): ActionReturnType => { addActionHandler('closeWebApp', (global, actions, payload): ActionReturnType => {
const { webApp, skipClosingConfirmation, tabId = getCurrentTabId() } = payload || {}; const { webApp, skipClosingConfirmation, tabId = getCurrentTabId() } = payload || {};

View File

@ -346,6 +346,7 @@ export const INITIAL_TAB_STATE: TabState = {
sessionKeys: [], sessionKeys: [],
modalState: 'maximized', modalState: 'maximized',
isModalOpen: false, isModalOpen: false,
isMoreAppsTabActive: false,
}, },
globalSearch: {}, globalSearch: {},

View File

@ -90,6 +90,7 @@ export function activateWebAppIfOpen<T extends GlobalState>(
global = updateTabState(global, { global = updateTabState(global, {
webApps: { webApps: {
...currentTabState.webApps, ...currentTabState.webApps,
isMoreAppsTabActive: false,
activeWebApp: newActiveWebApp, activeWebApp: newActiveWebApp,
modalState: 'maximized', modalState: 'maximized',
}, },
@ -120,6 +121,7 @@ export function addWebAppToOpenList<T extends GlobalState>(
webApps: { webApps: {
...currentTabState.webApps, ...currentTabState.webApps,
...makeActive && { activeWebApp: webApp }, ...makeActive && { activeWebApp: webApp },
isMoreAppsTabActive: false,
isModalOpen: openModalIfNotOpen, isModalOpen: openModalIfNotOpen,
modalState: 'maximized', modalState: 'maximized',
openedWebApps: { openedWebApps: {
@ -234,6 +236,7 @@ export function clearOpenedWebApps<T extends GlobalState>(
webApps: { webApps: {
...currentTabState.webApps, ...currentTabState.webApps,
activeWebApp: newActiveWebApp, activeWebApp: newActiveWebApp,
isMoreAppsTabActive: false,
openedWebApps: webAppsNotAllowedToClose, openedWebApps: webAppsNotAllowedToClose,
openedOrderedKeys: newOpenedKeys, openedOrderedKeys: newOpenedKeys,
}, },

View File

@ -697,6 +697,7 @@ export type TabState = {
openedWebApps: Record<string, WebApp>; openedWebApps: Record<string, WebApp>;
modalState : WebAppModalStateType; modalState : WebAppModalStateType;
isModalOpen: boolean; isModalOpen: boolean;
isMoreAppsTabActive: boolean;
}; };
botTrustRequest?: { botTrustRequest?: {
@ -3241,6 +3242,8 @@ export interface ActionPayloads {
usernames: string[]; usernames: string[];
}; };
closeActiveWebApp: WithTabId | undefined; closeActiveWebApp: WithTabId | undefined;
openMoreAppsTab: WithTabId | undefined;
closeMoreAppsTab: WithTabId | undefined;
closeWebApp: { closeWebApp: {
webApp: WebApp; webApp: WebApp;
skipClosingConfirmation?: boolean; skipClosingConfirmation?: boolean;

View File

@ -1,15 +1,15 @@
import { useEffect, useState } from '../lib/teact/teact'; import { useEffect, useState } from '../lib/teact/teact';
import type { ApiChat } from '../api/types'; import type { ApiPeer } from '../api/types';
import { ApiMediaFormat } from '../api/types'; import { ApiMediaFormat } from '../api/types';
import { getChatAvatarHash } from '../global/helpers'; import { getChatAvatarHash } from '../global/helpers';
import { getAverageColor, rgb2hex } from '../util/colors'; import { getAverageColor, rgb2hex } from '../util/colors';
import useMedia from './useMedia'; import useMedia from './useMedia';
function useAverageColor(chat: ApiChat, fallbackColor = '#00000000') { function useAverageColor(peer: ApiPeer, fallbackColor = '#00000000') {
const [color, setColor] = useState(fallbackColor); const [color, setColor] = useState(fallbackColor);
const imgBlobUrl = useMedia(getChatAvatarHash(chat), false, ApiMediaFormat.BlobUrl); const imgBlobUrl = useMedia(getChatAvatarHash(peer), false, ApiMediaFormat.BlobUrl);
useEffect(() => { useEffect(() => {
(async () => { (async () => {

View File

@ -1141,6 +1141,10 @@ export interface LangPair {
'StarGiftAvailability': undefined; 'StarGiftAvailability': undefined;
'StarsSubscribeInfoLinkText': undefined; 'StarsSubscribeInfoLinkText': undefined;
'StarsSubscribeInfoLink': undefined; 'StarsSubscribeInfoLink': undefined;
'OpenApp': undefined;
'PopularApps': undefined;
'SearchApps': undefined;
'Apps': undefined;
'AreYouSureCloseMiniApps': undefined; 'AreYouSureCloseMiniApps': undefined;
'CloseMiniApps': undefined; 'CloseMiniApps': undefined;
'DoNotAskAgain': undefined; 'DoNotAskAgain': undefined;