mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-06-05 20:54:14 +00:00
Step 5 of the planned vue->react migration. Settings is the first
entry whose state model didn't translate to the Vue-style "parent
passes a reactive object, children mutate it in place" pattern, so
the React port flips it to lifted state + a typed updateSetting
patch function.
* models/setting.ts — typed AllSetting class with the same field
defaults and equals() behavior the vue version had. The .js
twin is deleted; nothing else imported it.
* hooks/useAllSetting.ts — owns allSetting + oldAllSetting state,
exposes updateSetting(patch), saveDisabled is derived via useMemo
off equals() (no more 1Hz dirty-check timer).
* components/SettingListItem.tsx — children-based wrapper instead
of named slots. The vue twin stays alive because xray (BasicsTab,
DnsTab) still imports it; deleted when xray migrates.
The five tab components and the TwoFactorModal each accept
{ allSetting, updateSetting } and render with AntD v5's Collapse
items[] API. Every v-model:value="x" became
value={...} onChange={(e) => updateSetting({ key: e.target.value })}
or onChange={(v) => updateSetting({ key: v })} for non-input
controls.
SubscriptionFormatsTab is the trickiest — fragment / noises[] /
mux / direct routing rules are stored as JSON-encoded strings on
the wire. Parsing them once via useMemo per field, mutating the
parsed object on edit, and stringifying back into the patch keeps
the round-trip identical to the vue version.
SettingsPage hosts the tab navigation (with hash sync), the
save / restart action bar, the security-warnings alert banner,
and the restart flow that rebuilds the panel URL after the new
host/port/cert settings take effect.
69 lines
1.8 KiB
TypeScript
69 lines
1.8 KiB
TypeScript
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
import { HttpUtil } from '@/utils';
|
|
import { AllSetting } from '@/models/setting';
|
|
|
|
interface ApiMsg<T = unknown> {
|
|
success?: boolean;
|
|
obj?: T;
|
|
}
|
|
|
|
export function useAllSetting() {
|
|
const [allSetting, setAllSetting] = useState<AllSetting>(() => new AllSetting());
|
|
const [oldAllSetting, setOldAllSetting] = useState<AllSetting>(() => new AllSetting());
|
|
const [fetched, setFetched] = useState(false);
|
|
const [spinning, setSpinning] = useState(false);
|
|
const fetchedRef = useRef(false);
|
|
|
|
const applyServerState = useCallback((obj: unknown) => {
|
|
setAllSetting(new AllSetting(obj));
|
|
setOldAllSetting(new AllSetting(obj));
|
|
}, []);
|
|
|
|
const fetchAll = useCallback(async () => {
|
|
const msg = await HttpUtil.post('/panel/setting/all') as ApiMsg;
|
|
if (msg?.success) {
|
|
applyServerState(msg.obj);
|
|
fetchedRef.current = true;
|
|
setFetched(true);
|
|
}
|
|
}, [applyServerState]);
|
|
|
|
const saveAll = useCallback(async () => {
|
|
setSpinning(true);
|
|
try {
|
|
const msg = await HttpUtil.post('/panel/setting/update', allSetting) as ApiMsg;
|
|
if (msg?.success) await fetchAll();
|
|
} finally {
|
|
setSpinning(false);
|
|
}
|
|
}, [allSetting, fetchAll]);
|
|
|
|
const updateSetting = useCallback((patch: Partial<AllSetting>) => {
|
|
setAllSetting((prev) => {
|
|
const next = new AllSetting(prev);
|
|
Object.assign(next, patch);
|
|
return next;
|
|
});
|
|
}, []);
|
|
|
|
const saveDisabled = useMemo(
|
|
() => allSetting.equals(oldAllSetting),
|
|
[allSetting, oldAllSetting],
|
|
);
|
|
|
|
useEffect(() => {
|
|
// eslint-disable-next-line react-hooks/set-state-in-effect
|
|
fetchAll();
|
|
}, [fetchAll]);
|
|
|
|
return {
|
|
allSetting,
|
|
updateSetting,
|
|
fetched,
|
|
spinning,
|
|
setSpinning,
|
|
saveDisabled,
|
|
fetchAll,
|
|
saveAll,
|
|
};
|
|
}
|