+7
-14
src/components/FollowingItem.svelte
+7
-14
src/components/FollowingItem.svelte
···
1
-
<script lang="ts" module>
2
-
const profileCache = new SvelteMap<string, { displayName?: string; handle: string }>();
3
-
</script>
4
-
5
1
<script lang="ts">
6
2
import ProfilePicture from './ProfilePicture.svelte';
7
3
import BlockedUserIndicator from './BlockedUserIndicator.svelte';
···
10
6
import type { Did } from '@atcute/lexicons';
11
7
import type { calculateFollowedUserStats, Sort } from '$lib/following';
12
8
import { resolveDidDoc, type AtpClient } from '$lib/at/client.svelte';
13
-
import { SvelteMap } from 'svelte/reactivity';
14
-
import { router, getBlockRelationship } from '$lib/state.svelte';
9
+
import { router, getBlockRelationship, profiles, handles } from '$lib/state.svelte';
15
10
import { map } from '$lib/result';
16
11
17
12
interface Props {
···
31
26
);
32
27
const isBlocked = $derived(blockRel.userBlocked || blockRel.blockedByTarget);
33
28
34
-
const cached = $derived(profileCache.get(did));
35
-
const displayName = $derived(cached?.displayName);
36
-
const handle = $derived(cached?.handle ?? 'handle.invalid');
29
+
const displayName = $derived(profiles.get(did)?.displayName);
30
+
const handle = $derived(handles.get(did) ?? 'loading...');
37
31
38
32
let error = $state('');
39
33
40
34
const loadProfile = async (targetDid: Did) => {
41
-
if (profileCache.has(targetDid)) return;
35
+
if (profiles.has(targetDid) && handles.has(targetDid)) return;
42
36
43
37
try {
44
38
const [profileRes, handleRes] = await Promise.all([
···
47
41
]);
48
42
if (did !== targetDid) return;
49
43
50
-
profileCache.set(targetDid, {
51
-
handle: handleRes.ok ? handleRes.value : handle,
52
-
displayName: profileRes.ok ? profileRes.value.displayName : displayName
53
-
});
44
+
if (profileRes.ok) profiles.set(targetDid, profileRes.value);
45
+
if (handleRes.ok) handles.set(targetDid, handleRes.value);
46
+
else handles.set(targetDid, 'handle.invalid');
54
47
} catch (e) {
55
48
if (did !== targetDid) return;
56
49
console.error(`failed to load profile for ${targetDid}`, e);
+18
-12
src/components/PostComposer.svelte
+18
-12
src/components/PostComposer.svelte
···
44
44
);
45
45
46
46
const uploadVideo = async (blobUrl: string, mimeType: string) => {
47
-
const blob = await (await fetch(blobUrl)).blob();
48
-
return await client.uploadVideo(blob, mimeType, (status) => {
47
+
const file = await (await fetch(blobUrl)).blob();
48
+
return await client.uploadVideo(file, mimeType, (status) => {
49
49
if (status.stage === 'uploading' && status.progress !== undefined) {
50
50
_state.blobsState.set(blobUrl, { state: 'uploading', progress: status.progress * 0.5 });
51
51
} else if (status.stage === 'processing' && status.progress !== undefined) {
···
57
57
});
58
58
};
59
59
const uploadImage = async (blobUrl: string) => {
60
-
const blob = await (await fetch(blobUrl)).blob();
61
-
return await client.uploadBlob(blob, (progress) => {
60
+
const file = await (await fetch(blobUrl)).blob();
61
+
return await client.uploadBlob(file, (progress) => {
62
62
_state.blobsState.set(blobUrl, { state: 'uploading', progress });
63
63
});
64
64
};
···
101
101
video: upload.blob
102
102
};
103
103
}
104
-
console.log('media', media);
104
+
// console.log('media', media);
105
105
106
106
const record: AppBskyFeedPost.Main = {
107
107
$type: 'app.bsky.feed.post',
···
156
156
let fileInputEl: HTMLInputElement | undefined = $state();
157
157
let selectingFile = $state(false);
158
158
159
+
const canUpload = $derived(
160
+
!(
161
+
_state.attachedMedia?.$type === 'app.bsky.embed.video' ||
162
+
(_state.attachedMedia?.$type === 'app.bsky.embed.images' &&
163
+
_state.attachedMedia.images.length >= 4)
164
+
)
165
+
);
166
+
159
167
const unfocus = () => (_state.focus = 'null');
160
168
161
169
const handleFiles = (files: File[]) => {
162
-
if (!files || files.length === 0) return;
170
+
if (!canUpload || !files || files.length === 0) return;
163
171
164
172
const existingImages =
165
173
_state.attachedMedia?.$type === 'app.bsky.embed.images' ? _state.attachedMedia.images : [];
···
220
228
};
221
229
}
222
230
223
-
const handleUpload = (blobUrl: string, blob: Result<AtpBlob<string>, string>) => {
224
-
if (blob.ok) _state.blobsState.set(blobUrl, { state: 'uploaded', blob: blob.value });
225
-
else _state.blobsState.set(blobUrl, { state: 'error', message: blob.error });
231
+
const handleUpload = (blobUrl: string, res: Result<AtpBlob<string>, string>) => {
232
+
if (res.ok) _state.blobsState.set(blobUrl, { state: 'uploaded', blob: res.value });
233
+
else _state.blobsState.set(blobUrl, { state: 'error', message: res.error });
226
234
};
227
235
228
236
const media = _state.attachedMedia;
···
459
467
fileInputEl?.click();
460
468
}}
461
469
onmousedown={(e) => e.preventDefault()}
462
-
disabled={_state.attachedMedia?.$type === 'app.bsky.embed.video' ||
463
-
(_state.attachedMedia?.$type === 'app.bsky.embed.images' &&
464
-
_state.attachedMedia.images.length >= 4)}
470
+
disabled={!canUpload}
465
471
class="rounded-sm p-1.5 transition-all duration-150 enabled:hover:scale-110 disabled:cursor-not-allowed disabled:opacity-50"
466
472
style="background: color-mix(in srgb, {color} 15%, transparent); color: {color};"
467
473
title="attach media"