Files
redux-scraper/app/javascript/bundles/Main/components/FileDetails.tsx
Dylan Knutson 127dd9be51 Add Bluesky file display components and utilities
- Add SkySection component for displaying Bluesky-specific file information
- Add byteCountToHumanSize utility for formatting file sizes
- Update PostFiles, FileCarousel, FileDetails, and DisplayedFile components
- Enhance posts helper with file display logic
- Update post model and view templates
- Remove deprecated file details sky section partial
2025-08-12 18:14:13 +00:00

114 lines
3.1 KiB
TypeScript

import * as React from 'react';
import { PostFileState } from './PostFiles';
import { byteCountToHumanSize } from '../utils/byteCountToHumanSize';
import SkySection from './SkySection';
export interface FileDetailsProps {
contentType: string;
fileSize: number;
responseTimeMs: number;
responseStatusCode: number;
postFileState: PostFileState;
logEntryId: number;
logEntryPath: string;
}
export const FileDetails: React.FC<FileDetailsProps> = ({
contentType,
fileSize,
responseTimeMs,
responseStatusCode,
postFileState,
logEntryId,
logEntryPath,
}) => {
return (
<SkySection
title="File Details"
contentClassName="grid grid-cols-3 sm:grid-cols-6 text-sm"
>
<TitleStat
label="Type"
value={contentType}
iconClass="fa-solid fa-file"
/>
<TitleStat
label="Size"
value={byteCountToHumanSize(fileSize)}
iconClass="fa-solid fa-weight-hanging"
/>
<TitleStat
label="Time"
value={responseTimeMs == -1 ? undefined : `${responseTimeMs}ms`}
iconClass="fa-solid fa-clock"
/>
<TitleStat
label="Status"
value={responseStatusCode}
textClass={
responseStatusCode == 200 ? 'text-green-600' : 'text-red-600'
}
iconClass="fa-solid fa-signal"
/>
<TitleStat
label="State"
value={postFileState}
textClass={postFileState == 'ok' ? 'text-green-600' : 'text-red-600'}
iconClass="fa-solid fa-circle-check"
/>
<TitleStat label="Log Entry" iconClass="fa-solid fa-file-pen">
<a
href={logEntryPath}
target="_blank"
rel="noopener noreferrer"
className="font-medium text-blue-600 hover:text-blue-800"
>
#{logEntryId}
</a>
</TitleStat>
</SkySection>
);
};
const TitleStat: React.FC<{
label: string;
value?: string | number;
iconClass: string;
textClass?: string;
children?: React.ReactNode;
}> = ({ label, value, iconClass, textClass = 'text-slate-600', children }) => {
function valueElement(value: string | number | undefined) {
const defaultTextClass = 'font-normal';
if (value === undefined) {
return <span className="text-slate-500">&mdash;</span>;
} else if (typeof value === 'number') {
return (
<span className={`${textClass} ${defaultTextClass}`}>
{value.toLocaleString()}
</span>
);
} else {
return (
<span className={`${textClass} ${defaultTextClass}`}>{value}</span>
);
}
}
const gridInnerBorderClasses =
'border-r border-b border-slate-300 last:border-r-0 sm:last:border-r-0 [&:nth-child(3)]:border-r-0 sm:[&:nth-child(3)]:border-r [&:nth-last-child(-n+3)]:border-b-0 sm:[&:nth-last-child(-n+6)]:border-b-0';
return (
<div
className={`flex flex-col justify-center px-2 py-1 ${gridInnerBorderClasses}`}
>
<div className="flex items-center gap-2 font-light text-slate-600">
<i className={iconClass}></i>
<span>{label}</span>
</div>
{children || valueElement(value)}
</div>
);
};
export default FileDetails;