import {type ReactNode, useState} from 'react' import {Image, View} from 'react-native' import Svg, {G, Path, Rect} from 'react-native-svg' import {Trans, useLingui} from '@lingui/react/macro' import { getPdsFallbackFaviconUrls, isBridgedPdsUrl, isBskyPdsUrl, } from '#/state/queries/pds-label.util' import {useServiceQuery} from '#/state/queries/service' import {atoms as a, useBreakpoints, useTheme} from '#/alf' import * as Dialog from '#/components/Dialog' import {Database_Filled_Corner0_Rounded as DatabaseIcon} from '#/components/icons/Database' import {InlineLinkText} from '#/components/Link' import {Loader} from '#/components/Loader' import {Text} from '#/components/Typography' const failedFaviconUrls = new Set() const loadedFaviconUrls = new Set() function formatBskyPdsDisplayName(hostname: string): string { const match = hostname.match(/^([^.]+)\.([^.]+)\.host\.bsky\.network$/) if (match) { const name = match[1].charAt(0).toUpperCase() + match[1].slice(1) const rawRegion = match[2] const region = rawRegion .replace(/^us-east$/, 'US East') .replace(/^us-west$/, 'US West') .replace(/^eu-west$/, 'EU West') .replace( /^ap-(.+)$/, (_match: string, r: string) => `AP ${r.charAt(0).toUpperCase()}${r.slice(1)}`, ) return `${name} (${region})` } if (hostname === 'bsky.social') return 'Bluesky Social' return hostname } export function PdsDialog({ control, pdsUrl, faviconUrl, loadDescription = false, }: { control: Dialog.DialogControlProps pdsUrl: string faviconUrl: string | undefined loadDescription?: boolean }) { const {t: l} = useLingui() const t = useTheme() const {gtMobile} = useBreakpoints() let hostname = pdsUrl try { hostname = new URL(pdsUrl).hostname } catch {} const isBsky = isBskyPdsUrl(pdsUrl) const isBridged = isBridgedPdsUrl(pdsUrl) const displayName = isBsky ? formatBskyPdsDisplayName(hostname) : hostname const descriptionUrl = isBsky ? 'https://bsky.social' : pdsUrl const shouldLoadDescription = loadDescription && !isBridged const description = useServiceQuery(descriptionUrl, { enabled: shouldLoadDescription, }) const contactEmail = description.data?.contact?.email?.trim() const handleDomains = description.data?.availableUserDomains ?? [] const inviteCodeRequired = description.data?.inviteCodeRequired const formattedDomains = handleDomains.map(domain => domain.replace(/^\./, ''), ) return ( {isBridged ? Fediverse : displayName} {isBsky && ( Bluesky Social )} {!isBridged && ( This account’s data is stored on a Personal Data Server at{' '} {hostname} . )} {isBridged && ( This account is bridged from the{' '} Fediverse {' '} via{' '} Bridgy Fed . )} {shouldLoadDescription && description.isLoading && ( Loading server details… )} {shouldLoadDescription && description.isSuccess && description.data && ( {contactEmail ? ( {contactEmail} } /> ) : null} Invite code required ) : ( Open ) } /> {formattedDomains.length > 0 && ( {l`Handles`} {formattedDomains.map(domain => ( • {domain} ))} )} )} {shouldLoadDescription && description.isError && ( Couldn’t load extra details from this server. )} {!isBridged && ( Learn more {' '} about PDSes or how to{' '} self-host {' '} your own. )} ) } function DetailItem({label, valueText}: {label: string; valueText: ReactNode}) { return ( {label} {valueText} ) } function DetailLabelText({children}: {children: ReactNode}) { const t = useTheme() return ( {children} ) } function BskyBadgeSVG({size}: {size: number}) { return ( ) } function FediverseBadgeSVG({size}: {size: number}) { return ( ) } function DbBadgeIcon({ size, borderRadius, }: { size: number borderRadius: number }) { const t = useTheme() return ( ) } function FaviconBadgeIcon({ size, borderRadius, faviconUrls, }: { size: number borderRadius: number faviconUrls: string[] }) { const t = useTheme() const getNextUrl = (currentUrl?: string) => faviconUrls.find( url => url !== currentUrl && url && !failedFaviconUrls.has(url), ) const [currentUrl, setCurrentUrl] = useState(getNextUrl) // Remounted post headers should display cached favicons immediately. const [imageLoaded, setImageLoaded] = useState( () => !!currentUrl && loadedFaviconUrls.has(currentUrl), ) if (!currentUrl) { return } return ( {!imageLoaded ? ( ) : null} { loadedFaviconUrls.add(currentUrl) setImageLoaded(true) }} onError={() => { failedFaviconUrls.add(currentUrl) loadedFaviconUrls.delete(currentUrl) const nextUrl = getNextUrl(currentUrl) setImageLoaded(!!nextUrl && loadedFaviconUrls.has(nextUrl)) setCurrentUrl(nextUrl) }} /> ) } export function PdsBadgeIcon({ faviconUrl, pdsUrl, isBsky, isBridged, size, borderRadius, }: { faviconUrl?: string pdsUrl?: string isBsky: boolean isBridged: boolean size: number borderRadius?: number }) { const r = borderRadius ?? size / 5 if (isBsky) return if (isBridged) return const faviconCandidates = Array.from( new Set( [faviconUrl, ...(pdsUrl ? getPdsFallbackFaviconUrls(pdsUrl) : [])].filter( Boolean, ) as string[], ), ) if (faviconCandidates.length > 0) return ( ) return }