From 5f6cab6529a8c6b9234520aa264fb82f55e26132 Mon Sep 17 00:00:00 2001 From: CPTProgrammer <46586216+CPTProgrammer@users.noreply.github.com> Date: Thu, 16 Jul 2026 14:17:49 +0800 Subject: [PATCH] Add interactive version range display in VersionTable --- src/components/VersionTable.tsx | 249 ++++++++++++++++++++++++-------- src/lib/utils/mcVersion.test.ts | 8 +- src/lib/utils/mcVersion.ts | 18 ++- src/services/meta.ts | 15 ++ 4 files changed, 219 insertions(+), 71 deletions(-) diff --git a/src/components/VersionTable.tsx b/src/components/VersionTable.tsx index d5f32dc..24a2062 100644 --- a/src/components/VersionTable.tsx +++ b/src/components/VersionTable.tsx @@ -1,8 +1,10 @@ "use client"; -import { Table, Empty, Tag, Typography } from "antd"; -import { useMemo } from "react"; +import { useEffect, useState, useMemo, useCallback } from "react"; +import { Table, Empty, Tag, Typography, Select, Flex } 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; @@ -22,67 +24,192 @@ export function VersionTable({ loading, onLoadingChange, }: VersionTableProps) { - const dataSource: ArtifactRow[] = useMemo(() => { - if (!project) return []; - return project.artifacts.map((a) => ({ - key: a.mc_version, - mc_version: a.mc_version, - artifact: a.artifact, - sources: a.sources, - })); - }, [project]); + // ── MC version union from both platforms ── + const [allMcVersions, setAllMcVersions] = useState([]); - const columns = [ - { - title: "MC 版本", - dataIndex: "mc_version", - key: "mc_version", - width: 120, - render: (val: string) => {val}, - }, - { - title: "兼容范围", - dataIndex: "mc_version", - key: "range", - width: 200, - render: (_: string, __: ArtifactRow, index: number) => { - const isLast = - project && index === project.artifacts.length - 1; - // 兼容范围在发布前由 PublishModal 加载元数据后完整计算 - // 此处仅作占位显示 - return isLast ? ( - 加载元数据后计算 - ) : ( - - ); + // 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: "构建产物", - dataIndex: "artifact", - key: "artifact", - ellipsis: true, - render: (val: string | null) => - val ? ( - {val} - ) : ( - 文件不存在 - ), - }, - { - title: "源码", - dataIndex: "sources", - key: "sources", - ellipsis: true, - render: (val: string | null) => - val ? ( - {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} + + 截止 + +