Files
cobble/src/pages/admin/files/(partials)/storage-entry-row.tsx
T
tbenandClaude Opus 5.5 faa554d5e3 Open bot files in a preview modal instead of a new tab
Markdown rendered, CSV/TSV as a table, images, PDFs, audio, video and
sandboxed HTML in place, other text as code, with Download and Open in a
new tab. Used by file chips, message attachments, the files modal, task
files and the files browser.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-28 19:25:49 +01:00

79 lines
3.2 KiB
TypeScript

import Link from "@moduletrace/twui/layout/Link";
import Row from "@moduletrace/twui/layout/Row";
import Span from "@moduletrace/twui/layout/Span";
import Stack from "@moduletrace/twui/layout/Stack";
import { twMerge } from "tailwind-merge";
import { FolderOpen } from "lucide-react";
import type { MediaType, StorageEntry } from "@/src/types";
import formatBytes from "@/src/utils/format-bytes";
import relativeTime from "@/src/utils/relative-time";
import AttachmentIcon from "@/src/pages/admin/bots/[bot_id]/(partials)/attachment-icon";
import FilePreviewLink from "@/src/components/general/file-preview-link";
type Props = {
entry: StorageEntry;
selected?: boolean;
onOpenFolder: (path: string) => void;
};
/**
* One entry in the open folder: a folder opens in place, a file opens in the
* file preview (its own media record when it is one of the bot's files, else
* the browser's streaming route).
*/
export default function StorageEntryRow({ entry, selected, onOpenFolder }: Props) {
const is_folder = entry.kind == "folder";
return (
<Row
className={twMerge(
"w-full gap-3! flex-nowrap items-start py-2 border-0 border-b border-solid border-hairline last:border-b-0",
selected && "bg-ink/5",
)}
>
<Row className="mt-0.5 text-ink-secondary">
{is_folder ? (
<FolderOpen size={16} aria-hidden className="shrink-0" />
) : (
<AttachmentIcon media_type={entry.media_type as MediaType} size={16} />
)}
</Row>
<Stack className="min-w-0 flex-1 gap-0.5!">
{is_folder ? (
<Link
href={entry.url || "/admin/files"}
title={`Open ${entry.name}`}
onClick={(event) => {
event.preventDefault();
onOpenFolder(entry.path);
}}
className="text-[13px] font-bold leading-5 text-ink break-words hover:opacity-80"
>
{entry.name}
</Link>
) : (
<FilePreviewLink
name={entry.name}
url={entry.url || ""}
media_type={entry.media_type}
size={entry.size}
className="text-[13px] font-bold leading-5 text-ink break-words hover:opacity-80"
>
{entry.name}
</FilePreviewLink>
)}
<Span className="text-[12px] leading-4 text-ink-secondary">
{[
is_folder ? "Folder" : formatBytes({ bytes: entry.size }),
entry.label || "",
entry.modified_at ? relativeTime({ at: entry.modified_at }) : "",
entry.file_id ? "Saved by the bot" : "",
]
.filter(Boolean)
.join(" · ")}
</Span>
</Stack>
</Row>
);
}