tangled
alpha
login
or
join now
teal.fm
/
teal
110
fork
atom
Your music, beautifully tracked. All yours. (coming soon)
teal.fm
teal-fm
atproto
110
fork
atom
overview
issues
pulls
pipelines
pfp thing init
mmatt.net
10 months ago
cc70fca6
2c3991fe
+145
-71
3 changed files
expand all
collapse all
unified
split
apps
amethyst
components
actor
actorView.tsx
compose.yaml
package.json
+134
-62
apps/amethyst/components/actor/actorView.tsx
···
1
1
-
import { View, Image } from 'react-native';
2
2
-
import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar';
3
3
-
import { CardTitle } from '../../components/ui/card';
4
4
-
import { Text } from '@/components/ui/text';
5
5
-
import { useStore } from '@/stores/mainStore';
1
1
+
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
2
2
+
import { Text } from "@/components/ui/text";
3
3
+
import { useStore } from "@/stores/mainStore";
4
4
+
import { Image, View } from "react-native";
5
5
+
import { CardTitle } from "../../components/ui/card";
6
6
7
7
-
import ActorPlaysView from '@/components/play/actorPlaysView';
8
8
-
import { Button } from '@/components/ui/button';
9
9
-
import { Icon } from '@/lib/icons/iconWithClassName';
10
10
-
import { MoreHorizontal, Pen, Plus } from 'lucide-react-native';
11
11
-
import { Agent } from '@atproto/api';
12
12
-
import { useState, useEffect } from 'react';
13
13
-
import EditProfileModal from './editProfileView';
7
7
+
import ActorPlaysView from "@/components/play/actorPlaysView";
8
8
+
import { Button } from "@/components/ui/button";
9
9
+
import { Icon } from "@/lib/icons/iconWithClassName";
10
10
+
import { Agent } from "@atproto/api";
11
11
+
import { MoreHorizontal, Pen, Plus } from "lucide-react-native";
12
12
+
import { useEffect, useState } from "react";
13
13
+
import EditProfileModal from "./editProfileView";
14
14
15
15
-
import { Record as ProfileRecord } from '@teal/lexicons/src/types/fm/teal/alpha/actor/profile';
16
16
-
import { OutputSchema as GetProfileOutputSchema } from '@teal/lexicons/src/types/fm/teal/alpha/actor/getProfile';
17
17
-
import getImageCdnLink from '@/lib/atp/getImageCdnLink';
15
15
+
import getImageCdnLink from "@/lib/atp/getImageCdnLink";
16
16
+
import { OutputSchema as GetProfileOutputSchema } from "@teal/lexicons/src/types/fm/teal/alpha/actor/getProfile";
17
17
+
import { Record as ProfileRecord } from "@teal/lexicons/src/types/fm/teal/alpha/actor/profile";
18
18
+
19
19
+
const topAlbums = [
20
20
+
{
21
21
+
name: "Album 1",
22
22
+
artist: "Artist 1",
23
23
+
image:
24
24
+
"https://at.uwu.wang/did:plc:tas6hj2xjrqben5653v5kohk/bafkreihxjfnq7r6tst33pf34lh6tojeh6tw6kt3p23dmhxmb2klxckjxx4",
25
25
+
},
26
26
+
{
27
27
+
name: "Album 2",
28
28
+
artist: "Artist 2",
29
29
+
image:
30
30
+
"https://at.uwu.wang/did:plc:tas6hj2xjrqben5653v5kohk/bafkreihxjfnq7r6tst33pf34lh6tojeh6tw6kt3p23dmhxmb2klxckjxx4",
31
31
+
},
32
32
+
{
33
33
+
name: "Album 3",
34
34
+
artist: "Artist 3",
35
35
+
image:
36
36
+
"https://at.uwu.wang/did:plc:tas6hj2xjrqben5653v5kohk/bafkreihxjfnq7r6tst33pf34lh6tojeh6tw6kt3p23dmhxmb2klxckjxx4",
37
37
+
},
38
38
+
{
39
39
+
name: "Album 4",
40
40
+
artist: "Artist 4",
41
41
+
image:
42
42
+
"https://at.uwu.wang/did:plc:tas6hj2xjrqben5653v5kohk/bafkreihxjfnq7r6tst33pf34lh6tojeh6tw6kt3p23dmhxmb2klxckjxx4",
43
43
+
},
44
44
+
{
45
45
+
name: "Album 5",
46
46
+
artist: "Artist 5",
47
47
+
image:
48
48
+
"https://at.uwu.wang/did:plc:tas6hj2xjrqben5653v5kohk/bafkreihxjfnq7r6tst33pf34lh6tojeh6tw6kt3p23dmhxmb2klxckjxx4",
49
49
+
},
50
50
+
];
18
51
19
52
const GITHUB_AVATAR_URI =
20
20
-
'https://i.pinimg.com/originals/ef/a2/8d/efa28d18a04e7fa40ed49eeb0ab660db.jpg';
53
53
+
"https://i.pinimg.com/originals/ef/a2/8d/efa28d18a04e7fa40ed49eeb0ab660db.jpg";
21
54
22
55
export interface ActorViewProps {
23
56
actorDid: string;
···
27
60
export default function ActorView({ actorDid, pdsAgent }: ActorViewProps) {
28
61
const [isEditing, setIsEditing] = useState(false);
29
62
const [profile, setProfile] = useState<
30
30
-
GetProfileOutputSchema['actor'] | null
63
63
+
GetProfileOutputSchema["actor"] | null
31
64
>(null);
32
65
33
66
const tealDid = useStore((state) => state.tealDid);
···
41
74
}
42
75
try {
43
76
let res = await pdsAgent.call(
44
44
-
'fm.teal.alpha.actor.getProfile',
77
77
+
"fm.teal.alpha.actor.getProfile",
45
78
{ actor: actorDid },
46
79
{},
47
47
-
{ headers: { 'atproto-proxy': tealDid + '#teal_fm_appview' } },
80
80
+
{ headers: { "atproto-proxy": tealDid + "#teal_fm_appview" } }
48
81
);
49
82
if (isMounted) {
50
50
-
setProfile(res.data['actor'] as GetProfileOutputSchema['actor']);
83
83
+
setProfile(res.data["actor"] as GetProfileOutputSchema["actor"]);
51
84
}
52
85
} catch (error) {
53
53
-
console.error('Error fetching profile:', error);
86
86
+
console.error("Error fetching profile:", error);
54
87
}
55
88
};
56
89
···
61
94
};
62
95
}, [pdsAgent, actorDid, tealDid]);
63
96
64
64
-
const isSelf = actorDid === (pdsAgent?.did || '');
97
97
+
const isSelf = actorDid === (pdsAgent?.did || "");
65
98
66
99
const handleSave = async (
67
100
updatedProfile: { displayName: any; description: any },
68
101
newAvatarUri: string,
69
69
-
newBannerUri: string,
102
102
+
newBannerUri: string
70
103
) => {
71
104
if (!pdsAgent) {
72
105
return;
73
106
}
74
107
// Implement your save logic here (e.g., update your database or state)
75
75
-
console.log('Saving profile:', updatedProfile, newAvatarUri, newBannerUri);
108
108
+
console.log("Saving profile:", updatedProfile, newAvatarUri, newBannerUri);
76
109
77
110
// Update the local profile data
78
111
setProfile((prevProfile) => ({
···
87
120
let currentUser: ProfileRecord | undefined;
88
121
let cid: string | undefined;
89
122
try {
90
90
-
const res = await pdsAgent.call('com.atproto.repo.getRecord', {
123
123
+
const res = await pdsAgent.call("com.atproto.repo.getRecord", {
91
124
repo: pdsAgent.did,
92
92
-
collection: 'fm.teal.alpha.actor.profile',
93
93
-
rkey: 'self',
125
125
+
collection: "fm.teal.alpha.actor.profile",
126
126
+
rkey: "self",
94
127
});
95
128
currentUser = res.data.value;
96
129
cid = res.data.cid;
97
130
} catch (error) {
98
98
-
console.error('Error fetching user profile:', error);
131
131
+
console.error("Error fetching user profile:", error);
99
132
}
100
133
101
134
// upload blobs if necessary
···
103
136
let newBannerBlob = currentUser?.banner ?? undefined;
104
137
if (newAvatarUri) {
105
138
// if it is http/s url then do nothing
106
106
-
if (!newAvatarUri.startsWith('http')) {
107
107
-
console.log('Uploading avatar');
139
139
+
if (!newAvatarUri.startsWith("http")) {
140
140
+
console.log("Uploading avatar");
108
141
// its a b64 encoded data uri, decode it and get a blob
109
142
const data = await fetch(newAvatarUri).then((r) => r.blob());
110
110
-
const fileType = newAvatarUri.split(';')[0].split(':')[1];
143
143
+
const fileType = newAvatarUri.split(";")[0].split(":")[1];
111
144
console.log(fileType);
112
145
const blob = new Blob([data], { type: fileType });
113
146
newAvatarBlob = (await pdsAgent.uploadBlob(blob)).data.blob;
114
147
}
115
148
}
116
149
if (newBannerUri) {
117
117
-
if (!newBannerUri.startsWith('http')) {
118
118
-
console.log('Uploading banner');
150
150
+
if (!newBannerUri.startsWith("http")) {
151
151
+
console.log("Uploading banner");
119
152
const data = await fetch(newBannerUri).then((r) => r.blob());
120
120
-
const fileType = newBannerUri.split(';')[0].split(':')[1];
153
153
+
const fileType = newBannerUri.split(";")[0].split(":")[1];
121
154
console.log(fileType);
122
155
const blob = new Blob([data], { type: fileType });
123
156
newBannerBlob = (await pdsAgent.uploadBlob(blob)).data.blob;
124
157
}
125
158
}
126
159
127
127
-
console.log('done uploading');
160
160
+
console.log("done uploading");
128
161
129
162
let record: ProfileRecord = {
130
163
displayName: updatedProfile.displayName,
···
137
170
138
171
if (cid) {
139
172
post = await pdsAgent.call(
140
140
-
'com.atproto.repo.putRecord',
173
173
+
"com.atproto.repo.putRecord",
141
174
{},
142
175
{
143
176
repo: pdsAgent.did,
144
144
-
collection: 'fm.teal.alpha.actor.profile',
145
145
-
rkey: 'self',
177
177
+
collection: "fm.teal.alpha.actor.profile",
178
178
+
rkey: "self",
146
179
record,
147
180
swapRecord: cid,
148
148
-
},
181
181
+
}
149
182
);
150
183
} else {
151
184
post = await pdsAgent.call(
152
152
-
'com.atproto.repo.createRecord',
185
185
+
"com.atproto.repo.createRecord",
153
186
{},
154
187
{
155
188
repo: pdsAgent.did,
156
156
-
collection: 'fm.teal.alpha.actor.profile',
157
157
-
rkey: 'self',
189
189
+
collection: "fm.teal.alpha.actor.profile",
190
190
+
rkey: "self",
158
191
record,
159
159
-
},
192
192
+
}
160
193
);
161
194
}
162
195
···
184
217
<View className="flex flex-col items-left justify-start text-left max-w-2xl w-screen gap-1 p-4 px-8">
185
218
<View className="flex flex-row justify-between items-center">
186
219
<View className="flex justify-between">
187
187
-
<Avatar alt="Rick Sanchez's Avatar" className="w-24 h-24">
188
188
-
<AvatarImage
189
189
-
source={{
190
190
-
uri:
191
191
-
(profile.avatar &&
192
192
-
getImageCdnLink({
193
193
-
did: profile.did!,
194
194
-
hash: profile.avatar,
195
195
-
})) ||
196
196
-
GITHUB_AVATAR_URI,
197
197
-
}}
198
198
-
/>
199
199
-
<AvatarFallback>
200
200
-
<Text>{profile.displayName?.substring(0, 1) ?? 'R'}</Text>
201
201
-
</AvatarFallback>
202
202
-
</Avatar>
220
220
+
<View className="group">
221
221
+
<Avatar alt="Rick Sanchez's Avatar" className="w-24 h-24">
222
222
+
<AvatarImage
223
223
+
source={{
224
224
+
uri:
225
225
+
(profile.avatar &&
226
226
+
getImageCdnLink({
227
227
+
did: profile.did!,
228
228
+
hash: profile.avatar,
229
229
+
})) ||
230
230
+
GITHUB_AVATAR_URI,
231
231
+
}}
232
232
+
/>
233
233
+
<AvatarFallback>
234
234
+
<Text>{profile.displayName?.substring(0, 1) ?? "R"}</Text>
235
235
+
</AvatarFallback>
236
236
+
</Avatar>
237
237
+
<View>
238
238
+
{topAlbums.map((album, i: number) => (
239
239
+
<View
240
240
+
key={album.name}
241
241
+
className={`absolute ${
242
242
+
i === 0
243
243
+
? // #1, middle, yellow border
244
244
+
`-z-10 group-hover:-translate-y-[9.3rem] group-hover:translate-x-5 group-hover:rotate-0 group-hover:bg-yellow-200 group-hover:p-[0.125rem]`
245
245
+
: i === 1
246
246
+
? // #2, left of #1, silver border
247
247
+
`-z-[11] group-hover:-translate-y-[8.8rem] group-hover:-translate-x-2 group-hover:-rotate-12 group-hover:bg-stone-200 group-hover:p-[0.125rem]`
248
248
+
: i === 2
249
249
+
? // #3, right of #1, brown border
250
250
+
`-z-[11] group-hover:-translate-y-[8.8rem] group-hover:translate-x-[3.25rem] group-hover:rotate-12 group-hover:bg-yellow-900 group-hover:p-[0.125rem]`
251
251
+
: i === 3
252
252
+
? // #4, very left
253
253
+
`-z-[12] group-hover:-translate-x-8 group-hover:-translate-y-[7.5rem] group-hover:-rotate-45`
254
254
+
: // #5, very right
255
255
+
`-z-[12] group-hover:-translate-y-[7.5rem] group-hover:translate-x-[5.5rem] group-hover:rotate-45`
256
256
+
} -translate-y-[5.6rem] translate-x-5 rotate-0 rounded-xl opacity-0 transition-all duration-300 group-hover:opacity-100`}
257
257
+
>
258
258
+
{album.image ? (
259
259
+
<Image
260
260
+
source={{
261
261
+
uri: album.image,
262
262
+
width: 50,
263
263
+
height: 50,
264
264
+
}}
265
265
+
alt={`${album.name} by ${album.artist}`}
266
266
+
className="rounded-xl"
267
267
+
/>
268
268
+
) : (
269
269
+
<Text>XD</Text>
270
270
+
)}
271
271
+
</View>
272
272
+
))}
273
273
+
</View>
274
274
+
</View>
203
275
<CardTitle className="text-left flex w-full justify-between mt-2">
204
204
-
{profile.displayName ?? ' Richard'}
276
276
+
{profile.displayName ?? " Richard"}
205
277
</CardTitle>
206
278
</View>
207
279
<View className="mt-2 flex-row gap-2">
···
236
308
</View>
237
309
<View>
238
310
{profile
239
239
-
? profile.description?.split('\n').map((str, i) => (
311
311
+
? profile.description?.split("\n").map((str, i) => (
240
312
<Text
241
313
className="text-start self-start place-self-start"
242
314
key={i}
···
244
316
{str}
245
317
</Text>
246
318
)) || <Text>'A very mysterious person'</Text>
247
247
-
: 'Loading...'}
319
319
+
: "Loading..."}
248
320
</View>
249
321
</View>
250
322
<View className="max-w-2xl w-full gap-4 py-4 pl-8">
+10
-8
compose.yaml
···
1
1
services:
2
2
-
aqua-api:
3
3
-
image: ghcr.io/teal-fm/aqua:latest
4
4
-
container_name: aqua-api
5
5
-
# pass through db.sqlite
6
6
-
volumes:
7
7
-
- ./db.sqlite:/app/db.sqlite
2
2
+
cadet:
3
3
+
image: ghcr.io/mmattbtw/cadet:asdf
8
4
ports:
9
9
-
- "3000:3000"
5
5
+
- "9000:9000"
10
6
env_file:
11
11
-
- .env
7
7
+
- .env
8
8
+
satellite:
9
9
+
image: ghcr.io/mmattbtw/satellite:asdf
10
10
+
ports:
11
11
+
- "3132:3000"
12
12
+
env_file:
13
13
+
- .env
+1
-1
package.json
···
2
2
"name": "teal",
3
3
"private": true,
4
4
"version": "0.0.0",
5
5
-
"packageManager": "pnpm@9.15.0+sha256.09a8fe31a34fda706354680619f4002f4ccef6dadff93240d24ef6c831f0fd28",
5
5
+
"packageManager": "pnpm@10.10.0",
6
6
"scripts": {
7
7
"dev": "turbo dev",
8
8
"build": "pnpm turbo run build --filter='./packages/*' --filter='./apps/*'",