import { useState, useEffect, useCallback, useRef } from 'react'; import type { Did, AtUri } from '../api/types'; import { atprotoClient } from '../api/client'; import { MuteBlockContext, type MuteBlockContextValue } from './muteBlockContext'; import { getIgnoreBlocks } from '../settings/storage'; async function loadAllMutes(): Promise> { const dids = new Set(); let cursor: string | undefined; do { const resp = await atprotoClient.getMutes(100, cursor); for (const m of resp.mutes) { dids.add(m.did); } cursor = resp.cursor; } while (cursor); return dids; } async function loadAllBlocks(): Promise> { const dids = new Set(); let cursor: string | undefined; do { const resp = await atprotoClient.getBlocks(100, cursor); for (const b of resp.blocks) { dids.add(b.did); } cursor = resp.cursor; } while (cursor); return dids; } export default function MuteBlockProvider({ children }: { children: React.ReactNode }) { const [mutedDids, setMutedDids] = useState>(new Set()); const [blockedDids, setBlockedDids] = useState>(new Set()); const [blockedByDids, setBlockedByDids] = useState>(new Set()); const [loaded, setLoaded] = useState(false); const [ignoreBlocks, setIgnoreBlocks] = useState(() => getIgnoreBlocks()); const loadingRef = useRef(false); // Listen for storage changes so the toggle in settings takes effect immediately useEffect(() => { const handler = () => setIgnoreBlocks(getIgnoreBlocks()); window.addEventListener('storage', handler); return () => window.removeEventListener('storage', handler); }, []); const load = useCallback(async () => { if (loadingRef.current) return; loadingRef.current = true; try { const [mutes, blocks] = await Promise.all([loadAllMutes(), loadAllBlocks()]); setMutedDids(mutes); setBlockedDids(blocks); setLoaded(true); } catch (err) { console.error('Failed to load mutes/blocks:', err); setLoaded(true); } finally { loadingRef.current = false; } }, []); useEffect(() => { let cancelled = false; loadingRef.current = true; Promise.all([loadAllMutes(), loadAllBlocks()]) .then(([mutes, blocks]) => { if (cancelled) return; setMutedDids(mutes); setBlockedDids(blocks); setLoaded(true); loadingRef.current = false; }) .catch((err) => { if (cancelled) return; console.error('Failed to load mutes/blocks:', err); setLoaded(true); loadingRef.current = false; }); return () => { cancelled = true; }; }, []); const isMuted = useCallback((did: Did) => mutedDids.has(did), [mutedDids]); const isBlocked = useCallback((did: Did) => blockedDids.has(did), [blockedDids]); const isBlockedBy = useCallback((did: Did) => blockedByDids.has(did), [blockedByDids]); const addBlockedBy = useCallback((did: Did) => { setBlockedByDids((prev) => { if (prev.has(did)) return prev; const next = new Set(prev); next.add(did); return next; }); }, []); const muteActor = useCallback(async (did: Did) => { await atprotoClient.muteActor(did); setMutedDids((prev) => { const next = new Set(prev); next.add(did); return next; }); }, []); const unmuteActor = useCallback(async (did: Did) => { await atprotoClient.unmuteActor(did); setMutedDids((prev) => { const next = new Set(prev); next.delete(did); return next; }); }, []); const blockActor = useCallback(async (did: Did): Promise => { const result = await atprotoClient.block(did); setBlockedDids((prev) => { const next = new Set(prev); next.add(did); return next; }); return result.uri; }, []); const unblockActor = useCallback(async (blockUri: AtUri, did: Did) => { await atprotoClient.unblock(blockUri); setBlockedDids((prev) => { const next = new Set(prev); next.delete(did); return next; }); }, []); const reload = useCallback(async () => { loadingRef.current = false; await load(); }, [load]); const value: MuteBlockContextValue = { mutedDids, blockedDids, blockedByDids, loaded, ignoreBlocks, isMuted, isBlocked, isBlockedBy, addBlockedBy, muteActor, unmuteActor, blockActor, unblockActor, reload, }; return ( {children} ); }