Bluesky app fork with some witchin' additions 馃挮
at post-text-option 306 lines 8.7 kB view raw
1import {View} from 'react-native' 2import { 3 type InterpretedLabelValueDefinition, 4 type LabelPreference, 5} from '@atproto/api' 6import {msg, Trans} from '@lingui/macro' 7import {useLingui} from '@lingui/react' 8 9import {useGlobalLabelStrings} from '#/lib/moderation/useGlobalLabelStrings' 10import {useLabelBehaviorDescription} from '#/lib/moderation/useLabelBehaviorDescription' 11import {getLabelStrings} from '#/lib/moderation/useLabelInfo' 12import { 13 usePreferencesQuery, 14 usePreferencesSetContentLabelMutation, 15} from '#/state/queries/preferences' 16import {atoms as a, useBreakpoints, useTheme} from '#/alf' 17import * as ToggleButton from '#/components/forms/ToggleButton' 18import {InlineLinkText} from '#/components/Link' 19import {Text} from '#/components/Typography' 20import {CircleInfo_Stroke2_Corner0_Rounded as CircleInfo} from '../icons/CircleInfo' 21 22export function Outer({children}: React.PropsWithChildren<{}>) { 23 return ( 24 <View 25 style={[ 26 a.flex_row, 27 a.gap_sm, 28 a.px_lg, 29 a.py_lg, 30 a.justify_between, 31 a.flex_wrap, 32 ]}> 33 {children} 34 </View> 35 ) 36} 37 38export function Content({ 39 children, 40 name, 41 description, 42}: React.PropsWithChildren<{ 43 name: string 44 description: string 45}>) { 46 const t = useTheme() 47 const {gtPhone} = useBreakpoints() 48 49 return ( 50 <View style={[a.gap_xs, a.flex_1]}> 51 <Text emoji style={[a.font_semi_bold, gtPhone ? a.text_sm : a.text_md]}> 52 {name} 53 </Text> 54 <Text emoji style={[t.atoms.text_contrast_medium, a.leading_snug]}> 55 {description} 56 </Text> 57 58 {children} 59 </View> 60 ) 61} 62 63export function Buttons({ 64 name, 65 values, 66 onChange, 67 ignoreLabel, 68 warnLabel, 69 hideLabel, 70 disabled, 71}: { 72 name: string 73 values: ToggleButton.GroupProps['values'] 74 onChange: ToggleButton.GroupProps['onChange'] 75 ignoreLabel?: string 76 warnLabel?: string 77 hideLabel?: string 78 disabled?: boolean 79}) { 80 const {_} = useLingui() 81 82 return ( 83 <View style={[{minHeight: 35}, a.w_full]}> 84 <ToggleButton.Group 85 disabled={disabled} 86 label={_( 87 msg`Configure content filtering setting for category: ${name}`, 88 )} 89 values={values} 90 onChange={onChange}> 91 {ignoreLabel && ( 92 <ToggleButton.Button name="ignore" label={ignoreLabel}> 93 <ToggleButton.ButtonText>{ignoreLabel}</ToggleButton.ButtonText> 94 </ToggleButton.Button> 95 )} 96 {warnLabel && ( 97 <ToggleButton.Button name="warn" label={warnLabel}> 98 <ToggleButton.ButtonText>{warnLabel}</ToggleButton.ButtonText> 99 </ToggleButton.Button> 100 )} 101 {hideLabel && ( 102 <ToggleButton.Button name="hide" label={hideLabel}> 103 <ToggleButton.ButtonText>{hideLabel}</ToggleButton.ButtonText> 104 </ToggleButton.Button> 105 )} 106 </ToggleButton.Group> 107 </View> 108 ) 109} 110 111/** 112 * For use on the global Moderation screen to set prefs for a "global" label, 113 * not scoped to a single labeler. 114 */ 115export function GlobalLabelPreference({ 116 labelDefinition, 117 disabled, 118}: { 119 labelDefinition: InterpretedLabelValueDefinition 120 disabled?: boolean 121}) { 122 const {_} = useLingui() 123 124 const {identifier} = labelDefinition 125 const {data: preferences} = usePreferencesQuery() 126 const {mutate, variables} = usePreferencesSetContentLabelMutation() 127 const savedPref = preferences?.moderationPrefs.labels[identifier] 128 const pref = variables?.visibility ?? savedPref ?? 'warn' 129 130 const allLabelStrings = useGlobalLabelStrings() 131 const labelStrings = 132 labelDefinition.identifier in allLabelStrings 133 ? allLabelStrings[labelDefinition.identifier] 134 : { 135 name: labelDefinition.identifier, 136 description: `Labeled "${labelDefinition.identifier}"`, 137 } 138 139 const labelOptions = { 140 hide: _(msg`Hide`), 141 warn: _(msg`Warn`), 142 ignore: _(msg`Show`), 143 } 144 145 return ( 146 <Outer> 147 <Content 148 name={labelStrings.name} 149 description={labelStrings.description} 150 /> 151 <Buttons 152 name={labelStrings.name.toLowerCase()} 153 values={[pref]} 154 onChange={values => { 155 mutate({ 156 label: identifier, 157 visibility: values[0] as LabelPreference, 158 labelerDid: undefined, 159 }) 160 }} 161 ignoreLabel={labelOptions.ignore} 162 warnLabel={labelOptions.warn} 163 hideLabel={labelOptions.hide} 164 disabled={disabled} 165 /> 166 </Outer> 167 ) 168} 169 170/** 171 * For use on individual labeler pages 172 */ 173export function LabelerLabelPreference({ 174 labelDefinition, 175 disabled, 176 labelerDid, 177}: { 178 labelDefinition: InterpretedLabelValueDefinition 179 disabled?: boolean 180 labelerDid?: string 181}) { 182 const {_, i18n} = useLingui() 183 const t = useTheme() 184 const {gtPhone} = useBreakpoints() 185 186 const isGlobalLabel = !labelDefinition.definedBy 187 const {identifier} = labelDefinition 188 const {data: preferences} = usePreferencesQuery() 189 const {mutate, variables} = usePreferencesSetContentLabelMutation() 190 const savedPref = 191 labelerDid && !isGlobalLabel 192 ? preferences?.moderationPrefs.labelers.find(l => l.did === labelerDid) 193 ?.labels[identifier] 194 : preferences?.moderationPrefs.labels[identifier] 195 const pref = 196 variables?.visibility ?? 197 savedPref ?? 198 labelDefinition.defaultSetting ?? 199 'warn' 200 201 // does the 'warn' setting make sense for this label? 202 const canWarn = !( 203 labelDefinition.blurs === 'none' && labelDefinition.severity === 'none' 204 ) 205 // is this label adult only? 206 const adultOnly = labelDefinition.flags.includes('adult') 207 // is this label disabled because it's adult only? 208 const adultDisabled = 209 adultOnly && !preferences?.moderationPrefs.adultContentEnabled 210 // are there any reasons we cant configure this label here? 211 const cantConfigure = isGlobalLabel || adultDisabled 212 const showConfig = !disabled && (gtPhone || !cantConfigure) 213 214 // adjust the pref based on whether warn is available 215 let prefAdjusted = pref 216 if (adultDisabled) { 217 prefAdjusted = 'hide' 218 } else if (!canWarn && pref === 'warn') { 219 prefAdjusted = 'ignore' 220 } 221 222 // grab localized descriptions of the label and its settings 223 const currentPrefLabel = useLabelBehaviorDescription( 224 labelDefinition, 225 prefAdjusted, 226 ) 227 const hideLabel = useLabelBehaviorDescription(labelDefinition, 'hide') 228 const warnLabel = useLabelBehaviorDescription(labelDefinition, 'warn') 229 const ignoreLabel = useLabelBehaviorDescription(labelDefinition, 'ignore') 230 const globalLabelStrings = useGlobalLabelStrings() 231 const labelStrings = getLabelStrings( 232 i18n.locale, 233 globalLabelStrings, 234 labelDefinition, 235 ) 236 237 return ( 238 <Outer> 239 <Content name={labelStrings.name} description={labelStrings.description}> 240 {cantConfigure && ( 241 <View style={[a.flex_row, a.gap_xs, a.align_center, a.mt_xs]}> 242 <CircleInfo size="sm" fill={t.atoms.text_contrast_high.color} /> 243 244 <Text 245 style={[ 246 t.atoms.text_contrast_medium, 247 a.font_semi_bold, 248 a.italic, 249 ]}> 250 {adultDisabled ? ( 251 <Trans>Adult content is disabled.</Trans> 252 ) : isGlobalLabel ? ( 253 <Trans> 254 Configured in{' '} 255 <InlineLinkText 256 label={_(msg`moderation settings`)} 257 to="/moderation" 258 style={a.text_sm}> 259 moderation settings 260 </InlineLinkText> 261 . 262 </Trans> 263 ) : null} 264 </Text> 265 </View> 266 )} 267 </Content> 268 269 {showConfig && ( 270 <> 271 {cantConfigure ? ( 272 <View 273 style={[ 274 {minHeight: 35}, 275 a.px_md, 276 a.py_md, 277 a.rounded_sm, 278 a.border, 279 t.atoms.border_contrast_low, 280 a.self_start, 281 ]}> 282 <Text emoji style={[a.font_semi_bold, t.atoms.text_contrast_low]}> 283 {currentPrefLabel} 284 </Text> 285 </View> 286 ) : ( 287 <Buttons 288 name={labelStrings.name.toLowerCase()} 289 values={[pref]} 290 onChange={values => { 291 mutate({ 292 label: identifier, 293 visibility: values[0] as LabelPreference, 294 labelerDid, 295 }) 296 }} 297 ignoreLabel={ignoreLabel} 298 warnLabel={canWarn ? warnLabel : undefined} 299 hideLabel={hideLabel} 300 /> 301 )} 302 </> 303 )} 304 </Outer> 305 ) 306}