Files
ttstd-infinite-canvas/web/src/components/layout/app-changelog-modal.tsx
ddcat666 d2fbc413b3 feat: 完成短剧项目空间与结构化资产创作闭环 (#38)
* feat(canvas): 短剧项目空间 - 建立项目章节与画布关联 MVP

* feat(canvas): 短剧项目空间 - 完善项目资产、镜头与流程闭环

Refs #30

* fix(deploy): 本地部署 - 修复前端构建并忽略本地 PostgreSQL 配置

* feat(*): 短剧项目空间 - 完成项目、章节、资产、角色与画布创作闭环

Refs #30

---------

Co-authored-by: ddCat <ddcat666@88.com>
2026-07-27 10:45:11 +08:00

71 lines
3.6 KiB
TypeScript

import { Modal, Tag } from "antd";
import { motion, useReducedMotion } from "motion/react";
import { ScrollText } from "lucide-react";
import { useState, type CSSProperties } from "react";
import ReactMarkdown from "react-markdown";
import { aceternityMotion } from "@/lib/aceternity-motion";
export const APP_VERSION = __APP_VERSION__;
type AppChangelogButtonProps = {
className?: string;
style?: CSSProperties;
showVersion?: boolean;
showLabel?: boolean;
labelClassName?: string;
versionClassName?: string;
};
export function AppChangelogButton({ className, style, showVersion = false, showLabel = false, labelClassName, versionClassName }: AppChangelogButtonProps) {
const [open, setOpen] = useState(false);
return (
<>
<button type="button" className={className} style={style} onClick={() => setOpen(true)} aria-label="查看更新日志" title="更新日志">
<ScrollText className="size-4 shrink-0" />
{showLabel ? <span className={`whitespace-nowrap ${labelClassName || ""}`}>更新日志</span> : null}
{showVersion ? <span className={versionClassName}>v{APP_VERSION.replace(/^v/, "")}</span> : null}
</button>
<AppChangelogModal open={open} onClose={() => setOpen(false)} />
</>
);
}
function AppChangelogModal({ open, onClose }: { open: boolean; onClose: () => void }) {
const reducedMotion = useReducedMotion();
return (
<Modal
rootClassName="app-spatial-modal"
title={<div className="flex min-w-0 items-center gap-3 pr-8"><span className="grid size-9 shrink-0 place-items-center rounded-full border border-border bg-muted/45"><ScrollText className="size-4" /></span><div className="min-w-0"><div className="flex items-center gap-2 text-base font-semibold">更新日志<Tag variant="filled">v{APP_VERSION.replace(/^v/, "")}</Tag></div><div className="mt-0.5 text-xs font-normal text-foreground/45">产品能力、交互与稳定性变化</div></div></div>}
open={open}
width={760}
footer={null}
centered
onCancel={onClose}
styles={{ body: { maxHeight: "min(72vh, 760px)", overflowY: "auto", overscrollBehavior: "contain" } }}
modalRender={(node) => (
<motion.div initial={reducedMotion ? false : { opacity: 0, y: 14, scale: 0.975 }} animate={{ opacity: 1, y: 0, scale: 1 }} transition={{ duration: aceternityMotion.duration.panel, ease: aceternityMotion.easing.enter }}>
{node}
</motion.div>
)}
>
<div className="thin-scrollbar pr-2 text-sm leading-6 text-foreground/75">
<ReactMarkdown
components={{
h1: ({ children }) => <h2 className="mb-4 text-xl font-semibold text-foreground">{children}</h2>,
h2: ({ children }) => <h3 className="mb-2 mt-6 border-b border-border pb-2 text-base font-semibold text-foreground first:mt-0">{children}</h3>,
ul: ({ children }) => <ul className="space-y-2 pl-5">{children}</ul>,
li: ({ children }) => <li className="list-disc pl-1 marker:text-foreground/35">{children}</li>,
p: ({ children }) => <p className="my-2">{children}</p>,
code: ({ children }) => <code className="rounded bg-muted px-1 py-0.5 text-xs text-foreground">{children}</code>,
}}
>
{__APP_CHANGELOG__}
</ReactMarkdown>
</div>
</Modal>
);
}