Skip to content

Commit 68157c0

Browse files
committed
feat: webapp botapi 9.0
1 parent 79fdbfb commit 68157c0

38 files changed

+2801
-725
lines changed

‎src/components/browser.tsx

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -510,8 +510,9 @@ function Browser(props: {
510510
<div
511511
class={styles.BrowserBody}
512512
style={{
513-
width: movableState().width + 'px',
514-
height: movableState().height - additionalHeight + 'px'
513+
'width': movableState().width + 'px',
514+
'--browser-width': movableState().width + 'px',
515+
'height': movableState().height - additionalHeight + 'px'
515516
}}
516517
>
517518
<For each={state.pages}>{(page) => {
@@ -608,7 +609,6 @@ export async function openWebAppInAppBrowser(options: WebAppLaunchOptions) {
608609
header: document.createElement('div'),
609610
title: document.createElement('div'),
610611
body: document.createElement('div'),
611-
footer: document.createElement('div'),
612612
forceHide: () => setDestroy(true),
613613
onBackStatus: setNeedBackButton
614614
});
@@ -631,12 +631,7 @@ export async function openWebAppInAppBrowser(options: WebAppLaunchOptions) {
631631
menuButtons: webApp.getMenuButtons(),
632632
dispose,
633633
isConfirmationNeededOnClose: webApp.isConfirmationNeededOnClose,
634-
content: (
635-
<>
636-
{webApp.body}
637-
{webApp.footer}
638-
</>
639-
),
634+
content: webApp.body,
640635
get needBackButton() {
641636
return needBackButton();
642637
},
@@ -660,6 +655,10 @@ export async function openWebAppInAppBrowser(options: WebAppLaunchOptions) {
660655
} else {
661656
openInAppBrowser(initialState);
662657
}
658+
659+
createEffect(on(() => lastContext[0].collapsed, (collapsed) => {
660+
webApp.notifyVisible(!collapsed);
661+
}))
663662
});
664663
}
665664

Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
import {createEffect, createResource, For, JSX, on} from 'solid-js';
2+
import classNames from '../../../helpers/string/classNames';
3+
import {MessageEntity, ReplyMarkup} from '../../../layer';
4+
import {generateTail} from '../bubbles';
5+
import {I18nTsx} from '../../../helpers/solid/i18n';
6+
import wrapRichText from '../../../lib/richTextProcessor/wrapRichText';
7+
import rootScope from '../../../lib/rootScope';
8+
import wrapKeyboardButton from '../../wrappers/keyboardButton';
9+
import ripple from '../../ripple';
10+
11+
function ViaUsername(props: { botId: BotId }) {
12+
const [resource, ctx] = createResource(async() => {
13+
const via = document.createElement('span');
14+
via.innerText = '@' + (await rootScope.managers.appPeersManager.getPeerUsername(props.botId.toPeerId()));
15+
via.classList.add('peer-title');
16+
return via
17+
})
18+
19+
createEffect(on(() => props.botId, () => ctx.refetch()));
20+
21+
return <>{resource()}</>;
22+
}
23+
24+
export function BubbleLayout(props: {
25+
class?: string
26+
contentStyle?: Record<string, string>
27+
text?: string
28+
textEntities?: MessageEntity[]
29+
content?: JSX.Element
30+
tail?: boolean
31+
out?: boolean
32+
justMedia?: boolean
33+
group?: 'first' | 'last' | 'single'
34+
via?: BotId
35+
attachment?: JSX.Element
36+
replyMarkup?: ReplyMarkup.replyInlineMarkup
37+
}) {
38+
const renderVia = () => (
39+
<span class="is-via">
40+
<I18nTsx key="ViaBot" />
41+
{' '}
42+
<ViaUsername botId={props.via.toPeerId()} />
43+
</span>
44+
)
45+
46+
return (
47+
<div
48+
class={classNames(
49+
'bubble',
50+
props.tail && 'can-have-tail',
51+
props.out && 'is-out',
52+
(props.group === 'first' || props.group === 'single') && 'is-group-first',
53+
(props.group === 'last' || props.group === 'single') && 'is-group-last',
54+
props.via && 'must-have-name',
55+
props.justMedia && 'just-media',
56+
props.replyMarkup && 'with-reply-markup',
57+
props.class
58+
)}
59+
>
60+
<div class="bubble-content-wrapper">
61+
<div class="bubble-content" style={props.contentStyle}>
62+
{props.justMedia ? (
63+
<div class="floating-part name-with-reply" dir="auto">
64+
<div class="name">
65+
{renderVia()}
66+
</div>
67+
</div>
68+
) : (
69+
<div
70+
class={classNames(
71+
'name floating-part',
72+
!props.attachment && 'next-is-message'
73+
)}
74+
dir="auto"
75+
>
76+
{renderVia()}
77+
</div>
78+
)}
79+
{props.attachment}
80+
{(props.text || props.content) && (
81+
<div class={classNames('message spoilers-container', props.attachment && 'mt-shorter')}>
82+
{props.content ?? wrapRichText(props.text, {entities: props.textEntities})}
83+
</div>
84+
)}
85+
{props.tail && generateTail()}
86+
</div>
87+
88+
{props.replyMarkup && (
89+
<div class="reply-markup">
90+
<For each={props.replyMarkup.rows}>
91+
{(row, rowIdx) => {
92+
return (
93+
<div class="reply-markup-row">
94+
<For each={row.buttons}>
95+
{(button, btnIdx) => {
96+
const element = () => {
97+
const {buttonEl, text, buttonIcon} = wrapKeyboardButton({
98+
button,
99+
chat: {} as any
100+
});
101+
102+
if(rowIdx() === props.replyMarkup.rows.length - 1) {
103+
if(btnIdx() === 0) buttonEl.classList.add('is-first');
104+
if(btnIdx() === row.buttons.length - 1) buttonEl.classList.add('is-last');
105+
}
106+
107+
buttonEl.classList.add('reply-markup-button', 'rp');
108+
109+
const t = document.createElement('span');
110+
t.classList.add('reply-markup-button-text');
111+
t.append(text);
112+
113+
ripple(buttonEl);
114+
buttonEl.append(...[buttonIcon, t]);
115+
116+
return buttonEl
117+
}
118+
119+
return <>{element()}</>;
120+
}}
121+
</For>
122+
</div>
123+
);
124+
}}
125+
</For>
126+
</div>
127+
)}
128+
</div>
129+
</div>
130+
)
131+
}

‎src/components/chat/bubbles/fakeBubbles.module.scss

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@
22
position: relative;
33
--message-highlighting-color: hsla(85.5319, 36.9171%, 40.402%, .4);
44
--message-highlighting-hover-color: rgba(var(--message-highlighting-color-rgb), calc(var(--message-highlighting-alpha) + .24));
5+
--message-status-color: var(--secondary-text-color);
6+
--message-out-status-color: var(--message-out-primary-color);
7+
--message-out-icon-text-color: var(--message-out-background-color);
58

69
:global(.bubble-content-wrapper) {
710
align-items: center;

‎src/components/chat/bubbles/fakeBubbles.tsx

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,27 @@
1-
import {JSX} from 'solid-js';
1+
import {JSX, onMount} from 'solid-js';
22
import {ChatBackground} from './chatBackground';
33
import rootScope from '../../../lib/rootScope';
44
import themeController from '../../../helpers/themeController';
55

66
import classNames from '../../../helpers/string/classNames';
77
import styles from './fakeBubbles.module.scss';
8+
import {subscribeOn} from '../../../helpers/solid/subscribeOn';
89

910
export function FakeBubbles(props: {
1011
children: JSX.Element
1112
class?: string
13+
contentClass?: string
1214
peerId?: PeerId
1315
}) {
1416
let container!: HTMLDivElement;
1517

18+
onMount(() => {
19+
themeController.applyTheme(themeController.getTheme(), container);
20+
subscribeOn(rootScope)('theme_changed', () => {
21+
themeController.applyTheme(themeController.getTheme(), container);
22+
})
23+
})
24+
1625
return (
1726
<div ref={container} class={classNames(styles.root, props.class)}>
1827
<ChatBackground
@@ -21,7 +30,7 @@ export function FakeBubbles(props: {
2130
peerId={props.peerId}
2231
onHighlightColor={hsla => themeController.applyHighlightingColor({hsla, element: container})}
2332
/>
24-
<div class={/* @once */ styles.content}>
33+
<div class={classNames(styles.content, props.contentClass)}>
2534
{props.children}
2635
</div>
2736
</div>

‎src/components/peerProfile.ts

Lines changed: 62 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,10 @@ export default class PeerProfile {
9999
private personalChannel: Row;
100100
private personalChannelCounter: HTMLSpanElement;
101101

102+
private botPermissionsSection: SettingSection;
103+
private botPermissionsEmojiStatus: Row;
104+
private botPermissionsLocation: Row;
105+
102106
private bioLanguage: Promise<TranslatableLanguageISO>;
103107
private bioText: string;
104108

@@ -388,7 +392,47 @@ export default class PeerProfile {
388392
this.section.content.append(this.notifications.container);
389393
}
390394

391-
this.element.append(this.personalChannelSection.container, this.section.container);
395+
this.botPermissionsSection = new SettingSection({
396+
name: i18n('BotAllowAccessTo'),
397+
noDelimiter: true
398+
});
399+
this.botPermissionsEmojiStatus = new Row({
400+
checkboxField: new CheckboxField({toggle: true}),
401+
titleLangKey: 'BotAllowAccessToEmojiStatus',
402+
icon: 'smile',
403+
listenerSetter: this.listenerSetter
404+
})
405+
this.botPermissionsLocation = new Row({
406+
checkboxField: new CheckboxField({toggle: true}),
407+
titleLangKey: 'BotAllowAccessToLocation',
408+
icon: 'location',
409+
listenerSetter: this.listenerSetter
410+
})
411+
412+
listenerSetter.add(this.botPermissionsEmojiStatus.checkboxField.input)('change', (e) => {
413+
if(!e.isTrusted) {
414+
return;
415+
}
416+
this.managers.appBotsManager.toggleEmojiStatusPermission(this.peerId, this.botPermissionsEmojiStatus.checkboxField.checked);
417+
});
418+
419+
listenerSetter.add(this.botPermissionsLocation.checkboxField.input)('change', (e) => {
420+
if(!e.isTrusted) {
421+
return;
422+
}
423+
this.managers.appBotsManager.writeBotInternalStorage(this.peerId, 'locationPermission', String(this.botPermissionsLocation.checkboxField.checked));
424+
});
425+
426+
this.botPermissionsSection.content.append(
427+
this.botPermissionsEmojiStatus.container,
428+
this.botPermissionsLocation.container
429+
);
430+
431+
this.element.append(
432+
this.personalChannelSection.container,
433+
this.section.container,
434+
this.botPermissionsSection.container,
435+
);
392436

393437
if(IS_PARALLAX_SUPPORTED) {
394438
this.element.append(generateDelimiter());
@@ -544,7 +588,8 @@ export default class PeerProfile {
544588
this.link,
545589
this.businessHours,
546590
this.businessLocation,
547-
this.personalChannelSection
591+
this.personalChannelSection,
592+
this.botPermissionsSection
548593
].forEach((row) => {
549594
row.container.style.display = 'none';
550595
});
@@ -1005,6 +1050,21 @@ export default class PeerProfile {
10051050
callbacks.push(await m(this.fillPinnedGifts()));
10061051
}
10071052

1053+
if(peerFull._ === 'userFull' && peerFull.bot_info) {
1054+
const locationPermission = await m(this.managers.appBotsManager.readBotInternalStorage(peerId, 'locationPermission'));
1055+
if(peerFull.pFlags.bot_can_manage_emoji_status || locationPermission != null) {
1056+
callbacks.push(() => {
1057+
this.botPermissionsSection.container.style.display = '';
1058+
1059+
this.botPermissionsEmojiStatus.container.style.display = peerFull.pFlags.bot_can_manage_emoji_status ? '' : 'none';
1060+
this.botPermissionsEmojiStatus.checkboxField.checked = peerFull.pFlags.bot_can_manage_emoji_status;
1061+
1062+
this.botPermissionsLocation.container.style.display = locationPermission != null ? '' : 'none';
1063+
this.botPermissionsLocation.checkboxField.checked = locationPermission === 'true';
1064+
});
1065+
}
1066+
}
1067+
10081068
return () => {
10091069
callbacks.forEach((callback) => callback?.());
10101070
};

‎src/components/popups/index.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -446,6 +446,11 @@ export default class PopupElement<T extends EventListenerListeners = {}> extends
446446
this.addEventListener('closeAfterTimeout', dispose as any);
447447
}
448448

449+
protected appendSolidBody(callback: () => JSX.Element) {
450+
const dispose = render(callback, this.body);
451+
this.addEventListener('closeAfterTimeout', dispose as any);
452+
}
453+
449454
public static reAppend() {
450455
this.POPUPS.forEach((popup) => {
451456
const {element, container} = popup;

‎src/components/popups/sendGift.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,13 +37,13 @@ import maybe2x from '../../helpers/maybe2x';
3737
import {I18nTsx} from '../../helpers/solid/i18n';
3838
import {StarGiftBadge} from '../stargifts/stargiftBadge';
3939
import Scrollable from '../scrollable2';
40-
import {PeerTitleTsx} from '../wrappers/peerTitle';
4140
import {approxEquals} from '../../helpers/number/approxEquals';
4241
import getVisibleRect from '../../helpers/dom/getVisibleRect';
4342
import fastSmoothScroll from '../../helpers/fastSmoothScroll';
4443
import {useAppState} from '../../stores/appState';
4544
import PopupStarGiftUpgrade from './starGiftUpgrade';
4645
import anchorCallback from '../../helpers/dom/anchorCallback';
46+
import {PeerTitleTsx} from '../peerTitleTsx';
4747

4848
type GiftOption = MyStarGift | MyPremiumGiftOption;
4949

‎src/components/popups/starReaction.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
import PopupElement from '.';
88
import I18n, {i18n} from '../../lib/langPack';
9-
import wrapPeerTitle, {PeerTitleTsx} from '../wrappers/peerTitle';
9+
import wrapPeerTitle from '../wrappers/peerTitle';
1010
import {StarsBalance} from './stars';
1111
import {Accessor, createEffect, createMemo, createSignal, For, on, onCleanup, onMount, Show} from 'solid-js';
1212
import {easeOutCircApply} from '../../helpers/easing/easeOutCirc';
@@ -36,6 +36,7 @@ import {SEND_PAID_REACTION_ANONYMOUS_PEER_ID} from '../../lib/mtproto/mtproto_co
3636
import type Chat from '../chat/chat';
3737
import {PENDING_PAID_REACTIONS} from '../chat/reactions';
3838
import findAndSplice from '../../helpers/array/findAndSplice';
39+
import {PeerTitleTsx} from '../peerTitleTsx';
3940

4041
export default class PopupStarReaction extends PopupElement {
4142
constructor(private peerId: PeerId, private mid: number, private chat: Chat) {

‎src/components/popups/webApp.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
import PopupElement from '.';
88
import safeAssign from '../../helpers/object/safeAssign';
9+
import {fastRaf} from '../../helpers/schedulers';
910
import {AttachMenuBot} from '../../layer';
1011
import ButtonMenuToggle from '../buttonMenuToggle';
1112
import WebApp from '../webApp';
@@ -22,7 +23,6 @@ export default class PopupWebApp extends PopupElement {
2223
closable: true,
2324
overlayClosable: true,
2425
body: true,
25-
footer: true,
2626
title: true,
2727
onBackClick: () => this.webApp.onBackClick(),
2828
isConfirmationNeededOnClose: () => this.webApp.isConfirmationNeededOnClose()
@@ -35,7 +35,6 @@ export default class PopupWebApp extends PopupElement {
3535
header: this.header,
3636
title: this.title,
3737
body: this.body,
38-
footer: this.footer,
3938
forceHide: this.forceHide,
4039
onBackStatus: (visible) => this.btnCloseAnimatedIcon.classList.toggle('state-back', visible)
4140
});
@@ -49,6 +48,9 @@ export default class PopupWebApp extends PopupElement {
4948

5049
this.webApp.init(() => {
5150
this.show();
51+
fastRaf(() => {
52+
this.container.style.setProperty('--browser-width', `${this.container.clientWidth}px`);
53+
})
5254
});
5355
}
5456

0 commit comments

Comments
 (0)