import type { ReleaseAssetRow, ReleaseRow, RepositoryRow, } from "../../db/index.ts"; import { formatDate } from "../../lib/formatDate.ts"; import type { SessionUser } from "../../middleware/session.ts"; import { Layout } from "../layout.tsx"; import { RepoHeader } from "../repos/RepoHeader.tsx"; import { RepoNav } from "../repos/RepoNav.tsx"; function formatBytes(bytes: number): string { if (bytes < 1024) return `${bytes} B`; if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`; return `${(bytes / (1024 * 1024)).toFixed(1)} MB`; } interface SourceArchive { format: string; filename: string; size: number; } interface ReleaseDetailProps { user: SessionUser | null; repo: RepositoryRow; release: ReleaseRow; notesHtml: string; assets: ReleaseAssetRow[]; sourceArchives: SourceArchive[]; sourceArchivesPending?: boolean; } export function ReleaseDetail({ user, repo, release, notesHtml, assets, sourceArchives, sourceArchivesPending, }: ReleaseDetailProps) { const hasDownloads = sourceArchives.length > 0 || assets.length > 0 || sourceArchivesPending; return (

{release.name}

{!!release.tag_name && ( {release.tag_name} )}
{!!notesHtml && (
{notesHtml as "safe"}
)} {hasDownloads && (

Downloads

    {assets.map((asset) => (
  • {asset.filename} {formatBytes(asset.size)}
  • ))} {sourceArchives.map((archive) => (
  • {archive.filename} Source code ({archive.format}) {formatBytes(archive.size)}
  • ))} {sourceArchivesPending && ( [ ["zip", ".zip"], ["tar.gz", ".tar.gz"], ["tar.zst", ".tar.zst"], ] as const ) .filter( ([format]) => !sourceArchives.some( (a) => a.format === format, ), ) .map(([format, ext]) => (
  • {`${repo.name}-${release.tag_name}${ext}`} Source code ({format}) — generating…
  • ))}
)}
← All releases {user?.isAdmin && (
Delete release
Permanently delete this release and all its files?
)}
); }