tangled
alpha
login
or
join now
whey.party
/
red-dwarf
82
fork
atom
an independent Bluesky client using Constellation, PDS Queries, and other services
reddwarf.app
frontend
spa
bluesky
reddwarf
microcosm
client
app
82
fork
atom
overview
issues
25
pulls
pipelines
customizable microcosm urls
rimar1337
4 months ago
2f2f25fa
ba24c311
+102
-10
5 changed files
expand all
collapse all
unified
split
src
components
Login.tsx
routes
notifications.tsx
settings.tsx
utils
atoms.ts
useQuery.ts
+1
-1
src/components/Login.tsx
···
161
161
onClick={onClick}
162
162
className={`px-4 py-2 text-sm font-medium transition-colors rounded-full flex-1 ${
163
163
active
164
164
-
? "text-gray-950 dark:text-gray-200 border-gray-500 bg-gray-400 dark:bg-gray-500"
164
164
+
? "text-gray-50 dark:text-gray-200 border-gray-500 bg-gray-400 dark:bg-gray-500"
165
165
: "text-gray-600 dark:text-gray-300 hover:text-gray-700 dark:hover:text-gray-200"
166
166
}`}
167
167
>
+7
-3
src/routes/notifications.tsx
···
1
1
import { createFileRoute } from "@tanstack/react-router";
2
2
+
import { useAtom } from "jotai";
2
3
import React, { useEffect, useRef,useState } from "react";
3
4
4
5
import { useAuth } from "~/providers/UnifiedAuthProvider";
6
6
+
import { constellationURLAtom } from "~/utils/atoms";
5
7
6
8
const HANDLE_DID_CACHE_TIMEOUT = 60 * 60 * 1000; // 1 hour
7
9
···
70
72
}
71
73
}
72
74
75
75
+
const [constellationURL] = useAtom(constellationURLAtom)
76
76
+
73
77
useEffect(() => {
74
78
if (!did) return;
75
79
setLoading(true);
76
80
setError(null);
77
81
const urls = [
78
78
-
`https://constellation.microcosm.blue/links?target=${encodeURIComponent(did)}&collection=app.bsky.feed.post&path=.facets[app.bsky.richtext.facet].features[app.bsky.richtext.facet%23mention].did`,
79
79
-
`https://constellation.microcosm.blue/links?target=${encodeURIComponent(did)}&collection=app.bsky.feed.post&path=.facets[].features[app.bsky.richtext.facet%23mention].did`,
80
80
-
`https://constellation.microcosm.blue/links?target=${encodeURIComponent(did)}&collection=app.bsky.graph.follow&path=.subject`,
82
82
+
`https://${constellationURL}/links?target=${encodeURIComponent(did)}&collection=app.bsky.feed.post&path=.facets[app.bsky.richtext.facet].features[app.bsky.richtext.facet%23mention].did`,
83
83
+
`https://${constellationURL}/links?target=${encodeURIComponent(did)}&collection=app.bsky.feed.post&path=.facets[].features[app.bsky.richtext.facet%23mention].did`,
84
84
+
`https://${constellationURL}/links?target=${encodeURIComponent(did)}&collection=app.bsky.graph.follow&path=.subject`,
81
85
];
82
86
let ignore = false;
83
87
Promise.all(
+70
src/routes/settings.tsx
···
1
1
import { createFileRoute } from "@tanstack/react-router";
2
2
+
import { useAtom } from "jotai";
2
3
3
4
import { Header } from "~/components/Header";
4
5
import Login from "~/components/Login";
6
6
+
import {
7
7
+
constellationURLAtom,
8
8
+
defaultconstellationURL,
9
9
+
defaultslingshotURL,
10
10
+
slingshotURLAtom,
11
11
+
} from "~/utils/atoms";
5
12
6
13
export const Route = createFileRoute("/settings")({
7
14
component: Settings,
···
21
28
}}
22
29
/>
23
30
<Login />
31
31
+
<TextInputSetting
32
32
+
atom={constellationURLAtom}
33
33
+
title={"Constellation URL"}
34
34
+
description={
35
35
+
"customize the Constellation instance to be used by Red Dwarf"
36
36
+
}
37
37
+
init={defaultconstellationURL}
38
38
+
/>
39
39
+
<TextInputSetting
40
40
+
atom={slingshotURLAtom}
41
41
+
title={"Slingshot URL"}
42
42
+
description={"customize the Slingshot instance to be used by Red Dwarf"}
43
43
+
init={defaultslingshotURL}
44
44
+
/>
24
45
</>
25
46
);
26
47
}
48
48
+
49
49
+
export function TextInputSetting({
50
50
+
atom,
51
51
+
title,
52
52
+
description,
53
53
+
init,
54
54
+
}: {
55
55
+
atom: typeof constellationURLAtom;
56
56
+
title?: string;
57
57
+
description?: string;
58
58
+
init?: string;
59
59
+
}) {
60
60
+
const [value, setValue] = useAtom(atom);
61
61
+
return (
62
62
+
<div className="flex flex-col gap-2 p-4 rounded-2xl border border-gray-200 dark:border-gray-800 ">
63
63
+
<div>
64
64
+
{title && (
65
65
+
<h3 className="text-sm font-medium text-gray-900 dark:text-gray-100">
66
66
+
{title}
67
67
+
</h3>
68
68
+
)}
69
69
+
{description && (
70
70
+
<p className="text-sm text-gray-500 dark:text-gray-400">
71
71
+
{description}
72
72
+
</p>
73
73
+
)}
74
74
+
</div>
75
75
+
76
76
+
<div className="flex flex-row gap-2 items-center">
77
77
+
<input
78
78
+
type="text"
79
79
+
value={value}
80
80
+
onChange={(e) => setValue(e.target.value)}
81
81
+
className="flex-1 px-3 py-2 rounded-lg bg-white dark:bg-gray-800 border border-gray-300 dark:border-gray-700
82
82
+
text-gray-900 dark:text-gray-100 placeholder:text-gray-500 dark:placeholder:text-gray-400
83
83
+
focus:outline-none focus:ring-2 focus:ring-gray-400 dark:focus:ring-gray-600"
84
84
+
placeholder="Enter value..."
85
85
+
/>
86
86
+
<button
87
87
+
onClick={() => setValue(init ?? "")}
88
88
+
className="px-3 py-2 rounded-lg border border-gray-300 dark:border-gray-700 bg-gray-100 dark:bg-gray-800
89
89
+
text-gray-700 dark:text-gray-300 hover:bg-gray-200 dark:hover:bg-gray-700 transition"
90
90
+
>
91
91
+
Reset
92
92
+
</button>
93
93
+
</div>
94
94
+
</div>
95
95
+
);
96
96
+
}
+11
src/utils/atoms.ts
···
21
21
{}
22
22
);
23
23
24
24
+
export const defaultconstellationURL = 'constellation.microcosm.blue'
25
25
+
export const constellationURLAtom = atomWithStorage<string>(
26
26
+
'constellationURL',
27
27
+
defaultconstellationURL
28
28
+
)
29
29
+
export const defaultslingshotURL = 'slingshot.microcosm.blue'
30
30
+
export const slingshotURLAtom = atomWithStorage<string>(
31
31
+
'slingshotURL',
32
32
+
defaultslingshotURL
33
33
+
)
34
34
+
24
35
export const isAtTopAtom = atom<boolean>(true);
25
36
26
37
type ComposerState =
+13
-6
src/utils/useQuery.ts
···
7
7
useQuery,
8
8
type UseQueryResult} from "@tanstack/react-query";
9
9
10
10
+
import { constellationURLAtom, slingshotURLAtom, store } from "./atoms";
11
11
+
10
12
export function constructIdentityQuery(didorhandle?: string) {
11
13
return queryOptions({
12
14
queryKey: ["identity", didorhandle],
13
15
queryFn: async () => {
14
16
if (!didorhandle) return undefined as undefined
17
17
+
const slingshoturl = store.get(slingshotURLAtom)
15
18
const res = await fetch(
16
16
-
`https://slingshot.microcosm.blue/xrpc/com.bad-example.identity.resolveMiniDoc?identifier=${encodeURIComponent(didorhandle)}`
19
19
+
`https://${slingshoturl}/xrpc/com.bad-example.identity.resolveMiniDoc?identifier=${encodeURIComponent(didorhandle)}`
17
20
);
18
21
if (!res.ok) throw new Error("Failed to fetch post");
19
22
try {
···
63
66
queryKey: ["post", uri],
64
67
queryFn: async () => {
65
68
if (!uri) return undefined as undefined
69
69
+
const slingshoturl = store.get(slingshotURLAtom)
66
70
const res = await fetch(
67
67
-
`https://slingshot.microcosm.blue/xrpc/com.bad-example.repo.getUriRecord?at_uri=${encodeURIComponent(uri)}`
71
71
+
`https://${slingshoturl}/xrpc/com.bad-example.repo.getUriRecord?at_uri=${encodeURIComponent(uri)}`
68
72
);
69
73
let data: any;
70
74
try {
···
126
130
queryKey: ["profile", uri],
127
131
queryFn: async () => {
128
132
if (!uri) return undefined as undefined
133
133
+
const slingshoturl = store.get(slingshotURLAtom)
129
134
const res = await fetch(
130
130
-
`https://slingshot.microcosm.blue/xrpc/com.bad-example.repo.getUriRecord?at_uri=${encodeURIComponent(uri)}`
135
135
+
`https://${slingshoturl}/xrpc/com.bad-example.repo.getUriRecord?at_uri=${encodeURIComponent(uri)}`
131
136
);
132
137
let data: any;
133
138
try {
···
249
254
const path = query?.path
250
255
const cursor = query.cursor
251
256
const dids = query?.dids
257
257
+
const constellation = store.get(constellationURLAtom);
252
258
const res = await fetch(
253
253
-
`https://constellation.microcosm.blue${method}?target=${encodeURIComponent(target)}${collection ? `&collection=${encodeURIComponent(collection)}` : ""}${path ? `&path=${encodeURIComponent(path)}` : ""}${cursor ? `&cursor=${encodeURIComponent(cursor)}` : ""}${dids ? dids.map((did) => `&did=${encodeURIComponent(did)}`).join("") : ""}`
259
259
+
`https://${constellation}${method}?target=${encodeURIComponent(target)}${collection ? `&collection=${encodeURIComponent(collection)}` : ""}${path ? `&path=${encodeURIComponent(path)}` : ""}${cursor ? `&cursor=${encodeURIComponent(cursor)}` : ""}${dids ? dids.map((did) => `&did=${encodeURIComponent(did)}`).join("") : ""}`
254
260
);
255
261
if (!res.ok) throw new Error("Failed to fetch post");
256
262
try {
···
450
456
queryKey: ["arbitrary", uri],
451
457
queryFn: async () => {
452
458
if (!uri) return undefined as undefined
459
459
+
const slingshoturl = store.get(slingshotURLAtom)
453
460
const res = await fetch(
454
454
-
`https://slingshot.microcosm.blue/xrpc/com.bad-example.repo.getUriRecord?at_uri=${encodeURIComponent(uri)}`
461
461
+
`https://${slingshoturl}/xrpc/com.bad-example.repo.getUriRecord?at_uri=${encodeURIComponent(uri)}`
455
462
);
456
463
let data: any;
457
464
try {
···
624
631
collection: string
625
632
path: string
626
633
}) {
627
627
-
const constellationHost = 'constellation.microcosm.blue'
634
634
+
const constellationHost = store.get(constellationURLAtom)
628
635
console.log(
629
636
'yknowIReallyHateThisButWhateverGuardedConstructConstellationInfiniteQueryLinks',
630
637
query,