Bluesky app fork with some witchin' additions 馃挮
at main 308 lines 11 kB view raw
1import {useState} from 'react' 2import {Image, View} from 'react-native' 3import { 4 FontAwesomeIcon, 5 type FontAwesomeIconStyle, 6} from '@fortawesome/react-native-fontawesome' 7import {msg} from '@lingui/core/macro' 8import {useLingui} from '@lingui/react' 9import {Trans} from '@lingui/react/macro' 10 11import {isBridgedPdsUrl, isBskyPdsUrl} from '#/state/queries/pds-label' 12 13const failedFaviconUrls = new Set<string>() 14import Svg, {G, Path, Rect} from 'react-native-svg' 15 16import {atoms as a, useBreakpoints, useTheme} from '#/alf' 17import {Button, ButtonText} from '#/components/Button' 18import * as Dialog from '#/components/Dialog' 19import {InlineLinkText} from '#/components/Link' 20import {Text} from '#/components/Typography' 21 22function formatBskyPdsDisplayName(hostname: string): string { 23 const match = hostname.match(/^([^.]+)\.([^.]+)\.host\.bsky\.network$/) 24 if (match) { 25 const name = match[1].charAt(0).toUpperCase() + match[1].slice(1) 26 const rawRegion = match[2] 27 const region = rawRegion 28 .replace(/^us-east$/, 'US East') 29 .replace(/^us-west$/, 'US West') 30 .replace(/^eu-west$/, 'EU West') 31 .replace( 32 /^ap-(.+)$/, 33 (_match: string, r: string) => 34 `AP ${r.charAt(0).toUpperCase()}${r.slice(1)}`, 35 ) 36 return `${name} (${region})` 37 } 38 if (hostname === 'bsky.social') return 'Bluesky Social' 39 return hostname 40} 41 42export function PdsDialog({ 43 control, 44 pdsUrl, 45 faviconUrl, 46}: { 47 control: Dialog.DialogControlProps 48 pdsUrl: string 49 faviconUrl: string | undefined 50}) { 51 const {_} = useLingui() 52 const {gtMobile} = useBreakpoints() 53 54 let hostname = pdsUrl 55 try { 56 hostname = new URL(pdsUrl).hostname 57 } catch {} 58 59 const isBsky = isBskyPdsUrl(pdsUrl) 60 const isBridged = isBridgedPdsUrl(pdsUrl) 61 const displayName = isBsky ? formatBskyPdsDisplayName(hostname) : hostname 62 63 return ( 64 <Dialog.Outer control={control} nativeOptions={{preventExpansion: true}}> 65 <Dialog.Handle /> 66 <Dialog.ScrollableInner 67 label={_(msg`PDS Information`)} 68 style={[ 69 gtMobile ? {width: 'auto', maxWidth: 400, minWidth: 200} : a.w_full, 70 ]}> 71 <View style={[a.gap_md, a.pb_lg]}> 72 <View style={[a.flex_row, a.align_center, a.gap_md]}> 73 <PdsBadgeIcon 74 faviconUrl={faviconUrl} 75 isBsky={isBsky} 76 isBridged={isBridged} 77 size={36} 78 /> 79 <View style={[a.flex_1]}> 80 { 81 <Text 82 style={[a.text_2xl, a.font_semi_bold, a.leading_tight]} 83 numberOfLines={1}> 84 {isBridged ? <Trans>Fediverse</Trans> : displayName} 85 </Text> 86 } 87 {isBsky && 88 <Text style={[a.text_sm, a.leading_tight]}> 89 <Trans>Bluesky Social</Trans> 90 </Text> 91 } 92 </View> 93 </View> 94 95 <Text style={[a.text_md, a.leading_snug]}> 96 <Trans> 97 This badge represents the Personal Data Server this account is 98 stored on:{' '} 99 <InlineLinkText 100 to={pdsUrl} 101 label={displayName} 102 style={[a.text_md, a.font_semi_bold]}> 103 {displayName} 104 </InlineLinkText> 105 . A PDS is where posts, follows, and other data live on the AT 106 Protocol network. 107 </Trans> 108 </Text> 109 110 {isBridged && ( 111 <Text style={[a.text_md, a.leading_snug]}> 112 <Trans> 113 This account is bridged from the Fediverse via{' '} 114 <InlineLinkText 115 to="https://witchsky.app/profile/ap.brid.gy" 116 label="Bridgy Fed" 117 style={[a.text_md, a.font_semi_bold]}> 118 Bridgy Fed 119 </InlineLinkText> 120 .{' '} 121 {/* Their original account is avaiable at:{' '} 122 <InlineLinkText 123 to={BridgedUrl} 124 label="Federated account address" 125 style={[a.text_md, a.font_semi_bold]}> 126 {BridgedUrl} 127 </InlineLinkText> */} 128 </Trans> 129 </Text> 130 )} 131 132 {!isBridged && ( 133 <Text style={[a.text_md, a.leading_snug]}> 134 <Trans> 135 <InlineLinkText 136 to="https://atproto.com/guides/glossary#pds-personal-data-server" 137 label="PDS Glossary definition" 138 style={[a.text_md, a.font_semi_bold]}> 139 Learn more 140 </InlineLinkText>{' '} 141 about what a PDS is and how to{' '} 142 <InlineLinkText 143 to="https://atproto.com/guides/self-hosting#pds" 144 label="Self-hosting PDS documentation" 145 style={[a.text_md, a.font_semi_bold]}> 146 self-host 147 </InlineLinkText>{' '} 148 your own. 149 </Trans> 150 </Text> 151 )} 152 </View> 153 154 <View 155 style={[ 156 a.w_full, 157 a.gap_sm, 158 gtMobile 159 ? [a.flex_row, a.flex_row_reverse, a.justify_start] 160 : [a.flex_col], 161 ]}> 162 <Button 163 label={_(msg`Close dialog`)} 164 size="small" 165 variant="solid" 166 color="primary" 167 onPress={() => control.close()}> 168 <ButtonText> 169 <Trans>Close</Trans> 170 </ButtonText> 171 </Button> 172 </View> 173 174 <Dialog.Close /> 175 </Dialog.ScrollableInner> 176 </Dialog.Outer> 177 ) 178} 179 180function BskyBadgeSVG({size}: {size: number}) { 181 return ( 182 <Svg width={size} height={size} viewBox="0 0 24 24"> 183 <Rect width={24} height={24} rx={6} fill="#0085ff" /> 184 <G transform="translate(2.4 2.4) scale(0.8)"> 185 <Path 186 fill="#fff" 187 fillRule="evenodd" 188 clipRule="evenodd" 189 d="M6.335 4.212c2.293 1.76 4.76 5.327 5.665 7.241.906-1.914 3.372-5.482 5.665-7.241C19.319 2.942 22 1.96 22 5.086c0 .624-.35 5.244-.556 5.994-.713 2.608-3.315 3.273-5.629 2.87 4.045.704 5.074 3.035 2.852 5.366-4.22 4.426-6.066-1.111-6.54-2.53-.086-.26-.126-.382-.127-.278 0-.104-.041.018-.128.278-.473 1.419-2.318 6.956-6.539 2.53-2.222-2.331-1.193-4.662 2.852-5.366-2.314.403-4.916-.262-5.63-2.87C2.35 10.33 2 5.71 2 5.086c0-3.126 2.68-2.144 4.335-.874Z" 190 /> 191 </G> 192 </Svg> 193 ) 194} 195 196function FediverseBadgeSVG({size}: {size: number}) { 197 return ( 198 <Svg width={size} height={size} viewBox="0 0 24 24"> 199 <Rect width={24} height={24} rx={6} fill="#6364FF" /> 200 <G transform="translate(2.4 2.4) scale(0.03)"> 201 <Path 202 fill="#fff" 203 fillRule="evenodd" 204 clipRule="evenodd" 205 d="M426.8 590.9C407.1 590.4 389.3 579.3 380.2 561.8C371.2 544.4 372.3 523.4 383.2 507C394.1 490.6 413 481.5 432.6 483.1C452.3 483.6 470.1 494.7 479.2 512.2C488.2 529.6 487.1 550.6 476.2 567C465.3 583.4 446.4 592.5 426.8 590.9zM376.7 510.3C371.2 521.2 369.3 533.6 371.1 545.7L200.7 518.4C206.2 507.5 208.2 495.1 206.4 483L376.7 510.3zM144.7 545.6C125.1 545.1 107.3 533.9 98.3 516.5C89.2 499 90.4 478.1 101.3 461.7C112.1 445.4 131 436.2 150.6 437.8C170.2 438.3 188 449.5 197 466.9C206.1 484.4 204.9 505.3 194 521.7C183.2 538 164.3 547.2 144.7 545.6zM402.4 484.2C391.5 489.8 382.7 498.6 377 509.5L306.4 438.6L340 421.6L402.4 484.3zM518.1 325C526.8 333.6 537.9 339.3 550 341.4L471.4 494.8C462.7 486.2 451.6 480.5 439.5 478.4L518.1 325zM408.7 283.3L439.2 478.4C427.1 476.5 414.7 478.3 403.8 483.7L371.6 277.4L408.8 283.4zM382.4 392.9L206.2 482.2C204.2 470.1 198.6 459 190 450.2L376.6 355.6L382.4 392.8zM229.7 370.9L189.4 449.6C180.7 441 169.6 435.3 157.5 433.3L203.1 344.3L229.7 371zM156.7 433C144.6 431.2 132.3 433.2 121.3 438.6L94.7 268.3C106.8 270.1 119.2 268.2 130.1 262.7L156.7 433zM303.8 385.2L270.2 402.2L130.8 262.3C141.7 256.7 150.5 247.9 156.2 237L303.8 385.2zM501.3 292.4C503.3 304.5 508.9 315.6 517.5 324.3L428.2 369.5L422.4 332.3L501.3 292.3zM556.9 336.7C537.3 336.2 519.5 325 510.5 307.6C501.4 290.1 502.6 269.2 513.5 252.8C524.3 236.5 543.2 227.3 562.8 228.9C582.4 229.4 600.2 240.6 609.2 258C618.3 275.5 617.1 296.4 606.2 312.8C595.4 329.1 576.5 338.3 556.9 336.7zM316.6 122.7C325.3 131.3 336.4 137 348.4 139L253.1 325.1L226.5 298.4L316.5 122.6zM506.9 256.1C501.4 267 499.4 279.4 501.2 291.4L294.8 258.3L312 224.8L507 256.1zM100.7 263.6C81.1 263.1 63.3 251.9 54.3 234.5C45.2 217 46.4 196.1 57.3 179.7C68.1 163.4 87 154.2 106.6 155.8C126.2 156.3 144 167.5 153 184.9C162.1 202.4 160.9 223.3 150 239.7C139.2 256 120.3 265.2 100.7 263.6zM532.7 230.2C521.8 235.8 513 244.6 507.3 255.5L385.5 133.3C396.4 127.7 405.2 118.9 410.9 108L532.6 230.2zM261.3 216.6L244.1 250.1L156.7 236.1C162.1 225.2 164.1 212.8 162.2 200.7L261.2 216.6zM400.8 232.5L363.6 226.5L350 139.3C362.1 141 374.5 139 385.3 133.4L400.8 232.5zM299.8 90.2C301.8 102.3 307.4 113.4 316 122.1L162.1 200.1C160.1 188 154.5 176.9 145.9 168.2L299.8 90.2zM355.4 134.5C335.7 134 317.9 122.9 308.8 105.4C299.8 88 300.9 67 311.8 50.6C322.7 34.2 341.6 25.1 361.2 26.7C380.9 27.2 398.7 38.3 407.8 55.8C416.8 73.2 415.7 94.2 404.8 110.6C393.9 127 375 136.1 355.4 134.5z" 206 /> 207 </G> 208 </Svg> 209 ) 210} 211 212function DbBadgeIcon({ 213 size, 214 borderRadius, 215}: { 216 size: number 217 borderRadius: number 218}) { 219 const t = useTheme() 220 return ( 221 <View 222 style={[ 223 a.align_center, 224 a.justify_center, 225 { 226 width: size, 227 height: size, 228 borderRadius, 229 backgroundColor: t.atoms.bg_contrast_100.backgroundColor, 230 }, 231 ]}> 232 <FontAwesomeIcon 233 icon="database" 234 size={Math.round(size * 0.7)} 235 style={ 236 {color: t.atoms.text_contrast_medium.color} as FontAwesomeIconStyle 237 } 238 /> 239 </View> 240 ) 241} 242 243function FaviconBadgeIcon({ 244 size, 245 borderRadius, 246 faviconUrl, 247}: { 248 size: number 249 borderRadius: number 250 faviconUrl: string 251}) { 252 const t = useTheme() 253 const [imgError, setImgError] = useState(() => 254 failedFaviconUrls.has(faviconUrl), 255 ) 256 257 if (imgError) { 258 return <DbBadgeIcon size={size} borderRadius={borderRadius} /> 259 } 260 261 return ( 262 <View 263 style={[ 264 a.overflow_hidden, 265 a.align_center, 266 a.justify_center, 267 { 268 width: size, 269 height: size, 270 borderRadius, 271 backgroundColor: t.atoms.bg_contrast_100.backgroundColor, 272 }, 273 ]}> 274 <Image 275 source={{uri: faviconUrl}} 276 style={{width: size, height: size}} 277 accessibilityIgnoresInvertColors 278 onError={() => { 279 failedFaviconUrls.add(faviconUrl) 280 setImgError(true) 281 }} 282 /> 283 </View> 284 ) 285} 286 287export function PdsBadgeIcon({ 288 faviconUrl, 289 isBsky, 290 isBridged, 291 size, 292 borderRadius, 293}: { 294 faviconUrl?: string 295 isBsky: boolean 296 isBridged: boolean 297 size: number 298 borderRadius?: number 299}) { 300 const r = borderRadius ?? size / 5 301 if (isBsky) return <BskyBadgeSVG size={size} /> 302 if (isBridged) return <FediverseBadgeSVG size={size} /> 303 if (faviconUrl) 304 return ( 305 <FaviconBadgeIcon size={size} borderRadius={r} faviconUrl={faviconUrl} /> 306 ) 307 return <DbBadgeIcon size={size} borderRadius={r} /> 308}