Update profile to pull from labeler definition
This commit is contained in:
@@ -2,6 +2,7 @@ import React from 'react'
|
||||
import {View} from 'react-native'
|
||||
import {useLingui} from '@lingui/react'
|
||||
import {msg} from '@lingui/macro'
|
||||
import {InterprettedLabelValueDefinition} from '@atproto/api'
|
||||
|
||||
import {useLabelStrings} from '#/lib/moderation/useLabelStrings'
|
||||
|
||||
@@ -10,21 +11,26 @@ import {Text} from '#/components/Typography'
|
||||
import * as ToggleButton from '#/components/forms/ToggleButton'
|
||||
|
||||
export function ModerationLabelPref({
|
||||
label,
|
||||
labelValueDefinition,
|
||||
disabled,
|
||||
}: {
|
||||
label: string
|
||||
labelValueDefinition: InterprettedLabelValueDefinition
|
||||
disabled?: boolean
|
||||
}) {
|
||||
console.log({labelValueDefinition})
|
||||
const t = useTheme()
|
||||
const {_} = useLingui()
|
||||
const allLabelStrings = useLabelStrings()
|
||||
const labelStrings = allLabelStrings[label] || {
|
||||
general: {
|
||||
name: label,
|
||||
description: `Labeled "${label}"`,
|
||||
},
|
||||
}
|
||||
const labelStrings = labelValueDefinition.locales[0] // TODO look up locale
|
||||
? labelValueDefinition.locales[0]
|
||||
: labelValueDefinition.identifier in allLabelStrings
|
||||
? allLabelStrings[labelValueDefinition.identifier].general
|
||||
: {
|
||||
general: {
|
||||
name: labelValueDefinition.identifier,
|
||||
description: `Labeled "${labelValueDefinition.identifier}"`,
|
||||
},
|
||||
}
|
||||
|
||||
// TODO add onChange behavior when mod prefs are updated
|
||||
|
||||
@@ -46,16 +52,16 @@ export function ModerationLabelPref({
|
||||
a.align_center,
|
||||
]}>
|
||||
<View style={[a.gap_xs, {width: '50%'}]}>
|
||||
<Text style={[a.font_bold]}>{labelStrings.general.name}</Text>
|
||||
<Text style={[a.font_bold]}>{labelStrings.name}</Text>
|
||||
<Text style={[t.atoms.text_contrast_medium, a.leading_snug]}>
|
||||
{labelStrings.general.description}
|
||||
{labelStrings.description}
|
||||
</Text>
|
||||
</View>
|
||||
<View style={[a.justify_center, {minHeight: 35}]}>
|
||||
{!disabled && (
|
||||
<ToggleButton.Group
|
||||
label={_(
|
||||
msg`Configure content filtering setting for category: ${labelStrings.general.name.toLowerCase()}`,
|
||||
msg`Configure content filtering setting for category: ${labelStrings.name.toLowerCase()}`,
|
||||
)}
|
||||
values={['hide']}
|
||||
onChange={() => {}}>
|
||||
|
||||
+20
-1
@@ -1,5 +1,10 @@
|
||||
import React from 'react'
|
||||
import {ModerationCause, ModerationUI, AppBskyLabelerDefs} from '@atproto/api'
|
||||
import {
|
||||
ModerationCause,
|
||||
ModerationUI,
|
||||
InterprettedLabelValueDefinition,
|
||||
LABELS,
|
||||
} from '@atproto/api'
|
||||
|
||||
import {sanitizeDisplayName} from '#/lib/strings/display-names'
|
||||
import {sanitizeHandle} from '#/lib/strings/handles'
|
||||
@@ -47,3 +52,17 @@ export function getModerationServiceTitle({
|
||||
? sanitizeDisplayName(displayName)
|
||||
: sanitizeHandle(handle, '@')
|
||||
}
|
||||
|
||||
export function lookupLabelValueDefinition(
|
||||
labelValue: string,
|
||||
customDefs: InterprettedLabelValueDefinition[] | undefined,
|
||||
): InterprettedLabelValueDefinition | undefined {
|
||||
let def
|
||||
if (!labelValue.startsWith('!') && customDefs) {
|
||||
def = customDefs.find(d => d.identifier === labelValue)
|
||||
}
|
||||
if (!def) {
|
||||
def = LABELS[labelValue as keyof typeof LABELS]
|
||||
}
|
||||
return def
|
||||
}
|
||||
|
||||
+12
-12
@@ -38,16 +38,16 @@ import {
|
||||
|
||||
interface Props {
|
||||
profile: AppBskyActorDefs.ProfileViewDetailed
|
||||
modservice: AppBskyLabelerDefs.LabelerViewDetailed
|
||||
labeler: AppBskyLabelerDefs.LabelerViewDetailed
|
||||
descriptionRT: RichTextAPI | null
|
||||
moderationOpts: ModerationOpts
|
||||
hideBackButton?: boolean
|
||||
isPlaceholderProfile?: boolean
|
||||
}
|
||||
|
||||
let ProfileHeaderModerator = ({
|
||||
let ProfileHeaderLabeler = ({
|
||||
profile: profileUnshadowed,
|
||||
modservice,
|
||||
labeler,
|
||||
descriptionRT,
|
||||
moderationOpts,
|
||||
hideBackButton = false,
|
||||
@@ -74,12 +74,12 @@ let ProfileHeaderModerator = ({
|
||||
const {mutateAsync: unlikeMod, isPending: isUnlikePending} =
|
||||
useUnlikeMutation()
|
||||
const [likeUri, setLikeUri] = React.useState<string>(
|
||||
modservice.viewer?.like || '',
|
||||
labeler.viewer?.like || '',
|
||||
)
|
||||
const isLiked = !!likeUri
|
||||
|
||||
const onToggleLiked = React.useCallback(async () => {
|
||||
if (!modservice) {
|
||||
if (!labeler) {
|
||||
return
|
||||
}
|
||||
try {
|
||||
@@ -90,7 +90,7 @@ let ProfileHeaderModerator = ({
|
||||
track('CustomFeed:Unlike')
|
||||
setLikeUri('')
|
||||
} else {
|
||||
const res = await likeMod({uri: modservice.uri, cid: modservice.cid})
|
||||
const res = await likeMod({uri: labeler.uri, cid: labeler.cid})
|
||||
track('CustomFeed:Like')
|
||||
setLikeUri(res.uri)
|
||||
}
|
||||
@@ -102,7 +102,7 @@ let ProfileHeaderModerator = ({
|
||||
)
|
||||
logger.error(`Failed to toggle labeler like`, {message: e.message})
|
||||
}
|
||||
}, [modservice, likeUri, isLiked, likeMod, unlikeMod, track, _])
|
||||
}, [labeler, likeUri, isLiked, likeMod, unlikeMod, track, _])
|
||||
|
||||
const onPressEditProfile = React.useCallback(() => {
|
||||
track('ProfileHeader:EditProfileButtonClicked')
|
||||
@@ -213,13 +213,13 @@ let ProfileHeaderModerator = ({
|
||||
)}
|
||||
</Button>
|
||||
|
||||
{typeof modservice.likeCount === 'number' && (
|
||||
{typeof labeler.likeCount === 'number' && (
|
||||
<InlineLink
|
||||
to={'#todo'}
|
||||
style={[t.atoms.text_contrast_medium, a.font_bold]}>
|
||||
<Trans>
|
||||
Liked by {modservice.likeCount}{' '}
|
||||
{pluralize(modservice.likeCount, 'user')}
|
||||
Liked by {labeler.likeCount}{' '}
|
||||
{pluralize(labeler.likeCount, 'user')}
|
||||
</Trans>
|
||||
</InlineLink>
|
||||
)}
|
||||
@@ -230,5 +230,5 @@ let ProfileHeaderModerator = ({
|
||||
</ProfileHeaderShell>
|
||||
)
|
||||
}
|
||||
ProfileHeaderModerator = memo(ProfileHeaderModerator)
|
||||
export {ProfileHeaderModerator}
|
||||
ProfileHeaderLabeler = memo(ProfileHeaderLabeler)
|
||||
export {ProfileHeaderLabeler}
|
||||
@@ -10,7 +10,7 @@ import {LoadingPlaceholder} from 'view/com/util/LoadingPlaceholder'
|
||||
import {usePalette} from 'lib/hooks/usePalette'
|
||||
|
||||
import {ProfileHeaderStandard} from './ProfileHeaderStandard'
|
||||
import {ProfileHeaderModerator} from './ProfileHeaderModerator'
|
||||
import {ProfileHeaderLabeler} from './ProfileHeaderLabeler'
|
||||
|
||||
let ProfileHeaderLoading = (_props: {}): React.ReactNode => {
|
||||
const pal = usePalette('default')
|
||||
@@ -34,7 +34,7 @@ export {ProfileHeaderLoading}
|
||||
|
||||
interface Props {
|
||||
profile: AppBskyActorDefs.ProfileViewDetailed
|
||||
modservice: AppBskyLabelerDefs.LabelerViewDetailed | undefined
|
||||
labeler: AppBskyLabelerDefs.LabelerViewDetailed | undefined
|
||||
descriptionRT: RichTextAPI | null
|
||||
moderationOpts: ModerationOpts
|
||||
hideBackButton?: boolean
|
||||
@@ -43,10 +43,10 @@ interface Props {
|
||||
|
||||
let ProfileHeader = (props: Props): React.ReactNode => {
|
||||
if (props.profile.associated?.labeler) {
|
||||
if (!props.modservice) {
|
||||
if (!props.labeler) {
|
||||
return <ProfileHeaderLoading />
|
||||
}
|
||||
return <ProfileHeaderModerator {...props} modservice={props.modservice} />
|
||||
return <ProfileHeaderLabeler {...props} labeler={props.labeler} />
|
||||
}
|
||||
return <ProfileHeaderStandard {...props} />
|
||||
}
|
||||
|
||||
+73
-46
@@ -1,12 +1,18 @@
|
||||
import React from 'react'
|
||||
import {View} from 'react-native'
|
||||
import {AppBskyLabelerDefs, ModerationOpts} from '@atproto/api'
|
||||
import {
|
||||
AppBskyLabelerDefs,
|
||||
ModerationOpts,
|
||||
interpretLabelValueDefinitions,
|
||||
InterprettedLabelValueDefinition,
|
||||
} from '@atproto/api'
|
||||
import {Trans, msg} from '@lingui/macro'
|
||||
import {useLingui} from '@lingui/react'
|
||||
import {useSafeAreaFrame} from 'react-native-safe-area-context'
|
||||
|
||||
import {useLabelerSubscriptionMutation} from '#/state/queries/labeler'
|
||||
import {logger} from '#/logger'
|
||||
import {lookupLabelValueDefinition} from '#/lib/moderation'
|
||||
|
||||
import {useTheme, atoms as a} from '#/alf'
|
||||
import {Text} from '#/components/Typography'
|
||||
@@ -17,21 +23,20 @@ import {CenteredView, ScrollView} from '#/view/com/util/Views'
|
||||
import {ErrorState} from '../ErrorState'
|
||||
import {ModerationLabelPref} from '#/components/ModerationLabelPref'
|
||||
|
||||
export function ProfileContentFiltersSection({
|
||||
modServiceQuery,
|
||||
export function ProfileLabelsSection({
|
||||
isLabelerLoading,
|
||||
labelerInfo,
|
||||
labelerError,
|
||||
moderationOpts,
|
||||
}: {
|
||||
modServiceQuery: {
|
||||
data: AppBskyLabelerDefs.LabelerViewDetailed | undefined
|
||||
isLoading: boolean
|
||||
error: Error | null
|
||||
}
|
||||
isLabelerLoading: boolean
|
||||
labelerInfo: AppBskyLabelerDefs.LabelerViewDetailed | undefined
|
||||
labelerError: Error | null
|
||||
moderationOpts: ModerationOpts
|
||||
}) {
|
||||
const t = useTheme()
|
||||
const {_} = useLingui()
|
||||
const {height: minHeight} = useSafeAreaFrame()
|
||||
const {isLoading, error, data: modservice} = modServiceQuery
|
||||
return (
|
||||
<CenteredView>
|
||||
<View
|
||||
@@ -44,21 +49,21 @@ export function ProfileContentFiltersSection({
|
||||
minHeight,
|
||||
},
|
||||
]}>
|
||||
{isLoading ? (
|
||||
{isLabelerLoading ? (
|
||||
<View style={[a.w_full, a.align_center]}>
|
||||
<Loader size="xl" />
|
||||
</View>
|
||||
) : error || !modservice ? (
|
||||
) : labelerError || !labelerInfo ? (
|
||||
<ErrorState
|
||||
error={
|
||||
error?.toString() ||
|
||||
labelerError?.toString() ||
|
||||
_(msg`Something went wrong, please try again.`)
|
||||
}
|
||||
/>
|
||||
) : (
|
||||
<ProfileContentFiltersSectionInner
|
||||
<ProfileLabelsSectionInner
|
||||
moderationOpts={moderationOpts}
|
||||
modservice={modservice}
|
||||
labelerInfo={labelerInfo}
|
||||
/>
|
||||
)}
|
||||
</View>
|
||||
@@ -66,37 +71,46 @@ export function ProfileContentFiltersSection({
|
||||
)
|
||||
}
|
||||
|
||||
export function ProfileContentFiltersSectionInner({
|
||||
export function ProfileLabelsSectionInner({
|
||||
moderationOpts,
|
||||
modservice,
|
||||
labelerInfo,
|
||||
}: {
|
||||
moderationOpts: ModerationOpts
|
||||
modservice: AppBskyLabelerDefs.LabelerViewDetailed
|
||||
labelerInfo: AppBskyLabelerDefs.LabelerViewDetailed
|
||||
}) {
|
||||
const {_} = useLingui()
|
||||
const t = useTheme()
|
||||
const isEnabled = Boolean(
|
||||
moderationOpts.prefs.mods.find(mod => mod.did === modservice.creator.did),
|
||||
moderationOpts.prefs.mods.find(mod => mod.did === labelerInfo.creator.did),
|
||||
)
|
||||
const hasSession = true // TODO
|
||||
const {labelValues} = labelerInfo.policies
|
||||
const labelDefs = React.useMemo(() => {
|
||||
const customDefs = interpretLabelValueDefinitions(labelerInfo)
|
||||
return labelValues
|
||||
.map(val => lookupLabelValueDefinition(val, customDefs))
|
||||
.filter(
|
||||
def => def && def?.configurable,
|
||||
) as InterprettedLabelValueDefinition[]
|
||||
}, [labelerInfo, labelValues])
|
||||
|
||||
const {mutateAsync: toggleSubscription, variables} =
|
||||
useLabelerSubscriptionMutation()
|
||||
const isSubscribed =
|
||||
variables?.subscribe ??
|
||||
moderationOpts.prefs.mods.find(mod => mod.did === modservice.creator.did)
|
||||
moderationOpts.prefs.mods.find(mod => mod.did === labelerInfo.creator.did)
|
||||
|
||||
const onPressSubscribe = React.useCallback(async () => {
|
||||
try {
|
||||
await toggleSubscription({
|
||||
did: modservice.creator.did,
|
||||
did: labelerInfo.creator.did,
|
||||
subscribe: !isSubscribed,
|
||||
})
|
||||
} catch (e: any) {
|
||||
// setSubscriptionError(e.message)
|
||||
logger.error(`Failed to subscribe to labeler`, {message: e.message})
|
||||
}
|
||||
}, [toggleSubscription, isSubscribed, modservice])
|
||||
}, [toggleSubscription, isSubscribed, labelerInfo])
|
||||
|
||||
return (
|
||||
<ScrollView
|
||||
@@ -112,7 +126,7 @@ export function ProfileContentFiltersSectionInner({
|
||||
hide, warn, and categorize the network.
|
||||
</Trans>
|
||||
</Text>
|
||||
{!isSubscribed && (
|
||||
{labelValues.length === 0 ? (
|
||||
<Text
|
||||
style={[
|
||||
a.pt_xl,
|
||||
@@ -121,33 +135,46 @@ export function ProfileContentFiltersSectionInner({
|
||||
a.text_sm,
|
||||
]}>
|
||||
<Trans>
|
||||
Subscribe to @{modservice.creator.handle} to use these labels:
|
||||
This labeler hasn't declared what labels it publishes, and may not
|
||||
be active.
|
||||
</Trans>
|
||||
</Text>
|
||||
)}
|
||||
</View>
|
||||
<View
|
||||
style={[
|
||||
a.mt_xl,
|
||||
t.atoms.bg_contrast_25,
|
||||
a.rounded_md,
|
||||
a.border,
|
||||
t.atoms.border_contrast_low,
|
||||
]}>
|
||||
{
|
||||
undefined /* TODO modservice.policies.labelValues.map((def, i) => {
|
||||
return (
|
||||
<React.Fragment key={def.id}>
|
||||
{i !== 0 && <Divider />}
|
||||
<ModerationLabelPref
|
||||
disabled={isEnabled ? undefined : true}
|
||||
labelGroup={def.id}
|
||||
/>
|
||||
</React.Fragment>
|
||||
)
|
||||
})*/
|
||||
}
|
||||
) : !isSubscribed ? (
|
||||
<Text
|
||||
style={[
|
||||
a.pt_xl,
|
||||
t.atoms.text_contrast_high,
|
||||
a.leading_snug,
|
||||
a.text_sm,
|
||||
]}>
|
||||
<Trans>
|
||||
Subscribe to @{labelerInfo.creator.handle} to use these labels:
|
||||
</Trans>
|
||||
</Text>
|
||||
) : null}
|
||||
</View>
|
||||
{labelDefs.length > 0 && (
|
||||
<View
|
||||
style={[
|
||||
a.mt_xl,
|
||||
t.atoms.bg_contrast_25,
|
||||
a.rounded_md,
|
||||
a.border,
|
||||
t.atoms.border_contrast_low,
|
||||
]}>
|
||||
{labelDefs.map((labelDef, i) => {
|
||||
return (
|
||||
<React.Fragment key={labelDef?.identifier}>
|
||||
{i !== 0 && <Divider />}
|
||||
<ModerationLabelPref
|
||||
disabled={isEnabled ? undefined : true}
|
||||
labelValueDefinition={labelDef}
|
||||
/>
|
||||
</React.Fragment>
|
||||
)
|
||||
})}
|
||||
</View>
|
||||
)}
|
||||
|
||||
<View style={{height: 100}} />
|
||||
</ScrollView>
|
||||
@@ -37,7 +37,7 @@ import {listenSoftReset} from '#/state/events'
|
||||
import {isInvalidHandle} from '#/lib/strings/handles'
|
||||
|
||||
import {ProfileFeedSection} from '#/screens/Profile/Sections/Feed'
|
||||
import {ProfileContentFiltersSection} from '#/screens/Profile/Sections/ContentFilters'
|
||||
import {ProfileLabelsSection} from '#/screens/Profile/Sections/Labels'
|
||||
import {ProfileHeader, ProfileHeaderLoading} from '#/screens/Profile/Header'
|
||||
|
||||
interface SectionRef {
|
||||
@@ -139,7 +139,11 @@ function ProfileScreenLoaded({
|
||||
const setMinimalShellMode = useSetMinimalShellMode()
|
||||
const {openComposer} = useComposerControls()
|
||||
const {screen, track} = useAnalytics()
|
||||
const modServiceQuery = useLabelerInfoQuery({
|
||||
const {
|
||||
data: labelerInfo,
|
||||
error: labelerError,
|
||||
isLoading: isLabelerLoading,
|
||||
} = useLabelerInfoQuery({
|
||||
did: profile.did,
|
||||
enabled: !!profile.associated?.labeler,
|
||||
})
|
||||
@@ -312,7 +316,7 @@ function ProfileScreenLoaded({
|
||||
return (
|
||||
<ProfileHeader
|
||||
profile={profile}
|
||||
modservice={modServiceQuery.data}
|
||||
labeler={labelerInfo}
|
||||
descriptionRT={hasDescription ? descriptionRT : null}
|
||||
moderationOpts={moderationOpts}
|
||||
hideBackButton={hideBackButton}
|
||||
@@ -321,7 +325,7 @@ function ProfileScreenLoaded({
|
||||
)
|
||||
}, [
|
||||
profile,
|
||||
modServiceQuery,
|
||||
labelerInfo,
|
||||
descriptionRT,
|
||||
hasDescription,
|
||||
moderationOpts,
|
||||
@@ -344,9 +348,11 @@ function ProfileScreenLoaded({
|
||||
renderHeader={renderHeader}>
|
||||
{showFiltersTab
|
||||
? ({headerHeight, isFocused, scrollElRef}) => (
|
||||
<ProfileContentFiltersSection
|
||||
<ProfileLabelsSection
|
||||
// ref={moderationSectionRef}
|
||||
modServiceQuery={modServiceQuery}
|
||||
labelerInfo={labelerInfo}
|
||||
labelerError={labelerError}
|
||||
isLabelerLoading={isLabelerLoading}
|
||||
moderationOpts={moderationOpts}
|
||||
scrollElRef={scrollElRef as ListRef}
|
||||
headerOffset={headerHeight}
|
||||
|
||||
Reference in New Issue
Block a user