mirror of https://git.lenooby09.tech/LeNooby09/social-app.git
1/* eslint-disable no-restricted-imports */
2import React from 'react'
3import {View} from 'react-native'
4import {
5 AppBskyActorDefs,
6 AppBskyFeedDefs,
7 AppBskyFeedPost,
8 ComAtprotoLabelDefs,
9 interpretLabelValueDefinition,
10 LabelPreference,
11 LABELS,
12 mock,
13 moderatePost,
14 moderateProfile,
15 ModerationBehavior,
16 ModerationDecision,
17 ModerationOpts,
18 RichText,
19} from '@atproto/api'
20import {msg} from '@lingui/macro'
21import {useLingui} from '@lingui/react'
22
23import {useGlobalLabelStrings} from '#/lib/moderation/useGlobalLabelStrings'
24import {CommonNavigatorParams, NativeStackScreenProps} from '#/lib/routes/types'
25import {moderationOptsOverrideContext} from '#/state/preferences/moderation-opts'
26import {FeedNotification} from '#/state/queries/notifications/types'
27import {
28 groupNotifications,
29 shouldFilterNotif,
30} from '#/state/queries/notifications/util'
31import {useSession} from '#/state/session'
32import {CenteredView, ScrollView} from '#/view/com/util/Views'
33import {ProfileHeaderStandard} from '#/screens/Profile/Header/ProfileHeaderStandard'
34import {atoms as a, useTheme} from '#/alf'
35import {Button, ButtonIcon, ButtonText} from '#/components/Button'
36import {Divider} from '#/components/Divider'
37import * as Toggle from '#/components/forms/Toggle'
38import * as ToggleButton from '#/components/forms/ToggleButton'
39import {Check_Stroke2_Corner0_Rounded as Check} from '#/components/icons/Check'
40import {
41 ChevronBottom_Stroke2_Corner0_Rounded as ChevronBottom,
42 ChevronTop_Stroke2_Corner0_Rounded as ChevronTop,
43} from '#/components/icons/Chevron'
44import * as Layout from '#/components/Layout'
45import {H1, H3, P, Text} from '#/components/Typography'
46import {ScreenHider} from '../../components/moderation/ScreenHider'
47import {FeedItem as NotifFeedItem} from '../com/notifications/FeedItem'
48import {PostThreadItem} from '../com/post-thread/PostThreadItem'
49import {FeedItem} from '../com/posts/FeedItem'
50import {ProfileCard} from '../com/profile/ProfileCard'
51
52const LABEL_VALUES: (keyof typeof LABELS)[] = Object.keys(
53 LABELS,
54) as (keyof typeof LABELS)[]
55
56export const DebugModScreen = ({}: NativeStackScreenProps<
57 CommonNavigatorParams,
58 'DebugMod'
59>) => {
60 const t = useTheme()
61 const [scenario, setScenario] = React.useState<string[]>(['label'])
62 const [scenarioSwitches, setScenarioSwitches] = React.useState<string[]>([])
63 const [label, setLabel] = React.useState<string[]>([LABEL_VALUES[0]])
64 const [target, setTarget] = React.useState<string[]>(['account'])
65 const [visibility, setVisiblity] = React.useState<string[]>(['warn'])
66 const [customLabelDef, setCustomLabelDef] =
67 React.useState<ComAtprotoLabelDefs.LabelValueDefinition>({
68 identifier: 'custom',
69 blurs: 'content',
70 severity: 'alert',
71 defaultSetting: 'warn',
72 locales: [
73 {
74 lang: 'en',
75 name: 'Custom label',
76 description: 'A custom label created in this test environment',
77 },
78 ],
79 })
80 const [view, setView] = React.useState<string[]>(['post'])
81 const labelStrings = useGlobalLabelStrings()
82 const {currentAccount} = useSession()
83
84 const isTargetMe =
85 scenario[0] === 'label' && scenarioSwitches.includes('targetMe')
86 const isSelfLabel =
87 scenario[0] === 'label' && scenarioSwitches.includes('selfLabel')
88 const noAdult =
89 scenario[0] === 'label' && scenarioSwitches.includes('noAdult')
90 const isLoggedOut =
91 scenario[0] === 'label' && scenarioSwitches.includes('loggedOut')
92 const isFollowing = scenarioSwitches.includes('following')
93
94 const did =
95 isTargetMe && currentAccount ? currentAccount.did : 'did:web:bob.test'
96
97 const profile = React.useMemo(() => {
98 const mockedProfile = mock.profileViewBasic({
99 handle: `bob.test`,
100 displayName: 'Bob Robertson',
101 description: 'User with this as their bio',
102 labels:
103 scenario[0] === 'label' && target[0] === 'account'
104 ? [
105 mock.label({
106 src: isSelfLabel ? did : undefined,
107 val: label[0],
108 uri: `at://${did}/`,
109 }),
110 ]
111 : scenario[0] === 'label' && target[0] === 'profile'
112 ? [
113 mock.label({
114 src: isSelfLabel ? did : undefined,
115 val: label[0],
116 uri: `at://${did}/app.bsky.actor.profile/self`,
117 }),
118 ]
119 : undefined,
120 viewer: mock.actorViewerState({
121 following: isFollowing
122 ? `at://${currentAccount?.did || ''}/app.bsky.graph.follow/1234`
123 : undefined,
124 muted: scenario[0] === 'mute',
125 mutedByList: undefined,
126 blockedBy: undefined,
127 blocking:
128 scenario[0] === 'block'
129 ? `at://did:web:alice.test/app.bsky.actor.block/fake`
130 : undefined,
131 blockingByList: undefined,
132 }),
133 })
134 mockedProfile.did = did
135 mockedProfile.avatar = 'https://bsky.social/about/images/favicon-32x32.png'
136 mockedProfile.banner =
137 'https://bsky.social/about/images/social-card-default-gradient.png'
138 return mockedProfile
139 }, [scenario, target, label, isSelfLabel, did, isFollowing, currentAccount])
140
141 const post = React.useMemo(() => {
142 return mock.postView({
143 record: mock.post({
144 text: "This is the body of the post. It's where the text goes. You get the idea.",
145 }),
146 author: profile,
147 labels:
148 scenario[0] === 'label' && target[0] === 'post'
149 ? [
150 mock.label({
151 src: isSelfLabel ? did : undefined,
152 val: label[0],
153 uri: `at://${did}/app.bsky.feed.post/fake`,
154 }),
155 ]
156 : undefined,
157 embed:
158 target[0] === 'embed'
159 ? mock.embedRecordView({
160 record: mock.post({
161 text: 'Embed',
162 }),
163 labels:
164 scenario[0] === 'label' && target[0] === 'embed'
165 ? [
166 mock.label({
167 src: isSelfLabel ? did : undefined,
168 val: label[0],
169 uri: `at://${did}/app.bsky.feed.post/fake`,
170 }),
171 ]
172 : undefined,
173 author: profile,
174 })
175 : {
176 $type: 'app.bsky.embed.images#view',
177 images: [
178 {
179 thumb:
180 'https://bsky.social/about/images/social-card-default-gradient.png',
181 fullsize:
182 'https://bsky.social/about/images/social-card-default-gradient.png',
183 alt: '',
184 },
185 ],
186 },
187 })
188 }, [scenario, label, target, profile, isSelfLabel, did])
189
190 const replyNotif = React.useMemo(() => {
191 const notif = mock.replyNotification({
192 record: mock.post({
193 text: "This is the body of the post. It's where the text goes. You get the idea.",
194 reply: {
195 parent: {
196 uri: `at://${did}/app.bsky.feed.post/fake-parent`,
197 cid: 'bafyreiclp443lavogvhj3d2ob2cxbfuscni2k5jk7bebjzg7khl3esabwq',
198 },
199 root: {
200 uri: `at://${did}/app.bsky.feed.post/fake-parent`,
201 cid: 'bafyreiclp443lavogvhj3d2ob2cxbfuscni2k5jk7bebjzg7khl3esabwq',
202 },
203 },
204 }),
205 author: profile,
206 labels:
207 scenario[0] === 'label' && target[0] === 'post'
208 ? [
209 mock.label({
210 src: isSelfLabel ? did : undefined,
211 val: label[0],
212 uri: `at://${did}/app.bsky.feed.post/fake`,
213 }),
214 ]
215 : undefined,
216 })
217 const [item] = groupNotifications([notif])
218 item.subject = mock.postView({
219 record: notif.record as AppBskyFeedPost.Record,
220 author: profile,
221 labels: notif.labels,
222 })
223 return item
224 }, [scenario, label, target, profile, isSelfLabel, did])
225
226 const followNotif = React.useMemo(() => {
227 const notif = mock.followNotification({
228 author: profile,
229 subjectDid: currentAccount?.did || '',
230 })
231 const [item] = groupNotifications([notif])
232 return item
233 }, [profile, currentAccount])
234
235 const modOpts = React.useMemo(() => {
236 return {
237 userDid: isLoggedOut ? '' : isTargetMe ? did : 'did:web:alice.test',
238 prefs: {
239 adultContentEnabled: !noAdult,
240 labels: {
241 [label[0]]: visibility[0] as LabelPreference,
242 },
243 labelers: [
244 {
245 did: 'did:plc:fake-labeler',
246 labels: {[label[0]]: visibility[0] as LabelPreference},
247 },
248 ],
249 mutedWords: [],
250 hiddenPosts: [],
251 },
252 labelDefs: {
253 'did:plc:fake-labeler': [
254 interpretLabelValueDefinition(customLabelDef, 'did:plc:fake-labeler'),
255 ],
256 },
257 }
258 }, [label, visibility, noAdult, isLoggedOut, isTargetMe, did, customLabelDef])
259
260 const profileModeration = React.useMemo(() => {
261 return moderateProfile(profile, modOpts)
262 }, [profile, modOpts])
263 const postModeration = React.useMemo(() => {
264 return moderatePost(post, modOpts)
265 }, [post, modOpts])
266
267 return (
268 <Layout.Screen>
269 <moderationOptsOverrideContext.Provider value={modOpts}>
270 <ScrollView>
271 <CenteredView style={[t.atoms.bg, a.px_lg, a.py_lg]}>
272 <H1 style={[a.text_5xl, a.font_bold, a.pb_lg]}>
273 Moderation states
274 </H1>
275
276 <Heading title="" subtitle="Scenario" />
277 <ToggleButton.Group
278 label="Scenario"
279 values={scenario}
280 onChange={setScenario}>
281 <ToggleButton.Button name="label" label="Label">
282 <ToggleButton.ButtonText>Label</ToggleButton.ButtonText>
283 </ToggleButton.Button>
284 <ToggleButton.Button name="block" label="Block">
285 <ToggleButton.ButtonText>Block</ToggleButton.ButtonText>
286 </ToggleButton.Button>
287 <ToggleButton.Button name="mute" label="Mute">
288 <ToggleButton.ButtonText>Mute</ToggleButton.ButtonText>
289 </ToggleButton.Button>
290 </ToggleButton.Group>
291
292 {scenario[0] === 'label' && (
293 <>
294 <View
295 style={[
296 a.border,
297 a.rounded_sm,
298 a.mt_lg,
299 a.mb_lg,
300 a.p_lg,
301 t.atoms.border_contrast_medium,
302 ]}>
303 <Toggle.Group
304 label="Toggle"
305 type="radio"
306 values={label}
307 onChange={setLabel}>
308 <View style={[a.flex_row, a.gap_md, a.flex_wrap]}>
309 {LABEL_VALUES.map(labelValue => {
310 let targetFixed = target[0]
311 if (
312 targetFixed !== 'account' &&
313 targetFixed !== 'profile'
314 ) {
315 targetFixed = 'content'
316 }
317 const disabled =
318 isSelfLabel &&
319 LABELS[labelValue].flags.includes('no-self')
320 return (
321 <Toggle.Item
322 key={labelValue}
323 name={labelValue}
324 label={labelStrings[labelValue].name}
325 disabled={disabled}
326 style={disabled ? {opacity: 0.5} : undefined}>
327 <Toggle.Radio />
328 <Toggle.LabelText>{labelValue}</Toggle.LabelText>
329 </Toggle.Item>
330 )
331 })}
332 <Toggle.Item
333 name="custom"
334 label="Custom label"
335 disabled={isSelfLabel}
336 style={isSelfLabel ? {opacity: 0.5} : undefined}>
337 <Toggle.Radio />
338 <Toggle.LabelText>Custom label</Toggle.LabelText>
339 </Toggle.Item>
340 </View>
341 </Toggle.Group>
342
343 {label[0] === 'custom' ? (
344 <CustomLabelForm
345 def={customLabelDef}
346 setDef={setCustomLabelDef}
347 />
348 ) : (
349 <>
350 <View style={{height: 10}} />
351 <Divider />
352 </>
353 )}
354
355 <View style={{height: 10}} />
356
357 <SmallToggler label="Advanced">
358 <Toggle.Group
359 label="Toggle"
360 type="checkbox"
361 values={scenarioSwitches}
362 onChange={setScenarioSwitches}>
363 <View
364 style={[a.gap_md, a.flex_row, a.flex_wrap, a.pt_md]}>
365 <Toggle.Item name="targetMe" label="Target is me">
366 <Toggle.Checkbox />
367 <Toggle.LabelText>Target is me</Toggle.LabelText>
368 </Toggle.Item>
369 <Toggle.Item name="following" label="Following target">
370 <Toggle.Checkbox />
371 <Toggle.LabelText>Following target</Toggle.LabelText>
372 </Toggle.Item>
373 <Toggle.Item name="selfLabel" label="Self label">
374 <Toggle.Checkbox />
375 <Toggle.LabelText>Self label</Toggle.LabelText>
376 </Toggle.Item>
377 <Toggle.Item name="noAdult" label="Adult disabled">
378 <Toggle.Checkbox />
379 <Toggle.LabelText>Adult disabled</Toggle.LabelText>
380 </Toggle.Item>
381 <Toggle.Item name="loggedOut" label="Logged out">
382 <Toggle.Checkbox />
383 <Toggle.LabelText>Logged out</Toggle.LabelText>
384 </Toggle.Item>
385 </View>
386 </Toggle.Group>
387
388 {LABELS[label[0] as keyof typeof LABELS]?.configurable !==
389 false && (
390 <View style={[a.mt_md]}>
391 <Text
392 style={[
393 a.font_bold,
394 a.text_xs,
395 t.atoms.text,
396 a.pb_sm,
397 ]}>
398 Preference
399 </Text>
400 <Toggle.Group
401 label="Preference"
402 type="radio"
403 values={visibility}
404 onChange={setVisiblity}>
405 <View
406 style={[
407 a.flex_row,
408 a.gap_md,
409 a.flex_wrap,
410 a.align_center,
411 ]}>
412 <Toggle.Item name="hide" label="Hide">
413 <Toggle.Radio />
414 <Toggle.LabelText>Hide</Toggle.LabelText>
415 </Toggle.Item>
416 <Toggle.Item name="warn" label="Warn">
417 <Toggle.Radio />
418 <Toggle.LabelText>Warn</Toggle.LabelText>
419 </Toggle.Item>
420 <Toggle.Item name="ignore" label="Ignore">
421 <Toggle.Radio />
422 <Toggle.LabelText>Ignore</Toggle.LabelText>
423 </Toggle.Item>
424 </View>
425 </Toggle.Group>
426 </View>
427 )}
428 </SmallToggler>
429 </View>
430
431 <View style={[a.flex_row, a.flex_wrap, a.gap_md]}>
432 <View>
433 <Text
434 style={[
435 a.font_bold,
436 a.text_xs,
437 t.atoms.text,
438 a.pl_md,
439 a.pb_xs,
440 ]}>
441 Target
442 </Text>
443 <View
444 style={[
445 a.border,
446 a.rounded_full,
447 a.px_md,
448 a.py_sm,
449 t.atoms.border_contrast_medium,
450 t.atoms.bg,
451 ]}>
452 <Toggle.Group
453 label="Target"
454 type="radio"
455 values={target}
456 onChange={setTarget}>
457 <View style={[a.flex_row, a.gap_md, a.flex_wrap]}>
458 <Toggle.Item name="account" label="Account">
459 <Toggle.Radio />
460 <Toggle.LabelText>Account</Toggle.LabelText>
461 </Toggle.Item>
462 <Toggle.Item name="profile" label="Profile">
463 <Toggle.Radio />
464 <Toggle.LabelText>Profile</Toggle.LabelText>
465 </Toggle.Item>
466 <Toggle.Item name="post" label="Post">
467 <Toggle.Radio />
468 <Toggle.LabelText>Post</Toggle.LabelText>
469 </Toggle.Item>
470 <Toggle.Item name="embed" label="Embed">
471 <Toggle.Radio />
472 <Toggle.LabelText>Embed</Toggle.LabelText>
473 </Toggle.Item>
474 </View>
475 </Toggle.Group>
476 </View>
477 </View>
478 </View>
479 </>
480 )}
481
482 <Spacer />
483
484 <Heading title="" subtitle="Results" />
485
486 <ToggleButton.Group
487 label="Results"
488 values={view}
489 onChange={setView}>
490 <ToggleButton.Button name="post" label="Post">
491 <ToggleButton.ButtonText>Post</ToggleButton.ButtonText>
492 </ToggleButton.Button>
493 <ToggleButton.Button name="notifications" label="Notifications">
494 <ToggleButton.ButtonText>Notifications</ToggleButton.ButtonText>
495 </ToggleButton.Button>
496 <ToggleButton.Button name="account" label="Account">
497 <ToggleButton.ButtonText>Account</ToggleButton.ButtonText>
498 </ToggleButton.Button>
499 <ToggleButton.Button name="data" label="Data">
500 <ToggleButton.ButtonText>Data</ToggleButton.ButtonText>
501 </ToggleButton.Button>
502 </ToggleButton.Group>
503
504 <View
505 style={[
506 a.border,
507 a.rounded_sm,
508 a.mt_lg,
509 a.p_md,
510 t.atoms.border_contrast_medium,
511 ]}>
512 {view[0] === 'post' && (
513 <>
514 <Heading title="Post" subtitle="in feed" />
515 <MockPostFeedItem post={post} moderation={postModeration} />
516
517 <Heading title="Post" subtitle="viewed directly" />
518 <MockPostThreadItem post={post} moderation={postModeration} />
519
520 <Heading title="Post" subtitle="reply in thread" />
521 <MockPostThreadItem
522 post={post}
523 moderation={postModeration}
524 reply
525 />
526 </>
527 )}
528
529 {view[0] === 'notifications' && (
530 <>
531 <Heading title="Notification" subtitle="quote or reply" />
532 <MockNotifItem notif={replyNotif} moderationOpts={modOpts} />
533 <View style={{height: 20}} />
534 <Heading title="Notification" subtitle="follow or like" />
535 <MockNotifItem notif={followNotif} moderationOpts={modOpts} />
536 </>
537 )}
538
539 {view[0] === 'account' && (
540 <>
541 <Heading title="Account" subtitle="in listing" />
542 <MockAccountCard
543 profile={profile}
544 moderation={profileModeration}
545 />
546
547 <Heading title="Account" subtitle="viewing directly" />
548 <MockAccountScreen
549 profile={profile}
550 moderation={profileModeration}
551 moderationOpts={modOpts}
552 />
553 </>
554 )}
555
556 {view[0] === 'data' && (
557 <>
558 <ModerationUIView
559 label="Profile Moderation UI"
560 mod={profileModeration}
561 />
562 <ModerationUIView
563 label="Post Moderation UI"
564 mod={postModeration}
565 />
566 <DataView
567 label={label[0]}
568 data={LABELS[label[0] as keyof typeof LABELS]}
569 />
570 <DataView
571 label="Profile Moderation Data"
572 data={profileModeration}
573 />
574 <DataView
575 label="Post Moderation Data"
576 data={postModeration}
577 />
578 </>
579 )}
580 </View>
581
582 <View style={{height: 400}} />
583 </CenteredView>
584 </ScrollView>
585 </moderationOptsOverrideContext.Provider>
586 </Layout.Screen>
587 )
588}
589
590function Heading({title, subtitle}: {title: string; subtitle?: string}) {
591 const t = useTheme()
592 return (
593 <H3 style={[a.text_3xl, a.font_bold, a.pb_md]}>
594 {title}{' '}
595 {!!subtitle && (
596 <H3 style={[t.atoms.text_contrast_medium, a.text_lg]}>{subtitle}</H3>
597 )}
598 </H3>
599 )
600}
601
602function CustomLabelForm({
603 def,
604 setDef,
605}: {
606 def: ComAtprotoLabelDefs.LabelValueDefinition
607 setDef: React.Dispatch<
608 React.SetStateAction<ComAtprotoLabelDefs.LabelValueDefinition>
609 >
610}) {
611 const t = useTheme()
612 return (
613 <View
614 style={[
615 a.flex_row,
616 a.flex_wrap,
617 a.gap_md,
618 t.atoms.bg_contrast_25,
619 a.rounded_md,
620 a.p_md,
621 a.mt_md,
622 ]}>
623 <View>
624 <Text style={[a.font_bold, a.text_xs, t.atoms.text, a.pl_md, a.pb_xs]}>
625 Blurs
626 </Text>
627 <View
628 style={[
629 a.border,
630 a.rounded_full,
631 a.px_md,
632 a.py_sm,
633 t.atoms.border_contrast_medium,
634 t.atoms.bg,
635 ]}>
636 <Toggle.Group
637 label="Blurs"
638 type="radio"
639 values={[def.blurs]}
640 onChange={values => setDef(v => ({...v, blurs: values[0]}))}>
641 <View style={[a.flex_row, a.gap_md, a.flex_wrap]}>
642 <Toggle.Item name="content" label="Content">
643 <Toggle.Radio />
644 <Toggle.LabelText>Content</Toggle.LabelText>
645 </Toggle.Item>
646 <Toggle.Item name="media" label="Media">
647 <Toggle.Radio />
648 <Toggle.LabelText>Media</Toggle.LabelText>
649 </Toggle.Item>
650 <Toggle.Item name="none" label="None">
651 <Toggle.Radio />
652 <Toggle.LabelText>None</Toggle.LabelText>
653 </Toggle.Item>
654 </View>
655 </Toggle.Group>
656 </View>
657 </View>
658 <View>
659 <Text style={[a.font_bold, a.text_xs, t.atoms.text, a.pl_md, a.pb_xs]}>
660 Severity
661 </Text>
662 <View
663 style={[
664 a.border,
665 a.rounded_full,
666 a.px_md,
667 a.py_sm,
668 t.atoms.border_contrast_medium,
669 t.atoms.bg,
670 ]}>
671 <Toggle.Group
672 label="Severity"
673 type="radio"
674 values={[def.severity]}
675 onChange={values => setDef(v => ({...v, severity: values[0]}))}>
676 <View style={[a.flex_row, a.gap_md, a.flex_wrap, a.align_center]}>
677 <Toggle.Item name="alert" label="Alert">
678 <Toggle.Radio />
679 <Toggle.LabelText>Alert</Toggle.LabelText>
680 </Toggle.Item>
681 <Toggle.Item name="inform" label="Inform">
682 <Toggle.Radio />
683 <Toggle.LabelText>Inform</Toggle.LabelText>
684 </Toggle.Item>
685 <Toggle.Item name="none" label="None">
686 <Toggle.Radio />
687 <Toggle.LabelText>None</Toggle.LabelText>
688 </Toggle.Item>
689 </View>
690 </Toggle.Group>
691 </View>
692 </View>
693 </View>
694 )
695}
696
697function Toggler({label, children}: React.PropsWithChildren<{label: string}>) {
698 const t = useTheme()
699 const [show, setShow] = React.useState(false)
700 return (
701 <View style={a.mb_md}>
702 <View
703 style={[
704 t.atoms.border_contrast_medium,
705 a.border,
706 a.rounded_sm,
707 a.p_xs,
708 ]}>
709 <Button
710 variant="solid"
711 color="secondary"
712 label="Toggle visibility"
713 size="small"
714 onPress={() => setShow(!show)}>
715 <ButtonText>{label}</ButtonText>
716 <ButtonIcon
717 icon={show ? ChevronTop : ChevronBottom}
718 position="right"
719 />
720 </Button>
721 {show && children}
722 </View>
723 </View>
724 )
725}
726
727function SmallToggler({
728 label,
729 children,
730}: React.PropsWithChildren<{label: string}>) {
731 const [show, setShow] = React.useState(false)
732 return (
733 <View>
734 <View style={[a.flex_row]}>
735 <Button
736 variant="ghost"
737 color="secondary"
738 label="Toggle visibility"
739 size="tiny"
740 onPress={() => setShow(!show)}>
741 <ButtonText>{label}</ButtonText>
742 <ButtonIcon
743 icon={show ? ChevronTop : ChevronBottom}
744 position="right"
745 />
746 </Button>
747 </View>
748 {show && children}
749 </View>
750 )
751}
752
753function DataView({label, data}: {label: string; data: any}) {
754 return (
755 <Toggler label={label}>
756 <Text style={[{fontFamily: 'monospace'}, a.p_md]}>
757 {JSON.stringify(data, null, 2)}
758 </Text>
759 </Toggler>
760 )
761}
762
763function ModerationUIView({
764 mod,
765 label,
766}: {
767 mod: ModerationDecision
768 label: string
769}) {
770 return (
771 <Toggler label={label}>
772 <View style={a.p_lg}>
773 {[
774 'profileList',
775 'profileView',
776 'avatar',
777 'banner',
778 'displayName',
779 'contentList',
780 'contentView',
781 'contentMedia',
782 ].map(key => {
783 const ui = mod.ui(key as keyof ModerationBehavior)
784 return (
785 <View key={key} style={[a.flex_row, a.gap_md]}>
786 <Text style={[a.font_bold, {width: 100}]}>{key}</Text>
787 <Flag v={ui.filter} label="Filter" />
788 <Flag v={ui.blur} label="Blur" />
789 <Flag v={ui.alert} label="Alert" />
790 <Flag v={ui.inform} label="Inform" />
791 <Flag v={ui.noOverride} label="No-override" />
792 </View>
793 )
794 })}
795 </View>
796 </Toggler>
797 )
798}
799
800function Spacer() {
801 return <View style={{height: 30}} />
802}
803
804function MockPostFeedItem({
805 post,
806 moderation,
807}: {
808 post: AppBskyFeedDefs.PostView
809 moderation: ModerationDecision
810}) {
811 const t = useTheme()
812 if (moderation.ui('contentList').filter) {
813 return (
814 <P style={[t.atoms.bg_contrast_25, a.px_lg, a.py_md, a.mb_lg]}>
815 Filtered from the feed
816 </P>
817 )
818 }
819 return (
820 <FeedItem
821 post={post}
822 record={post.record as AppBskyFeedPost.Record}
823 moderation={moderation}
824 parentAuthor={undefined}
825 showReplyTo={false}
826 reason={undefined}
827 feedContext={''}
828 rootPost={post}
829 />
830 )
831}
832
833function MockPostThreadItem({
834 post,
835 moderation,
836 reply,
837}: {
838 post: AppBskyFeedDefs.PostView
839 moderation: ModerationDecision
840 reply?: boolean
841}) {
842 return (
843 <PostThreadItem
844 // @ts-ignore
845 post={post}
846 record={post.record as AppBskyFeedPost.Record}
847 moderation={moderation}
848 depth={reply ? 1 : 0}
849 isHighlightedPost={!reply}
850 treeView={false}
851 prevPost={undefined}
852 nextPost={undefined}
853 hasPrecedingItem={false}
854 overrideBlur={false}
855 onPostReply={() => {}}
856 />
857 )
858}
859
860function MockNotifItem({
861 notif,
862 moderationOpts,
863}: {
864 notif: FeedNotification
865 moderationOpts: ModerationOpts
866}) {
867 const t = useTheme()
868 if (shouldFilterNotif(notif.notification, moderationOpts)) {
869 return (
870 <P style={[t.atoms.bg_contrast_25, a.px_lg, a.py_md]}>
871 Filtered from the feed
872 </P>
873 )
874 }
875 return <NotifFeedItem item={notif} moderationOpts={moderationOpts} />
876}
877
878function MockAccountCard({
879 profile,
880 moderation,
881}: {
882 profile: AppBskyActorDefs.ProfileViewBasic
883 moderation: ModerationDecision
884}) {
885 const t = useTheme()
886
887 if (moderation.ui('profileList').filter) {
888 return (
889 <P style={[t.atoms.bg_contrast_25, a.px_lg, a.py_md, a.mb_lg]}>
890 Filtered from the listing
891 </P>
892 )
893 }
894
895 return <ProfileCard profile={profile} />
896}
897
898function MockAccountScreen({
899 profile,
900 moderation,
901 moderationOpts,
902}: {
903 profile: AppBskyActorDefs.ProfileViewBasic
904 moderation: ModerationDecision
905 moderationOpts: ModerationOpts
906}) {
907 const t = useTheme()
908 const {_} = useLingui()
909 return (
910 <View style={[t.atoms.border_contrast_medium, a.border, a.mb_md]}>
911 <ScreenHider
912 style={{}}
913 screenDescription={_(msg`profile`)}
914 modui={moderation.ui('profileView')}>
915 <ProfileHeaderStandard
916 // @ts-ignore ProfileViewBasic is close enough -prf
917 profile={profile}
918 moderationOpts={moderationOpts}
919 descriptionRT={new RichText({text: profile.description as string})}
920 />
921 </ScreenHider>
922 </View>
923 )
924}
925
926function Flag({v, label}: {v: boolean | undefined; label: string}) {
927 const t = useTheme()
928 return (
929 <View style={[a.flex_row, a.align_center, a.gap_xs]}>
930 <View
931 style={[
932 a.justify_center,
933 a.align_center,
934 a.rounded_xs,
935 a.border,
936 t.atoms.border_contrast_medium,
937 {
938 backgroundColor: t.palette.contrast_25,
939 width: 14,
940 height: 14,
941 },
942 ]}>
943 {v && <Check size="xs" fill={t.palette.contrast_900} />}
944 </View>
945 <P style={a.text_xs}>{label}</P>
946 </View>
947 )
948}