Files
photos/frontend/src/components/SelectionBar.jsx
T
nilsandClaude 16d2a56a78
ci / docker (push) Successful in 13s
Initial release: self-hosted client photo gallery
Rust (axum + sqlx) API and worker sharing a Postgres-backed job queue
(SKIP LOCKED, heartbeat, reaper, typed statuses), S3 storage with derived
keys and a fully private bucket, OIDC photographer login with per-request
allowlist checks, client share links with argon2 passwords and lockout,
cookie-based image authorization with sliding expiry, hand-rolled
spec-compliant streaming ZIP downloads with exact Content-Length,
React + Vite gallery frontend, single Docker image, Helm chart for
external S3 + Postgres, and Gitea CI.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-17 13:12:42 +02:00

53 lines
1.3 KiB
React

export function fmtBytes(bytes) {
if (!bytes) return '0 B'
const units = ['B', 'KB', 'MB', 'GB', 'TB']
const i = Math.min(Math.floor(Math.log2(bytes) / 10), units.length - 1)
const value = bytes / 2 ** (10 * i)
return `${value >= 100 || i === 0 ? Math.round(value) : value.toFixed(1)} ${units[i]}`
}
export default function SelectionBar({
count,
total,
selectedBytes,
totalBytes,
onSelectAll,
onClear,
onDownload,
onDownloadAll,
}) {
if (total === 0) return null
if (count === 0) {
return (
<div className="select-bar">
<span className="select-count">
{total} photo{total === 1 ? '' : 's'} · {fmtBytes(totalBytes)}
</span>
<button className="btn btn-primary" onClick={onDownloadAll}>
Download all as ZIP
</button>
</div>
)
}
return (
<div className="select-bar">
<span className="select-count">
{count} of {total} selected
</span>
{count < total && (
<button className="btn" onClick={onSelectAll}>
Select all
</button>
)}
<button className="btn" onClick={onClear}>
Clear
</button>
<button className="btn btn-primary" onClick={onDownload}>
Download {count} as ZIP ({fmtBytes(selectedBytes)})
</button>
</div>
)
}