Use theme tokens for spacing and sizing and update PublishModal

This commit is contained in:
CPTProgrammer 2026-07-16 14:34:56 +08:00
parent 5f6cab6529
commit 7bb932910a
No known key found for this signature in database
3 changed files with 136 additions and 85 deletions

View File

@ -1,7 +1,7 @@
"use client"; "use client";
import { useState, useCallback } from "react"; import { useState, useCallback } from "react";
import { Typography, Space, Card, Button } from "antd"; import { Typography, Space, Card, Button, theme } from "antd";
import { CloudUploadOutlined } from "@ant-design/icons"; import { CloudUploadOutlined } from "@ant-design/icons";
import { ConfigSelector } from "./ConfigSelector"; import { ConfigSelector } from "./ConfigSelector";
import { VersionTable } from "./VersionTable"; import { VersionTable } from "./VersionTable";
@ -16,6 +16,7 @@ interface MainPageProps {
} }
export function MainPage({ initialConfigs }: MainPageProps) { export function MainPage({ initialConfigs }: MainPageProps) {
const { token } = theme.useToken();
const [configs, setConfigs] = const [configs, setConfigs] =
useState<{ name: string; file: string }[]>(initialConfigs); useState<{ name: string; file: string }[]>(initialConfigs);
const [selectedConfigFile, setSelectedConfigFile] = useState<string | null>( const [selectedConfigFile, setSelectedConfigFile] = useState<string | null>(
@ -67,11 +68,11 @@ export function MainPage({ initialConfigs }: MainPageProps) {
style={{ style={{
maxWidth: 900, maxWidth: 900,
margin: "40px auto", margin: "40px auto",
padding: "0 24px", padding: `0 ${token.paddingLG}px`,
}} }}
> >
{/* Page header */} {/* Page header */}
<Title level={2} style={{ marginBottom: 24 }}> <Title level={2} style={{ marginBottom: token.marginLG }}>
Mod Releaser Mod Releaser
</Title> </Title>
@ -99,7 +100,7 @@ export function MainPage({ initialConfigs }: MainPageProps) {
style={{ style={{
display: "flex", display: "flex",
justifyContent: "flex-end", justifyContent: "flex-end",
marginTop: 24, marginTop: token.marginLG,
}} }}
> >
<Button <Button

View File

@ -14,6 +14,7 @@ import {
Progress, Progress,
Typography, Typography,
Empty, Empty,
theme,
} from "antd"; } from "antd";
import { ReloadOutlined } from "@ant-design/icons"; import { ReloadOutlined } from "@ant-design/icons";
import type { BaseVersion, Config, UploadReleaseType } from "@/types"; import type { BaseVersion, Config, UploadReleaseType } from "@/types";
@ -68,6 +69,7 @@ export function PublishModal({
project, project,
onClose, onClose,
}: PublishModalProps) { }: PublishModalProps) {
const { token } = theme.useToken();
const [currentStep, setCurrentStep] = useState(0); const [currentStep, setCurrentStep] = useState(0);
// Version selection // Version selection
@ -81,14 +83,19 @@ export function PublishModal({
"release", "release",
); );
// Progress // Progress & result
const [publishing, setPublishing] = useState(false); const [publishing, setPublishing] = useState(false);
const [publishDone, setPublishDone] = useState(false);
const [progress, setProgress] = useState< const [progress, setProgress] = useState<
Record<typeof platforms[number], { done: number; total: number }> Record<typeof platforms[number], { done: number; total: number }>
>({ >({
modrinth: { done: 0, total: 0 }, modrinth: { done: 0, total: 0 },
curseforge: { 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 // Platform existing versions — loaded when modal opens
const [loadingExisting, setLoadingExisting] = useState(false); const [loadingExisting, setLoadingExisting] = useState(false);
@ -133,7 +140,7 @@ export function PublishModal({
} }
setSelectedVersions(preselected); setSelectedVersions(preselected);
} }
setCurrentStep((prev) => Math.min(prev + 1, 1)); setCurrentStep((prev) => Math.min(prev + 1, 2));
}, [currentStep, publishVersions]); }, [currentStep, publishVersions]);
const handlePrev = useCallback(() => { const handlePrev = useCallback(() => {
@ -142,7 +149,12 @@ export function PublishModal({
const handleConfirmPublish = useCallback(async () => { const handleConfirmPublish = useCallback(async () => {
setPublishing(true); setPublishing(true);
// TODO: Call tRPC mutation to publish setPublishDone(false);
setPublishResult(null);
setCurrentStep(2);
// TODO: Call tRPC mutation to publish, update progress & result
setPublishResult({ success: 0, failed: 0 });
setPublishDone(true);
setPublishing(false); setPublishing(false);
}, []); }, []);
@ -152,6 +164,8 @@ export function PublishModal({
setSelectedVersions(new Set()); setSelectedVersions(new Set());
setChangelog(""); setChangelog("");
setVersionType("release"); setVersionType("release");
setPublishResult(null);
setPublishDone(false);
onClose(); onClose();
}, [publishing, onClose]); }, [publishing, onClose]);
@ -233,7 +247,7 @@ export function PublishModal({
display: "flex", display: "flex",
justifyContent: "space-between", justifyContent: "space-between",
alignItems: "center", alignItems: "center",
marginBottom: 16, marginBottom: token.margin,
}} }}
> >
<Text strong></Text> <Text strong></Text>
@ -250,9 +264,9 @@ export function PublishModal({
{loadingExisting ? ( {loadingExisting ? (
<div style={{ textAlign: "center", padding: 40 }}></div> <div style={{ textAlign: "center", padding: 40 }}></div>
) : ( ) : (
<Row gutter={16}> <Row gutter={token.margin}>
<Col span={12}> <Col span={12}>
<Text strong style={{ display: "block", marginBottom: 8 }}> <Text strong style={{ display: "block", marginBottom: token.marginXS }}>
Modrinth Modrinth
</Text> </Text>
{publishVersions.length > 0 ? ( {publishVersions.length > 0 ? (
@ -268,7 +282,7 @@ export function PublishModal({
)} )}
</Col> </Col>
<Col span={12}> <Col span={12}>
<Text strong style={{ display: "block", marginBottom: 8 }}> <Text strong style={{ display: "block", marginBottom: token.marginXS }}>
CurseForge CurseForge
</Text> </Text>
{publishVersions.length > 0 ? ( {publishVersions.length > 0 ? (
@ -292,6 +306,49 @@ export function PublishModal({
// --------------- Render: Step 2 — Publish Settings --------------- // --------------- Render: Step 2 — Publish Settings ---------------
const renderStep2 = () => { const renderStep2 = () => {
return (
<div>
{/* Changelog */}
<div style={{ marginBottom: token.marginLG }}>
<Text strong style={{ display: "block", marginBottom: token.marginSM }}>
Changelog (Markdown)
</Text>
{/* TODO: Replace with @uiw/react-md-editor */}
<textarea
style={{
width: "100%",
minHeight: 160,
border: `1px solid ${token.colorBorder}`,
borderRadius: token.borderRadius,
padding: token.paddingSM,
fontFamily: token.fontFamilyCode ?? "monospace",
fontSize: token.fontSizeSM,
}}
placeholder="输入 Markdown 格式的更新日志…"
value={changelog}
onChange={(e) => setChangelog(e.target.value)}
/>
</div>
{/* Version type */}
<div>
<Text strong style={{ display: "block", marginBottom: token.marginSM }}>
</Text>
<Select
value={versionType}
onChange={(v) => setVersionType(v)}
style={{ width: 160 }}
options={RELEASE_TYPES}
/>
</div>
</div>
);
};
// --------------- Render: Step 3 — Progress & Result ---------------
const renderStep3 = () => {
const hasModrinth = publishVersions.some( const hasModrinth = publishVersions.some(
(v) => (v) =>
!v.existsModrinth && !v.existsModrinth &&
@ -305,50 +362,15 @@ export function PublishModal({
return ( return (
<div> <div>
{/* Changelog */} {/* Progress bars */}
<div style={{ marginBottom: 24 }}> <div style={{ marginBottom: token.marginLG }}>
<Text strong style={{ display: "block", marginBottom: 8 }}>
Changelog (Markdown)
</Text>
{/* TODO: Replace with @uiw/react-md-editor */}
<textarea
style={{
width: "100%",
minHeight: 160,
border: "1px solid #d9d9d9",
borderRadius: 6,
padding: 8,
fontFamily: "monospace",
fontSize: 13,
}}
placeholder="输入 Markdown 格式的更新日志…"
value={changelog}
onChange={(e) => setChangelog(e.target.value)}
/>
</div>
{/* Version type */}
<div style={{ marginBottom: 24 }}>
<Text strong style={{ display: "block", marginBottom: 8 }}>
</Text>
<Select
value={versionType}
onChange={(v) => setVersionType(v)}
style={{ width: 160 }}
options={RELEASE_TYPES}
/>
</div>
{/* Publishing progress */}
{publishing && (
<div style={{ marginBottom: 24 }}>
{hasModrinth && ( {hasModrinth && (
<div style={{ marginBottom: 12 }}> <div style={{ marginBottom: token.marginSM }}>
<Text>Modrinth: </Text> <Text>Modrinth: </Text>
<Progress <Progress
percent={Math.round( percent={Math.round(
(progress.modrinth.done / Math.max(progress.modrinth.total, 1)) * (progress.modrinth.done /
Math.max(progress.modrinth.total, 1)) *
100, 100,
)} )}
format={() => format={() =>
@ -375,6 +397,24 @@ export function PublishModal({
</div> </div>
)} )}
</div> </div>
{/* Result */}
{publishDone && publishResult && (
<div
style={{
background: token.colorSuccessBg,
border: `1px solid ${token.colorSuccessBorder}`,
borderRadius: token.borderRadius,
padding: token.padding,
}}
>
<Text strong></Text>
<div style={{ marginTop: token.marginSM }}>
<Text>
{publishResult.success} {publishResult.failed}
</Text>
</div>
</div>
)} )}
</div> </div>
); );
@ -392,17 +432,21 @@ export function PublishModal({
footer={ footer={
publishing ? null : ( publishing ? null : (
<Space> <Space>
{currentStep > 0 && ( {currentStep > 0 && currentStep < 2 && (
<Button onClick={handlePrev}></Button> <Button onClick={handlePrev}></Button>
)} )}
{currentStep < 1 ? ( {currentStep === 0 ? (
<Button type="primary" onClick={handleNext}> <Button type="primary" onClick={handleNext}>
</Button> </Button>
) : ( ) : currentStep === 1 ? (
<Button type="primary" onClick={handleConfirmPublish}> <Button type="primary" onClick={handleConfirmPublish}>
</Button> </Button>
) : (
<Button type="primary" onClick={handleClose}>
</Button>
)} )}
</Space> </Space>
) )
@ -415,10 +459,15 @@ export function PublishModal({
items={[ items={[
{ title: "版本选择" }, { title: "版本选择" },
{ title: "发布设置" }, { title: "发布设置" },
{ title: "发布进度" },
]} ]}
/> />
{currentStep === 0 ? renderStep1() : renderStep2()} {currentStep === 0
? renderStep1()
: currentStep === 1
? renderStep2()
: renderStep3()}
</Modal> </Modal>
); );
} }

View File

@ -1,7 +1,7 @@
"use client"; "use client";
import { useEffect, useState, useMemo, useCallback } from "react"; import { useEffect, useState, useMemo, useCallback } from "react";
import { Table, Empty, Tag, Typography, Select, Flex } from "antd"; import { Table, Empty, Tag, Typography, Select, Flex, theme } from "antd";
import type { ParsedProject } from "@/services/project"; import type { ParsedProject } from "@/services/project";
import { computeVersionRanges, collectAutoRange, type McVersionEntry } from "@/lib/utils/mcVersion"; import { computeVersionRanges, collectAutoRange, type McVersionEntry } from "@/lib/utils/mcVersion";
import { getMcVersionUnion } from "@/services/meta"; import { getMcVersionUnion } from "@/services/meta";
@ -24,6 +24,7 @@ export function VersionTable({
loading, loading,
onLoadingChange, onLoadingChange,
}: VersionTableProps) { }: VersionTableProps) {
const { token } = theme.useToken();
// ── MC version union from both platforms ── // ── MC version union from both platforms ──
const [allMcVersions, setAllMcVersions] = useState<string[]>([]); const [allMcVersions, setAllMcVersions] = useState<string[]>([]);
@ -164,7 +165,7 @@ export function VersionTable({
<Tag color="blue">{entry.wildcard}</Tag> <Tag color="blue">{entry.wildcard}</Tag>
<Typography.Text <Typography.Text
type="secondary" type="secondary"
style={{ fontSize: 12, whiteSpace: "nowrap" }} style={{ fontSize: token.fontSizeSM, whiteSpace: "nowrap" }}
> >
</Typography.Text> </Typography.Text>