"use client";
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
import {
Agent,
AppBskyEmbedImages,
AppBskyFeedPost,
AtUri,
moderatePost,
ModerationPrefs,
} from "@atproto/api";
import { LoaderCircle } from "lucide-react";
import { motion } from "motion/react";
import Image from "next/image";
import Link from "next/link";
import Masonry from "react-masonry-css";
import { PostView } from "@atproto/api/dist/client/types/app/bsky/feed/defs";
import { SaveButton } from "./SaveButton";
import { UnsaveButton } from "./UnsaveButton";
import { LikeButton } from "./LikeButton";
import { useState, useEffect } from "react";
import {
DEFAULT_LOGGED_OUT_LABEL_PREFERENCES,
useModerationOpts,
} from "@/lib/hooks/useModerationOpts";
import { useAuth } from "@/lib/hooks/useAuth";
import { ContentWarning } from "./ContentWarning";
import clsx from "clsx";
export type FeedItem = {
id: string;
imageUrl: string;
alt?: string;
author?: {
avatar?: string;
displayName?: string;
handle: string;
did?: string;
};
text?: string;
uri: string;
aspectRatio?: { width: number; height: number };
blurDataURL?: string;
};
// Props for the Feed component
interface FeedProps {
/**
* Map of the index of the embedded media and post view
*/
feed?: [number, PostView][];
isLoading?: boolean;
showUnsaveButton?: boolean;
}
function getText(post: PostView) {
if (!AppBskyFeedPost.isRecord(post.record)) return;
return (post.record as AppBskyFeedPost.Record).text;
}
function getImageFromItem(it: PostView, index: number) {
if (
AppBskyEmbedImages.isMain(it.embed) ||
AppBskyEmbedImages.isView(it.embed)
) {
return it.embed.images[index];
} else return null;
}
// Add this function to prefetch and cache images
function prefetchAndCacheImages(feed: [number, PostView][] | undefined) {
if (!feed || typeof window === "undefined") return;
feed.forEach(([index, item]) => {
const image = getImageFromItem(item, index);
if (image && image.fullsize) {
const img = new window.Image();
img.src = image.fullsize;
// If service worker is active, explicitly add to cache
if ("serviceWorker" in navigator && navigator.serviceWorker.controller) {
fetch(image.fullsize, { mode: "no-cors" }).catch((err) =>
console.warn("Error prefetching image:", err)
);
}
}
});
}
function ImageCard({
item,
showUnsaveButton,
index,
}: {
item: PostView;
showUnsaveButton?: boolean;
index: number;
}) {
const image = getImageFromItem(item, index);
const [isDropdownOpen, setDropdownOpen] = useState(false);
const modOpts = useModerationOpts();
const { session, agent } = useAuth();
if (!image) return;
const ActionButton = showUnsaveButton ? UnsaveButton : SaveButton;
const txt = getText(item);
const opts: ModerationPrefs = modOpts.moderationPrefs ?? {
adultContentEnabled: false,
labelers: agent.appLabelers.map((did) => ({
did,
labels: DEFAULT_LOGGED_OUT_LABEL_PREFERENCES,
})),
hiddenPosts: [],
mutedWords: [],
labels: DEFAULT_LOGGED_OUT_LABEL_PREFERENCES,
};
const mod = moderatePost(item, {
prefs: opts,
labelDefs: modOpts.labelDefs,
userDid: session?.did,
});
// Debug code removed for production
return (