forked from
jollywhoppers.com/witchsky.app
Bluesky app fork with some witchin' additions 馃挮
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}