diff --git a/src/lib/VideoCard.svelte b/src/lib/VideoCard.svelte index 1bdd1f6..2b743ce 100644 --- a/src/lib/VideoCard.svelte +++ b/src/lib/VideoCard.svelte @@ -19,15 +19,18 @@ resolveHandle, getThumbnailUrl, getPlaylistUrl, + getProfile, type VideoRecord } from './api'; import { getCardOffsets, seededRandom } from './theme'; import WavyBorder from './WavyBorder.svelte'; + import WavyCircle from './WavyCircle.svelte'; let { video, onSelect }: { video: VideoRecord; onSelect: (video: VideoRecord) => void } = $props(); let creatorHandle = $state(''); + let creatorAvatar = $state(''); let thumbnailUrl: string | null = $state(null); // Extract rkey from URI for seeding @@ -53,7 +56,11 @@ onMount(() => { creatorHandle = video.value.creator; - resolveHandle(video.value.creator).then((h) => (creatorHandle = h)); + resolveHandle(video.value.creator).then((h) => { + creatorHandle = h; + const handle = h.replace('@', ''); + getProfile(handle).then((p) => { if (p.avatar) creatorAvatar = p.avatar; }).catch(() => {}); + }); getThumbnailUrl(video).then((url) => (thumbnailUrl = url)); }); @@ -197,7 +204,14 @@

{video.value.title}

- e.stopPropagation()}>{creatorHandle} + e.stopPropagation()}> + {#if creatorAvatar} + + + + {/if} + {creatorHandle} +

{formatDate(video.value.createdAt)}

@@ -323,9 +337,24 @@ overflow: hidden; } - .creator { - display: block; - margin: 4px 0 0; + .creator-row { + display: flex; + align-items: center; + gap: 6px; + margin: 6px 0 0; + text-decoration: none; + transition: transform 0.15s; + } + + .creator-row:hover { + transform: translateX(2px); + } + + .creator-row:hover .creator-name { + color: #FF3992; + } + + .creator-name { font-family: 'Fang', system-ui, sans-serif; font-size: 0.8rem; color: #0A182B; @@ -335,10 +364,6 @@ transition: color 0.15s; } - .creator:hover { - color: #FF3992; - } - .date { margin: 2px 0 0; font-family: 'Fang', system-ui, sans-serif; diff --git a/src/lib/api.ts b/src/lib/api.ts index 483068c..bfdab39 100644 --- a/src/lib/api.ts +++ b/src/lib/api.ts @@ -135,6 +135,64 @@ export async function listVideos(cursor?: string, pageSize = 9): Promise<{ }; } +/** Fetch videos by a specific creator DID */ +export async function listVideosByCreator(creatorDid: string): Promise { + const query = ` +query GetVideosByCreator($creatorDid: String!) { + placeStreamVideo(first: 50, where: { creator: { equalTo: $creatorDid } }) { + edges { + node { + uri + cid + creator + title + duration + createdAt + livestream + source { + ref + size + mimeType + } + } + } + } +}`; + + const res = await fetch(VODSLICE_API, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ query, variables: { creatorDid } }) + }); + + if (!res.ok) throw new Error(`Failed to fetch creator videos: ${res.status}`); + const json = await res.json(); + const edges = json.data?.placeStreamVideo?.edges || []; + + return edges.map((edge: VodSliceEdge) => { + const n = edge.node; + let livestream: { cid: string; uri: string } | undefined; + if (typeof n.livestream === 'string') { + try { livestream = JSON.parse(n.livestream); } catch { /* ignore */ } + } else if (n.livestream && typeof n.livestream === 'object') { + livestream = n.livestream as { uri: string; cid: string }; + } + return { + uri: n.uri, + cid: n.cid || '', + value: { + $type: COLLECTION, + title: n.title, + source: { ...n.source, $type: 'place.stream.muxl.defs#archiveBlob' }, + creator: n.creator, + duration: n.duration, + createdAt: n.createdAt, + livestream + } + }; + }); +} + /** Fetch all videos (for deep-link lookup). Uses pagination internally. */ export async function fetchAllVideos(): Promise { const all: VideoRecord[] = []; diff --git a/src/routes/profile/[handle]/+page.svelte b/src/routes/profile/[handle]/+page.svelte index 2a986cb..1969059 100644 --- a/src/routes/profile/[handle]/+page.svelte +++ b/src/routes/profile/[handle]/+page.svelte @@ -1,13 +1,18 @@ @@ -28,7 +46,7 @@
- + { window.location.href = '/'; }} /> {#if loading}
@@ -52,9 +70,9 @@
{#if profile.avatar}
- - {profile.displayName - + + {profile.displayName +
{/if} @@ -93,6 +111,35 @@
+ + + {#if selectedVideo} +
+ +
+ +

{selectedVideo.value.title}

+

+ {formatDate(selectedVideo.value.createdAt)} + ยท + {formatDuration(selectedVideo.value.duration)} +

+
+
+
+ {/if} + + + {#if videos.length > 0} +

videos

+
+ {#each videos as video (video.uri)} + + {/each} +
+ {:else} +

no videos found for this creator

+ {/if} {/if}