import { useMemo } from 'react'; import { useTranslation } from 'react-i18next'; import { Input, InputNumber, Select, Switch, Tabs, } from 'antd'; import { PartitionOutlined, RocketOutlined, SendOutlined, SettingOutlined, } from '@ant-design/icons'; import type { AllSetting } from '@/models/setting'; import { SettingListItem } from '@/components/ui'; import { useMediaQuery } from '@/hooks/useMediaQuery'; import { catTabLabel } from './catTabLabel'; import { sanitizePath, normalizePath } from './uriPath'; import SubJsonFinalMaskForm from './SubJsonFinalMaskForm'; import './SubscriptionFormatsTab.css'; interface SubscriptionFormatsTabProps { allSetting: AllSetting; updateSetting: (patch: Partial) => void; } const DEFAULT_MUX = { enabled: true, concurrency: 8, xudpConcurrency: 16, xudpProxyUDP443: 'reject', }; const DEFAULT_RULES: { type: string; outboundTag: string; domain?: string[]; ip?: string[] }[] = [ { type: 'field', outboundTag: 'direct', domain: ['geosite:category-ir'] }, { type: 'field', outboundTag: 'direct', ip: ['geoip:private', 'geoip:ir'] }, ]; const directIPsOptions = [ { label: 'Private IP', value: 'geoip:private' }, { label: '🇮🇷 Iran', value: 'geoip:ir' }, { label: '🇨🇳 China', value: 'geoip:cn' }, { label: '🇷🇺 Russia', value: 'geoip:ru' }, { label: '🇻🇳 Vietnam', value: 'geoip:vn' }, { label: '🇪🇸 Spain', value: 'geoip:es' }, { label: '🇮🇩 Indonesia', value: 'geoip:id' }, { label: '🇺🇦 Ukraine', value: 'geoip:ua' }, { label: '🇹🇷 Türkiye', value: 'geoip:tr' }, { label: '🇧🇷 Brazil', value: 'geoip:br' }, ]; const directDomainsOptions = [ { label: 'Private DNS', value: 'geosite:private' }, { label: '🇮🇷 Iran', value: 'geosite:category-ir' }, { label: '🇨🇳 China', value: 'geosite:cn' }, { label: '🇷🇺 Russia', value: 'geosite:category-ru' }, { label: 'Apple', value: 'geosite:apple' }, { label: 'Meta', value: 'geosite:meta' }, { label: 'Google', value: 'geosite:google' }, ]; function readJson(raw: string, fallback: T): T { try { if (!raw) return fallback; return JSON.parse(raw) as T; } catch { return fallback; } } export default function SubscriptionFormatsTab({ allSetting, updateSetting }: SubscriptionFormatsTabProps) { const { t } = useTranslation(); const { isMobile } = useMediaQuery(); const muxEnabled = allSetting.subJsonMux !== ''; const directEnabled = allSetting.subJsonRules !== ''; const muxObj = useMemo( () => (muxEnabled ? readJson(allSetting.subJsonMux, DEFAULT_MUX) : DEFAULT_MUX), [allSetting.subJsonMux, muxEnabled], ); function setMuxEnabled(v: boolean) { updateSetting({ subJsonMux: v ? JSON.stringify(DEFAULT_MUX) : '' }); } function setMuxField(key: K, value: typeof DEFAULT_MUX[K]) { const next = { ...muxObj, [key]: value }; updateSetting({ subJsonMux: JSON.stringify(next) }); } const ruleArray = useMemo(() => { if (!directEnabled) return null; return readJson(allSetting.subJsonRules, null); }, [allSetting.subJsonRules, directEnabled]); const directIPs = useMemo(() => { if (!ruleArray) return []; const ipRule = ruleArray.find((r) => r.ip); return ipRule?.ip ?? []; }, [ruleArray]); const directDomains = useMemo(() => { if (!ruleArray) return []; const dRule = ruleArray.find((r) => r.domain); return dRule?.domain ?? []; }, [ruleArray]); function setDirectEnabled(v: boolean) { updateSetting({ subJsonRules: v ? JSON.stringify(DEFAULT_RULES) : '' }); } function setDirectIPs(value: string[]) { if (!ruleArray) return; let rules = [...ruleArray]; if (value.length === 0) { rules = rules.filter((r) => !r.ip); } else { let idx = rules.findIndex((r) => r.ip); if (idx === -1) { rules.push({ ...DEFAULT_RULES[1] }); idx = rules.length - 1; } rules[idx] = { ...rules[idx], ip: [...value] }; } updateSetting({ subJsonRules: JSON.stringify(rules) }); } function setDirectDomains(value: string[]) { if (!ruleArray) return; let rules = [...ruleArray]; if (value.length === 0) { rules = rules.filter((r) => !r.domain); } else { let idx = rules.findIndex((r) => r.domain); if (idx === -1) { rules.push({ ...DEFAULT_RULES[0] }); idx = rules.length - 1; } rules[idx] = { ...rules[idx], domain: [...value] }; } updateSetting({ subJsonRules: JSON.stringify(rules) }); } return ( , t('pages.settings.panelSettings'), isMobile), children: ( <> {allSetting.subJsonEnable && ( <> JSON {t('pages.settings.subPath')}} description={t('pages.settings.subPathDesc')}> updateSetting({ subJsonPath: sanitizePath(e.target.value) })} onBlur={() => updateSetting({ subJsonPath: normalizePath(allSetting.subJsonPath) })} /> JSON {t('pages.settings.subURI')}} description={t('pages.settings.subURIDesc')}> updateSetting({ subJsonURI: e.target.value })} /> )} {allSetting.subClashEnable && ( <> Clash {t('pages.settings.subPath')}} description={t('pages.settings.subPathDesc')}> updateSetting({ subClashPath: sanitizePath(e.target.value) })} onBlur={() => updateSetting({ subClashPath: normalizePath(allSetting.subClashPath) })} /> Clash {t('pages.settings.subURI')}} description={t('pages.settings.subURIDesc')}> updateSetting({ subClashURI: e.target.value })} /> )} ), }, { key: '2', label: catTabLabel(, t('pages.settings.subFormats.finalMask'), isMobile), children: ( <> updateSetting({ subJsonFinalMask: v })} /> ), }, { key: '3', label: catTabLabel(, t('pages.settings.mux'), isMobile), children: ( <> {muxEnabled && (
setMuxField('concurrency', Number(v) || 0)} /> setMuxField('xudpConcurrency', Number(v) || 0)} /> {t('pages.settings.direct')} {t('domainName')}}>