Clean up ModServiceCard, rename to LabelingServiceCard
This commit is contained in:
@@ -391,7 +391,7 @@ export function Button({
|
|||||||
]}>
|
]}>
|
||||||
<LinearGradient
|
<LinearGradient
|
||||||
colors={
|
colors={
|
||||||
state.hovered || state.pressed || state.focused
|
state.hovered || state.pressed
|
||||||
? gradientHoverColors
|
? gradientHoverColors
|
||||||
: gradientColors
|
: gradientColors
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,184 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import {View} from 'react-native'
|
||||||
|
import {msg, Trans} from '@lingui/macro'
|
||||||
|
import {useLingui} from '@lingui/react'
|
||||||
|
import {AppBskyLabelerDefs} from '@atproto/api'
|
||||||
|
|
||||||
|
import {getLabelingServiceTitle} from '#/lib/moderation'
|
||||||
|
import {Link as InternalLink, LinkProps} from '#/components/Link'
|
||||||
|
import {Text} from '#/components/Typography'
|
||||||
|
import {useLabelerInfoQuery} from '#/state/queries/labeler'
|
||||||
|
import {atoms as a, useTheme, ViewStyleProp, useBreakpoints} from '#/alf'
|
||||||
|
import {RichText} from '#/components/RichText'
|
||||||
|
import {ChevronRight_Stroke2_Corner0_Rounded as ChevronRight} from '../icons/Chevron'
|
||||||
|
import {UserAvatar} from '#/view/com/util/UserAvatar'
|
||||||
|
import {sanitizeHandle} from '#/lib/strings/handles'
|
||||||
|
import {pluralize} from '#/lib/strings/helpers'
|
||||||
|
|
||||||
|
type LabelingServiceProps = {
|
||||||
|
labeler: AppBskyLabelerDefs.LabelerViewDetailed
|
||||||
|
}
|
||||||
|
|
||||||
|
export function Outer({
|
||||||
|
children,
|
||||||
|
style,
|
||||||
|
}: React.PropsWithChildren<ViewStyleProp>) {
|
||||||
|
return (
|
||||||
|
<View
|
||||||
|
style={[
|
||||||
|
a.flex_row,
|
||||||
|
a.align_center,
|
||||||
|
a.gap_sm,
|
||||||
|
a.w_full,
|
||||||
|
a.p_lg,
|
||||||
|
a.pr_md,
|
||||||
|
a.overflow_hidden,
|
||||||
|
style,
|
||||||
|
]}>
|
||||||
|
<View style={[a.flex_row, a.align_center, a.w_full, a.gap_md]}>
|
||||||
|
{children}
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function Avatar({avatar}: {avatar?: string}) {
|
||||||
|
return <UserAvatar type="labeler" size={40} avatar={avatar} />
|
||||||
|
}
|
||||||
|
|
||||||
|
export function Title({value}: {value: string}) {
|
||||||
|
return <Text style={[a.text_md, a.font_bold]}>{value}</Text>
|
||||||
|
}
|
||||||
|
|
||||||
|
export function Description({value, handle}: {value?: string; handle: string}) {
|
||||||
|
return value ? (
|
||||||
|
<RichText value={value} style={[]} />
|
||||||
|
) : (
|
||||||
|
<Text>
|
||||||
|
<Trans>By {sanitizeHandle(handle, '@')}</Trans>
|
||||||
|
</Text>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function LikeCount({count}: {count: number}) {
|
||||||
|
const t = useTheme()
|
||||||
|
return (
|
||||||
|
<Text
|
||||||
|
style={[
|
||||||
|
a.mt_sm,
|
||||||
|
a.text_sm,
|
||||||
|
t.atoms.text_contrast_medium,
|
||||||
|
{fontWeight: '500'},
|
||||||
|
]}>
|
||||||
|
<Trans>
|
||||||
|
Liked by {count} {pluralize(count, 'user')}
|
||||||
|
</Trans>
|
||||||
|
</Text>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function Content({children}: React.PropsWithChildren<{}>) {
|
||||||
|
const t = useTheme()
|
||||||
|
const {gtMobile} = useBreakpoints()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<View
|
||||||
|
style={[
|
||||||
|
a.flex_1,
|
||||||
|
a.flex_row,
|
||||||
|
a.gap_md,
|
||||||
|
a.align_center,
|
||||||
|
a.justify_between,
|
||||||
|
]}>
|
||||||
|
<View style={[a.gap_xs, a.flex_1, gtMobile && a.gap_sm]}>{children}</View>
|
||||||
|
|
||||||
|
<ChevronRight size="md" style={[a.z_10, t.atoms.text_contrast_low]} />
|
||||||
|
</View>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The canonical view for a moderation service. Use this or compose your own.
|
||||||
|
*/
|
||||||
|
export function Default({
|
||||||
|
labeler,
|
||||||
|
style,
|
||||||
|
}: LabelingServiceProps & ViewStyleProp) {
|
||||||
|
return (
|
||||||
|
<Outer style={style}>
|
||||||
|
<Avatar />
|
||||||
|
<Content>
|
||||||
|
<Title
|
||||||
|
value={getLabelingServiceTitle({
|
||||||
|
displayName: labeler.creator.displayName,
|
||||||
|
handle: labeler.creator.handle,
|
||||||
|
})}
|
||||||
|
/>
|
||||||
|
<Description
|
||||||
|
value={labeler.creator.description}
|
||||||
|
handle={labeler.creator.handle}
|
||||||
|
/>
|
||||||
|
{labeler.likeCount ? <LikeCount count={labeler.likeCount} /> : null}
|
||||||
|
</Content>
|
||||||
|
</Outer>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function Link({
|
||||||
|
children,
|
||||||
|
labeler,
|
||||||
|
}: LabelingServiceProps & Pick<LinkProps, 'children'>) {
|
||||||
|
const {_} = useLingui()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<InternalLink
|
||||||
|
to={{
|
||||||
|
screen: 'Profile',
|
||||||
|
params: {
|
||||||
|
name: labeler.creator.handle,
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
label={_(
|
||||||
|
msg`View the moderation service provided by @${labeler.creator.handle}`,
|
||||||
|
)}>
|
||||||
|
{children}
|
||||||
|
</InternalLink>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO not finished yet
|
||||||
|
export function DefaultSkeleton() {
|
||||||
|
return (
|
||||||
|
<View>
|
||||||
|
<Text>Loading</Text>
|
||||||
|
</View>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function Loader({
|
||||||
|
did,
|
||||||
|
loading: LoadingComponent = DefaultSkeleton,
|
||||||
|
error: ErrorComponent,
|
||||||
|
component: Component,
|
||||||
|
}: {
|
||||||
|
did: string
|
||||||
|
loading?: React.ComponentType<{}>
|
||||||
|
error?: React.ComponentType<{error: string}>
|
||||||
|
component: React.ComponentType<{
|
||||||
|
labeler: AppBskyLabelerDefs.LabelerViewDetailed
|
||||||
|
}>
|
||||||
|
}) {
|
||||||
|
const {isLoading, data, error} = useLabelerInfoQuery({did})
|
||||||
|
|
||||||
|
return isLoading ? (
|
||||||
|
LoadingComponent ? (
|
||||||
|
<LoadingComponent />
|
||||||
|
) : null
|
||||||
|
) : error || !data ? (
|
||||||
|
ErrorComponent ? (
|
||||||
|
<ErrorComponent error={error?.message || 'Unknown error'} />
|
||||||
|
) : null
|
||||||
|
) : (
|
||||||
|
<Component labeler={data} />
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -1,104 +0,0 @@
|
|||||||
import React from 'react'
|
|
||||||
import {View} from 'react-native'
|
|
||||||
import {Trans} from '@lingui/macro'
|
|
||||||
|
|
||||||
import {atoms as a, useTheme, ViewStyleProp, flatten} from '#/alf'
|
|
||||||
import {Text} from '#/components/Typography'
|
|
||||||
import {RichText} from '#/components/RichText'
|
|
||||||
import {ChevronRight_Stroke2_Corner0_Rounded as ChevronRight} from '../icons/Chevron'
|
|
||||||
import {UserAvatar} from '#/view/com/util/UserAvatar'
|
|
||||||
import {sanitizeHandle} from '#/lib/strings/handles'
|
|
||||||
import {pluralize} from '#/lib/strings/helpers'
|
|
||||||
|
|
||||||
export function Outer({
|
|
||||||
children,
|
|
||||||
style,
|
|
||||||
}: React.PropsWithChildren<ViewStyleProp>) {
|
|
||||||
return (
|
|
||||||
<View
|
|
||||||
style={[
|
|
||||||
a.flex_row,
|
|
||||||
a.align_center,
|
|
||||||
a.gap_sm,
|
|
||||||
a.w_full,
|
|
||||||
a.pt_md,
|
|
||||||
a.pb_md,
|
|
||||||
a.pl_md,
|
|
||||||
a.pr_lg,
|
|
||||||
a.overflow_hidden,
|
|
||||||
flatten(style),
|
|
||||||
]}>
|
|
||||||
<View style={[a.flex_row, a.align_center, a.w_full, a.gap_md]}>
|
|
||||||
{children}
|
|
||||||
</View>
|
|
||||||
</View>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export function Avatar({avatar}: {avatar?: string}) {
|
|
||||||
return <UserAvatar type="labeler" size={40} avatar={avatar} />
|
|
||||||
}
|
|
||||||
|
|
||||||
export function Title({value}: {value: string}) {
|
|
||||||
return <Text style={[a.text_md, a.font_bold, a.pb_2xs]}>{value}</Text>
|
|
||||||
}
|
|
||||||
|
|
||||||
export function Description({value, handle}: {value?: string; handle: string}) {
|
|
||||||
return value ? (
|
|
||||||
<RichText value={value} style={[]} />
|
|
||||||
) : (
|
|
||||||
<Text>
|
|
||||||
<Trans>
|
|
||||||
Moderation service managed by @{sanitizeHandle(handle, '@')}
|
|
||||||
</Trans>
|
|
||||||
</Text>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export function Content({
|
|
||||||
title,
|
|
||||||
description,
|
|
||||||
handle,
|
|
||||||
likeCount,
|
|
||||||
}: {
|
|
||||||
title: string
|
|
||||||
description?: string
|
|
||||||
handle: string
|
|
||||||
likeCount?: number
|
|
||||||
}) {
|
|
||||||
const t = useTheme()
|
|
||||||
|
|
||||||
return (
|
|
||||||
<View style={[a.flex_1, a.flex_row, a.align_center, a.justify_between]}>
|
|
||||||
<View>
|
|
||||||
<Text style={[a.text_md, a.font_bold, a.pb_2xs]}>{title}</Text>
|
|
||||||
|
|
||||||
{description ? (
|
|
||||||
<RichText value={description} style={[]} />
|
|
||||||
) : (
|
|
||||||
<Text>
|
|
||||||
<Trans>
|
|
||||||
Moderation service managed by {sanitizeHandle(handle, '@')}
|
|
||||||
</Trans>
|
|
||||||
</Text>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{typeof likeCount === 'number' && (
|
|
||||||
<Text
|
|
||||||
style={[
|
|
||||||
t.atoms.text_contrast_medium,
|
|
||||||
a.text_sm,
|
|
||||||
{fontWeight: '500'},
|
|
||||||
a.mt_sm,
|
|
||||||
]}>
|
|
||||||
<Trans>
|
|
||||||
Liked by {likeCount || 0} {pluralize(likeCount || 0, 'user')}
|
|
||||||
</Trans>
|
|
||||||
</Text>
|
|
||||||
)}
|
|
||||||
</View>
|
|
||||||
|
|
||||||
<ChevronRight size="md" style={[a.z_10, t.atoms.text_contrast_low]} />
|
|
||||||
</View>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,73 +0,0 @@
|
|||||||
import React from 'react'
|
|
||||||
import {View} from 'react-native'
|
|
||||||
import {msg} from '@lingui/macro'
|
|
||||||
import {useLingui} from '@lingui/react'
|
|
||||||
import {AppBskyLabelerDefs} from '@atproto/api'
|
|
||||||
|
|
||||||
import {Link as InternalLink, LinkProps} from '#/components/Link'
|
|
||||||
import {Text} from '#/components/Typography'
|
|
||||||
import {useLabelerInfoQuery} from '#/state/queries/labeler'
|
|
||||||
|
|
||||||
export * as Card from '#/components/ModerationServiceCard/Card'
|
|
||||||
|
|
||||||
type ModerationServiceProps = {
|
|
||||||
labeler: AppBskyLabelerDefs.LabelerViewDetailed
|
|
||||||
}
|
|
||||||
|
|
||||||
export function Link({
|
|
||||||
children,
|
|
||||||
labeler,
|
|
||||||
}: ModerationServiceProps & Pick<LinkProps, 'children'>) {
|
|
||||||
const {_} = useLingui()
|
|
||||||
|
|
||||||
return (
|
|
||||||
<InternalLink
|
|
||||||
to={{
|
|
||||||
screen: 'Profile',
|
|
||||||
params: {
|
|
||||||
name: labeler.creator.handle,
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
label={_(
|
|
||||||
msg`View the moderation service provided by @${labeler.creator.handle}`,
|
|
||||||
)}>
|
|
||||||
{children}
|
|
||||||
</InternalLink>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export function ModerationServiceCardSkeleton() {
|
|
||||||
return (
|
|
||||||
<View>
|
|
||||||
<Text>Loading</Text>
|
|
||||||
</View>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export function Loader({
|
|
||||||
did,
|
|
||||||
loading: LoadingComponent = ModerationServiceCardSkeleton,
|
|
||||||
error: ErrorComponent,
|
|
||||||
component: Component,
|
|
||||||
}: {
|
|
||||||
did: string
|
|
||||||
loading?: React.ComponentType<{}>
|
|
||||||
error?: React.ComponentType<{error: string}>
|
|
||||||
component: React.ComponentType<{
|
|
||||||
labeler: AppBskyLabelerDefs.LabelerViewDetailed
|
|
||||||
}>
|
|
||||||
}) {
|
|
||||||
const {isLoading, data, error} = useLabelerInfoQuery({did})
|
|
||||||
|
|
||||||
return isLoading ? (
|
|
||||||
LoadingComponent ? (
|
|
||||||
<LoadingComponent />
|
|
||||||
) : null
|
|
||||||
) : error || !data ? (
|
|
||||||
ErrorComponent ? (
|
|
||||||
<ErrorComponent error={error?.message || 'Unknown error'} />
|
|
||||||
) : null
|
|
||||||
) : (
|
|
||||||
<Component labeler={data} />
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -4,7 +4,7 @@ import {msg, Trans} from '@lingui/macro'
|
|||||||
import {useLingui} from '@lingui/react'
|
import {useLingui} from '@lingui/react'
|
||||||
import {AppBskyLabelerDefs} from '@atproto/api'
|
import {AppBskyLabelerDefs} from '@atproto/api'
|
||||||
|
|
||||||
import {getModerationServiceTitle} from '#/lib/moderation'
|
import {getLabelingServiceTitle} from '#/lib/moderation'
|
||||||
import {ReportOption} from '#/lib/moderation/useReportOptions'
|
import {ReportOption} from '#/lib/moderation/useReportOptions'
|
||||||
|
|
||||||
import {atoms as a, useTheme, tokens, native} from '#/alf'
|
import {atoms as a, useTheme, tokens, native} from '#/alf'
|
||||||
@@ -140,7 +140,7 @@ export function SubmitView({
|
|||||||
onChange={setSelectedServices}>
|
onChange={setSelectedServices}>
|
||||||
<View style={[a.flex_row, a.gap_sm, a.flex_wrap]}>
|
<View style={[a.flex_row, a.gap_sm, a.flex_wrap]}>
|
||||||
{labelers.map(labeler => {
|
{labelers.map(labeler => {
|
||||||
const title = getModerationServiceTitle({
|
const title = getLabelingServiceTitle({
|
||||||
displayName: labeler.creator.displayName,
|
displayName: labeler.creator.displayName,
|
||||||
handle: labeler.creator.handle,
|
handle: labeler.creator.handle,
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ export function isJustAMute(modui: ModerationUI): boolean {
|
|||||||
return modui.filters.length === 1 && modui.filters[0].type === 'muted'
|
return modui.filters.length === 1 && modui.filters[0].type === 'muted'
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getModerationServiceTitle({
|
export function getLabelingServiceTitle({
|
||||||
displayName,
|
displayName,
|
||||||
handle,
|
handle,
|
||||||
}: {
|
}: {
|
||||||
|
|||||||
@@ -39,8 +39,7 @@ import * as Toggle from '#/components/forms/Toggle'
|
|||||||
import {InlineLink, Link} from '#/components/Link'
|
import {InlineLink, Link} from '#/components/Link'
|
||||||
import {Button, ButtonText} from '#/components/Button'
|
import {Button, ButtonText} from '#/components/Button'
|
||||||
import {Loader} from '#/components/Loader'
|
import {Loader} from '#/components/Loader'
|
||||||
import {getModerationServiceTitle} from '#/lib/moderation'
|
import * as LabelingService from '#/components/LabelingServiceCard'
|
||||||
import * as ModerationServiceCard from '#/components/ModerationServiceCard'
|
|
||||||
import {GlobalModerationLabelPref} from '#/components/moderation/GlobalModerationLabelPref'
|
import {GlobalModerationLabelPref} from '#/components/moderation/GlobalModerationLabelPref'
|
||||||
import {useGlobalDialogsControlContext} from '#/components/dialogs/Context'
|
import {useGlobalDialogsControlContext} from '#/components/dialogs/Context'
|
||||||
import {useModalControls} from '#/state/modals'
|
import {useModalControls} from '#/state/modals'
|
||||||
@@ -396,21 +395,26 @@ export function ModerationScreenInner({
|
|||||||
return (
|
return (
|
||||||
<React.Fragment key={mod.creator.did}>
|
<React.Fragment key={mod.creator.did}>
|
||||||
{i !== 0 && <Divider />}
|
{i !== 0 && <Divider />}
|
||||||
<ModerationServiceCard.Link labeler={mod}>
|
<LabelingService.Link labeler={mod}>
|
||||||
<ModerationServiceCard.Card.Outer>
|
{state => (
|
||||||
<ModerationServiceCard.Card.Avatar
|
<LabelingService.Default
|
||||||
avatar={mod.creator.avatar}
|
labeler={mod}
|
||||||
|
style={[
|
||||||
|
i === 0 && {
|
||||||
|
borderTopLeftRadius: a.rounded_sm.borderRadius,
|
||||||
|
borderTopRightRadius: a.rounded_sm.borderRadius,
|
||||||
|
},
|
||||||
|
i === labelers.length - 1 && {
|
||||||
|
borderBottomLeftRadius: a.rounded_sm.borderRadius,
|
||||||
|
borderBottomRightRadius: a.rounded_sm.borderRadius,
|
||||||
|
},
|
||||||
|
(state.hovered || state.pressed) && [
|
||||||
|
t.atoms.bg_contrast_50,
|
||||||
|
],
|
||||||
|
]}
|
||||||
/>
|
/>
|
||||||
<ModerationServiceCard.Card.Content
|
)}
|
||||||
title={getModerationServiceTitle({
|
</LabelingService.Link>
|
||||||
displayName: mod.creator.displayName,
|
|
||||||
handle: mod.creator.handle,
|
|
||||||
})}
|
|
||||||
handle={mod.creator.handle}
|
|
||||||
description={mod.creator.description || ''}
|
|
||||||
/>
|
|
||||||
</ModerationServiceCard.Card.Outer>
|
|
||||||
</ModerationServiceCard.Link>
|
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
)
|
)
|
||||||
})}
|
})}
|
||||||
|
|||||||
Reference in New Issue
Block a user