tutu/src/App.tsx

172 lines
5 KiB
TypeScript
Raw Normal View History

2024-07-14 12:28:44 +00:00
import { Route, Router } from "@solidjs/router";
import { ThemeProvider } from "@suid/material";
import {
Component,
createEffect,
createMemo,
createRenderEffect,
createSignal,
ErrorBoundary,
lazy,
2024-09-26 09:23:40 +00:00
onCleanup,
} from "solid-js";
2024-07-14 12:28:44 +00:00
import { useRootTheme } from "./material/mui.js";
import {
Provider as ClientProvider,
createMastoClientFor,
} from "./masto/clients.js";
import { $accounts, updateAcctInf } from "./accounts/stores.js";
import { useStore } from "@nanostores/solid";
2024-09-26 09:23:40 +00:00
import { DateFnScope, useLanguage } from "./platform/i18n.jsx";
import { useRegisterSW } from "virtual:pwa-register/solid";
import {
isJSONRPCResult,
ResultDispatcher,
type JSONRPC,
} from "./serviceworker/workerrpc.js";
import {
Service
} from "./serviceworker/services.js"
import { makeEventListener } from "@solid-primitives/event-listener";
import { ServiceWorkerProvider } from "./platform/host.js";
2024-07-14 12:28:44 +00:00
const AccountSignIn = lazy(() => import("./accounts/SignIn.js"));
const AccountMastodonOAuth2Callback = lazy(
() => import("./accounts/MastodonOAuth2Callback.js"),
);
const TimelineHome = lazy(() => import("./timelines/Home.js"));
2024-07-22 13:57:04 +00:00
const Settings = lazy(() => import("./settings/Settings.js"));
const TootBottomSheet = lazy(() => import("./timelines/TootBottomSheet.js"));
const MotionSettings = lazy(() => import("./settings/Motions.js"));
const LanguageSettings = lazy(() => import("./settings/Language.js"));
const RegionSettings = lazy(() => import("./settings/Region.jsx"));
const UnexpectedError = lazy(() => import("./UnexpectedError.js"));
2024-07-14 12:28:44 +00:00
const Routing: Component = () => {
return (
<Router>
2024-07-22 13:57:04 +00:00
<Route path="/" component={TimelineHome}>
<Route path=""></Route>
<Route path="/settings" component={Settings}>
<Route path=""></Route>
<Route path="/language" component={LanguageSettings}></Route>
<Route path="/region" component={RegionSettings}></Route>
<Route path="/motions" component={MotionSettings}></Route>
</Route>
<Route path="/:acct/toot/:id" component={TootBottomSheet}></Route>
2024-07-22 13:57:04 +00:00
</Route>
2024-07-14 12:28:44 +00:00
<Route path={"/accounts"}>
<Route path={"/sign-in"} component={AccountSignIn} />
<Route
path={"/oauth2/mastodon"}
component={AccountMastodonOAuth2Callback}
/>
2024-07-14 12:28:44 +00:00
</Route>
</Router>
);
};
const App: Component = () => {
const theme = useRootTheme();
const accts = useStore($accounts);
2024-09-26 09:23:40 +00:00
const lang = useLanguage();
const [serviceWorker, setServiceWorker] = createSignal<ServiceWorker>();
const dispatcher = new ResultDispatcher();
let checkAge = 0;
const untilServiceWorkerAlive = async (
worker: ServiceWorker,
expectedAge: number,
) => {
const [call, ret] = dispatcher.createTypedCall<Service>("ping");
worker.postMessage(await call);
const result = await ret;
console.assert(!result.error, result);
if (expectedAge === checkAge) {
setServiceWorker(worker);
}
};
makeEventListener(window, "message", (event: MessageEvent<JSONRPC>) => {
if (isJSONRPCResult(event.data)) {
dispatcher.dispatch(event.data.id, event.data);
}
});
const {
needRefresh: [needRefresh],
offlineReady: [offlineReady],
} = useRegisterSW({
onRegisteredSW(scriptUrl, reg) {
console.info("service worker is registered from %s", scriptUrl);
const active = reg?.active;
if (!active) {
console.warn("No service is in activating or activated");
return;
}
untilServiceWorkerAlive(active, checkAge++);
},
});
const clients = createMemo(() => {
return accts().map((x) => ({
account: x,
client: createMastoClientFor(x),
}));
});
createEffect(() => {
const neededUpdates = accts()
.map((x, i) => [i, x] as const)
.filter(([, x]) => !x.inf);
if (neededUpdates.length > 0) {
// FIXME: we might need some kind of concurrent control
Promise.all(neededUpdates.map(([i]) => updateAcctInf(i))).then(
(x) => {
console.info("acct info updated for %d acct(s)", x.length);
},
(reason) => {
console.error("acct info update is fail", reason);
},
);
}
});
2024-09-26 09:23:40 +00:00
createRenderEffect(() => {
const root = document.querySelector(":root")!;
root.setAttribute("lang", lang());
});
onCleanup(() => {
const root = document.querySelector(":root")!;
root.removeAttribute("lang");
});
2024-07-14 12:28:44 +00:00
return (
<ErrorBoundary
fallback={(err, reset) => {
console.error(err);
2024-08-05 08:24:34 +00:00
return <UnexpectedError error={err} />;
}}
>
<ThemeProvider theme={theme()}>
2024-09-26 09:23:40 +00:00
<DateFnScope>
<ClientProvider value={clients}>
<ServiceWorkerProvider
value={{
needRefresh,
offlineReady,
serviceWorker,
}}
>
<Routing />
</ServiceWorkerProvider>
2024-09-26 09:23:40 +00:00
</ClientProvider>
</DateFnScope>
</ThemeProvider>
</ErrorBoundary>
2024-07-14 12:28:44 +00:00
);
};
export default App;