"use client"; import { useState, useCallback, useMemo } from "react"; import { Modal, Steps, Button, Space, Checkbox, Table, Select, Row, Col, Flex, Progress, Typography, Empty, theme, } from "antd"; import { ReloadOutlined } from "@ant-design/icons"; import type { BaseVersion, Config, UploadReleaseType } from "@/types"; import type { ParsedProject } from "@/services/project"; import { platforms } from "@/services/publish.schemas"; import { Template } from "@/lib/utils/template"; import type { McVersionEntry } from "@/lib/utils/mcVersion"; const { Text } = Typography; // --------------------------------------------------------------------------- // Types // --------------------------------------------------------------------------- interface PublishVersion { key: string; mc_version: string; artifact: string | null; sources: string | null; modrinthVersion: string; modrinthVersionName: string; existsModrinth: boolean; existsCurseforge: boolean; } interface PublishModalProps { open: boolean; config: Config; project: ParsedProject; versionRanges: McVersionEntry[] | null; onClose: () => void; } type ReleaseType = Extract; // --------------------------------------------------------------------------- // Helpers // --------------------------------------------------------------------------- const RELEASE_TYPE_LABEL_MAP: Record = { release: "Release", beta: "Beta", alpha: "Alpha" }; const RELEASE_TYPES = Object.entries(RELEASE_TYPE_LABEL_MAP).map(v => ({ value: v[0], label: v[1] })); // --------------------------------------------------------------------------- // Component // --------------------------------------------------------------------------- export function PublishModal({ open, config, project, versionRanges, onClose, }: PublishModalProps) { const { token } = theme.useToken(); const [currentStep, setCurrentStep] = useState(0); // Version selection const [selectedVersions, setSelectedVersions] = useState>( new Set(), ); // Publish settings const [changelog, setChangelog] = useState(""); const [versionType, setVersionType] = useState( "release", ); // Progress & result const [publishing, setPublishing] = useState(false); const [publishDone, setPublishDone] = useState(false); const [progress, setProgress] = useState< Record >({ modrinth: { done: 0, total: 0 }, curseforge: { done: 0, total: 0 }, }); const [publishResult, setPublishResult] = useState<{ success: number; failed: number; } | null>(null); // Platform existing versions — loaded when modal opens const [loadingExisting, setLoadingExisting] = useState(false); const [existingModrinth, setExistingModrinth] = useState([]); const [existingCurseforge, setExistingCurseforge] = useState([]); const loadExisting = useCallback(async () => { setLoadingExisting(true); try { // TODO: Fetch existing versions from both platforms via tRPC/Server Action // For now, placeholder setExistingModrinth([]); setExistingCurseforge([]); } catch { // ignore } finally { setLoadingExisting(false); } }, []); // Pre-compute version display values const versionTmpl = useMemo(() => new Template(config.modrinth.version), [config.modrinth.version]); const versionNameTmpl = useMemo(() => new Template(config.modrinth.version_name), [config.modrinth.version_name]); const rangeMap = useMemo(() => { const map = new Map(); if (versionRanges) { for (const entry of versionRanges) { map.set(entry.version, entry.wildcard); } } return map; }, [versionRanges]); // Build publish version rows from project artifacts const publishVersions: PublishVersion[] = useMemo(() => project.artifacts.map((a) => { const tmplValues = { version: project.version, mc_version: a.mc_version, mc_version_range: rangeMap.get(a.mc_version) ?? a.mc_version, }; return { key: a.mc_version, mc_version: a.mc_version, artifact: a.artifact, sources: a.sources, modrinthVersion: versionTmpl.format(tmplValues), modrinthVersionName: versionNameTmpl.format(tmplValues), existsModrinth: existingModrinth.includes(a.mc_version), existsCurseforge: existingCurseforge.includes(a.mc_version), }; }), [project, versionTmpl, versionNameTmpl, rangeMap, existingModrinth, existingCurseforge], ); // --------------- Steps handling --------------- const handleNext = useCallback(() => { if (currentStep === 0) { // Init selection with all non-existing entries const preselected = new Set(); for (const v of publishVersions) { if (!v.existsModrinth || !v.existsCurseforge) { preselected.add(v.key); } } setSelectedVersions(preselected); } setCurrentStep((prev) => Math.min(prev + 1, 2)); }, [currentStep, publishVersions]); const handlePrev = useCallback(() => { setCurrentStep((prev) => Math.max(prev - 1, 0)); }, []); const handleConfirmPublish = useCallback(async () => { setPublishing(true); setPublishDone(false); setPublishResult(null); setCurrentStep(2); // TODO: Call tRPC mutation to publish, update progress & result setPublishResult({ success: 0, failed: 0 }); setPublishDone(true); setPublishing(false); }, []); const handleClose = useCallback(() => { if (publishing) return; setCurrentStep(0); setSelectedVersions(new Set()); setChangelog(""); setVersionType("release"); setPublishResult(null); setPublishDone(false); onClose(); }, [publishing, onClose]); // --------------- Render: Step 1 — Version Selection --------------- const renderStep1 = () => { const modrinthColumns = [ { title: "版本号", dataIndex: "modrinthVersion", key: "modrinthVersion", }, { title: "版本名称", dataIndex: "modrinthVersionName", key: "modrinthVersionName", }, { title: "", key: "select", width: 50, render: (_: unknown, record: PublishVersion) => ( { const next = new Set(selectedVersions); if (e.target.checked) next.add(record.key); else next.delete(record.key); setSelectedVersions(next); }} /> ), }, ]; const curseforgeColumns = [ { title: "MC 版本", dataIndex: "mc_version", key: "mc_version", width: 100, }, { title: "显示名称", dataIndex: "artifact", key: "artifact", ellipsis: true, }, { title: "", key: "select", width: 50, render: (_: unknown, record: PublishVersion) => ( { const next = new Set(selectedVersions); if (e.target.checked) next.add(record.key); else next.delete(record.key); setSelectedVersions(next); }} /> ), }, ]; return (
选择要发布的版本
{loadingExisting ? (
加载中…
) : ( Modrinth {publishVersions.length > 0 ? ( ) : ( )} CurseForge {publishVersions.length > 0 ? (
) : ( )} )} ); }; // --------------- Render: Step 2 — Publish Settings --------------- const renderStep2 = () => { return (
{/* Changelog */}
Changelog (Markdown) {/* TODO: Replace with @uiw/react-md-editor */}