mirror of
https://github.com/shishantbiswas/bknd.git
synced 2026-03-16 04:27:21 +00:00
feat: enhance mime type handling and improve file metadata management
Updated mime types to include additional formats for text and application categories. Improved file metadata handling in the Storage class to accurately reflect file properties during uploads. Enhanced the DropzoneInner component with new icons for various file types and refined the MediaInfoModal to support a broader range of text formats for previews.
This commit is contained in:
@@ -71,22 +71,29 @@ export class Storage implements EmitsEvents {
|
|||||||
|
|
||||||
let info: FileUploadPayload = {
|
let info: FileUploadPayload = {
|
||||||
name,
|
name,
|
||||||
meta: {
|
meta: isFile(file)
|
||||||
|
? {
|
||||||
|
size: file.size,
|
||||||
|
type: file.type,
|
||||||
|
}
|
||||||
|
: {
|
||||||
size: 0,
|
size: 0,
|
||||||
type: "application/octet-stream",
|
type: "application/octet-stream",
|
||||||
},
|
},
|
||||||
etag: typeof result === "string" ? result : "",
|
etag: typeof result === "string" ? result : "",
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// normally only etag is returned
|
||||||
if (typeof result === "object") {
|
if (typeof result === "object") {
|
||||||
info = result;
|
info = result;
|
||||||
} else if (isFile(file)) {
|
|
||||||
info.meta.size = file.size;
|
|
||||||
info.meta.type = file.type;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// try to get better meta info
|
// try to get better meta info
|
||||||
if (!isMimeType(info.meta.type, ["application/octet-stream", "application/json"])) {
|
if (
|
||||||
|
!info.meta.type ||
|
||||||
|
["application/octet-stream", "application/json"].includes(info.meta.type) ||
|
||||||
|
!info.meta.size
|
||||||
|
) {
|
||||||
const meta = await this.#adapter.getObjectMeta(name);
|
const meta = await this.#adapter.getObjectMeta(name);
|
||||||
if (!meta) {
|
if (!meta) {
|
||||||
throw new Error("Failed to get object meta");
|
throw new Error("Failed to get object meta");
|
||||||
|
|||||||
@@ -2,8 +2,8 @@ export const Q = {
|
|||||||
video: ["mp4", "webm"],
|
video: ["mp4", "webm"],
|
||||||
audio: ["ogg"],
|
audio: ["ogg"],
|
||||||
image: ["jpeg", "png", "gif", "webp", "bmp", "tiff", "avif", "heic", "heif"],
|
image: ["jpeg", "png", "gif", "webp", "bmp", "tiff", "avif", "heic", "heif"],
|
||||||
text: ["html", "css", "mdx", "yaml", "vcard", "csv", "vtt"],
|
text: ["html", "css", "mdx", "yaml", "vcard", "csv", "vtt", "xml"],
|
||||||
application: ["zip", "xml", "toml", "json", "json5", "pdf"],
|
application: ["zip", "toml", "json", "json5", "pdf", "sql"],
|
||||||
font: ["woff", "woff2", "ttf", "otf"],
|
font: ["woff", "woff2", "ttf", "otf"],
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,22 @@
|
|||||||
import { type ComponentPropsWithoutRef, memo, type ReactNode, useCallback, useMemo } from "react";
|
import { type ComponentPropsWithoutRef, memo, type ReactNode, useCallback, useMemo } from "react";
|
||||||
import { twMerge } from "tailwind-merge";
|
import { twMerge } from "tailwind-merge";
|
||||||
import { useRenderCount } from "ui/hooks/use-render-count";
|
import { useRenderCount } from "ui/hooks/use-render-count";
|
||||||
import { TbDots, TbExternalLink, TbTrash, TbUpload } from "react-icons/tb";
|
import {
|
||||||
|
TbDots,
|
||||||
|
TbExternalLink,
|
||||||
|
TbFileTypeCsv,
|
||||||
|
TbFileText,
|
||||||
|
TbJson,
|
||||||
|
TbFileTypePdf,
|
||||||
|
TbMarkdown,
|
||||||
|
TbMusic,
|
||||||
|
TbTrash,
|
||||||
|
TbUpload,
|
||||||
|
TbFileTypeTxt,
|
||||||
|
TbFileTypeXml,
|
||||||
|
TbZip,
|
||||||
|
TbFileTypeSql,
|
||||||
|
} from "react-icons/tb";
|
||||||
import { Dropdown, type DropdownItem } from "ui/components/overlay/Dropdown";
|
import { Dropdown, type DropdownItem } from "ui/components/overlay/Dropdown";
|
||||||
import { IconButton } from "ui/components/buttons/IconButton";
|
import { IconButton } from "ui/components/buttons/IconButton";
|
||||||
import { formatNumber } from "core/utils";
|
import { formatNumber } from "core/utils";
|
||||||
@@ -85,7 +100,7 @@ const UploadPlaceholder = ({ onClick, text = "Upload files" }) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
type ReducedFile = Pick<FileState, "body" | "type" | "path" | "name" | "size">;
|
type ReducedFile = Omit<FileState, "state" | "progress">;
|
||||||
export type PreviewComponentProps = {
|
export type PreviewComponentProps = {
|
||||||
file: ReducedFile;
|
file: ReducedFile;
|
||||||
fallback?: (props: { file: ReducedFile }) => ReactNode;
|
fallback?: (props: { file: ReducedFile }) => ReactNode;
|
||||||
@@ -271,8 +286,59 @@ const VideoPreview = ({
|
|||||||
return <video {...props} src={objectUrl} />;
|
return <video {...props} src={objectUrl} />;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const Previews = [
|
||||||
|
{
|
||||||
|
mime: "text/plain",
|
||||||
|
Icon: TbFileTypeTxt,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
mime: "text/csv",
|
||||||
|
Icon: TbFileTypeCsv,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
mime: "text/xml",
|
||||||
|
Icon: TbFileTypeXml,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
mime: "text/markdown",
|
||||||
|
Icon: TbMarkdown,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
mime: /^text\/.*$/,
|
||||||
|
Icon: TbFileText,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
mime: "application/json",
|
||||||
|
Icon: TbJson,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
mime: "application/pdf",
|
||||||
|
Icon: TbFileTypePdf,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
mime: /^audio\/.*$/,
|
||||||
|
Icon: TbMusic,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
mime: "application/zip",
|
||||||
|
Icon: TbZip,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
mime: "application/sql",
|
||||||
|
Icon: TbFileTypeSql,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
const FallbackPreview = ({ file }: { file: ReducedFile }) => {
|
const FallbackPreview = ({ file }: { file: ReducedFile }) => {
|
||||||
|
const previewIcon = Previews.find((p) =>
|
||||||
|
p.mime instanceof RegExp ? p.mime.test(file.type) : p.mime === file.type,
|
||||||
|
);
|
||||||
|
if (previewIcon) {
|
||||||
|
return <previewIcon.Icon className="size-10 text-gray-400" />;
|
||||||
|
}
|
||||||
return (
|
return (
|
||||||
<div className="text-xs text-primary/50 text-center font-mono leading-none">{file.type}</div>
|
<div className="text-xs text-primary/50 text-center font-mono leading-none max-w-[90%] truncate">
|
||||||
|
{file.type}
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -156,6 +156,8 @@ const Item = ({
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const textFormats = [/^text\/.*$/, /application\/(json|ld\+json|javascript|xml|rtf|sql)/];
|
||||||
|
|
||||||
const FilePreview = ({ file }: { file: FileState }) => {
|
const FilePreview = ({ file }: { file: FileState }) => {
|
||||||
const objectUrl = typeof file.body === "string" ? file.body : URL.createObjectURL(file.body);
|
const objectUrl = typeof file.body === "string" ? file.body : URL.createObjectURL(file.body);
|
||||||
|
|
||||||
@@ -174,15 +176,7 @@ const FilePreview = ({ file }: { file: FileState }) => {
|
|||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (
|
if (textFormats.some((f) => f.test(file.type))) {
|
||||||
[
|
|
||||||
"text/plain",
|
|
||||||
"text/markdown",
|
|
||||||
"text/csv",
|
|
||||||
"text/tab-separated-values",
|
|
||||||
"application/json",
|
|
||||||
].includes(file.type)
|
|
||||||
) {
|
|
||||||
return <TextPreview file={file} />;
|
return <TextPreview file={file} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -227,7 +221,7 @@ const TextPreview = ({ file }: { file: FileState }) => {
|
|||||||
}, [file, useRange]);
|
}, [file, useRange]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<pre className="text-sm font-mono whitespace-pre-wrap break-all overflow-y-scroll w-250 md:max-w-[80dvw] h-[60dvh] md:h-[80dvh] py-4 px-6 debug">
|
<pre className="text-sm font-mono whitespace-pre-wrap break-all overflow-y-scroll w-250 md:max-w-[80dvw] h-[60dvh] md:h-[80dvh] py-4 px-6">
|
||||||
{text}
|
{text}
|
||||||
|
|
||||||
{useRange && (
|
{useRange && (
|
||||||
|
|||||||
Reference in New Issue
Block a user