import { ReactNode, useEffect, useMemo, useState, forwardRef } from "react"; import { useLockFn } from "ahooks"; import yaml from "js-yaml"; import { useTranslation } from "react-i18next"; import { DndContext, closestCenter, KeyboardSensor, PointerSensor, useSensor, useSensors, DragEndEvent, } from "@dnd-kit/core"; import { SortableContext, sortableKeyboardCoordinates, useSortable, } from "@dnd-kit/sortable"; import { CSS } from "@dnd-kit/utilities"; import { Virtuoso } from "react-virtuoso"; import MonacoEditor from "react-monaco-editor"; import { readProfileFile, saveProfileFile } from "@/services/cmds"; import getSystem from "@/utils/get-system"; import { useThemeMode } from "@/services/states"; import parseUri from "@/utils/uri-parser"; import { showNotice } from "@/services/noticeService"; // Компоненты import { BaseSearchBox } from "../base/base-search-box"; import { Button } from "@/components/ui/button"; import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter, DialogClose, } from "@/components/ui/dialog"; import { Textarea } from "@/components/ui/textarea"; import { Separator } from "@/components/ui/separator"; // Иконки import { GripVertical, Trash2, Undo2, ArrowDownToLine, ArrowUpToLine, } from "lucide-react"; interface Props { profileUid: string; property: string; open: boolean; onClose: () => void; onSave?: (prev?: string, curr?: string) => void; } // Новый, легковесный компонент для элемента списка, с поддержкой drag-and-drop const EditorProxyItem = ({ p_type, proxy, onDelete, id, }: { p_type: string; proxy: IProxyConfig; onDelete: () => void; id: string; }) => { const { attributes, listeners, setNodeRef, transform, transition, isDragging, } = useSortable({ id }); const style = { transform: CSS.Transform.toString(transform), transition, zIndex: isDragging ? 100 : undefined, }; const isDelete = p_type === "delete"; return (

{proxy.name}

); }; export const ProxiesEditorViewer = (props: Props) => { const { profileUid, property, open, onClose, onSave } = props; const { t } = useTranslation(); const themeMode = useThemeMode(); const [prevData, setPrevData] = useState(""); const [currData, setCurrData] = useState(""); const [visualization, setVisualization] = useState(true); const [match, setMatch] = useState(() => (_: string) => true); const [proxyUri, setProxyUri] = useState(""); const [proxyList, setProxyList] = useState([]); const [prependSeq, setPrependSeq] = useState([]); const [appendSeq, setAppendSeq] = useState([]); const [deleteSeq, setDeleteSeq] = useState([]); const filteredPrependSeq = useMemo( () => prependSeq.filter((proxy) => match(proxy.name)), [prependSeq, match], ); const filteredProxyList = useMemo( () => proxyList.filter((proxy) => match(proxy.name)), [proxyList, match], ); const filteredAppendSeq = useMemo( () => appendSeq.filter((proxy) => match(proxy.name)), [appendSeq, match], ); const sensors = useSensors( useSensor(PointerSensor), useSensor(KeyboardSensor, { coordinateGetter: sortableKeyboardCoordinates, }), ); const reorder = ( list: IProxyConfig[], startIndex: number, endIndex: number, ) => { const result = Array.from(list); const [removed] = result.splice(startIndex, 1); result.splice(endIndex, 0, removed); return result; }; const onPrependDragEnd = async (event: DragEndEvent) => { const { active, over } = event; if (over && active.id !== over.id) { let activeIndex = 0; let overIndex = 0; prependSeq.forEach((item, index) => { if (item.name === active.id) activeIndex = index; if (item.name === over.id) overIndex = index; }); setPrependSeq(reorder(prependSeq, activeIndex, overIndex)); } }; const onAppendDragEnd = async (event: DragEndEvent) => { const { active, over } = event; if (over && active.id !== over.id) { let activeIndex = 0; let overIndex = 0; appendSeq.forEach((item, index) => { if (item.name === active.id) activeIndex = index; if (item.name === over.id) overIndex = index; }); setAppendSeq(reorder(appendSeq, activeIndex, overIndex)); } }; const handleParseAsync = (cb: (proxies: IProxyConfig[]) => void) => { let proxies: IProxyConfig[] = []; let names: string[] = []; let uris = ""; try { uris = atob(proxyUri); } catch { uris = proxyUri; } const lines = uris.trim().split("\n"); let idx = 0; const batchSize = 50; function parseBatch() { const end = Math.min(idx + batchSize, lines.length); for (; idx < end; idx++) { const uri = lines[idx]; try { let proxy = parseUri(uri.trim()); if (!names.includes(proxy.name)) { proxies.push(proxy); names.push(proxy.name); } } catch (err: any) { // Ignore parse errors } } if (idx < lines.length) { setTimeout(parseBatch, 0); } else { cb(proxies); } } parseBatch(); }; const fetchProfile = async () => { let data = await readProfileFile(profileUid); let originProxiesObj = yaml.load(data) as { proxies: IProxyConfig[]; } | null; setProxyList(originProxiesObj?.proxies || []); }; const fetchContent = async () => { let data = await readProfileFile(property); let obj = yaml.load(data) as ISeqProfileConfig | null; setPrependSeq(obj?.prepend || []); setAppendSeq(obj?.append || []); setDeleteSeq(obj?.delete || []); setPrevData(data); setCurrData(data); }; useEffect(() => { if (currData === "" || visualization !== true) return; try { let obj = yaml.load(currData) as { prepend: []; append: []; delete: []; } | null; setPrependSeq(obj?.prepend || []); setAppendSeq(obj?.append || []); setDeleteSeq(obj?.delete || []); } catch (e) { console.error("Error parsing YAML in visualization mode:", e); } }, [visualization]); useEffect(() => { if (prependSeq && appendSeq && deleteSeq) { const serialize = () => { try { setCurrData( yaml.dump( { prepend: prependSeq, append: appendSeq, delete: deleteSeq }, { forceQuotes: true }, ), ); } catch (e) { console.error("Error dumping YAML:", e); } }; if (window.requestIdleCallback) { window.requestIdleCallback(serialize); } else { setTimeout(serialize, 0); } } }, [prependSeq, appendSeq, deleteSeq]); useEffect(() => { if (!open) return; fetchContent(); fetchProfile(); }, [open]); const handleSave = useLockFn(async () => { try { await saveProfileFile(property, currData); showNotice("success", t("Saved Successfully")); onSave?.(prevData, currData); onClose(); } catch (err: any) { showNotice("error", err.toString()); } }); return (
{t("Edit Proxies")}
{visualization ? (