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