You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
267 lines
11 KiB
267 lines
11 KiB
import { RefreshButton } from '@/components/RefreshButton' |
|
import { Label } from '@/components/ui/label' |
|
import { Select, SelectContent, SelectItem, SelectTrigger } from '@/components/ui/select' |
|
import { Switch } from '@/components/ui/switch' |
|
import { |
|
FONT_SIZE, |
|
MEDIA_AUTO_LOAD_POLICY, |
|
NOTIFICATION_LIST_STYLE, |
|
RANDOM_PUBLISH_RELAY_COUNT |
|
} from '@/constants' |
|
import { changeAppLanguage, SUPPORTED_APP_LANGUAGE_CODES, TLanguage } from '@/i18n' |
|
import { LanguageSelectOptionLines } from '@/lib/language-select-option-lines' |
|
import SecondaryPageLayout from '@/layouts/SecondaryPageLayout' |
|
import { usePrimaryNoteView } from '@/contexts/primary-note-view-context' |
|
import { cn, isSupportCheckConnectionType } from '@/lib/utils' |
|
import { useContentPolicy } from '@/providers/ContentPolicyProvider' |
|
import { useFontSize } from '@/providers/FontSizeProvider' |
|
import { useTheme } from '@/providers/ThemeProvider' |
|
import { useUserPreferences } from '@/providers/UserPreferencesProvider' |
|
import { useUserTrust } from '@/contexts/user-trust-context' |
|
import { TMediaAutoLoadPolicy } from '@/types' |
|
import { SelectValue } from '@radix-ui/react-select' |
|
import { ExternalLink } from 'lucide-react' |
|
import { forwardRef, HTMLProps, useCallback, useEffect, useState } from 'react' |
|
import { useTranslation } from 'react-i18next' |
|
|
|
const GeneralSettingsPage = forwardRef(({ index, hideTitlebar = false }: { index?: number; hideTitlebar?: boolean }, ref) => { |
|
const { t, i18n } = useTranslation() |
|
const { registerPrimaryPanelRefresh } = usePrimaryNoteView() |
|
const [contentKey, setContentKey] = useState(0) |
|
const bump = useCallback(() => setContentKey((k) => k + 1), []) |
|
const [language, setLanguage] = useState<TLanguage>(i18n.language as TLanguage) |
|
const { themeSetting, setThemeSetting } = useTheme() |
|
const { fontSize, setFontSize } = useFontSize() |
|
const { |
|
autoplay, |
|
setAutoplay, |
|
defaultShowNsfw, |
|
setDefaultShowNsfw, |
|
hideContentMentioningMutedUsers, |
|
setHideContentMentioningMutedUsers, |
|
mediaAutoLoadPolicy, |
|
setMediaAutoLoadPolicy |
|
} = useContentPolicy() |
|
const { hideUntrustedNotes, updateHideUntrustedNotes } = useUserTrust() |
|
const { |
|
notificationListStyle, |
|
updateNotificationListStyle, |
|
addRandomRelaysToPublish, |
|
updateAddRandomRelaysToPublish, |
|
showLiveActivitiesBanner, |
|
updateShowLiveActivitiesBanner |
|
} = useUserPreferences() |
|
|
|
const handleLanguageChange = async (value: TLanguage) => { |
|
await changeAppLanguage(value) |
|
setLanguage(value) |
|
} |
|
|
|
useEffect(() => { |
|
if (!hideTitlebar) { |
|
registerPrimaryPanelRefresh(null) |
|
return |
|
} |
|
registerPrimaryPanelRefresh(bump) |
|
return () => registerPrimaryPanelRefresh(null) |
|
}, [hideTitlebar, registerPrimaryPanelRefresh, bump]) |
|
|
|
return ( |
|
<SecondaryPageLayout |
|
ref={ref} |
|
index={index} |
|
title={hideTitlebar ? undefined : t('General')} |
|
controls={hideTitlebar ? undefined : <RefreshButton onClick={bump} />} |
|
> |
|
<div key={contentKey} className="space-y-4 mt-3"> |
|
<SettingItem> |
|
<Label htmlFor="languages" className="text-base font-normal"> |
|
{t('Languages')} |
|
</Label> |
|
<Select defaultValue="en" value={language} onValueChange={handleLanguageChange}> |
|
<SelectTrigger id="languages" className="min-w-[14rem] w-auto max-w-[min(100vw,22rem)]"> |
|
<SelectValue /> |
|
</SelectTrigger> |
|
<SelectContent className="min-w-[var(--radix-select-trigger-width)]"> |
|
{SUPPORTED_APP_LANGUAGE_CODES.map((key) => ( |
|
<SelectItem key={key} value={key}> |
|
<LanguageSelectOptionLines tag={key} /> |
|
</SelectItem> |
|
))} |
|
</SelectContent> |
|
</Select> |
|
</SettingItem> |
|
<SettingItem> |
|
<Label htmlFor="theme" className="text-base font-normal"> |
|
{t('Theme')} |
|
</Label> |
|
<Select defaultValue="system" value={themeSetting} onValueChange={setThemeSetting}> |
|
<SelectTrigger id="theme" className="w-48"> |
|
<SelectValue /> |
|
</SelectTrigger> |
|
<SelectContent> |
|
<SelectItem value="system">{t('System')}</SelectItem> |
|
<SelectItem value="light">{t('Light')}</SelectItem> |
|
<SelectItem value="dark">{t('Dark')}</SelectItem> |
|
</SelectContent> |
|
</Select> |
|
</SettingItem> |
|
<SettingItem> |
|
<Label htmlFor="font-size" className="text-base font-normal"> |
|
{t('Font size')} |
|
</Label> |
|
<Select defaultValue={FONT_SIZE.MEDIUM} value={fontSize} onValueChange={setFontSize}> |
|
<SelectTrigger id="font-size" className="w-48"> |
|
<SelectValue /> |
|
</SelectTrigger> |
|
<SelectContent> |
|
<SelectItem value={FONT_SIZE.SMALL}>{t('Small')}</SelectItem> |
|
<SelectItem value={FONT_SIZE.MEDIUM}>{t('Medium')}</SelectItem> |
|
<SelectItem value={FONT_SIZE.LARGE}>{t('Large')}</SelectItem> |
|
</SelectContent> |
|
</Select> |
|
</SettingItem> |
|
<SettingItem> |
|
<Label htmlFor="notification-list-style" className="text-base font-normal"> |
|
<div>{t('Notification list style')}</div> |
|
<div className="text-muted-foreground"> |
|
{notificationListStyle === NOTIFICATION_LIST_STYLE.DETAILED |
|
? t('See extra info for each notification') |
|
: t('See more notifications at a glance')} |
|
</div> |
|
</Label> |
|
<Select |
|
defaultValue={NOTIFICATION_LIST_STYLE.DETAILED} |
|
value={notificationListStyle} |
|
onValueChange={updateNotificationListStyle} |
|
> |
|
<SelectTrigger id="notification-list-style" className="w-48"> |
|
<SelectValue /> |
|
</SelectTrigger> |
|
<SelectContent> |
|
<SelectItem value={NOTIFICATION_LIST_STYLE.DETAILED}>{t('Detailed')}</SelectItem> |
|
<SelectItem value={NOTIFICATION_LIST_STYLE.COMPACT}>{t('Compact')}</SelectItem> |
|
</SelectContent> |
|
</Select> |
|
</SettingItem> |
|
<SettingItem> |
|
<Label htmlFor="media-auto-load-policy" className="text-base font-normal"> |
|
{t('Auto-load media')} |
|
</Label> |
|
<Select |
|
defaultValue="wifi-only" |
|
value={mediaAutoLoadPolicy} |
|
onValueChange={(value: TMediaAutoLoadPolicy) => |
|
setMediaAutoLoadPolicy(value as TMediaAutoLoadPolicy) |
|
} |
|
> |
|
<SelectTrigger id="media-auto-load-policy" className="w-48"> |
|
<SelectValue /> |
|
</SelectTrigger> |
|
<SelectContent> |
|
<SelectItem value={MEDIA_AUTO_LOAD_POLICY.ALWAYS}>{t('Always')}</SelectItem> |
|
{isSupportCheckConnectionType() && ( |
|
<SelectItem value={MEDIA_AUTO_LOAD_POLICY.WIFI_ONLY}>{t('Wi-Fi only')}</SelectItem> |
|
)} |
|
<SelectItem value={MEDIA_AUTO_LOAD_POLICY.NEVER}>{t('Never')}</SelectItem> |
|
</SelectContent> |
|
</Select> |
|
</SettingItem> |
|
<SettingItem> |
|
<Label htmlFor="autoplay" className="text-base font-normal"> |
|
<div>{t('Autoplay')}</div> |
|
<div className="text-muted-foreground">{t('Enable video autoplay on this device')}</div> |
|
</Label> |
|
<Switch id="autoplay" checked={autoplay} onCheckedChange={setAutoplay} /> |
|
</SettingItem> |
|
<SettingItem> |
|
<Label htmlFor="live-activities-banner" className="text-base font-normal"> |
|
<div>{t('liveActivities.settingsToggle')}</div> |
|
<div className="text-muted-foreground">{t('liveActivities.settingsHint')}</div> |
|
</Label> |
|
<Switch |
|
id="live-activities-banner" |
|
checked={showLiveActivitiesBanner} |
|
onCheckedChange={updateShowLiveActivitiesBanner} |
|
/> |
|
</SettingItem> |
|
<SettingItem> |
|
<Label htmlFor="add-random-relays" className="text-base font-normal"> |
|
<div>{t('Add random relays to every publish')}</div> |
|
<div className="text-muted-foreground"> |
|
{t('Add random relays to every publish description', { n: RANDOM_PUBLISH_RELAY_COUNT })} |
|
</div> |
|
</Label> |
|
<Switch |
|
id="add-random-relays" |
|
checked={addRandomRelaysToPublish} |
|
onCheckedChange={updateAddRandomRelaysToPublish} |
|
/> |
|
</SettingItem> |
|
<SettingItem> |
|
<Label htmlFor="hide-untrusted-notes" className="text-base font-normal"> |
|
{t('Hide untrusted notes')} |
|
</Label> |
|
<Switch |
|
id="hide-untrusted-notes" |
|
checked={hideUntrustedNotes} |
|
onCheckedChange={updateHideUntrustedNotes} |
|
/> |
|
</SettingItem> |
|
<SettingItem> |
|
<Label htmlFor="hide-content-mentioning-muted-users" className="text-base font-normal"> |
|
{t('Hide content mentioning muted users')} |
|
</Label> |
|
<Switch |
|
id="hide-content-mentioning-muted-users" |
|
checked={hideContentMentioningMutedUsers} |
|
onCheckedChange={setHideContentMentioningMutedUsers} |
|
/> |
|
</SettingItem> |
|
<SettingItem> |
|
<Label htmlFor="show-nsfw" className="text-base font-normal"> |
|
{t('Show NSFW content by default')} |
|
</Label> |
|
<Switch id="show-nsfw" checked={defaultShowNsfw} onCheckedChange={setDefaultShowNsfw} /> |
|
</SettingItem> |
|
{/* DEPRECATED: Double-panel setting removed for technical debt reduction */} |
|
<SettingItem> |
|
<div> |
|
<a |
|
className="flex items-center gap-1 cursor-pointer hover:underline" |
|
href="https://emojito.meme/browse" |
|
target="_blank" |
|
rel="noopener noreferrer" |
|
> |
|
{t('Custom emoji management')} |
|
<ExternalLink /> |
|
</a> |
|
<div className="text-muted-foreground"> |
|
{t('After changing emojis, you may need to refresh the page')} |
|
</div> |
|
</div> |
|
</SettingItem> |
|
</div> |
|
</SecondaryPageLayout> |
|
) |
|
}) |
|
GeneralSettingsPage.displayName = 'GeneralSettingsPage' |
|
export default GeneralSettingsPage |
|
|
|
const SettingItem = forwardRef<HTMLDivElement, HTMLProps<HTMLDivElement>>( |
|
({ children, className, ...props }, ref) => { |
|
return ( |
|
<div |
|
className={cn( |
|
'flex justify-between select-none items-center px-4 min-h-9 [&_svg]:size-4 [&_svg]:shrink-0', |
|
className |
|
)} |
|
{...props} |
|
ref={ref} |
|
> |
|
{children} |
|
</div> |
|
) |
|
} |
|
) |
|
SettingItem.displayName = 'SettingItem'
|
|
|