SocialGraphFilterProvider.tsx raw

   1  import { useFetchFollowGraph } from '@/hooks/useFetchFollowGraph'
   2  import storage, { dispatchSettingsChanged } from '@/services/local-storage.service'
   3  import { createContext, useCallback, useContext, useMemo, useState } from 'react'
   4  import { useNostr } from './NostrProvider'
   5  
   6  type TSocialGraphFilterContext = {
   7    // Settings
   8    proximityLevel: number | null // null = disabled, 1 = direct follows, 2 = follows of follows
   9    includeMode: boolean // true = include only graph members, false = exclude graph members
  10    updateProximityLevel: (level: number | null) => void
  11    updateIncludeMode: (include: boolean) => void
  12  
  13    // Cached data
  14    graphPubkeys: Set<string> // Pre-computed Set for O(1) lookup
  15    graphPubkeyCount: number
  16    isLoading: boolean
  17  
  18    // Filter function for use in feeds
  19    isPubkeyAllowed: (pubkey: string) => boolean
  20  }
  21  
  22  const SocialGraphFilterContext = createContext<TSocialGraphFilterContext | undefined>(undefined)
  23  
  24  export const useSocialGraphFilter = () => {
  25    const context = useContext(SocialGraphFilterContext)
  26    if (!context) {
  27      throw new Error('useSocialGraphFilter must be used within a SocialGraphFilterProvider')
  28    }
  29    return context
  30  }
  31  
  32  export function SocialGraphFilterProvider({ children }: { children: React.ReactNode }) {
  33    const { pubkey } = useNostr()
  34    const [proximityLevel, setProximityLevel] = useState<number | null>(
  35      storage.getSocialGraphProximity()
  36    )
  37    const [includeMode, setIncludeMode] = useState<boolean>(storage.getSocialGraphIncludeMode())
  38  
  39    // Fetch the follow graph when proximity is enabled
  40    const { pubkeysByDepth, isLoading } = useFetchFollowGraph(
  41      proximityLevel !== null ? pubkey : null,
  42      proximityLevel ?? 1
  43    )
  44  
  45    // Build the Set of graph pubkeys (always includes self)
  46    const graphPubkeys = useMemo(() => {
  47      const set = new Set<string>()
  48  
  49      // Always include self in the graph
  50      if (pubkey) {
  51        set.add(pubkey)
  52      }
  53  
  54      // Add pubkeys up to selected depth
  55      if (proximityLevel && pubkeysByDepth.length) {
  56        for (let depth = 0; depth < proximityLevel && depth < pubkeysByDepth.length; depth++) {
  57          pubkeysByDepth[depth].forEach((pk) => set.add(pk))
  58        }
  59      }
  60  
  61      return set
  62    }, [pubkey, proximityLevel, pubkeysByDepth])
  63  
  64    const graphPubkeyCount = graphPubkeys.size
  65  
  66    const updateProximityLevel = useCallback((level: number | null) => {
  67      storage.setSocialGraphProximity(level)
  68      setProximityLevel(level)
  69      dispatchSettingsChanged()
  70    }, [])
  71  
  72    const updateIncludeMode = useCallback((include: boolean) => {
  73      storage.setSocialGraphIncludeMode(include)
  74      setIncludeMode(include)
  75      dispatchSettingsChanged()
  76    }, [])
  77  
  78    const isPubkeyAllowed = useCallback(
  79      (targetPubkey: string): boolean => {
  80        // If filter disabled, allow all
  81        if (proximityLevel === null) return true
  82  
  83        // If loading, allow all (graceful degradation)
  84        if (isLoading) return true
  85  
  86        // Always allow self
  87        if (targetPubkey === pubkey) return true
  88  
  89        const isInGraph = graphPubkeys.has(targetPubkey)
  90  
  91        // Include mode: only allow if in graph
  92        // Exclude mode: only allow if NOT in graph
  93        return includeMode ? isInGraph : !isInGraph
  94      },
  95      [proximityLevel, isLoading, graphPubkeys, includeMode, pubkey]
  96    )
  97  
  98    return (
  99      <SocialGraphFilterContext.Provider
 100        value={{
 101          proximityLevel,
 102          includeMode,
 103          updateProximityLevel,
 104          updateIncludeMode,
 105          graphPubkeys,
 106          graphPubkeyCount,
 107          isLoading,
 108          isPubkeyAllowed
 109        }}
 110      >
 111        {children}
 112      </SocialGraphFilterContext.Provider>
 113    )
 114  }
 115