Skip to content

Commit

Permalink
Separate Telegram & Browser platforms (#37)
Browse files Browse the repository at this point in the history
* Separate Telegram & Browser platforms
  • Loading branch information
kubk authored May 8, 2024
1 parent e8b0f92 commit ced8f69
Show file tree
Hide file tree
Showing 86 changed files with 663 additions and 202 deletions.
3 changes: 2 additions & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@
"version": "0.0.0",
"type": "module",
"scripts": {
"start": "npx concurrently --kill-others-on-fail \"npm run dev:frontend:start\" \"npm run dev:api:start\" \"npm run dev:tunnel\"",
"start:telegram": "npx concurrently --kill-others-on-fail \"npm run dev:frontend:start\" \"npm run dev:api:start\" \"npm run dev:tunnel\"",
"start:browser": "npx concurrently --kill-others-on-fail \"npm run dev:frontend:start\" \"npm run dev:api:start\"",
"dev:frontend:start": "vite",
"dev:api:start": "npx wrangler pages dev /functions --compatibility-date=2023-09-22 --compatibility-flags=\"nodejs_compat\"",
"dev:tunnel": "../ngrok http --domain=causal-magpie-closing.ngrok-free.app 5173",
Expand Down Expand Up @@ -43,7 +44,6 @@
"mathml-tag-names": "^3.0.2",
"mobx": "^6.10.2",
"mobx-form-lite": "^0.9.61",
"mobx-log": "^2.2.3",
"mobx-persist-store": "^1.1.3",
"mobx-react-lite": "^4.0.5",
"notistack": "^3.0.1",
Expand Down Expand Up @@ -82,6 +82,7 @@
"eslint-plugin-react": "^7.33.2",
"eslint-plugin-react-hooks": "^4.6.0",
"eslint-plugin-react-refresh": "^0.4.3",
"mobx-log": "^2.2.3",
"pre-commit": "^1.2.2",
"prettier": "^3.0.3",
"typescript": "^5.0.2",
Expand Down
5 changes: 5 additions & 0 deletions src/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -59,3 +59,8 @@ div,
a {
-webkit-tap-highlight-color: transparent;
}

/* fix for browser version to avoid monospace */
textarea {
font-family: inherit;
}
2 changes: 1 addition & 1 deletion src/lib/mobx-form-lite-persistable/persistable-field.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { makePersistable } from "mobx-persist-store";
import { storageAdapter } from "../telegram/storage-adapter.ts";
import { storageAdapter } from "../platform/storage-adapter.ts";
import { FieldWithValue } from "mobx-form-lite";

export const persistableField = <T extends FieldWithValue<unknown>>(
Expand Down
113 changes: 113 additions & 0 deletions src/lib/platform/browser/browser-platform.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
import { Platform, PlatformTheme } from "../platform.ts";
import { makeAutoObservable } from "mobx";
import { assert } from "../../typescript/assert.ts";
import { BooleanToggle } from "mobx-form-lite";

const cssVariables = {
"--tg-theme-hint-color": "#999999",
"--tg-theme-secondary-bg-color": "#efeff3",
"--tg-theme-text-color": "#000000",
"--tg-theme-section-bg-color": "#ffffff",
"--tg-theme-header-bg-color": "#efeff3",
"--tg-theme-accent-text-color": "#2481cc",
"--tg-color-scheme": "light",
"--tg-viewport-height": "100vh",
"--tg-theme-destructive-text-color": "#ff3b30",
"--tg-theme-button-color": "#2481cc",
"--tg-theme-bg-color": "#ffffff",
"--tg-theme-subtitle-text-color": "#999999",
"--tg-theme-button-text-color": "#ffffff",
"--tg-theme-section-header-text-color": "#6d6d71",
"--tg-theme-link-color": "#2481cc",
"--tg-viewport-stable-height": "100vh",
};

export class BrowserPlatform implements Platform {
mainButtonInfo?: {
text: string;
onClick: () => void;
condition?: () => boolean;
};
isMainButtonLoading = new BooleanToggle(false);
backButtonInfo?: {
onClick: () => void;
};

constructor() {
makeAutoObservable(
this,
{
getTheme: false,
getInitData: false,
initialize: false,
openInternalLink: false,
openExternalLink: false,
},
{
autoBind: true,
},
);
}

getTheme(): PlatformTheme {
return {
buttonColor: cssVariables["--tg-theme-button-color"],
hintColor: cssVariables["--tg-theme-hint-color"],
buttonTextColor: cssVariables["--tg-theme-button-text-color"],
};
}

showMainButton(text: string, onClick: () => void, condition?: () => boolean) {
this.mainButtonInfo = {
text,
onClick,
condition,
};
}

hideMainButton() {
this.mainButtonInfo = undefined;
}

get isMainButtonVisible() {
return this.mainButtonInfo !== undefined;
}

showBackButton(onClick: () => void) {
this.backButtonInfo = {
onClick,
};
}

hideBackButton() {
this.backButtonInfo = undefined;
}

get isBackButtonVisible() {
return this.backButtonInfo !== undefined;
}

getInitData(): string {
const userQuery = import.meta.env.VITE_USER_QUERY;
assert(typeof userQuery === "string", "VITE_USER_QUERY is not defined");
return userQuery;
}

initialize() {
for (const variable in cssVariables) {
document.documentElement.style.setProperty(
variable,
// @ts-ignore
cssVariables[variable],
);
}
}

openInternalLink(link: string) {
window.location.href = link;
}

openExternalLink(link: string) {
window.open(link, "_blank");
}
}
3 changes: 3 additions & 0 deletions src/lib/platform/browser/show-alert-browser.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export const showAlertBrowser = (text: string) => {
alert(text);
};
70 changes: 70 additions & 0 deletions src/lib/platform/browser/show-confirm-browser.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import React, { useState } from "react";
import ReactDOM from "react-dom";
import { createRoot } from "react-dom/client";
import { theme } from "../../../ui/theme.tsx";
import { Button } from "../../../ui/button.tsx";
import { Flex } from "../../../ui/flex.tsx";
import { t } from "../../../translations/t.ts";
import { ShowConfirmType } from "../platform.ts";
import { css } from "@emotion/css";

export const showConfirmBrowser: ShowConfirmType = (text) => {
return new Promise((resolve) => {
const Confirmation = () => {
const [isOpen, setIsOpen] = useState(true);

const handleConfirm = () => {
setIsOpen(false);
resolve(true);
};

const handleCancel = () => {
setIsOpen(false);
resolve(false);
};

if (!isOpen) {
return null;
}

return ReactDOM.createPortal(
<div
className={css({
position: "fixed",
top: 0,
left: 0,
right: 0,
bottom: 0,
backgroundColor: "rgba(0, 0, 0, 0.5)",
zIndex: theme.zIndex.confirmAlert,
display: "flex",
justifyContent: "center",
alignItems: "center",
})}
>
<div
className={css({
backgroundColor: "white",
width: 250,
padding: 20,
borderRadius: theme.borderRadius,
textAlign: "center",
})}
>
<p>{text}</p>
<Flex gap={8}>
<Button onClick={handleConfirm}>{t("confirm_ok")}</Button>
<Button outline onClick={handleCancel}>
{t("confirm_cancel")}
</Button>
</Flex>
</div>
</div>,
document.body,
);
};

const element = document.createElement("div");
createRoot(element).render(<Confirmation />);
});
};
16 changes: 16 additions & 0 deletions src/lib/platform/browser/use-back-button-browser.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { platform } from "../platform.ts";
import { useMount } from "../../react/use-mount.ts";
import { assert } from "../../typescript/assert.ts";
import { BrowserPlatform } from "./browser-platform.ts";

export const useBackButtonBrowser = (onClick: () => void) => {
useMount(() => {
assert(platform instanceof BrowserPlatform);
platform.showBackButton(onClick);

return () => {
assert(platform instanceof BrowserPlatform);
platform.hideBackButton();
};
});
};
24 changes: 24 additions & 0 deletions src/lib/platform/browser/use-main-button-browser.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { platform, UseMainButtonType } from "../platform.ts";
import { useMount } from "../../react/use-mount.ts";
import { assert } from "../../typescript/assert.ts";
import { BrowserPlatform } from "./browser-platform.ts";

export const useMainButtonBrowser: UseMainButtonType = (
text,
onClick,
condition,
) => {
useMount(() => {
assert(platform instanceof BrowserPlatform);
platform.showMainButton(
typeof text === "string" ? text : text(),
onClick,
condition,
);

return () => {
assert(platform instanceof BrowserPlatform);
platform.hideMainButton();
};
});
};
18 changes: 18 additions & 0 deletions src/lib/platform/browser/use-main-button-progress-browser.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { useMount } from "../../react/use-mount.ts";
import { autorun } from "mobx";
import { platform } from "../platform.ts";
import { assert } from "../../typescript/assert.ts";
import { BrowserPlatform } from "./browser-platform.ts";

export const useMainButtonProgressBrowser = (cb: () => boolean) => {
useMount(() => {
return autorun(() => {
assert(platform instanceof BrowserPlatform);
if (cb()) {
platform.isMainButtonLoading.setTrue();
} else {
platform.isMainButtonLoading.setFalse();
}
});
});
};
33 changes: 33 additions & 0 deletions src/lib/platform/platform.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { TelegramPlatform } from "./telegram/telegram-platform.ts";
import { BrowserPlatform } from "./browser/browser-platform.ts";
import { isRunningWithinTelegram } from "./is-running-within-telegram.ts";

export type PlatformTheme = {
buttonColor: string;
hintColor: string;
buttonTextColor: string;
};

export interface Platform {
getInitData(): string;
initialize(): void;
openExternalLink(link: string): void;
openInternalLink(link: string): void;
getTheme(): PlatformTheme;
}

export type UseMainButtonType = (
text: string | (() => string),
onClick: () => void,
condition?: () => boolean,
) => void;

export type ShowConfirmType = (text: string) => Promise<boolean>;

const createPlatform = (): Platform => {
return isRunningWithinTelegram()
? new TelegramPlatform()
: new BrowserPlatform();
};

export const platform = createPlatform();
7 changes: 7 additions & 0 deletions src/lib/platform/show-alert.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { platform } from "./platform.ts";
import { TelegramPlatform } from "./telegram/telegram-platform.ts";
import { showAlertTelegram } from "./telegram/show-alert-telegram.ts";
import { showAlertBrowser } from "./browser/show-alert-browser.ts";

export const showAlert =
platform instanceof TelegramPlatform ? showAlertTelegram : showAlertBrowser;
9 changes: 9 additions & 0 deletions src/lib/platform/show-confirm.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { platform } from "./platform.ts";
import { TelegramPlatform } from "./telegram/telegram-platform.ts";
import { showConfirmTelegram } from "./telegram/show-confirm-telegram.ts";
import { showConfirmBrowser } from "./browser/show-confirm-browser.tsx";

export const showConfirm =
platform instanceof TelegramPlatform
? showConfirmTelegram
: showConfirmBrowser;
8 changes: 8 additions & 0 deletions src/lib/platform/storage-adapter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { cloudStorageAdapter } from "./telegram/cloud-storage.ts";
import { platform } from "./platform.ts";
import { TelegramPlatform } from "./telegram/telegram-platform.ts";

export const storageAdapter =
platform instanceof TelegramPlatform && platform.isCloudStorageAvailable()
? cloudStorageAdapter
: window.localStorage;
File renamed without changes.
11 changes: 11 additions & 0 deletions src/lib/platform/telegram/css-var-to-value.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
export const cssVarToValue = (cssProperty: string) => {
const cssPropertyClean = cssProperty.replace("var(", "").replace(")", "");
const result = getComputedStyle(document.documentElement).getPropertyValue(
cssPropertyClean,
);
if (!result) {
console.warn("Variable " + cssPropertyClean + " is not available");
return "#00000";
}
return result;
};
File renamed without changes.
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import React, { ReactNode, useEffect, useRef } from "react";
import WebApp from "@twa-dev/sdk";
import { css } from "@emotion/css";
import { throttle } from "../throttle/throttle.ts";
import { throttle } from "../../throttle/throttle.ts";

type Props = {
condition: boolean;
Expand Down
Loading

0 comments on commit ced8f69

Please sign in to comment.