s
This commit is contained in:
@@ -362,28 +362,62 @@ export function LiveYoutubeUnmannedView({
|
||||
const [laneDrafts, setLaneDrafts] = useState<Record<string, ProLaneDraft>>({});
|
||||
const [laneSaving, setLaneSaving] = useState(false);
|
||||
const [laneStatus, setLaneStatus] = useState<string | null>(null);
|
||||
const [activeViewRefreshToken, setActiveViewRefreshToken] = useState(0);
|
||||
const urlListDirtyRef = useRef(false);
|
||||
const singleUrlDraftRef = useRef(urlConfig);
|
||||
const proUrlDraftRef = useRef(proUrlDraft);
|
||||
const currentProcRef = useRef(currentProc);
|
||||
const urlRequestSeqRef = useRef(0);
|
||||
const ytIniRequestSeqRef = useRef(0);
|
||||
|
||||
const applySingleUrlDraft = useCallback(
|
||||
(next: string) => {
|
||||
singleUrlDraftRef.current = next;
|
||||
setUrlConfig(next);
|
||||
},
|
||||
[setUrlConfig],
|
||||
);
|
||||
|
||||
const applyProUrlDraft = useCallback((next: string) => {
|
||||
proUrlDraftRef.current = next;
|
||||
setProUrlDraft(next);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
urlListDirtyRef.current = false;
|
||||
}, [currentProc]);
|
||||
|
||||
useEffect(() => {
|
||||
currentProcRef.current = currentProc;
|
||||
}, [currentProc]);
|
||||
|
||||
useEffect(() => {
|
||||
singleUrlDraftRef.current = urlConfig;
|
||||
}, [urlConfig]);
|
||||
|
||||
useEffect(() => {
|
||||
proUrlDraftRef.current = proUrlDraft;
|
||||
}, [proUrlDraft]);
|
||||
|
||||
const pullUrlConfigFromServer = useCallback(async () => {
|
||||
if (!currentProc) return;
|
||||
const targetProc = currentProcRef.current.trim();
|
||||
if (!targetProc) return;
|
||||
const requestId = ++urlRequestSeqRef.current;
|
||||
try {
|
||||
const data = await fetchJson<{ content?: string }>(
|
||||
`/get_url_config?process=${encodeURIComponent(currentProc)}`,
|
||||
`/get_url_config?process=${encodeURIComponent(targetProc)}`,
|
||||
);
|
||||
if (requestId !== urlRequestSeqRef.current || targetProc !== currentProcRef.current.trim()) return;
|
||||
const c = data.content ?? "";
|
||||
if (urlListDirtyRef.current) return;
|
||||
if (mode === "single") setUrlConfig(c);
|
||||
if (mode === "single") applySingleUrlDraft(c);
|
||||
else {
|
||||
const localDraft = channels.find((row) => row.id === activeCh)?.urlLines ?? "";
|
||||
if (shouldPreserveLocalUrlDraft(localDraft, c)) {
|
||||
setProUrlDraft(localDraft);
|
||||
applyProUrlDraft(localDraft);
|
||||
return;
|
||||
}
|
||||
setProUrlDraft(c);
|
||||
applyProUrlDraft(c);
|
||||
if (activeCh) {
|
||||
setChannels((prev) => {
|
||||
const next = prev.map((x) => (x.id === activeCh ? { ...x, urlLines: c } : x));
|
||||
@@ -395,7 +429,7 @@ export function LiveYoutubeUnmannedView({
|
||||
} catch {
|
||||
/* ignore */
|
||||
}
|
||||
}, [activeCh, channels, currentProc, fetchJson, mode, setUrlConfig]);
|
||||
}, [activeCh, applyProUrlDraft, applySingleUrlDraft, channels, fetchJson, mode]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!currentProc || !urlAutoRefresh) return;
|
||||
@@ -407,28 +441,31 @@ export function LiveYoutubeUnmannedView({
|
||||
}, [currentProc, urlAutoRefresh, pullUrlConfigFromServer]);
|
||||
|
||||
const reloadUrlManual = async () => {
|
||||
if (!currentProc) return;
|
||||
const targetProc = currentProcRef.current.trim();
|
||||
if (!targetProc) return;
|
||||
setUrlReloading(true);
|
||||
setUrlFetchNote(null);
|
||||
urlListDirtyRef.current = false;
|
||||
const requestId = ++urlRequestSeqRef.current;
|
||||
try {
|
||||
const data = await fetchJson<{ content?: string }>(
|
||||
`/get_url_config?process=${encodeURIComponent(currentProc)}`,
|
||||
`/get_url_config?process=${encodeURIComponent(targetProc)}`,
|
||||
);
|
||||
if (requestId !== urlRequestSeqRef.current || targetProc !== currentProcRef.current.trim()) return;
|
||||
const c = data.content ?? "";
|
||||
if (urlListDirtyRef.current) {
|
||||
setUrlFetchNote(t("检测到本地未保存修改,已保留当前草稿", "Kept the local unsaved draft"));
|
||||
return;
|
||||
}
|
||||
if (mode === "single") setUrlConfig(c);
|
||||
if (mode === "single") applySingleUrlDraft(c);
|
||||
else {
|
||||
const localDraft = channels.find((row) => row.id === activeCh)?.urlLines ?? "";
|
||||
if (shouldPreserveLocalUrlDraft(localDraft, c)) {
|
||||
setProUrlDraft(localDraft);
|
||||
applyProUrlDraft(localDraft);
|
||||
setUrlFetchNote(t("服务器暂无已保存 URL,已保留本地草稿", "Server has no saved URLs, kept the local draft"));
|
||||
return;
|
||||
}
|
||||
setProUrlDraft(c);
|
||||
applyProUrlDraft(c);
|
||||
if (activeCh) {
|
||||
setChannels((prev) => {
|
||||
const next = prev.map((x) => (x.id === activeCh ? { ...x, urlLines: c } : x));
|
||||
@@ -439,9 +476,12 @@ export function LiveYoutubeUnmannedView({
|
||||
}
|
||||
setUrlFetchNote(t("已从服务器读取 URL_config", "Reloaded URL_config from server"));
|
||||
} catch (e) {
|
||||
if (requestId !== urlRequestSeqRef.current || targetProc !== currentProcRef.current.trim()) return;
|
||||
setUrlFetchNote((e as Error).message);
|
||||
} finally {
|
||||
setUrlReloading(false);
|
||||
if (requestId === urlRequestSeqRef.current && targetProc === currentProcRef.current.trim()) {
|
||||
setUrlReloading(false);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -552,11 +592,6 @@ export function LiveYoutubeUnmannedView({
|
||||
);
|
||||
|
||||
const active = channels.find((c) => c.id === activeCh) || channels[0];
|
||||
const activeSavedUrlDraft = useMemo(() => (active?.urlLines ?? "").trim(), [active?.urlLines]);
|
||||
const activeUrlDirty = useMemo(
|
||||
() => (active ? proUrlDraft !== (active.urlLines ?? "").trim() : false),
|
||||
[active, proUrlDraft],
|
||||
);
|
||||
const activeLaneDraft = useMemo(
|
||||
() => (active ? laneDrafts[active.id] ?? laneDraftFromChannel(active) : null),
|
||||
[active, laneDrafts],
|
||||
@@ -663,12 +698,14 @@ export function LiveYoutubeUnmannedView({
|
||||
const setModePersist = useCallback(
|
||||
(m: "single" | "pro") => {
|
||||
if (m === "single" && mode === "pro" && active) {
|
||||
if (activeUrlDirty) {
|
||||
persistChannels(channels.map((x) => (x.id === active.id ? { ...x, urlLines: proUrlDraft } : x)));
|
||||
const latestProUrlDraft = proUrlDraftRef.current;
|
||||
if (latestProUrlDraft !== (active.urlLines ?? "").trim()) {
|
||||
persistChannels(channels.map((x) => (x.id === active.id ? { ...x, urlLines: latestProUrlDraft } : x)));
|
||||
}
|
||||
}
|
||||
if (m === "pro" && channels.length) {
|
||||
const first = channels[0];
|
||||
const latestSingleUrlDraft = singleUrlDraftRef.current.trim();
|
||||
const youtubePm2Ok = staticYoutubeEntries.some((e) => e.pm2 === currentProc);
|
||||
const pm2Name =
|
||||
youtubePm2Ok && currentProc.trim()
|
||||
@@ -678,14 +715,14 @@ export function LiveYoutubeUnmannedView({
|
||||
{
|
||||
...first,
|
||||
streamKey: ytFields.key.trim() || first.streamKey || "",
|
||||
urlLines: urlConfig.trim() || first.urlLines || "",
|
||||
urlLines: latestSingleUrlDraft || first.urlLines || "",
|
||||
pm2Name,
|
||||
},
|
||||
...channels.slice(1),
|
||||
];
|
||||
persistChannels(next);
|
||||
const ch = next.find((c) => c.id === activeCh) ?? next[0];
|
||||
setProUrlDraft((ch?.urlLines ?? "").trim() || urlConfig.trim());
|
||||
applyProUrlDraft((ch?.urlLines ?? "").trim() || latestSingleUrlDraft);
|
||||
}
|
||||
setMode(m);
|
||||
try {
|
||||
@@ -697,11 +734,9 @@ export function LiveYoutubeUnmannedView({
|
||||
[
|
||||
mode,
|
||||
active,
|
||||
activeUrlDirty,
|
||||
channels,
|
||||
proUrlDraft,
|
||||
applyProUrlDraft,
|
||||
ytFields.key,
|
||||
urlConfig,
|
||||
currentProc,
|
||||
staticYoutubeEntries,
|
||||
activeCh,
|
||||
@@ -711,15 +746,16 @@ export function LiveYoutubeUnmannedView({
|
||||
|
||||
const selectProChannel = useCallback(
|
||||
(c: YoutubeProChannel) => {
|
||||
if (active && mode === "pro" && activeUrlDirty) {
|
||||
persistChannels(channels.map((x) => (x.id === active.id ? { ...x, urlLines: proUrlDraft } : x)));
|
||||
const latestProUrlDraft = proUrlDraftRef.current;
|
||||
if (active && mode === "pro" && latestProUrlDraft !== (active.urlLines ?? "").trim()) {
|
||||
persistChannels(channels.map((x) => (x.id === active.id ? { ...x, urlLines: latestProUrlDraft } : x)));
|
||||
}
|
||||
const u = (c.urlLines ?? "").trim();
|
||||
setProUrlDraft(u);
|
||||
applyProUrlDraft(u);
|
||||
setActiveCh(c.id);
|
||||
setCurrentProc(resolveChannelPm2(c));
|
||||
},
|
||||
[active, activeUrlDirty, mode, channels, proUrlDraft, setCurrentProc, persistChannels],
|
||||
[active, applyProUrlDraft, mode, channels, setCurrentProc, persistChannels],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -730,62 +766,36 @@ export function LiveYoutubeUnmannedView({
|
||||
setCurrentProc(want);
|
||||
}, [mode, activeCh, channels, setCurrentProc]);
|
||||
|
||||
useEffect(() => {
|
||||
if (mode !== "pro" || !currentProc || !activeCh) return;
|
||||
let cancelled = false;
|
||||
const targetChannelId = activeCh;
|
||||
void (async () => {
|
||||
try {
|
||||
const data = await fetchJson<{ content?: string }>(
|
||||
`/get_url_config?process=${encodeURIComponent(currentProc)}`,
|
||||
);
|
||||
if (cancelled || urlListDirtyRef.current) return;
|
||||
const c = data.content ?? "";
|
||||
const localDraft = channels.find((row) => row.id === targetChannelId)?.urlLines ?? "";
|
||||
if (shouldPreserveLocalUrlDraft(localDraft, c)) {
|
||||
setProUrlDraft(localDraft);
|
||||
return;
|
||||
}
|
||||
setProUrlDraft(c);
|
||||
urlListDirtyRef.current = false;
|
||||
setChannels((prev) => {
|
||||
const next = prev.map((x) => (x.id === targetChannelId ? { ...x, urlLines: c } : x));
|
||||
saveYoutubeProChannels(next);
|
||||
return next;
|
||||
});
|
||||
} catch {
|
||||
if (!cancelled && !urlListDirtyRef.current) {
|
||||
setProUrlDraft(activeSavedUrlDraft);
|
||||
}
|
||||
}
|
||||
})();
|
||||
return () => {
|
||||
cancelled = true;
|
||||
};
|
||||
}, [activeCh, activeSavedUrlDraft, channels, currentProc, fetchJson, mode]);
|
||||
|
||||
const loadYoutubeIni = useCallback(async () => {
|
||||
if (!currentProc) return;
|
||||
const targetProc = currentProcRef.current.trim();
|
||||
if (!targetProc) return;
|
||||
const requestId = ++ytIniRequestSeqRef.current;
|
||||
setYtIniLoading(true);
|
||||
setYtIniStatus(null);
|
||||
try {
|
||||
const data = await fetchJson<{ content?: string }>(
|
||||
`/get_config?process=${encodeURIComponent(currentProc)}`,
|
||||
`/get_config?process=${encodeURIComponent(targetProc)}`,
|
||||
);
|
||||
if (requestId !== ytIniRequestSeqRef.current || targetProc !== currentProcRef.current.trim()) return;
|
||||
const text = data.content ?? "";
|
||||
setYoutubeIniText(text);
|
||||
setYtFields(parseYoutubeIniFields(text));
|
||||
} catch (e) {
|
||||
if (requestId !== ytIniRequestSeqRef.current || targetProc !== currentProcRef.current.trim()) return;
|
||||
setYtIniStatus((e as Error).message);
|
||||
} finally {
|
||||
setYtIniLoading(false);
|
||||
if (requestId === ytIniRequestSeqRef.current && targetProc === currentProcRef.current.trim()) {
|
||||
setYtIniLoading(false);
|
||||
}
|
||||
}
|
||||
}, [currentProc, fetchJson]);
|
||||
}, [fetchJson]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!currentProc) return;
|
||||
void pullUrlConfigFromServer();
|
||||
void loadYoutubeIni();
|
||||
}, [currentProc, loadYoutubeIni]);
|
||||
setActiveViewRefreshToken((prev) => prev + 1);
|
||||
}, [activeCh, currentProc, loadYoutubeIni, mode, pullUrlConfigFromServer]);
|
||||
|
||||
const persistActiveLaneDraft = useCallback(
|
||||
async () => {
|
||||
@@ -795,6 +805,7 @@ export function LiveYoutubeUnmannedView({
|
||||
if (!activeLaneValidation.ok) {
|
||||
throw new Error(activeLaneValidation.message);
|
||||
}
|
||||
const latestProUrlDraft = proUrlDraftRef.current;
|
||||
const next = channels.map((row) =>
|
||||
row.id === active.id
|
||||
? {
|
||||
@@ -803,7 +814,7 @@ export function LiveYoutubeUnmannedView({
|
||||
streamKey: activeLaneValidation.normalizedStreamKey,
|
||||
pm2Name: activeLaneValidation.normalizedPm2,
|
||||
notes: activeLaneValidation.normalizedNotes,
|
||||
urlLines: proUrlDraft,
|
||||
urlLines: latestProUrlDraft,
|
||||
}
|
||||
: row,
|
||||
);
|
||||
@@ -828,7 +839,7 @@ export function LiveYoutubeUnmannedView({
|
||||
streamKey: activeLaneValidation.normalizedStreamKey,
|
||||
pm2Name: activeLaneValidation.normalizedPm2,
|
||||
notes: activeLaneValidation.normalizedNotes,
|
||||
urlLines: proUrlDraft,
|
||||
urlLines: latestProUrlDraft,
|
||||
} satisfies YoutubeProChannel),
|
||||
pm2: activeLaneValidation.normalizedPm2,
|
||||
};
|
||||
@@ -839,7 +850,6 @@ export function LiveYoutubeUnmannedView({
|
||||
activeLaneValidation,
|
||||
applyChannelsLocal,
|
||||
channels,
|
||||
proUrlDraft,
|
||||
syncChannelsRemote,
|
||||
t,
|
||||
],
|
||||
@@ -933,9 +943,10 @@ export function LiveYoutubeUnmannedView({
|
||||
try {
|
||||
setLaneSaving(true);
|
||||
setLaneStatus(null);
|
||||
const latestProUrlDraft = proUrlDraftRef.current;
|
||||
const saved = await persistActiveLaneDraft();
|
||||
setCurrentProc(saved.pm2);
|
||||
await Promise.resolve(onSaveUrlConfig(proUrlDraft, saved.pm2));
|
||||
await Promise.resolve(onSaveUrlConfig(latestProUrlDraft, saved.pm2));
|
||||
urlListDirtyRef.current = false;
|
||||
setLaneStatus(t("线路与地址已保存", "Lane settings and URLs saved"));
|
||||
} catch (e) {
|
||||
@@ -948,8 +959,13 @@ export function LiveYoutubeUnmannedView({
|
||||
};
|
||||
|
||||
const saveSingleUrlAndClearDirty = async () => {
|
||||
const targetPm2 = currentProc.trim();
|
||||
if (!targetPm2) {
|
||||
notify(t("请选择进程", "Pick a process"));
|
||||
return;
|
||||
}
|
||||
try {
|
||||
await Promise.resolve(onSaveUrlConfig());
|
||||
await Promise.resolve(onSaveUrlConfig(singleUrlDraftRef.current, targetPm2));
|
||||
urlListDirtyRef.current = false;
|
||||
} catch {
|
||||
/* parent 已 toast */
|
||||
@@ -957,7 +973,7 @@ export function LiveYoutubeUnmannedView({
|
||||
};
|
||||
|
||||
const dedupeUrlDraft = async () => {
|
||||
const source = mode === "single" ? urlConfig : proUrlDraft;
|
||||
const source = mode === "single" ? singleUrlDraftRef.current : proUrlDraftRef.current;
|
||||
setUrlOptimizing(true);
|
||||
setUrlFetchNote(null);
|
||||
try {
|
||||
@@ -972,9 +988,9 @@ export function LiveYoutubeUnmannedView({
|
||||
}),
|
||||
});
|
||||
const next = data.content ?? source;
|
||||
if (mode === "single") setUrlConfig(next);
|
||||
if (mode === "single") applySingleUrlDraft(next);
|
||||
else {
|
||||
setProUrlDraft(next);
|
||||
applyProUrlDraft(next);
|
||||
}
|
||||
urlListDirtyRef.current = true;
|
||||
setUrlFetchNote(data.message || t("已去重当前 URL 列表", "Deduplicated current URL list"));
|
||||
@@ -1574,7 +1590,7 @@ export function LiveYoutubeUnmannedView({
|
||||
persistChannels([...channels, nc]);
|
||||
setActiveCh(id);
|
||||
setCurrentProc(resolveChannelPm2(nc));
|
||||
setProUrlDraft("");
|
||||
applyProUrlDraft("");
|
||||
}}
|
||||
>
|
||||
<Plus className="h-3.5 w-3.5" />
|
||||
@@ -1692,7 +1708,7 @@ export function LiveYoutubeUnmannedView({
|
||||
value={proUrlDraft}
|
||||
onChange={(e) => {
|
||||
urlListDirtyRef.current = true;
|
||||
setProUrlDraft(e.target.value);
|
||||
applyProUrlDraft(e.target.value);
|
||||
}}
|
||||
spellCheck={false}
|
||||
placeholder="https://live.douyin.com/..."
|
||||
@@ -1732,7 +1748,7 @@ export function LiveYoutubeUnmannedView({
|
||||
value={urlConfig}
|
||||
onChange={(e) => {
|
||||
urlListDirtyRef.current = true;
|
||||
setUrlConfig(e.target.value);
|
||||
applySingleUrlDraft(e.target.value);
|
||||
}}
|
||||
spellCheck={false}
|
||||
/>
|
||||
@@ -1755,6 +1771,7 @@ export function LiveYoutubeUnmannedView({
|
||||
keySuffixUi={keySuffixUi}
|
||||
showKeyRow
|
||||
pollMs={800}
|
||||
refreshToken={activeViewRefreshToken}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user