about summary refs log tree commit diff
path: root/src/components/moderation/LabelPreference.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'src/components/moderation/LabelPreference.tsx')
-rw-r--r--src/components/moderation/LabelPreference.tsx294
1 files changed, 294 insertions, 0 deletions
diff --git a/src/components/moderation/LabelPreference.tsx b/src/components/moderation/LabelPreference.tsx
new file mode 100644
index 000000000..7d4bd9c32
--- /dev/null
+++ b/src/components/moderation/LabelPreference.tsx
@@ -0,0 +1,294 @@
+import React from 'react'
+import {View} from 'react-native'
+import {InterpretedLabelValueDefinition, LabelPreference} from '@atproto/api'
+import {useLingui} from '@lingui/react'
+import {msg, Trans} from '@lingui/macro'
+
+import {useGlobalLabelStrings} from '#/lib/moderation/useGlobalLabelStrings'
+import {
+  usePreferencesQuery,
+  usePreferencesSetContentLabelMutation,
+} from '#/state/queries/preferences'
+import {useLabelBehaviorDescription} from '#/lib/moderation/useLabelBehaviorDescription'
+import {getLabelStrings} from '#/lib/moderation/useLabelInfo'
+
+import {useTheme, atoms as a, useBreakpoints} from '#/alf'
+import {Text} from '#/components/Typography'
+import {InlineLink} from '#/components/Link'
+import {CircleInfo_Stroke2_Corner0_Rounded as CircleInfo} from '../icons/CircleInfo'
+import * as ToggleButton from '#/components/forms/ToggleButton'
+
+export function Outer({children}: React.PropsWithChildren<{}>) {
+  return (
+    <View
+      style={[
+        a.flex_row,
+        a.gap_md,
+        a.px_lg,
+        a.py_lg,
+        a.justify_between,
+        a.flex_wrap,
+      ]}>
+      {children}
+    </View>
+  )
+}
+
+export function Content({
+  children,
+  name,
+  description,
+}: React.PropsWithChildren<{
+  name: string
+  description: string
+}>) {
+  const t = useTheme()
+  const {gtPhone} = useBreakpoints()
+
+  return (
+    <View style={[a.gap_xs, a.flex_1]}>
+      <Text style={[a.font_bold, gtPhone ? a.text_sm : a.text_md]}>{name}</Text>
+      <Text style={[t.atoms.text_contrast_medium, a.leading_snug]}>
+        {description}
+      </Text>
+
+      {children}
+    </View>
+  )
+}
+
+export function Buttons({
+  name,
+  values,
+  onChange,
+  ignoreLabel,
+  warnLabel,
+  hideLabel,
+}: {
+  name: string
+  values: ToggleButton.GroupProps['values']
+  onChange: ToggleButton.GroupProps['onChange']
+  ignoreLabel?: string
+  warnLabel?: string
+  hideLabel?: string
+}) {
+  const {_} = useLingui()
+  const {gtPhone} = useBreakpoints()
+
+  return (
+    <View style={[{minHeight: 35}, gtPhone ? undefined : a.w_full]}>
+      <ToggleButton.Group
+        label={_(
+          msg`Configure content filtering setting for category: ${name}`,
+        )}
+        values={values}
+        onChange={onChange}>
+        {ignoreLabel && (
+          <ToggleButton.Button name="ignore" label={ignoreLabel}>
+            {ignoreLabel}
+          </ToggleButton.Button>
+        )}
+        {warnLabel && (
+          <ToggleButton.Button name="warn" label={warnLabel}>
+            {warnLabel}
+          </ToggleButton.Button>
+        )}
+        {hideLabel && (
+          <ToggleButton.Button name="hide" label={hideLabel}>
+            {hideLabel}
+          </ToggleButton.Button>
+        )}
+      </ToggleButton.Group>
+    </View>
+  )
+}
+
+/**
+ * For use on the global Moderation screen to set prefs for a "global" label,
+ * not scoped to a single labeler.
+ */
+export function GlobalLabelPreference({
+  labelDefinition,
+  disabled,
+}: {
+  labelDefinition: InterpretedLabelValueDefinition
+  disabled?: boolean
+}) {
+  const {_} = useLingui()
+
+  const {identifier} = labelDefinition
+  const {data: preferences} = usePreferencesQuery()
+  const {mutate, variables} = usePreferencesSetContentLabelMutation()
+  const savedPref = preferences?.moderationPrefs.labels[identifier]
+  const pref = variables?.visibility ?? savedPref ?? 'warn'
+
+  const allLabelStrings = useGlobalLabelStrings()
+  const labelStrings =
+    labelDefinition.identifier in allLabelStrings
+      ? allLabelStrings[labelDefinition.identifier]
+      : {
+          name: labelDefinition.identifier,
+          description: `Labeled "${labelDefinition.identifier}"`,
+        }
+
+  const labelOptions = {
+    hide: _(msg`Hide`),
+    warn: _(msg`Warn`),
+    ignore: _(msg`Show`),
+  }
+
+  return (
+    <Outer>
+      <Content
+        name={labelStrings.name}
+        description={labelStrings.description}
+      />
+      {!disabled && (
+        <Buttons
+          name={labelStrings.name.toLowerCase()}
+          values={[pref]}
+          onChange={values => {
+            mutate({
+              label: identifier,
+              visibility: values[0] as LabelPreference,
+              labelerDid: undefined,
+            })
+          }}
+          ignoreLabel={labelOptions.ignore}
+          warnLabel={labelOptions.warn}
+          hideLabel={labelOptions.hide}
+        />
+      )}
+    </Outer>
+  )
+}
+
+/**
+ * For use on individual labeler pages
+ */
+export function LabelerLabelPreference({
+  labelDefinition,
+  disabled,
+  labelerDid,
+}: {
+  labelDefinition: InterpretedLabelValueDefinition
+  disabled?: boolean
+  labelerDid?: string
+}) {
+  const {i18n} = useLingui()
+  const t = useTheme()
+  const {gtPhone} = useBreakpoints()
+
+  const isGlobalLabel = !labelDefinition.definedBy
+  const {identifier} = labelDefinition
+  const {data: preferences} = usePreferencesQuery()
+  const {mutate, variables} = usePreferencesSetContentLabelMutation()
+  const savedPref =
+    labelerDid && !isGlobalLabel
+      ? preferences?.moderationPrefs.labelers.find(l => l.did === labelerDid)
+          ?.labels[identifier]
+      : preferences?.moderationPrefs.labels[identifier]
+  const pref =
+    variables?.visibility ??
+    savedPref ??
+    labelDefinition.defaultSetting ??
+    'warn'
+
+  // does the 'warn' setting make sense for this label?
+  const canWarn = !(
+    labelDefinition.blurs === 'none' && labelDefinition.severity === 'none'
+  )
+  // is this label adult only?
+  const adultOnly = labelDefinition.flags.includes('adult')
+  // is this label disabled because it's adult only?
+  const adultDisabled =
+    adultOnly && !preferences?.moderationPrefs.adultContentEnabled
+  // are there any reasons we cant configure this label here?
+  const cantConfigure = isGlobalLabel || adultDisabled
+  const showConfig = !disabled && (gtPhone || !cantConfigure)
+
+  // adjust the pref based on whether warn is available
+  let prefAdjusted = pref
+  if (adultDisabled) {
+    prefAdjusted = 'hide'
+  } else if (!canWarn && pref === 'warn') {
+    prefAdjusted = 'ignore'
+  }
+
+  // grab localized descriptions of the label and its settings
+  const currentPrefLabel = useLabelBehaviorDescription(
+    labelDefinition,
+    prefAdjusted,
+  )
+  const hideLabel = useLabelBehaviorDescription(labelDefinition, 'hide')
+  const warnLabel = useLabelBehaviorDescription(labelDefinition, 'warn')
+  const ignoreLabel = useLabelBehaviorDescription(labelDefinition, 'ignore')
+  const globalLabelStrings = useGlobalLabelStrings()
+  const labelStrings = getLabelStrings(
+    i18n.locale,
+    globalLabelStrings,
+    labelDefinition,
+  )
+
+  return (
+    <Outer>
+      <Content name={labelStrings.name} description={labelStrings.description}>
+        {cantConfigure && (
+          <View style={[a.flex_row, a.gap_xs, a.align_center, a.mt_xs]}>
+            <CircleInfo size="sm" fill={t.atoms.text_contrast_high.color} />
+
+            <Text
+              style={[t.atoms.text_contrast_medium, a.font_semibold, a.italic]}>
+              {adultDisabled ? (
+                <Trans>Adult content is disabled.</Trans>
+              ) : isGlobalLabel ? (
+                <Trans>
+                  Configured in{' '}
+                  <InlineLink to="/moderation" style={a.text_sm}>
+                    moderation settings
+                  </InlineLink>
+                  .
+                </Trans>
+              ) : null}
+            </Text>
+          </View>
+        )}
+      </Content>
+
+      {showConfig && (
+        <View style={[gtPhone ? undefined : a.w_full]}>
+          {cantConfigure ? (
+            <View
+              style={[
+                {minHeight: 35},
+                a.px_md,
+                a.py_md,
+                a.rounded_sm,
+                a.border,
+                t.atoms.border_contrast_low,
+              ]}>
+              <Text style={[a.font_bold, t.atoms.text_contrast_low]}>
+                {currentPrefLabel}
+              </Text>
+            </View>
+          ) : (
+            <Buttons
+              name={labelStrings.name.toLowerCase()}
+              values={[pref]}
+              onChange={values => {
+                mutate({
+                  label: identifier,
+                  visibility: values[0] as LabelPreference,
+                  labelerDid,
+                })
+              }}
+              ignoreLabel={ignoreLabel}
+              warnLabel={canWarn ? warnLabel : undefined}
+              hideLabel={hideLabel}
+            />
+          )}
+        </View>
+      )}
+    </Outer>
+  )
+}