// js/components/VideoList.js import { nostrClient } from "../nostr.js"; import { formatTimeAgo } from "../utils/timeUtils.js"; import { escapeHTML } from "../utils/htmlUtils.js"; export class VideoList { constructor() { this.videoList = document.getElementById("videoList"); this.pubkey = null; // We'll need this for private video filtering } setPubkey(pubkey) { this.pubkey = pubkey; } async loadVideos() { console.log("Starting loadVideos..."); try { const videos = await nostrClient.fetchVideos(); console.log("Raw videos from nostrClient:", videos); if (!videos) { console.log("No videos received"); throw new Error("No videos received from relays"); } // Convert to array if not already const videosArray = Array.isArray(videos) ? videos : [videos]; // Filter private videos const displayedVideos = videosArray.filter((video) => { if (!video.isPrivate) { return true; } return this.pubkey && video.pubkey === this.pubkey; }); if (displayedVideos.length === 0) { console.log("No valid videos found after filtering."); this.renderEmptyState( "No public videos available yet. Be the first to upload one!" ); return; } console.log("Processing filtered videos:", displayedVideos); await this.renderVideoList(displayedVideos); console.log(`Rendered ${displayedVideos.length} videos successfully`); } catch (error) { console.log("Failed to fetch videos:", error); this.renderEmptyState( "No videos available at the moment. Please try again later." ); } } renderEmptyState(message) { if (this.videoList) { this.videoList.innerHTML = `

${escapeHTML(message)}

`; } } async renderVideoList(videos) { try { console.log("RENDER VIDEO LIST - Start", { videosReceived: videos, videosCount: videos ? videos.length : "N/A", videosType: typeof videos, }); if (!videos || videos.length === 0) { this.renderEmptyState("No videos found."); return; } // Sort by creation date const videoArray = [...videos].sort( (a, b) => b.created_at - a.created_at ); // Fetch user profiles const userProfiles = await this.fetchUserProfiles(videoArray); // Build HTML for each video const renderedVideos = videoArray .map((video, index) => this.renderVideoCard(video, index, userProfiles)) .filter(Boolean); if (renderedVideos.length === 0) { this.renderEmptyState("No valid videos to display."); return; } this.videoList.innerHTML = renderedVideos.join(""); console.log("Videos rendered successfully"); } catch (error) { console.error("Rendering error:", error); this.renderEmptyState("Error loading videos."); } } async fetchUserProfiles(videos) { const userProfiles = new Map(); const uniquePubkeys = [...new Set(videos.map((v) => v.pubkey))]; for (const pubkey of uniquePubkeys) { try { const profile = await nostrClient.fetchUserProfile(pubkey); userProfiles.set(pubkey, profile); } catch (error) { console.error(`Profile fetch error for ${pubkey}:`, error); userProfiles.set(pubkey, { name: "Unknown", picture: `https://robohash.org/${pubkey}`, }); } } return userProfiles; } renderVideoCard(video, index, userProfiles) { try { if (!this.validateVideo(video, index)) { console.error(`Invalid video: ${video.title}`); return ""; } const profile = userProfiles.get(video.pubkey) || { name: "Unknown", picture: `https://robohash.org/${video.pubkey}`, }; const canEdit = video.pubkey === this.pubkey; const highlightClass = video.isPrivate && canEdit ? "border-2 border-yellow-500" : "border-none"; return `
${this.renderThumbnail(video)} ${this.renderCardInfo(video, profile)}
`; } catch (error) { console.error(`Error processing video ${index}:`, error); return ""; } } renderThumbnail(video) { return `
${ video.thumbnail ? this.renderThumbnailImage(video) : this.renderPlaceholderThumbnail() }
`; } renderThumbnailImage(video) { return ` ${escapeHTML(video.title)} `; } renderPlaceholderThumbnail() { return `
`; } renderCardInfo(video, profile) { const timeAgo = formatTimeAgo(video.created_at); const canEdit = video.pubkey === this.pubkey; return `

${escapeHTML(video.title)}

${profile.name}

${escapeHTML(profile.name)}

${timeAgo}
${this.renderGearMenu(video, canEdit)}
`; } renderGearMenu(video, canEdit) { if (!canEdit) return ""; return `
`; } validateVideo(video, index) { const validationResults = { hasId: Boolean(video?.id), isValidId: typeof video?.id === "string" && video.id.trim().length > 0, hasVideo: Boolean(video), hasTitle: Boolean(video?.title), hasMagnet: Boolean(video?.magnet), hasMode: Boolean(video?.mode), hasPubkey: Boolean(video?.pubkey), isValidTitle: typeof video?.title === "string" && video.title.length > 0, isValidMagnet: typeof video?.magnet === "string" && video.magnet.length > 0, isValidMode: typeof video?.mode === "string" && ["dev", "live"].includes(video.mode), }; const passed = Object.values(validationResults).every(Boolean); console.log( `Video ${video?.title} validation results:`, validationResults, passed ? "PASSED" : "FAILED" ); return passed; } } export const videoList = new VideoList();