Stage 17 (contour round 3): Telegram Mini Apps polish, board scroll, keyboard overlay
CI / changes (pull_request) Successful in 2s
CI / unit (pull_request) Successful in 8s
CI / integration (pull_request) Successful in 10s
CI / ui (pull_request) Successful in 27s
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Successful in 54s
CI / changes (pull_request) Successful in 2s
CI / unit (pull_request) Successful in 8s
CI / integration (pull_request) Successful in 10s
CI / ui (pull_request) Successful in 27s
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Successful in 54s
- Telegram (lib/telegram.ts): chrome colours (setHeaderColor/setBackgroundColor/setBottomBarColor) match Telegram's header/bg/bottom bar to the app; native BackButton on sub-screens (app chevron hidden in TG); HapticFeedback on tile place/commit/error; enableClosingConfirmation while a game is open; disableVerticalSwipes so swipe-to-minimise doesn't fight tile drag / board scroll - #9 board-only vertical scroll: Screen 'column' mode lets the board area scroll while score/status/rack/tab bar stay fixed (zoom keeps its own scroll) - #10 check-word dialog opens in Modal keyboard-overlay mode (top-anchored, keyboard overlays the empty area) — no resize/relayout jank; other modals stay keyboard-aware - docs: UI_DESIGN Telegram integration + vertical fit/keyboard; PLAN round 2-3 follow-ups
This commit is contained in:
@@ -9,7 +9,16 @@ import { GatewayError } from './client';
|
||||
import { navigate, router } from './router.svelte';
|
||||
import { errorKey, localeFrom, setLocale, t, type Locale } from './i18n/index.svelte';
|
||||
import { applyReduceMotion, applyTelegramTheme, applyTheme, type ThemePref } from './theme';
|
||||
import { insideTelegram, onTelegramPath, telegramColorScheme, telegramLaunch, telegramOnEvent } from './telegram';
|
||||
import {
|
||||
insideTelegram,
|
||||
onTelegramPath,
|
||||
telegramColorScheme,
|
||||
telegramDisableVerticalSwipes,
|
||||
telegramHaptic,
|
||||
telegramLaunch,
|
||||
telegramOnEvent,
|
||||
telegramSetChrome,
|
||||
} from './telegram';
|
||||
import { parseStartParam } from './deeplink';
|
||||
import { clearSession, loadPrefs, loadSession, saveSession, savePrefs } from './session';
|
||||
import { clearGameCache } from './gamecache';
|
||||
@@ -93,6 +102,7 @@ export function showToast(text: string, kind: Toast['kind'] = 'info'): void {
|
||||
|
||||
/** handleError maps a GatewayError to a toast; an invalid session logs out. */
|
||||
export function handleError(err: unknown): void {
|
||||
telegramHaptic('error');
|
||||
if (err instanceof GatewayError) {
|
||||
if (err.code === 'session_invalid' || err.code === 'unauthenticated') {
|
||||
void logout();
|
||||
@@ -200,6 +210,20 @@ export async function applyLinkResult(r: LinkResult): Promise<void> {
|
||||
app.profile = await gateway.profileGet();
|
||||
}
|
||||
|
||||
/**
|
||||
* syncTelegramChrome paints Telegram's header/background/bottom bar from the app's live
|
||||
* theme tokens, so the surrounding chrome matches the UI. Called after the theme is applied.
|
||||
*/
|
||||
function syncTelegramChrome(): void {
|
||||
if (typeof document === 'undefined') return;
|
||||
const cs = getComputedStyle(document.documentElement);
|
||||
telegramSetChrome(
|
||||
cs.getPropertyValue('--bg-elev').trim(),
|
||||
cs.getPropertyValue('--bg').trim(),
|
||||
cs.getPropertyValue('--bg-elev').trim(),
|
||||
);
|
||||
}
|
||||
|
||||
export async function bootstrap(): Promise<void> {
|
||||
const prefs = await loadPrefs();
|
||||
app.theme = prefs.theme ?? 'auto';
|
||||
@@ -232,6 +256,10 @@ export async function bootstrap(): Promise<void> {
|
||||
// so the OS prefers-color-scheme (which leaks into the Telegram Desktop webview)
|
||||
// cannot fight it. Falls back to the stored preference when the SDK omits it.
|
||||
applyTheme(telegramColorScheme() ?? app.theme);
|
||||
// Match Telegram's chrome to the app and stop its swipe-down-to-minimise from
|
||||
// fighting tile drag / board scroll.
|
||||
syncTelegramChrome();
|
||||
telegramDisableVerticalSwipes();
|
||||
try {
|
||||
await adoptSession(await gateway.authTelegram(launch.initData));
|
||||
await routeStartParam(launch.startParam);
|
||||
|
||||
@@ -13,6 +13,23 @@ interface TelegramWebApp {
|
||||
ready?: () => void;
|
||||
expand?: () => void;
|
||||
onEvent?: (event: string, handler: () => void) => void;
|
||||
setHeaderColor?: (color: string) => void;
|
||||
setBackgroundColor?: (color: string) => void;
|
||||
setBottomBarColor?: (color: string) => void;
|
||||
disableVerticalSwipes?: () => void;
|
||||
enableClosingConfirmation?: () => void;
|
||||
disableClosingConfirmation?: () => void;
|
||||
HapticFeedback?: {
|
||||
impactOccurred?: (style: string) => void;
|
||||
notificationOccurred?: (type: string) => void;
|
||||
selectionChanged?: () => void;
|
||||
};
|
||||
BackButton?: {
|
||||
show?: () => void;
|
||||
hide?: () => void;
|
||||
onClick?: (cb: () => void) => void;
|
||||
offClick?: (cb: () => void) => void;
|
||||
};
|
||||
}
|
||||
|
||||
function webApp(): TelegramWebApp | undefined {
|
||||
@@ -70,6 +87,71 @@ export function telegramColorScheme(): 'light' | 'dark' | undefined {
|
||||
return webApp()?.colorScheme;
|
||||
}
|
||||
|
||||
/**
|
||||
* telegramSetChrome paints Telegram's own header, background and bottom bar to match the
|
||||
* app's colours, so the surrounding Telegram chrome does not clash with the UI. No-op
|
||||
* outside Telegram or on a client predating a given setter.
|
||||
*/
|
||||
export function telegramSetChrome(header: string, background: string, bottom: string): void {
|
||||
const w = webApp();
|
||||
if (header) w?.setHeaderColor?.(header);
|
||||
if (background) w?.setBackgroundColor?.(background);
|
||||
if (bottom) w?.setBottomBarColor?.(bottom);
|
||||
}
|
||||
|
||||
/**
|
||||
* telegramDisableVerticalSwipes turns off Telegram's swipe-down-to-minimise gesture so
|
||||
* it does not fight tile drag-and-drop or the board's vertical scroll.
|
||||
*/
|
||||
export function telegramDisableVerticalSwipes(): void {
|
||||
webApp()?.disableVerticalSwipes?.();
|
||||
}
|
||||
|
||||
/** Haptic is the set of feedbacks the app triggers. */
|
||||
export type Haptic = 'select' | 'success' | 'error' | 'warning' | 'light' | 'medium' | 'heavy';
|
||||
|
||||
/** telegramHaptic fires a Telegram haptic; a no-op outside Telegram or on older clients. */
|
||||
export function telegramHaptic(kind: Haptic): void {
|
||||
const h = webApp()?.HapticFeedback;
|
||||
if (!h) return;
|
||||
if (kind === 'select') h.selectionChanged?.();
|
||||
else if (kind === 'success' || kind === 'error' || kind === 'warning') h.notificationOccurred?.(kind);
|
||||
else h.impactOccurred?.(kind);
|
||||
}
|
||||
|
||||
/**
|
||||
* telegramClosingConfirmation toggles the confirmation Telegram shows when the user
|
||||
* swipes the Mini App closed — enabled during an active game so it is not lost by accident.
|
||||
*/
|
||||
export function telegramClosingConfirmation(on: boolean): void {
|
||||
const w = webApp();
|
||||
if (on) w?.enableClosingConfirmation?.();
|
||||
else w?.disableClosingConfirmation?.();
|
||||
}
|
||||
|
||||
let backHandler: (() => void) | null = null;
|
||||
|
||||
/**
|
||||
* telegramBackButton shows or hides Telegram's native header back button, wiring its
|
||||
* click to onClick (replacing any previous handler). The app hides its own back chevron
|
||||
* inside Telegram so only the native control shows.
|
||||
*/
|
||||
export function telegramBackButton(show: boolean, onClick?: () => void): void {
|
||||
const b = webApp()?.BackButton;
|
||||
if (!b) return;
|
||||
if (backHandler) b.offClick?.(backHandler);
|
||||
backHandler = null;
|
||||
if (show) {
|
||||
if (onClick) {
|
||||
backHandler = onClick;
|
||||
b.onClick?.(onClick);
|
||||
}
|
||||
b.show?.();
|
||||
} else {
|
||||
b.hide?.();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* startParamFromURL reads a startapp parameter from the page URL — a bot web_app
|
||||
* launch button carries the deep-link there rather than in initDataUnsafe.
|
||||
|
||||
Reference in New Issue
Block a user