diff --git a/src/components/ModerationLabelPref/index.tsx b/src/components/ModerationLabelPref/index.tsx index 96c219f083..f7aed47ed3 100644 --- a/src/components/ModerationLabelPref/index.tsx +++ b/src/components/ModerationLabelPref/index.tsx @@ -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, ]}> - {labelStrings.general.name} + {labelStrings.name} - {labelStrings.general.description} + {labelStrings.description} {!disabled && ( {}}> diff --git a/src/lib/moderation.ts b/src/lib/moderation.ts index fa9b21c08a..944a528c96 100644 --- a/src/lib/moderation.ts +++ b/src/lib/moderation.ts @@ -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 +} diff --git a/src/screens/Profile/Header/ProfileHeaderModerator.tsx b/src/screens/Profile/Header/ProfileHeaderLabeler.tsx similarity index 92% rename from src/screens/Profile/Header/ProfileHeaderModerator.tsx rename to src/screens/Profile/Header/ProfileHeaderLabeler.tsx index 14a0e05dfe..dbaeae315c 100644 --- a/src/screens/Profile/Header/ProfileHeaderModerator.tsx +++ b/src/screens/Profile/Header/ProfileHeaderLabeler.tsx @@ -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( - 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 = ({ )} - {typeof modservice.likeCount === 'number' && ( + {typeof labeler.likeCount === 'number' && ( - Liked by {modservice.likeCount}{' '} - {pluralize(modservice.likeCount, 'user')} + Liked by {labeler.likeCount}{' '} + {pluralize(labeler.likeCount, 'user')} )} @@ -230,5 +230,5 @@ let ProfileHeaderModerator = ({ ) } -ProfileHeaderModerator = memo(ProfileHeaderModerator) -export {ProfileHeaderModerator} +ProfileHeaderLabeler = memo(ProfileHeaderLabeler) +export {ProfileHeaderLabeler} diff --git a/src/screens/Profile/Header/index.tsx b/src/screens/Profile/Header/index.tsx index 669410eef7..1280dd8b10 100644 --- a/src/screens/Profile/Header/index.tsx +++ b/src/screens/Profile/Header/index.tsx @@ -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 } - return + return } return } diff --git a/src/screens/Profile/Sections/ContentFilters.tsx b/src/screens/Profile/Sections/Labels.tsx similarity index 53% rename from src/screens/Profile/Sections/ContentFilters.tsx rename to src/screens/Profile/Sections/Labels.tsx index ac273dd8e0..5338def58a 100644 --- a/src/screens/Profile/Sections/ContentFilters.tsx +++ b/src/screens/Profile/Sections/Labels.tsx @@ -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 ( - {isLoading ? ( + {isLabelerLoading ? ( - ) : error || !modservice ? ( + ) : labelerError || !labelerInfo ? ( ) : ( - )} @@ -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 ( - {!isSubscribed && ( + {labelValues.length === 0 ? ( - Subscribe to @{modservice.creator.handle} to use these labels: + This labeler hasn't declared what labels it publishes, and may not + be active. - )} - - - { - undefined /* TODO modservice.policies.labelValues.map((def, i) => { - return ( - - {i !== 0 && } - - - ) - })*/ - } + ) : !isSubscribed ? ( + + + Subscribe to @{labelerInfo.creator.handle} to use these labels: + + + ) : null} + {labelDefs.length > 0 && ( + + {labelDefs.map((labelDef, i) => { + return ( + + {i !== 0 && } + + + ) + })} + + )} diff --git a/src/view/screens/Profile.tsx b/src/view/screens/Profile.tsx index 0d795cab0b..2b0f01d733 100644 --- a/src/view/screens/Profile.tsx +++ b/src/view/screens/Profile.tsx @@ -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 ( {showFiltersTab ? ({headerHeight, isFocused, scrollElRef}) => ( -