Skip to content

Commit

Permalink
Merge pull request #103 from Telegram-Mini-Apps/bugfix/resize-problem
Browse files Browse the repository at this point in the history
Fix a bug in SDK for Solid. Add local playground
  • Loading branch information
heyqbnk authored Sep 30, 2023
2 parents 2326cee + 230e77a commit 3f21a7d
Show file tree
Hide file tree
Showing 13 changed files with 210 additions and 92 deletions.
5 changes: 5 additions & 0 deletions .changeset/forty-ravens-love.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@tma.js/bridge": patch
---

Add logging in case resize event occured
5 changes: 5 additions & 0 deletions .changeset/grumpy-tables-camp.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@tma.js/sdk-solid": patch
---

Fix a bug with incorrect components events tracking
11 changes: 11 additions & 0 deletions apps/local-playground/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# Local Playground

This application can be used by developers to test the code, written in the current monorepo.
We created this playground to avoid writing the same code from one package to another, adding
`index.html` and `vite.config.ts` files.

## Usage

1. First of all just import any code from the other packages.
2. Run `pnpm run dev`.
3. Open URL from the console.
14 changes: 14 additions & 0 deletions apps/local-playground/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<html>
<head>
<meta charset="UTF-8">
<meta
name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0"
>
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<script type="module" src="./index.ts"></script>
</body>
</html>
8 changes: 8 additions & 0 deletions apps/local-playground/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
/*
* You can import any code from the other folders in mono-repo. For example, you could
* use this code:
*
* import { postEvent } from '../../packages/bridge/src/index.ts';
*
* And test postEvent function here.
* */
11 changes: 11 additions & 0 deletions apps/local-playground/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"name": "local-playground",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "tsc && vite build",
"preview": "vite preview"
}
}
26 changes: 26 additions & 0 deletions apps/local-playground/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
{
"$schema": "https://json.schemastore.org/tsconfig",
"compilerOptions": {
"composite": false,
"declaration": false,
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"inlineSources": true,
"isolatedModules": true,
"lib": [
"esnext",
"DOM"
],
"module": "ESNext",
"moduleResolution": "NodeNext",
"noUnusedLocals": true,
"noUnusedParameters": true,
"preserveWatchOutput": true,
"resolveJsonModule": true,
"skipLibCheck": true,
"sourceMap": true,
"strict": true,
"target": "ESNext"
},
"exclude": ["node_modules"]
}
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,8 @@
"ts-jest": "^29.1.1",
"tslib": "^2.6.0",
"turbo": "^1.10.14",
"typescript": "^5.1.6"
"typescript": "^5.1.6",
"vite": "^4.4.9"
},
"packageManager": "[email protected]",
"name": "monorepo",
Expand Down
2 changes: 1 addition & 1 deletion packages/bridge/src/events/emitter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ export function createEmitter(): EventEmitter {
// add our own listener to make sure, viewport information is always fresh.
// Issue: https://github.com/Telegram-Web-Apps/tma.js/issues/10
window.addEventListener('resize', () => {
emitter.emit('viewport_changed', {
emit('viewport_changed', {
width: window.innerWidth,
height: window.innerHeight,
is_state_stable: true,
Expand Down
52 changes: 52 additions & 0 deletions packages/sdk-solid/src/useDynamicInitResultValue.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import { Accessor, createEffect, createSignal, onCleanup } from 'solid-js';
import { SDKInitResult, SDKInitResultKey, SDKInitResultValue } from './types.js';
import { useInitResultValue } from './useInitResultValue.js';

interface Trackable {
on: (event: any, ...args: any[]) => void;
off: (event: any, ...args: any[]) => void;
}

type EventName<T extends Trackable> = T extends {
on(event: infer E, ...args: any[]): any
} ? E : never;

type DynamicComponentKey = {
[K in SDKInitResultKey]: SDKInitResultValue<K> extends Trackable ? K : never;
}[SDKInitResultKey];

/**
* Extracts value from the SDK init result by specified key and listens to its all specified
* events, making the returned value to update.
* @param initResult - SDK init result.
* @param key - SDK init result key.
* @param events - tracked events list.
*/
export function useDynamicInitResultValue<K extends DynamicComponentKey>(
initResult: Accessor<SDKInitResult>,
key: K,
events: EventName<SDKInitResultValue<K>>[],
): Accessor<SDKInitResultValue<K>> {
// Get original value from init result.
const initResultValue = useInitResultValue(initResult, key);

// Here we store value, which should always update it in case, some of its props
// were changed/
const [value, setValue] = createSignal(initResultValue(), { equals: false });

createEffect(() => {
const value = initResultValue();
const listener = () => {
// We use prev => prev on purpose. This will make Solid sure, something inside
// dynamic value changed.
setValue(prev => prev);
};

events.forEach(event => value.on(event, listener));
onCleanup(() => {
events.forEach(event => value.off(event, listener));
});
});

return value;
}
14 changes: 14 additions & 0 deletions packages/sdk-solid/src/useInitResultValue.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { SDKInitResult, SDKInitResultKey, SDKInitResultValue } from './types.js';
import { Accessor, createMemo } from 'solid-js';

/**
* Extracts value from the SDK init result by key.
* @param result - init result accessor.
* @param key - key to extract.
*/
export function useInitResultValue<K extends SDKInitResultKey>(
result: Accessor<SDKInitResult>,
key: K
): Accessor<SDKInitResultValue<K>> {
return createMemo(() => result()[key]);

Check warning on line 13 in packages/sdk-solid/src/useInitResultValue.ts

View workflow job for this annotation

GitHub Actions / Validate and create Pull Request

For proper analysis, a variable should be used to capture the result of this function call
}
67 changes: 12 additions & 55 deletions packages/sdk-solid/src/useSDK.ts
Original file line number Diff line number Diff line change
@@ -1,57 +1,14 @@
import { type Accessor, createEffect, createMemo, createSignal, onCleanup } from 'solid-js';
import { createMemo, type Accessor } from 'solid-js';

import type { SDKInitResult, SDKInitResultKey, SDKInitResultValue } from './types.js';
import { useSDKContext } from './hooks.js';

interface Trackable {
on: (event: any, ...args: any[]) => void;
off: (event: any, ...args: any[]) => void;
}

type EventName<T extends Trackable> = T extends {
on(event: infer E, ...args: any[]): any
} ? E : never;

type DynamicComponentKey = {
[K in SDKInitResultKey]: SDKInitResultValue<K> extends Trackable
? K
: never;
}[SDKInitResultKey];
import { useInitResultValue } from './useInitResultValue.js';
import { useDynamicInitResultValue } from './useDynamicInitResultValue.js';
import type { SDKInitResultKey, SDKInitResultValue } from './types.js';

export type SDK = {
[K in SDKInitResultKey]: Accessor<SDKInitResultValue<K>>
};

function useDynamicComponent<K extends DynamicComponentKey>(
initResult: Accessor<SDKInitResult>,
key: K,
events: EventName<SDKInitResultValue<K>>[],
): Accessor<SDKInitResultValue<K>> {
const [component, setComponent] = createSignal(initResult()[key], { equals: false });

createEffect(() => {
const obj = component();

events.forEach(event => {
(obj as any).on(event, () => setComponent(() => obj));
});

onCleanup(() => {
events.forEach(event => {
(obj as any).off(event, () => setComponent(() => obj));
});
});
});

return component;
}

function useInitResultValue<K extends SDKInitResultKey>(initResult: Accessor<SDKInitResult>, key: K) {
const value = createMemo<SDKInitResultValue<K>>(() => initResult()[key]);

return value;
}

/**
* Returns ready to use SDK components.
*/
Expand All @@ -68,30 +25,30 @@ export function useSDK(): SDK {
});

return {
backButton: useDynamicComponent(sdk, 'backButton', ['isVisibleChanged']),
closingBehavior: useDynamicComponent(sdk, 'closingBehavior', ['isConfirmationNeededChanged']),
backButton: useDynamicInitResultValue(sdk, 'backButton', ['isVisibleChanged']),
closingBehavior: useDynamicInitResultValue(sdk, 'closingBehavior', ['isConfirmationNeededChanged']),
cloudStorage: useInitResultValue(sdk, 'cloudStorage'),
haptic: useInitResultValue(sdk, 'haptic'),
initData: useInitResultValue(sdk, 'initData'),
initDataRaw: useInitResultValue(sdk, 'initDataRaw'),
mainButton: useDynamicComponent(sdk, 'mainButton', [
mainButton: useDynamicInitResultValue(sdk, 'mainButton', [
'backgroundColorChanged',
'isVisibleChanged',
'isProgressVisibleChanged',
'isEnabledChanged',
'textChanged',
'textColorChanged',
]),
popup: useDynamicComponent(sdk, 'popup', ['isOpenedChanged']),
popup: useDynamicInitResultValue(sdk, 'popup', ['isOpenedChanged']),
postEvent: useInitResultValue(sdk, 'postEvent'),
qrScanner: useDynamicComponent(sdk, 'qrScanner', ['isOpenedChanged']),
themeParams: useDynamicComponent(sdk, 'themeParams', ['changed']),
viewport: useDynamicComponent(sdk, 'viewport', [
qrScanner: useDynamicInitResultValue(sdk, 'qrScanner', ['isOpenedChanged']),
themeParams: useDynamicInitResultValue(sdk, 'themeParams', ['changed']),
viewport: useDynamicInitResultValue(sdk, 'viewport', [
'heightChanged',
'isExpandedChanged',
'stableHeightChanged',
'widthChanged',
]),
webApp: useDynamicComponent(sdk, 'webApp', ['backgroundColorChanged', 'headerColorChanged']),
webApp: useDynamicInitResultValue(sdk, 'webApp', ['backgroundColorChanged', 'headerColorChanged']),
};
}
Loading

0 comments on commit 3f21a7d

Please sign in to comment.