Skip to content

Commit

Permalink
Router allow go back (#12)
Browse files Browse the repository at this point in the history
- Router allow go back, add router history
- Fix tab navigation in MacOS client
  • Loading branch information
kubk authored Nov 17, 2023
1 parent 8cbc8d1 commit 452c1f8
Show file tree
Hide file tree
Showing 30 changed files with 262 additions and 302 deletions.
26 changes: 5 additions & 21 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -4,31 +4,15 @@
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vite + React + TS</title>
<script>
var _rollbarConfig = {
accessToken: "1892d9139f4a4ef6a837124fc1c71b90",
captureUncaught: true,
captureUnhandledRejections: true,
environment: '%MODE%',
payload: {
client: {
javascript: {
code_version: '1.0.1',
}
},
}
};
!function(r){var e={};function o(n){if(e[n])return e[n].exports;var t=e[n]={i:n,l:!1,exports:{}};return r[n].call(t.exports,t,t.exports,o),t.l=!0,t.exports}o.m=r,o.c=e,o.d=function(r,e,n){o.o(r,e)||Object.defineProperty(r,e,{enumerable:!0,get:n})},o.r=function(r){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(r,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(r,"__esModule",{value:!0})},o.t=function(r,e){if(1&e&&(r=o(r)),8&e)return r;if(4&e&&"object"==typeof r&&r&&r.__esModule)return r;var n=Object.create(null);if(o.r(n),Object.defineProperty(n,"default",{enumerable:!0,value:r}),2&e&&"string"!=typeof r)for(var t in r)o.d(n,t,function(e){return r[e]}.bind(null,t));return n},o.n=function(r){var e=r&&r.__esModule?function(){return r.default}:function(){return r};return o.d(e,"a",e),e},o.o=function(r,e){return Object.prototype.hasOwnProperty.call(r,e)},o.p="",o(o.s=0)}([function(r,e,o){"use strict";var n=o(1),t=o(5);_rollbarConfig=_rollbarConfig||{},_rollbarConfig.rollbarJsUrl=_rollbarConfig.rollbarJsUrl||"https://cdn.rollbar.com/rollbarjs/refs/tags/v2.26.0/rollbar.min.js",_rollbarConfig.async=void 0===_rollbarConfig.async||_rollbarConfig.async;var a=n.setupShim(window,_rollbarConfig),l=t(_rollbarConfig);window.rollbar=n.Rollbar,a.loadFull(window,document,!_rollbarConfig.async,_rollbarConfig,l)},function(r,e,o){"use strict";var n=o(2),t=o(3);function a(r){return function(){try{return r.apply(this,arguments)}catch(r){try{console.error("[Rollbar]: Internal error",r)}catch(r){}}}}var l=0;function i(r,e){this.options=r,this._rollbarOldOnError=null;var o=l++;this.shimId=function(){return o},"undefined"!=typeof window&&window._rollbarShims&&(window._rollbarShims[o]={handler:e,messages:[]})}var s=o(4),d=function(r,e){return new i(r,e)},c=function(r){return new s(d,r)};function u(r){return a((function(){var e=this,o=Array.prototype.slice.call(arguments,0),n={shim:e,method:r,args:o,ts:new Date};window._rollbarShims[this.shimId()].messages.push(n)}))}i.prototype.loadFull=function(r,e,o,n,t){var l=!1,i=e.createElement("script"),s=e.getElementsByTagName("script")[0],d=s.parentNode;i.crossOrigin="",i.src=n.rollbarJsUrl,o||(i.async=!0),i.onload=i.onreadystatechange=a((function(){if(!(l||this.readyState&&"loaded"!==this.readyState&&"complete"!==this.readyState)){i.onload=i.onreadystatechange=null;try{d.removeChild(i)}catch(r){}l=!0,function(){var e;if(void 0===r._rollbarDidLoad){e=new Error("rollbar.js did not load");for(var o,n,a,l,i=0;o=r._rollbarShims[i++];)for(o=o.messages||[];n=o.shift();)for(a=n.args||[],i=0;i<a.length;++i)if("function"==typeof(l=a[i])){l(e);break}}"function"==typeof t&&t(e)}()}})),d.insertBefore(i,s)},i.prototype.wrap=function(r,e,o){try{var n;if(n="function"==typeof e?e:function(){return e||{}},"function"!=typeof r)return r;if(r._isWrap)return r;if(!r._rollbar_wrapped&&(r._rollbar_wrapped=function(){o&&"function"==typeof o&&o.apply(this,arguments);try{return r.apply(this,arguments)}catch(o){var e=o;throw e&&("string"==typeof e&&(e=new String(e)),e._rollbarContext=n()||{},e._rollbarContext._wrappedSource=r.toString(),window._rollbarWrappedError=e),e}},r._rollbar_wrapped._isWrap=!0,r.hasOwnProperty))for(var t in r)r.hasOwnProperty(t)&&(r._rollbar_wrapped[t]=r[t]);return r._rollbar_wrapped}catch(e){return r}};for(var p="log,debug,info,warn,warning,error,critical,global,configure,handleUncaughtException,handleAnonymousErrors,handleUnhandledRejection,captureEvent,captureDomContentLoaded,captureLoad".split(","),f=0;f<p.length;++f)i.prototype[p[f]]=u(p[f]);r.exports={setupShim:function(r,e){if(r){var o=e.globalAlias||"Rollbar";if("object"==typeof r[o])return r[o];r._rollbarShims={},r._rollbarWrappedError=null;var l=new c(e);return a((function(){e.captureUncaught&&(l._rollbarOldOnError=r.onerror,n.captureUncaughtExceptions(r,l,!0),e.wrapGlobalEventHandlers&&t(r,l,!0)),e.captureUnhandledRejections&&n.captureUnhandledRejections(r,l,!0);var a=e.autoInstrument;return!1!==e.enabled&&(void 0===a||!0===a||"object"==typeof a&&a.network)&&r.addEventListener&&(r.addEventListener("load",l.captureLoad.bind(l)),r.addEventListener("DOMContentLoaded",l.captureDomContentLoaded.bind(l))),r[o]=l,l}))()}},Rollbar:c}},function(r,e,o){"use strict";function n(r,e,o,n){r._rollbarWrappedError&&(n[4]||(n[4]=r._rollbarWrappedError),n[5]||(n[5]=r._rollbarWrappedError._rollbarContext),r._rollbarWrappedError=null);var t=e.handleUncaughtException.apply(e,n);o&&o.apply(r,n),"anonymous"===t&&(e.anonymousErrorsPending+=1)}r.exports={captureUncaughtExceptions:function(r,e,o){if(r){var t;if("function"==typeof e._rollbarOldOnError)t=e._rollbarOldOnError;else if(r.onerror){for(t=r.onerror;t._rollbarOldOnError;)t=t._rollbarOldOnError;e._rollbarOldOnError=t}e.handleAnonymousErrors();var a=function(){var o=Array.prototype.slice.call(arguments,0);n(r,e,t,o)};o&&(a._rollbarOldOnError=t),r.onerror=a}},captureUnhandledRejections:function(r,e,o){if(r){"function"==typeof r._rollbarURH&&r._rollbarURH.belongsToShim&&r.removeEventListener("unhandledrejection",r._rollbarURH);var n=function(r){var o,n,t;try{o=r.reason}catch(r){o=void 0}try{n=r.promise}catch(r){n="[unhandledrejection] error getting `promise` from event"}try{t=r.detail,!o&&t&&(o=t.reason,n=t.promise)}catch(r){}o||(o="[unhandledrejection] error getting `reason` from event"),e&&e.handleUnhandledRejection&&e.handleUnhandledRejection(o,n)};n.belongsToShim=o,r._rollbarURH=n,r.addEventListener("unhandledrejection",n)}}}},function(r,e,o){"use strict";function n(r,e,o){if(e.hasOwnProperty&&e.hasOwnProperty("addEventListener")){for(var n=e.addEventListener;n._rollbarOldAdd&&n.belongsToShim;)n=n._rollbarOldAdd;var t=function(e,o,t){n.call(this,e,r.wrap(o),t)};t._rollbarOldAdd=n,t.belongsToShim=o,e.addEventListener=t;for(var a=e.removeEventListener;a._rollbarOldRemove&&a.belongsToShim;)a=a._rollbarOldRemove;var l=function(r,e,o){a.call(this,r,e&&e._rollbar_wrapped||e,o)};l._rollbarOldRemove=a,l.belongsToShim=o,e.removeEventListener=l}}r.exports=function(r,e,o){if(r){var t,a,l="EventTarget,Window,Node,ApplicationCache,AudioTrackList,ChannelMergerNode,CryptoOperation,EventSource,FileReader,HTMLUnknownElement,IDBDatabase,IDBRequest,IDBTransaction,KeyOperation,MediaController,MessagePort,ModalWindow,Notification,SVGElementInstance,Screen,TextTrack,TextTrackCue,TextTrackList,WebSocket,WebSocketWorker,Worker,XMLHttpRequest,XMLHttpRequestEventTarget,XMLHttpRequestUpload".split(",");for(t=0;t<l.length;++t)r[a=l[t]]&&r[a].prototype&&n(e,r[a].prototype,o)}}},function(r,e,o){"use strict";function n(r,e){this.impl=r(e,this),this.options=e,function(r){for(var e=function(r){return function(){var e=Array.prototype.slice.call(arguments,0);if(this.impl[r])return this.impl[r].apply(this.impl,e)}},o="log,debug,info,warn,warning,error,critical,global,configure,handleUncaughtException,handleAnonymousErrors,handleUnhandledRejection,_createItem,wrap,loadFull,shimId,captureEvent,captureDomContentLoaded,captureLoad".split(","),n=0;n<o.length;n++)r[o[n]]=e(o[n])}(n.prototype)}n.prototype._swapAndProcessMessages=function(r,e){var o,n,t;for(this.impl=r(this.options);o=e.shift();)n=o.method,t=o.args,this[n]&&"function"==typeof this[n]&&("captureDomContentLoaded"===n||"captureLoad"===n?this[n].apply(this,[t[0],o.ts]):this[n].apply(this,t));return this},r.exports=n},function(r,e,o){"use strict";r.exports=function(r){return function(e){if(!e&&!window._rollbarInitialized){for(var o,n,t=(r=r||{}).globalAlias||"Rollbar",a=window.rollbar,l=function(r){return new a(r)},i=0;o=window._rollbarShims[i++];)n||(n=o.handler),o.handler._swapAndProcessMessages(l,o.messages);window[t]=n,window._rollbarInitialized=!0}}}}]);
</script>
<title>MemoCard</title>
</head>
<script src="https://telegram.org/js/telegram-web-app.js"></script>
<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
<!-- <script src="https://cdn.jsdelivr.net/npm/eruda"></script>-->
<!-- <script>-->
<!-- eruda.init();-->
<!-- </script>-->
<script src="https://cdn.jsdelivr.net/npm/eruda"></script>
<script>
eruda.init();
</script>
</html>
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
"dev:frontend:start": "vite",
"dev:api:start": "npx wrangler pages dev /functions --compatibility-date=2023-09-22",
"dev:tunnel": "../ngrok http --domain=causal-magpie-closing.ngrok-free.app 5173",
"build": "vite build",
"build": "cp index.build.html index.html && vite build",
"typecheck": "npx tsc",
"lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
"test:api": "npx vitest --dir functions/",
Expand Down
50 changes: 50 additions & 0 deletions src/lib/keyboard/useMacTabNavigationFix.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import { useEffect } from "react";

const isMacOS = /Mac OS X/i.test(navigator.userAgent);

// Custom hook for handling tab navigation in Safari on macOS
// It's either a bug of Telegram for Mac or some issues with Safari WebView
export const useMacTabNavigationFix = () => {
useEffect(() => {
if (!isMacOS) {
return;
}
const handleKeyDown = (event: KeyboardEvent) => {
if (event.key === "Tab") {
event.preventDefault();

const inputs = Array.from(document.querySelectorAll("input, textarea"));
const activeElement = document.activeElement;
// @ts-expect-error
const currentIndex = inputs.indexOf(activeElement);
if (currentIndex === -1) {
// No input is currently focused, focus the first input
if (inputs.length > 0) {
// @ts-expect-error
inputs[0].focus();
}
} else {
if (event.shiftKey && currentIndex > 0) {
// Shift + Tab was pressed
// @ts-expect-error
inputs[currentIndex - 1].focus();
} else if (
!event.shiftKey &&
currentIndex >= 0 &&
currentIndex < inputs.length - 1
) {
// Only Tab was pressed
// @ts-expect-error
inputs[currentIndex + 1].focus();
}
}
}
};

// Only keyup works here, events like keydown and keypress don't
document.addEventListener("keyup", handleKeyDown);
return () => {
document.removeEventListener("keyup", handleKeyDown);
};
}, []);
};
10 changes: 5 additions & 5 deletions src/screens/app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { observer } from "mobx-react-lite";
import { MainScreen } from "./deck-list/main-screen.tsx";
import { DeckScreen } from "./deck-review/deck-screen.tsx";
import { ReviewStoreProvider } from "../store/review-store-context.tsx";
import { Screen, screenStore } from "../store/screen-store.ts";
import { screenStore } from "../store/screen-store.ts";
import { DeckFormScreen } from "./deck-form/deck-form-screen.tsx";
import { DeckFormStoreProvider } from "../store/deck-form-store-context.tsx";
import { QuickAddCardForm } from "./deck-form/quick-add-card-form.tsx";
Expand All @@ -15,19 +15,19 @@ export const App = observer(() => {
return (
<div>
<VersionWarning />
{screenStore.screen === Screen.Main && <MainScreen />}
{screenStore.screen.type === "main" && <MainScreen />}
{screenStore.isDeckPreviewScreen && (
<ReviewStoreProvider>
<DeckScreen />
</ReviewStoreProvider>
)}
{screenStore.screen === Screen.DeckForm && (
{screenStore.screen.type === "deckForm" && (
<DeckFormStoreProvider>
<DeckFormScreen />
</DeckFormStoreProvider>
)}
{screenStore.screen === Screen.CardQuickAddForm && <QuickAddCardForm />}
{screenStore.screen === Screen.UserSettings && (
{screenStore.screen.type === "cardQuickAddForm" && <QuickAddCardForm />}
{screenStore.screen.type === "userSettings" && (
<UserSettingsStoreProvider>
<UserSettingsMain />
</UserSettingsStoreProvider>
Expand Down
6 changes: 3 additions & 3 deletions src/screens/deck-form/card-form-view.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,15 +24,15 @@ export const CardFormView = observer((props: Props) => {
>
<h3 className={css({ textAlign: "center" })}>Add card</h3>
<Label text={"Front"} isRequired>
<Input {...cardForm.front.props} rows={5} type={"textarea"} />
<Input field={cardForm.front} rows={5} type={"textarea"} />
</Label>

<Label text={"Back"} isRequired>
<Input {...cardForm.back.props} rows={5} type={"textarea"} />
<Input field={cardForm.back} rows={5} type={"textarea"} />
</Label>

<Label text={"Example / Note"}>
<Input {...cardForm.example.props} rows={3} type={"textarea"} />
<Input field={cardForm.example} rows={3} type={"textarea"} />
</Label>
</div>
);
Expand Down
11 changes: 8 additions & 3 deletions src/screens/deck-form/deck-form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,13 @@ import { screenStore } from "../../store/screen-store.ts";
import { useMount } from "../../lib/react/use-mount.ts";
import { useBackButton } from "../../lib/telegram/use-back-button.tsx";
import { useTelegramProgress } from "../../lib/telegram/use-telegram-progress.tsx";
import { assert } from "../../lib/typescript/assert.ts";
import { useMacTabNavigationFix } from "../../lib/keyboard/useMacTabNavigationFix.tsx";

export const DeckForm = observer(() => {
const deckFormStore = useDeckFormStore();
const screen = screenStore.screen;
assert(screen.type === "deckForm");

useMount(() => {
deckFormStore.loadForm();
Expand All @@ -25,6 +29,7 @@ export const DeckForm = observer(() => {
deckFormStore.onDeckBack();
});
useTelegramProgress(() => deckFormStore.isSending);
useMacTabNavigationFix();

if (!deckFormStore.form) {
return null;
Expand All @@ -41,15 +46,15 @@ export const DeckForm = observer(() => {
})}
>
<h3 className={css({ textAlign: "center" })}>
{screenStore.deckFormId ? "Edit deck" : "Add deck"}
{screen.deckId ? "Edit deck" : "Add deck"}
</h3>
<Label text={"Title"} isRequired>
<Input {...deckFormStore.form.title.props} />
<Input field={deckFormStore.form.title} />
</Label>

<Label text={"Description"}>
<Input
{...deckFormStore.form.description.props}
field={deckFormStore.form.description}
rows={5}
type={"textarea"}
/>
Expand Down
2 changes: 2 additions & 0 deletions src/screens/deck-form/quick-add-card-form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { useMainButton } from "../../lib/telegram/use-main-button.tsx";
import { useBackButton } from "../../lib/telegram/use-back-button.tsx";
import { QuickAddCardFormStore } from "../../store/quick-add-card-form-store.ts";
import { useTelegramProgress } from "../../lib/telegram/use-telegram-progress.tsx";
import { useMacTabNavigationFix } from "../../lib/keyboard/useMacTabNavigationFix.tsx";

export const QuickAddCardForm = observer(() => {
const [quickAddCardStore] = useState(() => new QuickAddCardFormStore());
Expand All @@ -16,6 +17,7 @@ export const QuickAddCardForm = observer(() => {
quickAddCardStore.onBack();
});
useTelegramProgress(() => quickAddCardStore.isSending);
useMacTabNavigationFix();

return <CardFormView cardForm={quickAddCardStore.form} />;
});
2 changes: 1 addition & 1 deletion src/screens/deck-list/deck-loading.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ export const DeckLoading = () => {
cursor: "pointer",
gap: 4,
borderRadius: theme.borderRadius,
padding: "13px 12px",
padding: "14px 12px",
background: theme.secondaryBgColor,
})}
>
Expand Down
6 changes: 3 additions & 3 deletions src/screens/deck-list/main-screen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ export const MainScreen = observer(() => {
color: theme.linkColor,
})}
onClick={() => {
screenStore.navigateToDeckForm();
screenStore.go({ type: "deckForm" });
}}
>
create one
Expand All @@ -88,7 +88,7 @@ export const MainScreen = observer(() => {
<Button
icon={"mdi-plus"}
onClick={() => {
screenStore.navigateToDeckForm();
screenStore.go({ type: "deckForm" });
}}
>
Add deck
Expand Down Expand Up @@ -157,7 +157,7 @@ export const MainScreen = observer(() => {
icon={"mdi-cog"}
disabled={deckListStore.myInfo?.state !== "fulfilled"}
onClick={() => {
screenStore.navigateToUserSettings();
screenStore.go({ type: "userSettings" });
}}
>
Settings
Expand Down
2 changes: 1 addition & 1 deletion src/screens/deck-list/my-deck.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ export const MyDeck = observer((props: Props) => {
<motion.div
whileTap={whileTap}
onClick={() => {
screenStore.navigateToMineDeck(deck.id);
screenStore.go({ type: "deckMine", deckId: deck.id });
}}
className={css({
display: "flex",
Expand Down
2 changes: 1 addition & 1 deletion src/screens/deck-list/public-deck.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ export const PublicDeck = observer((props: Props) => {
background: theme.secondaryBgColor,
})}
onClick={() => {
screenStore.navigateToPublicDeck(deck.id);
screenStore.go({ type: "deckPublic", deckId: deck.id });
}}
>
<div
Expand Down
2 changes: 1 addition & 1 deletion src/screens/deck-review/deck-finished.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ export const DeckFinished = observer(() => {
reviewStore.submitFinished();
});
useMainButton("Go back", () => {
screenStore.navigateToMain();
screenStore.go({ type: "main" });
});
useTelegramProgress(() => reviewStore.isReviewSending);

Expand Down
18 changes: 12 additions & 6 deletions src/screens/deck-review/deck-preview.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { observer } from "mobx-react-lite";
import { deckListStore } from "../../store/deck-list-store.ts";
import { assert } from "../../lib/typescript/assert.ts";
import { css } from "@emotion/css";
import { theme } from "../../ui/theme.tsx";
import React from "react";
Expand All @@ -14,11 +13,9 @@ import { useMainButton } from "../../lib/telegram/use-main-button.tsx";

export const DeckPreview = observer(() => {
const reviewStore = useReviewStore();
const deck = deckListStore.selectedDeck;
assert(deck, "Deck should not be empty before preview");

useBackButton(() => {
screenStore.navigateToMain();
screenStore.go({ type: "main" });
});

useMainButton(
Expand All @@ -29,13 +26,19 @@ export const DeckPreview = observer(() => {
() => deckListStore.canReview,
);

const deck = deckListStore.selectedDeck;
if (!deck) {
return null;
}

return (
<div
className={css({
display: "flex",
flexDirection: "column",
gap: 16,
paddingTop: 12,
paddingBottom: 12,
})}
>
<div
Expand Down Expand Up @@ -90,7 +93,10 @@ export const DeckPreview = observer(() => {
noPseudoClasses
outline
onClick={() => {
screenStore.navigateToQuickCardAdd(deck.id);
screenStore.go({
type: "cardQuickAddForm",
deckId: deck.id,
});
}}
>
Add card
Expand All @@ -103,7 +109,7 @@ export const DeckPreview = observer(() => {
noPseudoClasses
outline
onClick={() => {
screenStore.navigateToDeckForm(deck.id);
screenStore.go({ type: "deckForm", deckId: deck.id });
}}
>
Edit
Expand Down
4 changes: 2 additions & 2 deletions src/screens/shared/version-warning.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { css } from "@emotion/css";
import { theme } from "../../ui/theme.tsx";

export const VersionWarning = () => {
if (WebApp.version !== "6.0") {
if (WebApp.isVersionAtLeast("6.1")) {
return null;
}

Expand All @@ -28,7 +28,7 @@ export const VersionWarning = () => {
fontSize: 12,
})}
>
Please update your Telegram to ensure stable functioning of this bot.
Please update your Telegram to ensure stable functioning of this app.
</div>
</div>
);
Expand Down
Loading

0 comments on commit 452c1f8

Please sign in to comment.