tangled
alpha
login
or
join now
witchcraft.systems
/
pds-dash
15
fork
atom
this repo has no description
15
fork
atom
overview
issues
pulls
pipelines
Somewhat sensible design (WIP)
astrra.space
10 months ago
dcca38a9
164571ec
verified
This commit was signed with the committer's
known signature
.
astrra.space
SSH Key Fingerprint:
SHA256:jQDNS75/33T59Ey4yAzrUPP/5YQaXEetsW8hwUae+ag=
+162
-75
5 changed files
expand all
collapse all
unified
split
src
App.svelte
app.css
lib
AccountComponent.svelte
PostComponent.svelte
pdsfetch.ts
+35
-1
src/App.svelte
···
7
7
</script>
8
8
9
9
<main>
10
10
-
<h1>Welcome to the Feed</h1>
10
10
+
<div id="Content">
11
11
{#await accountsPromise}
12
12
<p>Loading...</p>
13
13
{:then accountsData}
14
14
<div id="Account">
15
15
+
<h1 id="Header">ATProto PDS</h1>
16
16
+
<p>Home to {accountsData.length} accounts</p>
15
17
{#each accountsData as accountObject}
16
18
<AccountComponent account={accountObject} />
17
19
{/each}
···
29
31
{/each}
30
32
</div>
31
33
{/await}
34
34
+
</div>
32
35
</main>
33
36
34
37
<style>
38
38
+
#Content {
39
39
+
display: flex;
40
40
+
/* split the screen in half, left for accounts, right for posts */
41
41
+
width: 100%;
42
42
+
height: 100%;
43
43
+
flex-direction: row;
44
44
+
justify-content: space-between;
45
45
+
align-items: center;
46
46
+
background-color: #12082b;
47
47
+
color: #ffffff;
48
48
+
}
49
49
+
#Feed {
50
50
+
width: 65%;
51
51
+
height: 80vh;
52
52
+
overflow-y: scroll;
53
53
+
padding: 20px;
54
54
+
}
55
55
+
#Account {
56
56
+
width: 35%;
57
57
+
height: 80vh;
58
58
+
overflow-y: scroll;
59
59
+
padding: 20px;
60
60
+
background-color: #070311;
61
61
+
62
62
+
border-radius: 10px;
63
63
+
}
64
64
+
#Header {
65
65
+
text-align: center;
66
66
+
font-size: 2em;
67
67
+
margin-bottom: 20px;
68
68
+
}
35
69
</style>
+20
-46
src/app.css
···
1
1
-
:root {
2
2
-
font-family: system-ui, Avenir, Helvetica, Arial, sans-serif;
3
3
-
line-height: 1.5;
4
4
-
font-weight: 400;
1
1
+
@font-face {
2
2
+
font-family: 'ProggyClean';
3
3
+
src: url(https://witchcraft.systems/ProggyCleanNerdFont-Regular.ttf);
4
4
+
}
5
5
6
6
-
color-scheme: light dark;
7
7
-
color: rgba(255, 255, 255, 0.87);
8
8
-
background-color: #242424;
6
6
+
::-webkit-scrollbar {
7
7
+
width: 0px;
8
8
+
background: transparent;
9
9
+
}
9
10
10
10
-
font-synthesis: none;
11
11
-
text-rendering: optimizeLegibility;
12
12
-
-webkit-font-smoothing: antialiased;
13
13
-
-moz-osx-font-smoothing: grayscale;
11
11
+
* {
12
12
+
scrollbar-width: thin;
13
13
+
scrollbar-color: transparent transparent;
14
14
+
-ms-overflow-style: none; /* IE and Edge */
15
15
+
-webkit-overflow-scrolling: touch;
16
16
+
-webkit-scrollbar: none; /* Safari */
14
17
}
15
18
16
19
a {
···
28
31
place-items: center;
29
32
min-width: 320px;
30
33
min-height: 100vh;
34
34
+
background-color: #12082b;
35
35
+
font-family: 'ProggyClean', monospace;
36
36
+
font-size: 24px;
37
37
+
color: white;
38
38
+
border-color: #8054f0;
31
39
}
32
40
33
41
h1 {
···
35
43
line-height: 1.1;
36
44
}
37
45
38
38
-
.card {
39
39
-
padding: 2em;
40
40
-
}
41
41
-
42
46
#app {
43
43
-
max-width: 1280px;
47
47
+
max-width: 1400px;
44
48
margin: 0 auto;
45
49
padding: 2rem;
46
50
text-align: center;
47
51
}
48
52
49
49
-
button {
50
50
-
border-radius: 8px;
51
51
-
border: 1px solid transparent;
52
52
-
padding: 0.6em 1.2em;
53
53
-
font-size: 1em;
54
54
-
font-weight: 500;
55
55
-
font-family: inherit;
56
56
-
background-color: #1a1a1a;
57
57
-
cursor: pointer;
58
58
-
transition: border-color 0.25s;
59
59
-
}
60
60
-
button:hover {
61
61
-
border-color: #646cff;
62
62
-
}
63
63
-
button:focus,
64
64
-
button:focus-visible {
65
65
-
outline: 4px auto -webkit-focus-ring-color;
66
66
-
}
67
53
68
68
-
@media (prefers-color-scheme: light) {
69
69
-
:root {
70
70
-
color: #213547;
71
71
-
background-color: #ffffff;
72
72
-
}
73
73
-
a:hover {
74
74
-
color: #747bff;
75
75
-
}
76
76
-
button {
77
77
-
background-color: #f9f9f9;
78
78
-
}
79
79
-
}
+20
-4
src/lib/AccountComponent.svelte
···
1
1
<script lang="ts">
2
2
import type { AccountMetadata } from "./pdsfetch";
3
3
const { account }: { account: AccountMetadata } = $props();
4
4
+
import { Config } from "../../config";
4
5
</script>
5
6
<div id="accountContainer">
6
7
{#if account.avatarCid}
7
8
<img
8
9
id="avatar"
9
10
alt="avatar of {account.displayName}"
10
10
-
src="https://pds.witchcraft.systems/xrpc/com.atproto.sync.getBlob?did={account.did}&cid={account.avatarCid}"
11
11
+
src="{Config.PDS_URL}/xrpc/com.atproto.sync.getBlob?did={account.did}&cid={account.avatarCid}"
11
12
/>
12
13
{/if}
13
13
-
<p>{account.displayName}</p>
14
14
+
<div id="accountName">{account.displayName || account.did}</div>
14
15
</div>
15
16
<style>
16
17
#accountContainer {
17
17
-
display: column;
18
18
+
display: flex;
18
19
text-align: start;
19
19
-
border: 2px solid black;
20
20
+
align-items: center;
21
21
+
background-color: #0d0620;
20
22
padding: 4%;
23
23
+
margin: 10px;
24
24
+
25
25
+
/* round corners */
26
26
+
border-radius: 10px;
27
27
+
}
28
28
+
#accountName {
29
29
+
margin-left: 10px;
30
30
+
font-size: 0.9em;
31
31
+
32
32
+
/* replace overflow with ellipsis */
33
33
+
overflow: hidden;
34
34
+
text-overflow: ellipsis;
35
35
+
white-space: nowrap;
36
36
+
max-width: 80%;
21
37
}
22
38
#avatar {
23
39
width: 50px;
+51
-10
src/lib/PostComponent.svelte
···
13
13
alt="avatar of {post.displayName}"
14
14
/>
15
15
{/if}
16
16
-
<p>{post.displayName} | {post.timenotstamp}</p>
16
16
+
<div id="headerText">{post.displayName} | {post.timenotstamp}</div>
17
17
</div>
18
18
<div id="postContent">
19
19
-
<p>{post.text}</p>
20
19
{#if post.replyingDid}
21
21
-
<p>Replying to: {post.replyingDid}</p>
22
22
-
{/if}
20
20
+
<p id="replyingText">replying to: {post.replyingDid}</p>
21
21
+
{/if}
22
22
+
<p id="postText">{post.text}</p>
23
23
{#if post.imagesCid}
24
24
<div id="imagesContainer">
25
25
{#each post.imagesCid as imageLink}
···
42
42
43
43
<style>
44
44
#postContainer {
45
45
-
display: column;
45
45
+
display: flex;
46
46
+
flex-direction: column;
47
47
+
border: 1px solid #8054f0;
48
48
+
background-color: black;
49
49
+
margin-bottom: -1px;
50
50
+
}
51
51
+
#postHeader {
52
52
+
display: flex;
53
53
+
flex-direction: row;
54
54
+
align-items: center;
55
55
+
justify-content: start;
56
56
+
background-color: #1f1145;
57
57
+
padding: 0px 0px;
58
58
+
height: fit-content;
59
59
+
border-bottom: 1px solid #8054f0;
60
60
+
font-weight: bold;
61
61
+
}
62
62
+
#postContent {
63
63
+
display: flex;
46
64
text-align: start;
47
47
-
border: 2px solid black;
48
48
-
padding: 4%;
65
65
+
flex-direction: column;
66
66
+
padding: 10px;
67
67
+
background-color: #0d0620;
68
68
+
color: white;
69
69
+
}
70
70
+
#replyingText {
71
71
+
font-size: 0.7em;
72
72
+
color: white;
73
73
+
margin: 0;
74
74
+
margin-bottom: 10px;
75
75
+
padding: 0;
49
76
}
50
50
-
#postHeader {
51
51
-
text-decoration: underline;
77
77
+
#postText {
78
78
+
margin: 0;
79
79
+
padding: 0;
80
80
+
}
81
81
+
#headerText {
82
82
+
margin-left: 10px;
83
83
+
font-size: 0.9em;
84
84
+
text-align: start;
52
85
}
53
86
#avatar {
54
87
width: 50px;
55
88
height: 50px;
56
56
-
border-radius: 50%;
89
89
+
margin: 0px;
90
90
+
margin-left: 0px;
91
91
+
border-right: #8054f0 1px solid;
57
92
}
58
93
#embedImages {
94
94
+
width: 50%;
95
95
+
height: 50%;
96
96
+
margin-top: 0px;
97
97
+
margin-bottom: -5px;
98
98
+
}
99
99
+
#embedVideo {
59
100
width: 50%;
60
101
height: 50%;
61
102
}
+36
-14
src/lib/pdsfetch.ts
···
5
5
AppBskyFeedPost,
6
6
ComAtprotoRepoListRecords,
7
7
} from "@atcute/client/lexicons";
8
8
+
import { Config } from "../../config";
8
9
// import { ComAtprotoRepoListRecords.Record } from "@atcute/client/lexicons";
9
10
// import { AppBskyFeedPost } from "@atcute/client/lexicons";
10
11
// import { AppBskyActorDefs } from "@atcute/client/lexicons";
···
87
88
88
89
const rpc = new XRPC({
89
90
handler: simpleFetchHandler({
90
90
-
service: "https://pds.witchcraft.systems",
91
91
+
service: Config.PDS_URL,
91
92
}),
92
93
});
93
94
···
99
100
};
100
101
const getAccountMetadata = async (did: `did:${string}:${string}`) => {
101
102
// gonna assume self exists in the app.bsky.actor.profile
103
103
+
try {
102
104
const { data } = await rpc.get("com.atproto.repo.getRecord", {
103
105
params: {
104
106
repo: did,
···
116
118
account.avatarCid = value.avatar.ref["$link"];
117
119
}
118
120
return account;
121
121
+
}
122
122
+
catch (e) {
123
123
+
console.error(`Error fetching metadata for ${did}:`, e);
124
124
+
return {
125
125
+
did: "error",
126
126
+
displayName: "",
127
127
+
avatarCid: null,
128
128
+
};
129
129
+
}
119
130
};
120
131
121
132
const getAllMetadataFromPds = async () => {
···
125
136
return await getAccountMetadata(repo);
126
137
}),
127
138
);
128
128
-
return metadata;
139
139
+
return metadata.filter(account => account.did !== "error");
129
140
};
130
141
131
142
const fetchPosts = async (did: string) => {
132
132
-
const { data } = await rpc.get("com.atproto.repo.listRecords", {
133
133
-
params: {
134
134
-
repo: did,
135
135
-
collection: "app.bsky.feed.post",
136
136
-
limit: 5,
137
137
-
},
138
138
-
});
139
139
-
return {
140
140
-
records: data.records as ComAtprotoRepoListRecords.Record[],
141
141
-
did: did,
142
142
-
};
143
143
+
try {
144
144
+
const { data } = await rpc.get("com.atproto.repo.listRecords", {
145
145
+
params: {
146
146
+
repo: did,
147
147
+
collection: "app.bsky.feed.post",
148
148
+
limit: 5,
149
149
+
},
150
150
+
});
151
151
+
return {
152
152
+
records: data.records as ComAtprotoRepoListRecords.Record[],
153
153
+
did: did,
154
154
+
error: false
155
155
+
};
156
156
+
} catch (e) {
157
157
+
console.error(`Error fetching posts for ${did}:`, e);
158
158
+
return {
159
159
+
records: [],
160
160
+
did: did,
161
161
+
error: true
162
162
+
};
163
163
+
}
143
164
};
144
165
145
166
const fetchAllPosts = async () => {
···
149
170
await fetchPosts(metadata.did)
150
171
),
151
172
);
152
152
-
const posts: Post[] = postRecords.flatMap((userFetch) =>
173
173
+
const validPostRecords = postRecords.filter(record => !record.error);
174
174
+
const posts: Post[] = validPostRecords.flatMap((userFetch) =>
153
175
userFetch.records.map((record) => {
154
176
const user = users.find((user: AccountMetadata) =>
155
177
user.did == userFetch.did