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 { dispatchSettingsChanged } from '@/services/local-storage.service'
  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      nsfwDisplayPolicy,
  25      setNsfwDisplayPolicy,
  26      hideContentMentioningMutedUsers,
  27      setHideContentMentioningMutedUsers,
  28      mediaAutoLoadPolicy,
  29      setMediaAutoLoadPolicy,
  30      verboseLogging,
  31      setVerboseLogging,
  32      enableMarkdown,
  33      setEnableMarkdown
  34    } = useContentPolicy()
  35    const { quickReaction, updateQuickReaction, quickReactionEmoji, updateQuickReactionEmoji } =
  36      useUserPreferences()
  37  
  38    const handleLanguageChange = (value: TLanguage) => {
  39      i18n.changeLanguage(value)
  40      setLanguage(value)
  41      dispatchSettingsChanged()
  42    }
  43  
  44    return (
  45      <SecondaryPageLayout ref={ref} index={index} title={t('General')}>
  46        <div className="space-y-4 mt-3">
  47          <SettingItem>
  48            <Label htmlFor="languages" className="text-base font-normal">
  49              {t('Languages')}
  50            </Label>
  51            <Select defaultValue="en" value={language} onValueChange={handleLanguageChange}>
  52              <SelectTrigger id="languages" className="w-48">
  53                <SelectValue />
  54              </SelectTrigger>
  55              <SelectContent>
  56                {Object.entries(LocalizedLanguageNames).map(([key, value]) => (
  57                  <SelectItem key={key} value={key}>
  58                    {value}
  59                  </SelectItem>
  60                ))}
  61              </SelectContent>
  62            </Select>
  63          </SettingItem>
  64          <SettingItem>
  65            <Label htmlFor="media-auto-load-policy" className="text-base font-normal">
  66              {t('Auto-load media')}
  67            </Label>
  68            <Select
  69              defaultValue="wifi-only"
  70              value={mediaAutoLoadPolicy}
  71              onValueChange={(value: TMediaAutoLoadPolicy) =>
  72                setMediaAutoLoadPolicy(value as TMediaAutoLoadPolicy)
  73              }
  74            >
  75              <SelectTrigger id="media-auto-load-policy" className="w-48">
  76                <SelectValue />
  77              </SelectTrigger>
  78              <SelectContent>
  79                <SelectItem value={MEDIA_AUTO_LOAD_POLICY.ALWAYS}>{t('Always')}</SelectItem>
  80                {isSupportCheckConnectionType() && (
  81                  <SelectItem value={MEDIA_AUTO_LOAD_POLICY.WIFI_ONLY}>{t('Wi-Fi only')}</SelectItem>
  82                )}
  83                <SelectItem value={MEDIA_AUTO_LOAD_POLICY.NEVER}>{t('Never')}</SelectItem>
  84              </SelectContent>
  85            </Select>
  86          </SettingItem>
  87          <SettingItem>
  88            <Label htmlFor="enable-markdown" className="text-base font-normal">
  89              <div>{t('Render Markdown')}</div>
  90              <div className="text-muted-foreground">{t('Parse and render markdown formatting in notes')}</div>
  91            </Label>
  92            <Switch id="enable-markdown" checked={enableMarkdown} onCheckedChange={setEnableMarkdown} />
  93          </SettingItem>
  94          <SettingItem>
  95            <Label htmlFor="hide-content-mentioning-muted-users" className="text-base font-normal">
  96              {t('Hide content mentioning muted users')}
  97            </Label>
  98            <Switch
  99              id="hide-content-mentioning-muted-users"
 100              checked={hideContentMentioningMutedUsers}
 101              onCheckedChange={setHideContentMentioningMutedUsers}
 102            />
 103          </SettingItem>
 104          <SettingItem>
 105            <Label htmlFor="nsfw-display-policy" className="text-base font-normal">
 106              {t('NSFW content display')}
 107            </Label>
 108            <Select
 109              value={nsfwDisplayPolicy}
 110              onValueChange={(value: TNsfwDisplayPolicy) => setNsfwDisplayPolicy(value)}
 111            >
 112              <SelectTrigger id="nsfw-display-policy" className="w-48">
 113                <SelectValue />
 114              </SelectTrigger>
 115              <SelectContent>
 116                <SelectItem value={NSFW_DISPLAY_POLICY.HIDE}>{t('Hide completely')}</SelectItem>
 117                <SelectItem value={NSFW_DISPLAY_POLICY.HIDE_CONTENT}>
 118                  {t('Show but hide content')}
 119                </SelectItem>
 120                <SelectItem value={NSFW_DISPLAY_POLICY.SHOW}>{t('Show directly')}</SelectItem>
 121              </SelectContent>
 122            </Select>
 123          </SettingItem>
 124          <SettingItem>
 125            <Label htmlFor="quick-reaction" className="text-base font-normal">
 126              <div>{t('Quick reaction')}</div>
 127              <div className="text-muted-foreground">
 128                {t('If enabled, you can react with a single click. Click and hold for more options')}
 129              </div>
 130            </Label>
 131            <Switch
 132              id="quick-reaction"
 133              checked={quickReaction}
 134              onCheckedChange={updateQuickReaction}
 135            />
 136          </SettingItem>
 137          {quickReaction && (
 138            <SettingItem>
 139              <Label htmlFor="quick-reaction-emoji" className="text-base font-normal">
 140                {t('Quick reaction emoji')}
 141              </Label>
 142              <div className="flex items-center gap-2">
 143                <Button
 144                  variant="ghost"
 145                  size="icon"
 146                  onClick={() => updateQuickReactionEmoji('+')}
 147                  className="text-muted-foreground hover:text-foreground"
 148                >
 149                  <RotateCcw />
 150                </Button>
 151                <EmojiPickerDialog
 152                  onEmojiClick={(emoji) => {
 153                    if (!emoji) return
 154                    updateQuickReactionEmoji(emoji)
 155                  }}
 156                >
 157                  <Button variant="ghost" size="icon" className="border">
 158                    <Emoji emoji={quickReactionEmoji} />
 159                  </Button>
 160                </EmojiPickerDialog>
 161              </div>
 162            </SettingItem>
 163          )}
 164          <SettingItem>
 165            <Label htmlFor="verbose-logging" className="text-base font-normal">
 166              <div>{t('Verbose logging')}</div>
 167              <div className="text-muted-foreground">
 168                {t('Enable detailed logging for debugging (check browser console)')}
 169              </div>
 170            </Label>
 171            <Switch
 172              id="verbose-logging"
 173              checked={verboseLogging}
 174              onCheckedChange={setVerboseLogging}
 175            />
 176          </SettingItem>
 177        </div>
 178      </SecondaryPageLayout>
 179    )
 180  })
 181  GeneralSettingsPage.displayName = 'GeneralSettingsPage'
 182  export default GeneralSettingsPage
 183  
 184  const SettingItem = forwardRef<HTMLDivElement, HTMLProps<HTMLDivElement>>(
 185    ({ children, className, ...props }, ref) => {
 186      return (
 187        <div
 188          className={cn(
 189            'flex justify-between select-none items-center px-4 min-h-9 [&_svg]:size-4 [&_svg]:shrink-0',
 190            className
 191          )}
 192          {...props}
 193          ref={ref}
 194        >
 195          {children}
 196        </div>
 197      )
 198    }
 199  )
 200  SettingItem.displayName = 'SettingItem'
 201