+21
-3
README.md
+21
-3
README.md
···
1
1
# Red Dwarf
2
2
Red Dwarf is a Bluesky client that does not use any AppView servers, instead it gathers the data from [Constellation](https://constellation.microcosm.blue/) and each users' PDS.
3
3
4
-

4
+

5
5
6
6
huge thanks to [Microcosm](https://microcosm.blue/) for making this possible
7
7
···
52
52
and for list feeds, you can just use something like graze or skyfeed to input a list of users and output a custom feed
53
53
54
54
## Tanstack Router
55
-
it does the job, nothing very specific was used here
55
+
something specific was used here
56
+
57
+
so tanstack router is used as the base, but the home route is using tanstack-router-keepalive to preserve the route for better responsiveness, and it also saves scroll position of feeds into jotai (persistent)
58
+
59
+
i previously used a tanstack router loader to ensure the tanstack query cache is ready to prevent scroll jumps but it is way too slow so i replaced it with tanstack-router-keepalive
60
+
61
+
## Icons
62
+
this project uses Material icons. do not the light variant. sometimes i use `Mdi` if the icon needed doesnt exist in `MaterialSymbols`
56
63
57
-
im planning to use the loader system on select pages to prevent loss of scroll positon and state though its really complex so i havent done it yet but the migration to tanstack query is a huge first step towards this goal
64
+
the project uses unplugin icon auto import, so you can just use the component and itll just work!
65
+
66
+
the format is:
67
+
```tsx
68
+
<IconMaterialSymbols{icon name here} />
69
+
// or
70
+
<IconMdi{icon name here} />
71
+
```
72
+
73
+
you can get the full list of icon names from iconify ([Material Symbols](https://icon-sets.iconify.design/material-symbols/) or [MDI](https://icon-sets.iconify.design/mdi/))
74
+
75
+
while it is nice to keep everything consistent by using material icons, if the icon you need is not provided by either material symbols nor mdi, you are allowed to just grab any icon from any pack (please do prioritize icons that fit in)
public/screenshot.jpg
public/screenshot.jpg
This is a binary file and will not be displayed.
public/screenshot.png
public/screenshot.png
This is a binary file and will not be displayed.
+12
-1
src/auto-imports.d.ts
+12
-1
src/auto-imports.d.ts
···
6
6
// biome-ignore lint: disable
7
7
export {}
8
8
declare global {
9
-
9
+
const IconMaterialSymbolsAccountCircle: typeof import('~icons/material-symbols/account-circle.jsx').default
10
+
const IconMaterialSymbolsAccountCircleOutline: typeof import('~icons/material-symbols/account-circle-outline.jsx').default
11
+
const IconMaterialSymbolsHome: typeof import('~icons/material-symbols/home.jsx').default
12
+
const IconMaterialSymbolsHomeOutline: typeof import('~icons/material-symbols/home-outline.jsx').default
13
+
const IconMaterialSymbolsNotifications: typeof import('~icons/material-symbols/notifications.jsx').default
14
+
const IconMaterialSymbolsNotificationsOutline: typeof import('~icons/material-symbols/notifications-outline.jsx').default
15
+
const IconMaterialSymbolsSearch: typeof import('~icons/material-symbols/search.jsx').default
16
+
const IconMaterialSymbolsSettings: typeof import('~icons/material-symbols/settings.jsx').default
17
+
const IconMaterialSymbolsSettingsOutline: typeof import('~icons/material-symbols/settings-outline.jsx').default
18
+
const IconMaterialSymbolsTag: typeof import('~icons/material-symbols/tag.jsx').default
19
+
const IconMdiAccountCircle: typeof import('~icons/mdi/account-circle.jsx').default
20
+
const IconMdiPencilOutline: typeof import('~icons/mdi/pencil-outline.jsx').default
10
21
}
+27
-32
src/routes/__root.tsx
+27
-32
src/routes/__root.tsx
···
21
21
import { NotFound } from "~/components/NotFound";
22
22
import { UnifiedAuthProvider, useAuth } from "~/providers/UnifiedAuthProvider";
23
23
import { seo } from "~/utils/seo";
24
-
import IconHome from "~icons/material-symbols/home"
25
-
import IconHomeOutline from "~icons/material-symbols/home-outline"
26
-
import IconNotifications from "~icons/material-symbols/notifications"
27
-
import IconNotificationsOutline from "~icons/material-symbols/notifications-outline"
28
-
import IconSearch from "~icons/material-symbols/search"
29
-
import IconSettings from "~icons/material-symbols/settings"
30
-
import IconSettingsOutline from "~icons/material-symbols/settings-outline"
31
-
import IconTag from "~icons/material-symbols/tag"
32
-
import IconAccountCircleOutline from "~icons/mdi/account-circle-outline"
33
-
import IconPencilOutline from "~icons/mdi/pencil-outline"
34
24
35
25
export const Route = createRootRouteWithContext<{
36
26
queryClient: QueryClient;
···
204
194
}
205
195
>
206
196
{!isHome ? (
207
-
<IconHomeOutline width={28} height={28} />
197
+
<IconMaterialSymbolsHomeOutline width={28} height={28} />
208
198
) : (
209
-
<IconHome width={28} height={28} />
199
+
<IconMaterialSymbolsHome width={28} height={28} />
210
200
)}
211
201
<span>Home</span>
212
202
</Link>
···
218
208
}
219
209
>
220
210
{!isNotifications ? (
221
-
<IconNotificationsOutline width={28} height={28} />
211
+
<IconMaterialSymbolsNotificationsOutline width={28} height={28} />
222
212
) : (
223
-
<IconNotifications width={28} height={28} />
213
+
<IconMaterialSymbolsNotifications width={28} height={28} />
224
214
)}
225
215
<span>Notifications</span>
226
216
</Link>
···
231
221
}`}
232
222
>
233
223
{location.pathname.startsWith("/feeds") ? (
234
-
<IconTag width={28} height={28} />
224
+
<IconMaterialSymbolsTag width={28} height={28} />
235
225
) : (
236
-
<IconTag width={28} height={28} />
226
+
<IconMaterialSymbolsTag width={28} height={28} />
237
227
)}
238
228
<span>Feeds</span>
239
229
</Link>
···
245
235
}`}
246
236
>
247
237
{location.pathname.startsWith("/search") ? (
248
-
<IconSearch width={28} height={28} />
238
+
<IconMaterialSymbolsSearch width={28} height={28} />
249
239
) : (
250
-
<IconSearch width={28} height={28} />
240
+
<IconMaterialSymbolsSearch width={28} height={28} />
251
241
)}
252
242
<span>Search</span>
253
243
</Link>
···
266
256
}}
267
257
type="button"
268
258
>
269
-
<IconAccountCircleOutline width={28} height={28} />
259
+
{!isProfile ? (
260
+
<IconMaterialSymbolsAccountCircleOutline width={28} height={28} />
261
+
) : (
262
+
<IconMaterialSymbolsAccountCircle width={28} height={28} />
263
+
)
264
+
}
270
265
<span>Profile</span>
271
266
</button>
272
267
<Link
···
276
271
}`}
277
272
>
278
273
{!location.pathname.startsWith("/settings") ? (
279
-
<IconSettingsOutline width={28} height={28} />
274
+
<IconMaterialSymbolsSettingsOutline width={28} height={28} />
280
275
) : (
281
-
<IconSettings width={28} height={28} />
276
+
<IconMaterialSymbolsSettings width={28} height={28} />
282
277
)}
283
278
<span>Settings</span>
284
279
</Link>
···
287
282
onClick={() => setPostOpen(true)}
288
283
type="button"
289
284
>
290
-
<IconPencilOutline
285
+
<IconMdiPencilOutline
291
286
width={24}
292
287
height={24}
293
288
className="text-gray-600 dark:text-gray-400"
···
331
326
type="button"
332
327
aria-label="Create Post"
333
328
>
334
-
<IconPencilOutline
329
+
<IconMdiPencilOutline
335
330
width={24}
336
331
height={24}
337
332
className="text-gray-600 dark:text-gray-400"
···
384
379
}`}
385
380
>
386
381
{!isHome ? (
387
-
<IconHomeOutline width={24} height={24} />
382
+
<IconMaterialSymbolsHomeOutline width={24} height={24} />
388
383
) : (
389
-
<IconHome width={24} height={24} />
384
+
<IconMaterialSymbolsHome width={24} height={24} />
390
385
)}
391
386
<span className="text-xs mt-1">Home</span>
392
387
</Link>
···
399
394
}`}
400
395
>
401
396
{!location.pathname.startsWith("/search") ? (
402
-
<IconSearch width={24} height={24} />
397
+
<IconMaterialSymbolsSearch width={24} height={24} />
403
398
) : (
404
-
<IconSearch width={24} height={24} />
399
+
<IconMaterialSymbolsSearch width={24} height={24} />
405
400
)}
406
401
<span className="text-xs mt-1">Search</span>
407
402
</Link>
···
414
409
}`}
415
410
>
416
411
{!isNotifications ? (
417
-
<IconNotificationsOutline width={24} height={24} />
412
+
<IconMaterialSymbolsNotificationsOutline width={24} height={24} />
418
413
) : (
419
-
<IconNotifications width={24} height={24} />
414
+
<IconMaterialSymbolsNotifications width={24} height={24} />
420
415
)}
421
416
<span className="text-xs mt-1">Notifications</span>
422
417
</Link>
···
437
432
}}
438
433
type="button"
439
434
>
440
-
<IconAccountCircleOutline width={24} height={24} />
435
+
<IconMaterialSymbolsAccountCircleOutline width={24} height={24} />
441
436
<span className="text-xs mt-1">Profile</span>
442
437
</button>
443
438
<Link
···
449
444
}`}
450
445
>
451
446
{!location.pathname.startsWith("/settings") ? (
452
-
<IconSettingsOutline width={24} height={24} />
447
+
<IconMaterialSymbolsSettingsOutline width={24} height={24} />
453
448
) : (
454
-
<IconSettings width={24} height={24} />
449
+
<IconMaterialSymbolsSettings width={24} height={24} />
455
450
)}
456
451
<span className="text-xs mt-1">Settings</span>
457
452
</Link>