"use client"; import { useEffect, useState, useMemo, useCallback } from "react"; import { Table, Empty, Tag, Typography, Select, Flex, theme } from "antd"; import type { ParsedProject } from "@/services/project"; import { computeVersionRanges, collectAutoRange, type McVersionEntry } from "@/lib/utils/mcVersion"; import { getMcVersionUnion } from "@/services/meta"; interface ArtifactRow { key: string; mc_version: string; artifact: string | null; sources: string | null; } interface VersionTableProps { project: ParsedProject | null; loading: boolean; onLoadingChange: (loading: boolean) => void; } export function VersionTable({ project, loading, onLoadingChange, }: VersionTableProps) { const { token } = theme.useToken(); // ── MC version union from both platforms ── const [allMcVersions, setAllMcVersions] = useState([]); // Last version end selection (the editable dropdown value) const [lastVersionEnd, setLastVersionEnd] = useState(null); // Fetch MC version union when project changes useEffect(() => { if (!project || project.artifacts.length === 0) { setAllMcVersions([]); return; } let cancelled = false; onLoadingChange(true); getMcVersionUnion() .then((versions) => { if (cancelled) return; setAllMcVersions(versions); // Default last version end to the latest available version setLastVersionEnd(versions[versions.length - 1] ?? null); }) .finally(() => { if (!cancelled) { onLoadingChange(false); } }); return () => { cancelled = true; }; }, [project, onLoadingChange]); // ── Compute version ranges ── const versionRanges: McVersionEntry[] | null = useMemo(() => { if (!project || allMcVersions.length === 0) return null; const projectVersions = project.artifacts.map((a) => a.mc_version); // computeVersionRanges expects ascending order; project versions are descending const ascending = [...projectVersions].reverse(); const entries = computeVersionRanges( ascending, allMcVersions, lastVersionEnd ?? undefined, ); // Reverse back to match descending display order return [...entries].reverse(); }, [project, allMcVersions, lastVersionEnd]); // ── Dropdown options: 同系列中 >= 最后一个项目版本的版本 ── const lastVersionDropdownOptions = useMemo(() => { if (!project || allMcVersions.length === 0) return []; // Descending display, index 0 is the largest (= last in compatibility) const lastVer = project.artifacts[0]?.mc_version; if (!lastVer) return []; const series = collectAutoRange(lastVer, allMcVersions); return series .filter( (v) => v.localeCompare(lastVer, undefined, { numeric: true }) >= 0, ) .map((v) => ({ value: v, label: v })); }, [project, allMcVersions]); // Reset lastVersionEnd to latest in the same series useEffect(() => { if (allMcVersions.length === 0 || !project) return; const lastVer = project.artifacts[0]?.mc_version; if (!lastVer) return; const series = collectAutoRange(lastVer, allMcVersions); setLastVersionEnd(series[series.length - 1] ?? null); }, [allMcVersions, project]); // ── Table data ── const dataSource: (ArtifactRow & { rangeEntry?: McVersionEntry })[] = useMemo(() => { if (!project) return []; return project.artifacts.map((a, i) => ({ key: a.mc_version, mc_version: a.mc_version, artifact: a.artifact, sources: a.sources, rangeEntry: versionRanges?.[i], })); }, [project, versionRanges]); // ── Dropdown change handler ── const handleLastVersionEndChange = useCallback((value: string) => { setLastVersionEnd(value); }, []); // ── Columns ── const columns = useMemo( () => [ { title: "MC 版本", dataIndex: "mc_version", key: "mc_version", width: 120, render: (val: string) => {val}, }, { title: "兼容范围", key: "range", width: 280, render: ( _: unknown, record: ArtifactRow & { rangeEntry?: McVersionEntry }, index: number, ) => { const entry = record.rangeEntry; if (!entry) { return ( 加载元数据中… ); } // Descending order: index 0 is the largest (= last in compatibility terms) const isLast = index === 0; if (!isLast) { // Non-last: show wildcard if it differs, otherwise explicit return {entry.wildcard}; } // Last version: range + editable end-version dropdown return ( {entry.wildcard} 截止