an appview-less Bluesky client using Constellation and PDS Queries
reddwarf.app
frontend
spa
bluesky
reddwarf
microcosm
1import "~/styles/app.css";
2
3import { createSyncStoragePersister } from "@tanstack/query-sync-storage-persister";
4import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
5import { persistQueryClient } from "@tanstack/react-query-persist-client";
6import { createRouter, RouterProvider } from "@tanstack/react-router";
7import { useSetAtom } from "jotai";
8import { useEffect } from "react";
9//import { StrictMode } from "react";
10import ReactDOM from "react-dom/client";
11
12import reportWebVitals from "./reportWebVitals.ts";
13// Import the generated route tree
14import { routeTree } from "./routeTree.gen";
15import { isAtTopAtom } from "./utils/atoms.ts";
16
17const queryClient = new QueryClient({
18 defaultOptions: {
19 queries: {
20 gcTime: 1000 * 60 * 60 * 24 * 24, // 24 days
21 },
22 },
23});
24const localStoragePersister = createSyncStoragePersister({
25 storage: window.localStorage,
26});
27
28persistQueryClient({
29 queryClient,
30 persister: localStoragePersister,
31});
32
33// Create a new router instance
34const router = createRouter({
35 routeTree,
36 context: { queryClient },
37 defaultPreload: "intent",
38 scrollRestoration: true,
39 defaultStructuralSharing: true,
40 defaultPreloadStaleTime: 0,
41});
42
43// Register the router instance for type safety
44declare module "@tanstack/react-router" {
45 interface Register {
46 router: typeof router;
47 }
48}
49
50// Render the app
51const rootElement = document.getElementById("app");
52if (rootElement && !rootElement.innerHTML) {
53 const root = ReactDOM.createRoot(rootElement);
54 root.render(
55 // double queries annoys me
56 // <StrictMode>
57 <QueryClientProvider client={queryClient}>
58 <ScrollTopWatcher />
59 <RouterProvider router={router} />
60 </QueryClientProvider>
61 // </StrictMode>
62 );
63}
64
65// If you want to start measuring performance in your app, pass a function
66// to log results (for example: reportWebVitals(// /*mass comment*/ console.log))
67// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
68reportWebVitals();
69
70export default function ScrollTopWatcher() {
71 const setIsAtTop = useSetAtom(isAtTopAtom);
72 useEffect(() => {
73 const meta = document.querySelector('meta[name="theme-color"]');
74 let lastAtTop = window.scrollY === 0;
75 let timeoutId: number | undefined;
76
77 const setVars = (atTop: boolean) => {
78 const root = document.documentElement;
79 root.style.setProperty("--is-top", atTop ? "1" : "0");
80
81 const bg = getComputedStyle(root).getPropertyValue("--header-bg").trim();
82 if (meta && bg) meta.setAttribute("content", bg);
83 setIsAtTop(atTop);
84 };
85
86 const check = () => {
87 const atTop = window.scrollY === 0;
88 if (atTop !== lastAtTop) {
89 lastAtTop = atTop;
90 setVars(atTop);
91 }
92 };
93
94 const handleScroll = () => {
95 if (timeoutId) clearTimeout(timeoutId);
96 timeoutId = window.setTimeout(check, 2);
97 };
98
99 // initialize
100 setVars(lastAtTop);
101 window.addEventListener("scroll", handleScroll, { passive: true });
102
103 return () => {
104 window.removeEventListener("scroll", handleScroll);
105 if (timeoutId) clearTimeout(timeoutId);
106 };
107 }, []);
108
109 return null;
110}