feat: complete TASK-WP2-07 latest exports

This commit is contained in:
suyx
2026-08-03 01:43:09 +08:00
parent 2bb4f86d04
commit 1b0a05bbcf
20 changed files with 1791 additions and 18 deletions
+37 -3
View File
@@ -114,6 +114,17 @@ interface ProjectDetailPayload extends ProjectSummary {
updated_at: string;
}>;
images: Array<{ created_at: string; generation_id: string; image_id: string }>;
latest_exports: Array<{
byte_size: number;
created_at: string;
download_url: string;
export_id: string;
format: "jpg" | "png";
pixel_height: number;
pixel_width: number;
sha256: string;
state_version: number;
}>;
pixel_height?: number;
pixel_width?: number;
save_status: "saved";
@@ -767,6 +778,7 @@ export function ProjectDetailPage({ projectId }: { projectId: string }) {
const normalizedProject = {
...nextProject,
canvas_state: nextProject.canvas_state ?? initialCanvasState(nextProject),
latest_exports: nextProject.latest_exports ?? [],
save_status: nextProject.save_status ?? "saved",
};
setProject(normalizedProject);
@@ -944,8 +956,8 @@ export function ProjectDetailPage({ projectId }: { projectId: string }) {
<ProjectPlaceholder ratio={project.ratio} status={project.status} />
<div className="project-actions">
<button disabled={conflicted || atHistoryLimit} onClick={() => window.location.assign(`/app?continue=${project.project_id}`)} type="button"></button>
<button disabled={conflicted || !project.current_image_id} type="button"></button>
<button disabled={conflicted || !project.current_image_id} type="button"></button>
{conflicted || !project.current_image_id ? <button disabled type="button"></button> : <a href={`/app/projects/${project.project_id}/editor`}></a>}
{conflicted || !project.current_image_id ? <button disabled type="button"></button> : <a href={`/api/v1/private-assets/projects/${project.project_id}/images/${project.current_image_id}`}></a>}
</div>
{atHistoryLimit ? <p className="project-blocker"></p> : null}
{project.status === "failed_empty" && !conflicted ? <a className="project-retry" href={`/app?retry=${project.project_id}`}></a> : null}
@@ -959,13 +971,35 @@ export function ProjectDetailPage({ projectId }: { projectId: string }) {
{project.images.toReversed().map((image, index) => (
<li key={image.image_id} data-current={image.image_id === project.current_image_id}>
<ProjectPlaceholder ratio={project.ratio} status="active" />
<div><strong> {project.images.length - index}</strong><time dateTime={image.created_at}>{formatUpdatedAt(image.created_at)}</time></div>
<div><strong> {project.images.length - index}</strong><time dateTime={image.created_at}>{formatUpdatedAt(image.created_at)}</time><a href={`/api/v1/private-assets/projects/${project.project_id}/images/${image.image_id}`}></a></div>
</li>
))}
</ol>
)}
</section>
</div>
<section aria-labelledby="latest-exports-title" className="project-latest-exports">
<header><div><p>LOCAL LATEST</p><h2 id="latest-exports-title"></h2></div><span></span></header>
{project.latest_exports.length === 0 ? (
<div className="latest-exports-empty">
<strong></strong>
<div>
<a href={`/app/projects/${project.project_id}/editor`}></a>
{project.current_image_id ? <a href={`/api/v1/private-assets/projects/${project.project_id}/images/${project.current_image_id}`}></a> : null}
</div>
</div>
) : (
<ul>
{project.latest_exports.map((item) => (
<li key={item.export_id}>
<div><strong>{item.format.toUpperCase()}</strong><span>{item.pixel_width} × {item.pixel_height}</span></div>
<time dateTime={item.created_at}>{formatUpdatedAt(item.created_at)}</time>
<a href={item.download_url}></a>
</li>
))}
</ul>
)}
</section>
</main>
{pendingNavigation ? (
<div className="project-leave-overlay" role="presentation">