index.tsx raw

   1  import Emoji from '@/components/Emoji'
   2  import EmojiPickerDialog from '@/components/EmojiPickerDialog'
   3  import { Button } from '@/components/ui/button'
   4  import { Label } from '@/components/ui/label'
   5  import { Select, SelectContent, SelectItem, SelectTrigger } from '@/components/ui/select'
   6  import { Switch } from '@/components/ui/switch'
   7  import { MEDIA_AUTO_LOAD_POLICY, NSFW_DISPLAY_POLICY } from '@/constants'
   8  import { LocalizedLanguageNames, TLanguage } from '@/i18n'
   9  import SecondaryPageLayout from '@/layouts/SecondaryPageLayout'
  10  import { cn, isSupportCheckConnectionType } from '@/lib/utils'
  11  import { useContentPolicy } from '@/providers/ContentPolicyProvider'
  12  import { useUserPreferences } from '@/providers/UserPreferencesProvider'
  13  import { useUserTrust } from '@/providers/UserTrustProvider'
  14  import { TMediaAutoLoadPolicy, TNsfwDisplayPolicy } from '@/types'
  15  import { SelectValue } from '@radix-ui/react-select'
  16  import { RotateCcw } from 'lucide-react'
  17  import { forwardRef, HTMLProps, useState } from 'react'
  18  import { useTranslation } from 'react-i18next'
  19  
  20  const GeneralSettingsPage = forwardRef(({ index }: { index?: number }, ref) => {
  21    const { t, i18n } = useTranslation()
  22    const [language, setLanguage] = useState<TLanguage>(i18n.language as TLanguage)
  23    const {
  24      autoplay,
  25      setAutoplay,
  26      nsfwDisplayPolicy,
  27      setNsfwDisplayPolicy,
  28      hideContentMentioningMutedUsers,
  29      setHideContentMentioningMutedUsers,
  30      mediaAutoLoadPolicy,
  31      setMediaAutoLoadPolicy,
  32      verboseLogging,
  33      setVerboseLogging,
  34      enableMarkdown,
  35      setEnableMarkdown
  36    } = useContentPolicy()
  37    const {
  38      hideUntrustedNotes,
  39      updateHideUntrustedNotes,
  40      hideUntrustedInteractions,
  41      updateHideUntrustedInteractions,
  42      hideUntrustedNotifications,
  43      updateHideUntrustedNotifications
  44    } = useUserTrust()
  45    const { quickReaction, updateQuickReaction, quickReactionEmoji, updateQuickReactionEmoji } =
  46      useUserPreferences()
  47  
  48    const handleLanguageChange = (value: TLanguage) => {
  49      i18n.changeLanguage(value)
  50      setLanguage(value)
  51    }
  52  
  53    return (
  54      <SecondaryPageLayout ref={ref} index={index} title={t('General')}>
  55        <div className="space-y-4 mt-3">
  56          <SettingItem>
  57            <Label htmlFor="languages" className="text-base font-normal">
  58              {t('Languages')}
  59            </Label>
  60            <Select defaultValue="en" value={language} onValueChange={handleLanguageChange}>
  61              <SelectTrigger id="languages" className="w-48">
  62                <SelectValue />
  63              </SelectTrigger>
  64              <SelectContent>
  65                {Object.entries(LocalizedLanguageNames).map(([key, value]) => (
  66                  <SelectItem key={key} value={key}>
  67                    {value}
  68                  </SelectItem>
  69                ))}
  70              </SelectContent>
  71            </Select>
  72          </SettingItem>
  73          <SettingItem>
  74            <Label htmlFor="media-auto-load-policy" className="text-base font-normal">
  75              {t('Auto-load media')}
  76            </Label>
  77            <Select
  78              defaultValue="wifi-only"
  79              value={mediaAutoLoadPolicy}
  80              onValueChange={(value: TMediaAutoLoadPolicy) =>
  81                setMediaAutoLoadPolicy(value as TMediaAutoLoadPolicy)
  82              }
  83            >
  84              <SelectTrigger id="media-auto-load-policy" className="w-48">
  85                <SelectValue />
  86              </SelectTrigger>
  87              <SelectContent>
  88                <SelectItem value={MEDIA_AUTO_LOAD_POLICY.ALWAYS}>{t('Always')}</SelectItem>
  89                {isSupportCheckConnectionType() && (
  90                  <SelectItem value={MEDIA_AUTO_LOAD_POLICY.WIFI_ONLY}>{t('Wi-Fi only')}</SelectItem>
  91                )}
  92                <SelectItem value={MEDIA_AUTO_LOAD_POLICY.NEVER}>{t('Never')}</SelectItem>
  93              </SelectContent>
  94            </Select>
  95          </SettingItem>
  96          <SettingItem>
  97            <Label htmlFor="autoplay" className="text-base font-normal">
  98              <div>{t('Autoplay')}</div>
  99              <div className="text-muted-foreground">{t('Enable video autoplay on this device')}</div>
 100            </Label>
 101            <Switch id="autoplay" checked={autoplay} onCheckedChange={setAutoplay} />
 102          </SettingItem>
 103          <SettingItem>
 104            <Label htmlFor="enable-markdown" className="text-base font-normal">
 105              <div>{t('Render Markdown')}</div>
 106              <div className="text-muted-foreground">{t('Parse and render markdown formatting in notes')}</div>
 107            </Label>
 108            <Switch id="enable-markdown" checked={enableMarkdown} onCheckedChange={setEnableMarkdown} />
 109          </SettingItem>
 110          <SettingItem>
 111            <Label htmlFor="hide-untrusted-notes" className="text-base font-normal">
 112              {t('Hide untrusted notes')}
 113            </Label>
 114            <Switch
 115              id="hide-untrusted-notes"
 116              checked={hideUntrustedNotes}
 117              onCheckedChange={updateHideUntrustedNotes}
 118            />
 119          </SettingItem>
 120          <SettingItem>
 121            <Label htmlFor="hide-untrusted-interactions" className="text-base font-normal">
 122              {t('Hide untrusted interactions')}
 123            </Label>
 124            <Switch
 125              id="hide-untrusted-interactions"
 126              checked={hideUntrustedInteractions}
 127              onCheckedChange={updateHideUntrustedInteractions}
 128            />
 129          </SettingItem>
 130          <SettingItem>
 131            <Label htmlFor="hide-untrusted-notifications" className="text-base font-normal">
 132              {t('Hide untrusted notifications')}
 133            </Label>
 134            <Switch
 135              id="hide-untrusted-notifications"
 136              checked={hideUntrustedNotifications}
 137              onCheckedChange={updateHideUntrustedNotifications}
 138            />
 139          </SettingItem>
 140          <SettingItem>
 141            <Label htmlFor="hide-content-mentioning-muted-users" className="text-base font-normal">
 142              {t('Hide content mentioning muted users')}
 143            </Label>
 144            <Switch
 145              id="hide-content-mentioning-muted-users"
 146              checked={hideContentMentioningMutedUsers}
 147              onCheckedChange={setHideContentMentioningMutedUsers}
 148            />
 149          </SettingItem>
 150          <SettingItem>
 151            <Label htmlFor="nsfw-display-policy" className="text-base font-normal">
 152              {t('NSFW content display')}
 153            </Label>
 154            <Select
 155              value={nsfwDisplayPolicy}
 156              onValueChange={(value: TNsfwDisplayPolicy) => setNsfwDisplayPolicy(value)}
 157            >
 158              <SelectTrigger id="nsfw-display-policy" className="w-48">
 159                <SelectValue />
 160              </SelectTrigger>
 161              <SelectContent>
 162                <SelectItem value={NSFW_DISPLAY_POLICY.HIDE}>{t('Hide completely')}</SelectItem>
 163                <SelectItem value={NSFW_DISPLAY_POLICY.HIDE_CONTENT}>
 164                  {t('Show but hide content')}
 165                </SelectItem>
 166                <SelectItem value={NSFW_DISPLAY_POLICY.SHOW}>{t('Show directly')}</SelectItem>
 167              </SelectContent>
 168            </Select>
 169          </SettingItem>
 170          <SettingItem>
 171            <Label htmlFor="quick-reaction" className="text-base font-normal">
 172              <div>{t('Quick reaction')}</div>
 173              <div className="text-muted-foreground">
 174                {t('If enabled, you can react with a single click. Click and hold for more options')}
 175              </div>
 176            </Label>
 177            <Switch
 178              id="quick-reaction"
 179              checked={quickReaction}
 180              onCheckedChange={updateQuickReaction}
 181            />
 182          </SettingItem>
 183          {quickReaction && (
 184            <SettingItem>
 185              <Label htmlFor="quick-reaction-emoji" className="text-base font-normal">
 186                {t('Quick reaction emoji')}
 187              </Label>
 188              <div className="flex items-center gap-2">
 189                <Button
 190                  variant="ghost"
 191                  size="icon"
 192                  onClick={() => updateQuickReactionEmoji('+')}
 193                  className="text-muted-foreground hover:text-foreground"
 194                >
 195                  <RotateCcw />
 196                </Button>
 197                <EmojiPickerDialog
 198                  onEmojiClick={(emoji) => {
 199                    if (!emoji) return
 200                    updateQuickReactionEmoji(emoji)
 201                  }}
 202                >
 203                  <Button variant="ghost" size="icon" className="border">
 204                    <Emoji emoji={quickReactionEmoji} />
 205                  </Button>
 206                </EmojiPickerDialog>
 207              </div>
 208            </SettingItem>
 209          )}
 210          <SettingItem>
 211            <Label htmlFor="verbose-logging" className="text-base font-normal">
 212              <div>{t('Verbose logging')}</div>
 213              <div className="text-muted-foreground">
 214                {t('Enable detailed logging for debugging (check browser console)')}
 215              </div>
 216            </Label>
 217            <Switch
 218              id="verbose-logging"
 219              checked={verboseLogging}
 220              onCheckedChange={setVerboseLogging}
 221            />
 222          </SettingItem>
 223        </div>
 224      </SecondaryPageLayout>
 225    )
 226  })
 227  GeneralSettingsPage.displayName = 'GeneralSettingsPage'
 228  export default GeneralSettingsPage
 229  
 230  const SettingItem = forwardRef<HTMLDivElement, HTMLProps<HTMLDivElement>>(
 231    ({ children, className, ...props }, ref) => {
 232      return (
 233        <div
 234          className={cn(
 235            'flex justify-between select-none items-center px-4 min-h-9 [&_svg]:size-4 [&_svg]:shrink-0',
 236            className
 237          )}
 238          {...props}
 239          ref={ref}
 240        >
 241          {children}
 242        </div>
 243      )
 244    }
 245  )
 246  SettingItem.displayName = 'SettingItem'
 247