"use client"; import { useDeferredValue, useState } from "react"; import { useFormStatus } from "react-dom"; import { deleteProxyGroupAction, saveMainProxyGroupAction, saveProxyGroupAction, } from "./actions"; type ProxyGroupNodeOption = { slug: string; label: string; meta: string; }; type ProxyGroupEditorProps = { mode?: "custom" | "main"; group?: { id: string; name: string; memberSlugs: string[]; }; nodes: ProxyGroupNodeOption[]; }; function normalizeSelectedSlugs( initialSlugs: string[], nodes: ProxyGroupNodeOption[], includeMissingNodes = false, ) { const availableSlugs = new Set(nodes.map((node) => node.slug)); const seen = new Set(); const selectedSlugs = initialSlugs.filter((slug) => { if (!availableSlugs.has(slug) || seen.has(slug)) { return false; } seen.add(slug); return true; }); if (!includeMissingNodes) { return selectedSlugs; } for (const node of nodes) { if (!seen.has(node.slug)) { selectedSlugs.push(node.slug); seen.add(node.slug); } } return selectedSlugs; } function SaveButton({ isEditing, isMainGroup }: { isEditing: boolean; isMainGroup: boolean }) { const { pending } = useFormStatus(); const label = isMainGroup ? "保存主组顺序" : isEditing ? "保存分组" : "新建分组"; const pendingLabel = isMainGroup ? "保存中..." : isEditing ? "保存中..." : "创建中..."; return ( ); } function DeleteButton() { const { pending } = useFormStatus(); return ( ); } export function ProxyGroupEditor({ mode = "custom", group, nodes }: ProxyGroupEditorProps) { const isMainGroup = mode === "main"; const isEditing = Boolean(group) && !isMainGroup; const [query, setQuery] = useState(""); const deferredQuery = useDeferredValue(query); const [selectedSlugs, setSelectedSlugs] = useState(() => normalizeSelectedSlugs(group?.memberSlugs ?? [], nodes, isMainGroup), ); const selectedSlugSet = new Set(selectedSlugs); const nodesBySlug = new Map(nodes.map((node) => [node.slug, node])); const selectedNodes = selectedSlugs .map((slug) => nodesBySlug.get(slug)) .filter((node): node is ProxyGroupNodeOption => Boolean(node)); const normalizedQuery = deferredQuery.trim().toLowerCase(); const remainingCount = nodes.length - selectedNodes.length; const availableNodes = nodes.filter((node) => { if (selectedSlugSet.has(node.slug)) { return false; } if (!normalizedQuery) { return true; } return [node.label, node.meta, node.slug].some((value) => value.toLowerCase().includes(normalizedQuery), ); }); function addNode(slug: string) { setSelectedSlugs((current) => { if (current.includes(slug)) { return current; } return [...current, slug]; }); } function removeNode(slug: string) { setSelectedSlugs((current) => current.filter((currentSlug) => currentSlug !== slug)); } function moveNode(slug: string, direction: -1 | 1) { setSelectedSlugs((current) => { const index = current.indexOf(slug); const nextIndex = index + direction; if (index === -1 || nextIndex < 0 || nextIndex >= current.length) { return current; } const next = [...current]; [next[index], next[nextIndex]] = [next[nextIndex], next[index]]; return next; }); } function addAllVisibleNodes() { setSelectedSlugs((current) => { const next = [...current]; const seen = new Set(current); for (const node of availableNodes) { if (seen.has(node.slug)) { continue; } next.push(node.slug); seen.add(node.slug); } return next; }); } function clearSelection() { setSelectedSlugs([]); } return (
{group && !isMainGroup ? : null}

{isMainGroup ? `默认主分组:${group?.name ?? "FeieProxy"}` : isEditing ? group?.name : "新建分组"}

已选 {selectedNodes.length} {!isMainGroup ? ( 未加入 {remainingCount} ) : null}
{!isMainGroup ? ( ) : null}
{!isMainGroup ? (
候选节点
{availableNodes.length > 0 ? (
{availableNodes.map((node) => ( ))}
) : (

{remainingCount === 0 ? "所有可用节点都已加入当前分组。" : "没有匹配当前筛选条件的节点。"}

)}
) : null}
{isMainGroup ? "节点顺序" : "已选顺序"}
{!isMainGroup ? ( ) : null}
{selectedNodes.length > 0 ? (
    {selectedNodes.map((node, index) => (
  1. {String(index + 1).padStart(2, "0")}
    {node.label} {node.meta}
    {!isMainGroup ? ( ) : null}
  2. ))}
) : (

{isMainGroup ? "暂无可用节点。" : "还没有选择节点。先从左侧添加,再在这里调整最终顺序。"}

)}
{group && !isMainGroup ? (
) : null}
); }