MiniApps: More apps tab (#5137)
This commit is contained in:
parent
c0be156229
commit
92ebef4f49
@ -1370,6 +1370,10 @@
|
||||
"StarsSubscribeInfoLinkText" = "Terms of Service";
|
||||
"StarsSubscribeInfoLink" = "https://telegram.org/tos/stars";
|
||||
"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?";
|
||||
"CloseMiniApps" = "Close Mini Apps";
|
||||
"DoNotAskAgain" = "Don't ask again";
|
||||
|
||||
@ -18,6 +18,8 @@ type OwnProps = {
|
||||
badgeIcon?: IconName;
|
||||
className?: string;
|
||||
badgeClassName?: string;
|
||||
badgeIconClassName?: string;
|
||||
textClassName?: string;
|
||||
onClick?: NoneToVoidFunction;
|
||||
};
|
||||
|
||||
@ -28,6 +30,8 @@ const PeerBadge = ({
|
||||
badgeIcon,
|
||||
className,
|
||||
badgeClassName,
|
||||
badgeIconClassName,
|
||||
textClassName,
|
||||
onClick,
|
||||
}: OwnProps) => {
|
||||
return (
|
||||
@ -39,12 +43,12 @@ const PeerBadge = ({
|
||||
<Avatar size="large" peer={peer} />
|
||||
{badgeText && (
|
||||
<div className={buildClassName(styles.badge, badgeClassName)}>
|
||||
{badgeIcon && <Icon name={badgeIcon} />}
|
||||
{badgeIcon && <Icon name={badgeIcon} className={badgeIconClassName} />}
|
||||
{badgeText}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{text && <p className={styles.text}>{text}</p>}
|
||||
{text && <p className={buildClassName(styles.text, textClassName)}>{text}</p>}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@ -207,12 +207,14 @@
|
||||
.Link {
|
||||
float: right;
|
||||
color: var(--color-links);
|
||||
font-weight: 400;
|
||||
font-weight: 500;
|
||||
margin-right: 1.5rem;
|
||||
transition: opacity 0.15s ease-in;
|
||||
|
||||
&:focus,
|
||||
&:active,
|
||||
&:hover {
|
||||
text-decoration: underline;
|
||||
text-decoration: none;
|
||||
opacity: 0.85;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -67,7 +67,7 @@ const MinimizedWebAppModal = ({
|
||||
if (!isMinimizedState) return undefined;
|
||||
|
||||
function renderTitle() {
|
||||
const activeTabName = activeTabBot?.firstName;
|
||||
const activeTabName = peers.length > 0 && peers[0]?.firstName;
|
||||
const title = openedTabsCount && activeTabName && openedTabsCount > 1
|
||||
? `${lang('MiniAppsMoreTabs',
|
||||
{
|
||||
|
||||
49
src/components/modals/webApp/MoreAppsTabContent.module.scss
Normal file
49
src/components/modals/webApp/MoreAppsTabContent.module.scss
Normal 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;
|
||||
}
|
||||
}
|
||||
143
src/components/modals/webApp/MoreAppsTabContent.tsx
Normal file
143
src/components/modals/webApp/MoreAppsTabContent.tsx
Normal 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));
|
||||
39
src/components/modals/webApp/WebAppGridItem.module.scss
Normal file
39
src/components/modals/webApp/WebAppGridItem.module.scss
Normal 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;
|
||||
}
|
||||
87
src/components/modals/webApp/WebAppGridtem.tsx
Normal file
87
src/components/modals/webApp/WebAppGridtem.tsx
Normal 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));
|
||||
@ -161,7 +161,7 @@
|
||||
padding-left: 1rem;
|
||||
padding-right: 1rem;
|
||||
|
||||
font-size: 0.875rem;
|
||||
font-size: 1rem;
|
||||
font-weight: 500;
|
||||
text-overflow: ellipsis;
|
||||
|
||||
@ -220,6 +220,7 @@
|
||||
transform: scaleX(-1);
|
||||
}
|
||||
|
||||
.more-apps-tab-icon,
|
||||
.avatar-container {
|
||||
position: relative;
|
||||
display: flex;
|
||||
@ -227,6 +228,11 @@
|
||||
margin-right: 0.5rem;
|
||||
}
|
||||
|
||||
.more-apps-tab-icon {
|
||||
font-size: 1.5rem;
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
|
||||
.web-app-tab-more-menu {
|
||||
z-index: 1;
|
||||
position: absolute;
|
||||
@ -255,6 +261,7 @@
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
|
||||
.more-apps-button,
|
||||
.window-state-button,
|
||||
.header-button {
|
||||
width: 1.75rem !important;
|
||||
|
||||
@ -26,6 +26,7 @@ import useAppLayout from '../../../hooks/useAppLayout';
|
||||
import useContextMenuHandlers from '../../../hooks/useContextMenuHandlers';
|
||||
import useDraggable from '../../../hooks/useDraggable';
|
||||
import useHorizontalScroll from '../../../hooks/useHorizontalScroll';
|
||||
import useLang from '../../../hooks/useLang';
|
||||
import useLastCallback from '../../../hooks/useLastCallback';
|
||||
import useOldLang from '../../../hooks/useOldLang';
|
||||
|
||||
@ -37,6 +38,7 @@ import Menu from '../../ui/Menu';
|
||||
import MenuItem from '../../ui/MenuItem';
|
||||
import Modal from '../../ui/Modal';
|
||||
import MinimizedWebAppModal from './MinimizedWebAppModal';
|
||||
import MoreAppsTabContent from './MoreAppsTabContent';
|
||||
import WebAppModalTabContent from './WebAppModalTabContent';
|
||||
|
||||
import styles from './WebAppModal.module.scss';
|
||||
@ -77,6 +79,8 @@ const WebAppModal: FC<OwnProps & StateProps> = ({
|
||||
changeWebAppModalState,
|
||||
openWebAppTab,
|
||||
updateWebApp,
|
||||
openMoreAppsTab,
|
||||
closeMoreAppsTab,
|
||||
} = getActions();
|
||||
|
||||
const maximizedStateSize = useMemo(() => {
|
||||
@ -94,7 +98,7 @@ const WebAppModal: FC<OwnProps & StateProps> = ({
|
||||
}
|
||||
|
||||
const {
|
||||
openedWebApps, activeWebApp, openedOrderedKeys, sessionKeys,
|
||||
openedWebApps, activeWebApp, openedOrderedKeys, sessionKeys, isMoreAppsTabActive,
|
||||
} = modal || {};
|
||||
const {
|
||||
isBackButtonVisible, headerColor, backgroundColor, isSettingsButtonVisible,
|
||||
@ -168,7 +172,8 @@ const WebAppModal: FC<OwnProps & StateProps> = ({
|
||||
}
|
||||
}, [currentWidth, currentHeight, isMaximizedState, minimizedStateSize, setFrameSize]);
|
||||
|
||||
const lang = useOldLang();
|
||||
const oldLang = useOldLang();
|
||||
const lang = useLang();
|
||||
const {
|
||||
queryId,
|
||||
} = activeWebApp || {};
|
||||
@ -229,6 +234,10 @@ const WebAppModal: FC<OwnProps & StateProps> = ({
|
||||
closeWebAppModal();
|
||||
});
|
||||
|
||||
const handleCloseMoreAppsTab = useLastCallback(() => {
|
||||
closeMoreAppsTab();
|
||||
});
|
||||
|
||||
const handleTabClose = useLastCallback(() => {
|
||||
if (openTabsCount > 1) {
|
||||
closeActiveWebApp();
|
||||
@ -268,6 +277,10 @@ const WebAppModal: FC<OwnProps & StateProps> = ({
|
||||
changeWebAppModalState();
|
||||
});
|
||||
|
||||
const handleOpenMoreAppsTabClick = useLastCallback(() => {
|
||||
openMoreAppsTab();
|
||||
});
|
||||
|
||||
const handleTabClick = useLastCallback((tab: WebAppModalTab) => {
|
||||
openWebAppTab({ webApp: tab.webApp });
|
||||
});
|
||||
@ -304,12 +317,12 @@ const WebAppModal: FC<OwnProps & StateProps> = ({
|
||||
return (
|
||||
<>
|
||||
{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 && (
|
||||
<MenuItem icon="settings" onClick={handleSettingsButtonClick}>
|
||||
{lang('Settings')}
|
||||
{oldLang('Settings')}
|
||||
</MenuItem>
|
||||
)}
|
||||
{bot?.isAttachBot && (
|
||||
@ -318,7 +331,7 @@ const WebAppModal: FC<OwnProps & StateProps> = ({
|
||||
onClick={handleToggleClick}
|
||||
destructive={Boolean(attachBot)}
|
||||
>
|
||||
{lang(attachBot ? 'WebApp.RemoveBot' : 'WebApp.AddToAttachmentAdd')}
|
||||
{oldLang(attachBot ? 'WebApp.RemoveBot' : 'WebApp.AddToAttachmentAdd')}
|
||||
</MenuItem>
|
||||
)}
|
||||
</>
|
||||
@ -368,12 +381,13 @@ const WebAppModal: FC<OwnProps & StateProps> = ({
|
||||
);
|
||||
|
||||
const headerTextVar = useMemo(() => {
|
||||
if (isMoreAppsTabActive) return 'color-text';
|
||||
if (!headerColor) return undefined;
|
||||
const { r, g, b } = hexToRgb(headerColor);
|
||||
const luma = getColorLuma([r, g, b]);
|
||||
const adaptedLuma = theme === 'dark' ? 255 - luma : luma;
|
||||
return adaptedLuma > LUMA_THRESHOLD ? 'color-text' : 'color-background';
|
||||
}, [headerColor, theme]);
|
||||
}, [headerColor, theme, isMoreAppsTabActive]);
|
||||
|
||||
function renderTabCurveBorder(className: string) {
|
||||
return (
|
||||
@ -422,7 +436,7 @@ const WebAppModal: FC<OwnProps & StateProps> = ({
|
||||
round
|
||||
color="translucent"
|
||||
size="tiny"
|
||||
ariaLabel={lang('Close')}
|
||||
ariaLabel={oldLang('Close')}
|
||||
onClick={handleTabClose}
|
||||
>
|
||||
<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
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
useHorizontalScroll(containerRef, !isOpen || !isMaximizedState || !(containerRef.current));
|
||||
@ -456,6 +517,8 @@ const WebAppModal: FC<OwnProps & StateProps> = ({
|
||||
/>
|
||||
)
|
||||
))}
|
||||
{isMoreAppsTabActive && renderMoreAppsTab()}
|
||||
{!isMoreAppsTabActive && renderMoreAppsButton()}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@ -488,7 +551,7 @@ const WebAppModal: FC<OwnProps & StateProps> = ({
|
||||
round
|
||||
color="translucent"
|
||||
size="tiny"
|
||||
ariaLabel={lang(isBackButtonVisible ? 'Back' : 'Close')}
|
||||
ariaLabel={oldLang(isBackButtonVisible ? 'Back' : 'Close')}
|
||||
onClick={handleBackClick}
|
||||
>
|
||||
<div className={backButtonClassName} />
|
||||
@ -496,15 +559,6 @@ const WebAppModal: FC<OwnProps & StateProps> = ({
|
||||
{renderTabs()}
|
||||
{renderMoreMenu()}
|
||||
|
||||
{/* <Button
|
||||
round
|
||||
color="translucent"
|
||||
size="tiny"
|
||||
>
|
||||
<Icon className={styles.icon} name="add" />
|
||||
</Button>
|
||||
*/}
|
||||
|
||||
<Button
|
||||
className={buildClassName(
|
||||
styles.windowStateButton,
|
||||
@ -534,13 +588,13 @@ const WebAppModal: FC<OwnProps & StateProps> = ({
|
||||
round
|
||||
color="translucent"
|
||||
size="smaller"
|
||||
ariaLabel={lang(isBackButtonVisible ? 'Back' : 'Close')}
|
||||
ariaLabel={oldLang(isBackButtonVisible ? 'Back' : 'Close')}
|
||||
onClick={handleBackClick}
|
||||
>
|
||||
<div className={backButtonClassName} />
|
||||
</Button>
|
||||
<div className="modal-title">{attachBot?.shortName ?? bot?.firstName}</div>
|
||||
{renderDropdownMoreMenu()}
|
||||
{!isMoreAppsTabActive && renderDropdownMoreMenu()}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@ -574,6 +628,7 @@ const WebAppModal: FC<OwnProps & StateProps> = ({
|
||||
isMultiTabSupported={supportMultiTabMode}
|
||||
/>
|
||||
))}
|
||||
{ isMoreAppsTabActive && (<MoreAppsTabContent />)}
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
@ -888,6 +888,43 @@ addActionHandler('closeActiveWebApp', (global, actions, payload): ActionReturnTy
|
||||
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 => {
|
||||
const { webApp, skipClosingConfirmation, tabId = getCurrentTabId() } = payload || {};
|
||||
|
||||
|
||||
@ -346,6 +346,7 @@ export const INITIAL_TAB_STATE: TabState = {
|
||||
sessionKeys: [],
|
||||
modalState: 'maximized',
|
||||
isModalOpen: false,
|
||||
isMoreAppsTabActive: false,
|
||||
},
|
||||
|
||||
globalSearch: {},
|
||||
|
||||
@ -90,6 +90,7 @@ export function activateWebAppIfOpen<T extends GlobalState>(
|
||||
global = updateTabState(global, {
|
||||
webApps: {
|
||||
...currentTabState.webApps,
|
||||
isMoreAppsTabActive: false,
|
||||
activeWebApp: newActiveWebApp,
|
||||
modalState: 'maximized',
|
||||
},
|
||||
@ -120,6 +121,7 @@ export function addWebAppToOpenList<T extends GlobalState>(
|
||||
webApps: {
|
||||
...currentTabState.webApps,
|
||||
...makeActive && { activeWebApp: webApp },
|
||||
isMoreAppsTabActive: false,
|
||||
isModalOpen: openModalIfNotOpen,
|
||||
modalState: 'maximized',
|
||||
openedWebApps: {
|
||||
@ -234,6 +236,7 @@ export function clearOpenedWebApps<T extends GlobalState>(
|
||||
webApps: {
|
||||
...currentTabState.webApps,
|
||||
activeWebApp: newActiveWebApp,
|
||||
isMoreAppsTabActive: false,
|
||||
openedWebApps: webAppsNotAllowedToClose,
|
||||
openedOrderedKeys: newOpenedKeys,
|
||||
},
|
||||
|
||||
@ -697,6 +697,7 @@ export type TabState = {
|
||||
openedWebApps: Record<string, WebApp>;
|
||||
modalState : WebAppModalStateType;
|
||||
isModalOpen: boolean;
|
||||
isMoreAppsTabActive: boolean;
|
||||
};
|
||||
|
||||
botTrustRequest?: {
|
||||
@ -3241,6 +3242,8 @@ export interface ActionPayloads {
|
||||
usernames: string[];
|
||||
};
|
||||
closeActiveWebApp: WithTabId | undefined;
|
||||
openMoreAppsTab: WithTabId | undefined;
|
||||
closeMoreAppsTab: WithTabId | undefined;
|
||||
closeWebApp: {
|
||||
webApp: WebApp;
|
||||
skipClosingConfirmation?: boolean;
|
||||
|
||||
@ -1,15 +1,15 @@
|
||||
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 { getChatAvatarHash } from '../global/helpers';
|
||||
import { getAverageColor, rgb2hex } from '../util/colors';
|
||||
import useMedia from './useMedia';
|
||||
|
||||
function useAverageColor(chat: ApiChat, fallbackColor = '#00000000') {
|
||||
function useAverageColor(peer: ApiPeer, fallbackColor = '#00000000') {
|
||||
const [color, setColor] = useState(fallbackColor);
|
||||
const imgBlobUrl = useMedia(getChatAvatarHash(chat), false, ApiMediaFormat.BlobUrl);
|
||||
const imgBlobUrl = useMedia(getChatAvatarHash(peer), false, ApiMediaFormat.BlobUrl);
|
||||
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
|
||||
4
src/types/language.d.ts
vendored
4
src/types/language.d.ts
vendored
@ -1141,6 +1141,10 @@ export interface LangPair {
|
||||
'StarGiftAvailability': undefined;
|
||||
'StarsSubscribeInfoLinkText': undefined;
|
||||
'StarsSubscribeInfoLink': undefined;
|
||||
'OpenApp': undefined;
|
||||
'PopularApps': undefined;
|
||||
'SearchApps': undefined;
|
||||
'Apps': undefined;
|
||||
'AreYouSureCloseMiniApps': undefined;
|
||||
'CloseMiniApps': undefined;
|
||||
'DoNotAskAgain': undefined;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user