mirror of
https://github.com/khairul169/garage-webui.git
synced 2025-04-28 06:49:32 +07:00
150 lines
4.3 KiB
TypeScript
150 lines
4.3 KiB
TypeScript
import { Table } from "react-daisyui";
|
|
import { useBrowseObjects } from "./hooks";
|
|
import { dayjs, readableBytes } from "@/lib/utils";
|
|
import mime from "mime/lite";
|
|
import { Object } from "./types";
|
|
import { API_URL } from "@/lib/api";
|
|
import { FileArchive, FileIcon, FileType, Folder } from "lucide-react";
|
|
import { useBucketContext } from "../context";
|
|
import ObjectActions from "./object-actions";
|
|
import GotoTopButton from "@/components/ui/goto-top-btn";
|
|
|
|
type Props = {
|
|
prefix?: string;
|
|
onPrefixChange?: (prefix: string) => void;
|
|
};
|
|
|
|
const ObjectList = ({ prefix, onPrefixChange }: Props) => {
|
|
const { bucketName } = useBucketContext();
|
|
const { data } = useBrowseObjects(bucketName, { prefix, limit: 1000 });
|
|
|
|
const onObjectClick = (object: Object) => {
|
|
window.open(API_URL + object.viewUrl, "_blank");
|
|
};
|
|
|
|
return (
|
|
<div className="overflow-x-auto overflow-y-hidden">
|
|
<Table>
|
|
<Table.Head>
|
|
<span>Name</span>
|
|
<span>Size</span>
|
|
<span>Last Modified</span>
|
|
</Table.Head>
|
|
|
|
<Table.Body>
|
|
{!data?.prefixes?.length && !data?.objects?.length && (
|
|
<tr>
|
|
<td className="text-center py-8" colSpan={3}>
|
|
No objects
|
|
</td>
|
|
</tr>
|
|
)}
|
|
|
|
{data?.prefixes.map((prefix) => (
|
|
<tr
|
|
key={prefix}
|
|
className="hover:bg-neutral/60 hover:text-neutral-content group"
|
|
>
|
|
<td
|
|
className="cursor-pointer"
|
|
role="button"
|
|
onClick={() => onPrefixChange?.(prefix)}
|
|
>
|
|
<span className="flex items-center gap-2 font-normal">
|
|
<Folder size={20} className="text-primary" />
|
|
{prefix
|
|
.substring(0, prefix.lastIndexOf("/"))
|
|
.split("/")
|
|
.pop()}
|
|
</span>
|
|
</td>
|
|
<td colSpan={2} />
|
|
<ObjectActions object={{ objectKey: prefix, downloadUrl: "" }} />
|
|
</tr>
|
|
))}
|
|
|
|
{data?.objects.map((object, idx) => {
|
|
const extIdx = object.objectKey.lastIndexOf(".");
|
|
const filename =
|
|
extIdx >= 0
|
|
? object.objectKey.substring(0, extIdx)
|
|
: object.objectKey;
|
|
const ext = extIdx >= 0 ? object.objectKey.substring(extIdx) : null;
|
|
|
|
return (
|
|
<tr
|
|
key={object.objectKey}
|
|
className="hover:bg-neutral/60 hover:text-neutral-content group"
|
|
>
|
|
<td
|
|
className="cursor-pointer"
|
|
role="button"
|
|
onClick={() => onObjectClick(object)}
|
|
>
|
|
<span className="flex items-center font-normal w-full">
|
|
<FilePreview ext={ext?.substring(1)} object={object} />
|
|
<span className="truncate max-w-[40vw]">{filename}</span>
|
|
{ext && <span className="text-base-content/60">{ext}</span>}
|
|
</span>
|
|
</td>
|
|
<td className="whitespace-nowrap">
|
|
{readableBytes(object.size)}
|
|
</td>
|
|
<td className="whitespace-nowrap">
|
|
{dayjs(object.lastModified).fromNow()}
|
|
</td>
|
|
<ObjectActions
|
|
prefix={data.prefix}
|
|
object={object}
|
|
end={idx >= data.objects.length - 2}
|
|
/>
|
|
</tr>
|
|
);
|
|
})}
|
|
</Table.Body>
|
|
</Table>
|
|
|
|
<GotoTopButton />
|
|
</div>
|
|
);
|
|
};
|
|
|
|
type FilePreviewProps = {
|
|
ext?: string | null;
|
|
object: Object;
|
|
};
|
|
|
|
const FilePreview = ({ ext, object }: FilePreviewProps) => {
|
|
const type = mime.getType(ext || "")?.split("/")[0];
|
|
let Icon = FileIcon;
|
|
|
|
if (
|
|
["zip", "rar", "7z", "iso", "tar", "gz", "bz2", "xz"].includes(ext || "")
|
|
) {
|
|
Icon = FileArchive;
|
|
}
|
|
|
|
if (type === "image") {
|
|
return (
|
|
<img
|
|
src={API_URL + object.viewUrl}
|
|
alt={object.objectKey}
|
|
className="size-5 object-cover overflow-hidden mr-2"
|
|
/>
|
|
);
|
|
}
|
|
|
|
if (type === "text") {
|
|
Icon = FileType;
|
|
}
|
|
|
|
return (
|
|
<Icon
|
|
size={20}
|
|
className="text-base-content/60 group-hover:text-neutral-content/80 mr-2"
|
|
/>
|
|
);
|
|
};
|
|
|
|
export default ObjectList;
|