mirror of
https://github.com/kyantech/Palmr.git
synced 2025-10-23 06:11:58 +00:00
feat: enhance file upload and preview functionality
- Improved the uploadSmallFile method to handle various request body types (buffer, string, object, stream) more effectively. - Added error handling for unsupported request body types. - Implemented JSON file preview capability in FilePreviewModal, allowing users to view formatted JSON content. - Updated localization files to include "retry" messages in multiple languages for better user experience during upload errors.
This commit is contained in:
@@ -98,43 +98,82 @@ export class FilesystemController {
|
||||
} catch (error) {
|
||||
try {
|
||||
await fs.promises.unlink(tempPath);
|
||||
} catch (error) {
|
||||
console.error("Error deleting temp file:", error);
|
||||
} catch (cleanupError) {
|
||||
console.error("Error deleting temp file:", cleanupError);
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
private async uploadSmallFile(request: FastifyRequest, provider: FilesystemStorageProvider, objectName: string) {
|
||||
const stream = request.body as any;
|
||||
const chunks: Buffer[] = [];
|
||||
const body = request.body as any;
|
||||
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
stream.on("data", (chunk: Buffer) => {
|
||||
chunks.push(chunk);
|
||||
});
|
||||
if (Buffer.isBuffer(body)) {
|
||||
if (body.length === 0) {
|
||||
throw new Error("No file data received");
|
||||
}
|
||||
await provider.uploadFile(objectName, body);
|
||||
return;
|
||||
}
|
||||
|
||||
stream.on("end", async () => {
|
||||
try {
|
||||
const buffer = Buffer.concat(chunks);
|
||||
if (typeof body === "string") {
|
||||
const buffer = Buffer.from(body, "utf8");
|
||||
if (buffer.length === 0) {
|
||||
throw new Error("No file data received");
|
||||
}
|
||||
await provider.uploadFile(objectName, buffer);
|
||||
return;
|
||||
}
|
||||
|
||||
if (buffer.length === 0) {
|
||||
throw new Error("No file data received");
|
||||
if (typeof body === "object" && body !== null && !body.on) {
|
||||
const buffer = Buffer.from(JSON.stringify(body), "utf8");
|
||||
if (buffer.length === 0) {
|
||||
throw new Error("No file data received");
|
||||
}
|
||||
await provider.uploadFile(objectName, buffer);
|
||||
return;
|
||||
}
|
||||
|
||||
if (body && typeof body.on === "function") {
|
||||
const chunks: Buffer[] = [];
|
||||
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
body.on("data", (chunk: Buffer) => {
|
||||
chunks.push(chunk);
|
||||
});
|
||||
|
||||
body.on("end", async () => {
|
||||
try {
|
||||
const buffer = Buffer.concat(chunks);
|
||||
|
||||
if (buffer.length === 0) {
|
||||
throw new Error("No file data received");
|
||||
}
|
||||
|
||||
await provider.uploadFile(objectName, buffer);
|
||||
resolve();
|
||||
} catch (error) {
|
||||
console.error("Error uploading small file:", error);
|
||||
reject(error);
|
||||
}
|
||||
});
|
||||
|
||||
await provider.uploadFile(objectName, buffer);
|
||||
resolve();
|
||||
} catch (error) {
|
||||
console.error("Error uploading small file:", error);
|
||||
body.on("error", (error: Error) => {
|
||||
console.error("Error reading upload stream:", error);
|
||||
reject(error);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
stream.on("error", (error: Error) => {
|
||||
console.error("Error reading upload stream:", error);
|
||||
reject(error);
|
||||
});
|
||||
});
|
||||
try {
|
||||
const buffer = Buffer.from(body);
|
||||
if (buffer.length === 0) {
|
||||
throw new Error("No file data received");
|
||||
}
|
||||
await provider.uploadFile(objectName, buffer);
|
||||
} catch (error) {
|
||||
throw new Error(`Unsupported request body type: ${typeof body}. Expected stream, buffer, string, or object.`);
|
||||
}
|
||||
}
|
||||
|
||||
async download(request: FastifyRequest, reply: FastifyReply) {
|
||||
|
@@ -9,6 +9,10 @@ export async function filesystemRoutes(app: FastifyInstance) {
|
||||
return payload;
|
||||
});
|
||||
|
||||
app.addContentTypeParser("application/json", async (request: FastifyRequest, payload: any) => {
|
||||
return payload;
|
||||
});
|
||||
|
||||
app.put(
|
||||
"/filesystem/upload/:token",
|
||||
{
|
||||
|
@@ -758,6 +758,7 @@
|
||||
"uploadProgress": "تقدم الرفع",
|
||||
"upload": "رفع",
|
||||
"startUploads": "بدء الرفع",
|
||||
"retry": "إعادة المحاولة",
|
||||
"finish": "إنهاء",
|
||||
"success": "تم رفع الملف بنجاح",
|
||||
"allSuccess": "{count, plural, =1 {تم رفع الملف بنجاح} other {تم رفع # ملف بنجاح}}",
|
||||
@@ -1280,6 +1281,7 @@
|
||||
"linkInactive": "هذا الرابط غير نشط.",
|
||||
"linkExpired": "هذا الرابط منتهي الصلاحية.",
|
||||
"uploadFailed": "خطأ في رفع الملف",
|
||||
"retry": "إعادة المحاولة",
|
||||
"fileTooLarge": "الملف كبير جداً. الحجم الأقصى: {maxSize}",
|
||||
"fileTypeNotAllowed": "نوع الملف غير مسموح به. الأنواع المقبولة: {allowedTypes}",
|
||||
"maxFilesExceeded": "الحد الأقصى المسموح به هو {maxFiles} ملف/ملفات",
|
||||
|
@@ -758,6 +758,7 @@
|
||||
"uploadProgress": "Upload-Fortschritt",
|
||||
"upload": "Hochladen",
|
||||
"startUploads": "Uploads Starten",
|
||||
"retry": "Wiederholen",
|
||||
"finish": "Beenden",
|
||||
"success": "Datei erfolgreich hochgeladen",
|
||||
"allSuccess": "{count, plural, =1 {Datei erfolgreich hochgeladen} other {# Dateien erfolgreich hochgeladen}}",
|
||||
@@ -1280,6 +1281,7 @@
|
||||
"linkInactive": "Dieser Link ist inaktiv.",
|
||||
"linkExpired": "Dieser Link ist abgelaufen.",
|
||||
"uploadFailed": "Fehler beim Hochladen der Datei",
|
||||
"retry": "Wiederholen",
|
||||
"fileTooLarge": "Datei zu groß. Maximale Größe: {maxSize}",
|
||||
"fileTypeNotAllowed": "Dateityp nicht erlaubt. Erlaubte Typen: {allowedTypes}",
|
||||
"maxFilesExceeded": "Maximal {maxFiles} Dateien erlaubt",
|
||||
|
@@ -816,6 +816,7 @@
|
||||
"uploadProgress": "Upload progress",
|
||||
"upload": "Upload",
|
||||
"startUploads": "Start Uploads",
|
||||
"retry": "Retry",
|
||||
"finish": "Finish",
|
||||
"success": "File uploaded successfully",
|
||||
"allSuccess": "{count, plural, =1 {File uploaded successfully} other {# files uploaded successfully}}",
|
||||
@@ -1280,6 +1281,7 @@
|
||||
"linkInactive": "This link is inactive.",
|
||||
"linkExpired": "This link has expired.",
|
||||
"uploadFailed": "Error uploading file",
|
||||
"retry": "Retry",
|
||||
"fileTooLarge": "File too large. Maximum size: {maxSize}",
|
||||
"fileTypeNotAllowed": "File type not allowed. Accepted types: {allowedTypes}",
|
||||
"maxFilesExceeded": "Maximum of {maxFiles} files allowed",
|
||||
|
@@ -758,6 +758,7 @@
|
||||
"uploadProgress": "Progreso de la subida",
|
||||
"upload": "Subir",
|
||||
"startUploads": "Iniciar Subidas",
|
||||
"retry": "Reintentar",
|
||||
"finish": "Finalizar",
|
||||
"success": "Archivo subido exitosamente",
|
||||
"allSuccess": "{count, plural, =1 {Archivo subido exitosamente} other {# archivos subidos exitosamente}}",
|
||||
@@ -1280,6 +1281,7 @@
|
||||
"linkInactive": "Este enlace está inactivo.",
|
||||
"linkExpired": "Este enlace ha expirado.",
|
||||
"uploadFailed": "Error al subir archivo",
|
||||
"retry": "Reintentar",
|
||||
"fileTooLarge": "Archivo demasiado grande. Tamaño máximo: {maxSize}",
|
||||
"fileTypeNotAllowed": "Tipo de archivo no permitido. Tipos aceptados: {allowedTypes}",
|
||||
"maxFilesExceeded": "Máximo de {maxFiles} archivos permitidos",
|
||||
|
@@ -758,6 +758,7 @@
|
||||
"uploadProgress": "Progression du téléchargement",
|
||||
"upload": "Télécharger",
|
||||
"startUploads": "Commencer les Téléchargements",
|
||||
"retry": "Réessayer",
|
||||
"finish": "Terminer",
|
||||
"success": "Fichier téléchargé avec succès",
|
||||
"allSuccess": "{count, plural, =1 {Fichier téléchargé avec succès} other {# fichiers téléchargés avec succès}}",
|
||||
@@ -1280,6 +1281,7 @@
|
||||
"linkInactive": "Ce lien est inactif.",
|
||||
"linkExpired": "Ce lien a expiré.",
|
||||
"uploadFailed": "Erreur lors de l'envoi du fichier",
|
||||
"retry": "Réessayer",
|
||||
"fileTooLarge": "Fichier trop volumineux. Taille maximale : {maxSize}",
|
||||
"fileTypeNotAllowed": "Type de fichier non autorisé. Types acceptés : {allowedTypes}",
|
||||
"maxFilesExceeded": "Maximum de {maxFiles} fichiers autorisés",
|
||||
|
@@ -758,6 +758,7 @@
|
||||
"uploadProgress": "अपलोड प्रगति",
|
||||
"upload": "अपलोड",
|
||||
"startUploads": "अपलोड शुरू करें",
|
||||
"retry": "पुनः प्रयास करें",
|
||||
"finish": "समाप्त",
|
||||
"success": "फ़ाइल सफलतापूर्वक अपलोड की गई",
|
||||
"allSuccess": "{count, plural, =1 {फ़ाइल सफलतापूर्वक अपलोड की गई} other {# फ़ाइलें सफलतापूर्वक अपलोड की गईं}}",
|
||||
@@ -1280,6 +1281,7 @@
|
||||
"linkInactive": "यह लिंक निष्क्रिय है।",
|
||||
"linkExpired": "यह लिंक समाप्त हो गया है।",
|
||||
"uploadFailed": "फ़ाइल अपलोड करने में त्रुटि",
|
||||
"retry": "पुनः प्रयास करें",
|
||||
"fileTooLarge": "फ़ाइल बहुत बड़ी है। अधिकतम आकार: {maxSize}",
|
||||
"fileTypeNotAllowed": "फ़ाइल प्रकार अनुमत नहीं है। स्वीकृत प्रकार: {allowedTypes}",
|
||||
"maxFilesExceeded": "अधिकतम {maxFiles} फ़ाइलें अनुमत हैं",
|
||||
|
@@ -758,6 +758,7 @@
|
||||
"uploadProgress": "Progresso caricamento",
|
||||
"upload": "Carica",
|
||||
"startUploads": "Inizia Caricamenti",
|
||||
"retry": "Riprova",
|
||||
"finish": "Termina",
|
||||
"success": "File caricato con successo",
|
||||
"allSuccess": "{count, plural, =1 {File caricato con successo} other {# file caricati con successo}}",
|
||||
@@ -1280,6 +1281,7 @@
|
||||
"linkInactive": "Questo link è inattivo.",
|
||||
"linkExpired": "Questo link è scaduto.",
|
||||
"uploadFailed": "Errore durante l'invio del file",
|
||||
"retry": "Riprova",
|
||||
"fileTooLarge": "File troppo grande. Dimensione massima: {maxSize}",
|
||||
"fileTypeNotAllowed": "Tipo di file non consentito. Tipi accettati: {allowedTypes}",
|
||||
"maxFilesExceeded": "Massimo {maxFiles} file consentiti",
|
||||
|
@@ -771,6 +771,7 @@
|
||||
},
|
||||
"multipleTitle": "複数ファイルをアップロード",
|
||||
"startUploads": "アップロードを開始",
|
||||
"retry": "再試行",
|
||||
"allSuccess": "{count, plural, =1 {ファイルがアップロードされました} other {#個のファイルがアップロードされました}}",
|
||||
"partialSuccess": "{success}個のファイルがアップロードされ、{error}個が失敗しました",
|
||||
"dragAndDrop": "またはここにファイルをドラッグ&ドロップ"
|
||||
@@ -1280,6 +1281,7 @@
|
||||
"linkInactive": "このリンクは無効です。",
|
||||
"linkExpired": "このリンクは期限切れです。",
|
||||
"uploadFailed": "ファイルのアップロードに失敗しました",
|
||||
"retry": "再試行",
|
||||
"fileTooLarge": "ファイルが大きすぎます。最大サイズ: {maxSize}",
|
||||
"fileTypeNotAllowed": "このファイル形式は許可されていません。許可される形式: {allowedTypes}",
|
||||
"maxFilesExceeded": "最大 {maxFiles} ファイルまで許可されています",
|
||||
|
@@ -758,6 +758,7 @@
|
||||
"uploadProgress": "업로드 진행률",
|
||||
"upload": "업로드",
|
||||
"startUploads": "업로드 시작",
|
||||
"retry": "다시 시도",
|
||||
"finish": "완료",
|
||||
"success": "파일이 성공적으로 업로드되었습니다",
|
||||
"allSuccess": "{count, plural, =1 {파일이 성공적으로 업로드되었습니다} other {# 개 파일이 성공적으로 업로드되었습니다}}",
|
||||
@@ -1280,6 +1281,7 @@
|
||||
"linkInactive": "이 링크는 비활성 상태입니다.",
|
||||
"linkExpired": "이 링크는 만료되었습니다.",
|
||||
"uploadFailed": "파일 업로드 오류",
|
||||
"retry": "다시 시도",
|
||||
"fileTooLarge": "파일이 너무 큽니다. 최대 크기: {maxSize}",
|
||||
"fileTypeNotAllowed": "허용되지 않는 파일 유형입니다. 허용된 유형: {allowedTypes}",
|
||||
"maxFilesExceeded": "최대 {maxFiles}개의 파일만 허용됩니다",
|
||||
|
@@ -758,6 +758,7 @@
|
||||
"uploadProgress": "Upload voortgang",
|
||||
"upload": "Uploaden",
|
||||
"startUploads": "Uploads Starten",
|
||||
"retry": "Opnieuw Proberen",
|
||||
"finish": "Voltooien",
|
||||
"success": "Bestand succesvol geüpload",
|
||||
"allSuccess": "{count, plural, =1 {Bestand succesvol geüpload} other {# bestanden succesvol geüpload}}",
|
||||
@@ -1280,6 +1281,7 @@
|
||||
"linkInactive": "Deze link is inactief.",
|
||||
"linkExpired": "Deze link is verlopen.",
|
||||
"uploadFailed": "Fout bij uploaden bestand",
|
||||
"retry": "Opnieuw Proberen",
|
||||
"fileTooLarge": "Bestand te groot. Maximum grootte: {maxSize}",
|
||||
"fileTypeNotAllowed": "Bestandstype niet toegestaan. Toegestane types: {allowedTypes}",
|
||||
"maxFilesExceeded": "Maximum van {maxFiles} bestanden toegestaan",
|
||||
|
@@ -816,6 +816,7 @@
|
||||
"uploadProgress": "Postęp przesyłania",
|
||||
"upload": "Prześlij",
|
||||
"startUploads": "Rozpocznij przesyłanie",
|
||||
"retry": "Spróbuj Ponownie",
|
||||
"finish": "Zakończ",
|
||||
"success": "Plik przesłany pomyślnie",
|
||||
"allSuccess": "{count, plural, =1 {Plik przesłany pomyślnie} other {# plików przesłanych pomyślnie}}",
|
||||
@@ -1280,6 +1281,7 @@
|
||||
"linkInactive": "Ten link jest nieaktywny.",
|
||||
"linkExpired": "Ten link wygasł.",
|
||||
"uploadFailed": "Błąd przesyłania pliku",
|
||||
"retry": "Spróbuj Ponownie",
|
||||
"fileTooLarge": "Plik za duży. Maksymalny rozmiar: {maxSize}",
|
||||
"fileTypeNotAllowed": "Typ pliku niedozwolony. Akceptowane typy: {allowedTypes}",
|
||||
"maxFilesExceeded": "Dozwolono maksymalnie {maxFiles} plików",
|
||||
|
@@ -780,6 +780,7 @@
|
||||
"uploadProgress": "Progresso do upload",
|
||||
"upload": "Enviar",
|
||||
"startUploads": "Iniciar Uploads",
|
||||
"retry": "Tentar Novamente",
|
||||
"finish": "Concluir",
|
||||
"success": "Arquivo enviado com sucesso",
|
||||
"allSuccess": "{count, plural, =1 {Arquivo enviado com sucesso} other {# arquivos enviados com sucesso}}",
|
||||
@@ -1276,6 +1277,7 @@
|
||||
"linkInactive": "Este link está inativo.",
|
||||
"linkExpired": "Este link expirou.",
|
||||
"uploadFailed": "Erro ao enviar arquivo",
|
||||
"retry": "Tentar Novamente",
|
||||
"fileTooLarge": "Arquivo muito grande. Tamanho máximo: {maxSize}",
|
||||
"fileTypeNotAllowed": "Tipo de arquivo não permitido. Tipos aceitos: {allowedTypes}",
|
||||
"maxFilesExceeded": "Máximo de {maxFiles} arquivos permitidos",
|
||||
|
@@ -758,6 +758,7 @@
|
||||
"uploadProgress": "Прогресс загрузки",
|
||||
"upload": "Загрузить",
|
||||
"startUploads": "Начать Загрузку",
|
||||
"retry": "Повторить",
|
||||
"finish": "Завершить",
|
||||
"success": "Файл успешно загружен",
|
||||
"allSuccess": "{count, plural, =1 {Файл успешно загружен} other {# файлов успешно загружено}}",
|
||||
@@ -1280,6 +1281,7 @@
|
||||
"linkInactive": "Эта ссылка неактивна.",
|
||||
"linkExpired": "Срок действия этой ссылки истек.",
|
||||
"uploadFailed": "Ошибка при загрузке файла",
|
||||
"retry": "Повторить",
|
||||
"fileTooLarge": "Файл слишком большой. Максимальный размер: {maxSize}",
|
||||
"fileTypeNotAllowed": "Тип файла не разрешен. Разрешенные типы: {allowedTypes}",
|
||||
"maxFilesExceeded": "Максимально разрешено {maxFiles} файлов",
|
||||
|
@@ -758,6 +758,7 @@
|
||||
"uploadProgress": "Yükleme ilerlemesi",
|
||||
"upload": "Yükle",
|
||||
"startUploads": "Yüklemeleri Başlat",
|
||||
"retry": "Tekrar Dene",
|
||||
"finish": "Bitir",
|
||||
"success": "Dosya başarıyla yüklendi",
|
||||
"allSuccess": "{count, plural, =1 {Dosya başarıyla yüklendi} other {# dosya başarıyla yüklendi}}",
|
||||
@@ -1280,6 +1281,7 @@
|
||||
"linkInactive": "Bu bağlantı pasif durumda.",
|
||||
"linkExpired": "Bu bağlantının süresi doldu.",
|
||||
"uploadFailed": "Dosya yüklenirken hata oluştu",
|
||||
"retry": "Tekrar Dene",
|
||||
"fileTooLarge": "Dosya çok büyük. Maksimum boyut: {maxSize}",
|
||||
"fileTypeNotAllowed": "Dosya türüne izin verilmiyor. İzin verilen türler: {allowedTypes}",
|
||||
"maxFilesExceeded": "Maksimum {maxFiles} dosyaya izin veriliyor",
|
||||
|
@@ -758,6 +758,7 @@
|
||||
"uploadProgress": "上传进度",
|
||||
"upload": "上传",
|
||||
"startUploads": "开始上传",
|
||||
"retry": "重试",
|
||||
"finish": "完成",
|
||||
"success": "文件上传成功",
|
||||
"allSuccess": "{count, plural, =1 {文件上传成功} other {# 个文件上传成功}}",
|
||||
@@ -1280,6 +1281,7 @@
|
||||
"linkInactive": "此链接已停用。",
|
||||
"linkExpired": "此链接已过期。",
|
||||
"uploadFailed": "上传文件时出错",
|
||||
"retry": "重试",
|
||||
"fileTooLarge": "文件太大。最大大小:{maxSize}",
|
||||
"fileTypeNotAllowed": "不允许的文件类型。允许的类型:{allowedTypes}",
|
||||
"maxFilesExceeded": "最多允许 {maxFiles} 个文件",
|
||||
|
@@ -331,6 +331,28 @@ export function FileUploadSection({ reverseShare, password, alias, onUploadSucce
|
||||
<IconX className="h-4 w-4" />
|
||||
</Button>
|
||||
)}
|
||||
{fileWithProgress.status === FILE_STATUS.ERROR && (
|
||||
<div className="flex gap-1">
|
||||
<Button
|
||||
size="sm"
|
||||
variant="ghost"
|
||||
onClick={() => {
|
||||
setFiles((prev) =>
|
||||
prev.map((file, i) =>
|
||||
i === index ? { ...file, status: FILE_STATUS.PENDING, error: undefined } : file
|
||||
)
|
||||
);
|
||||
}}
|
||||
disabled={isUploading}
|
||||
title={t("reverseShares.upload.retry")}
|
||||
>
|
||||
<IconUpload className="h-4 w-4" />
|
||||
</Button>
|
||||
<Button size="sm" variant="ghost" onClick={() => removeFile(index)} disabled={isUploading}>
|
||||
<IconX className="h-4 w-4" />
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
@@ -32,6 +32,7 @@ export function FilePreviewModal({ isOpen, onClose, file }: FilePreviewModalProp
|
||||
const [downloadUrl, setDownloadUrl] = useState<string | null>(null);
|
||||
const [pdfLoadFailed, setPdfLoadFailed] = useState(false);
|
||||
const [isLoadingPreview, setIsLoadingPreview] = useState(false);
|
||||
const [jsonContent, setJsonContent] = useState<string | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (isOpen && file.objectName && !isLoadingPreview) {
|
||||
@@ -41,6 +42,7 @@ export function FilePreviewModal({ isOpen, onClose, file }: FilePreviewModalProp
|
||||
setPdfAsBlob(false);
|
||||
setDownloadUrl(null);
|
||||
setPdfLoadFailed(false);
|
||||
setJsonContent(null);
|
||||
loadPreview();
|
||||
}
|
||||
}, [file.objectName, isOpen]);
|
||||
@@ -88,6 +90,8 @@ export function FilePreviewModal({ isOpen, onClose, file }: FilePreviewModalProp
|
||||
await loadAudioPreview(url);
|
||||
} else if (fileType === "pdf") {
|
||||
await loadPdfPreview(url);
|
||||
} else if (fileType === "json") {
|
||||
await loadJsonPreview(url);
|
||||
} else {
|
||||
setPreviewUrl(url);
|
||||
}
|
||||
@@ -155,6 +159,29 @@ export function FilePreviewModal({ isOpen, onClose, file }: FilePreviewModalProp
|
||||
}
|
||||
};
|
||||
|
||||
const loadJsonPreview = async (url: string) => {
|
||||
try {
|
||||
const response = await fetch(url);
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
}
|
||||
|
||||
const text = await response.text();
|
||||
try {
|
||||
// Validate and format JSON
|
||||
const parsed = JSON.parse(text);
|
||||
const formatted = JSON.stringify(parsed, null, 2);
|
||||
setJsonContent(formatted);
|
||||
} catch (jsonError) {
|
||||
// If it's not valid JSON, show as plain text
|
||||
setJsonContent(text);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to load JSON:", error);
|
||||
setJsonContent(null);
|
||||
}
|
||||
};
|
||||
|
||||
const handlePdfLoadError = async () => {
|
||||
if (pdfLoadFailed || pdfAsBlob) return;
|
||||
|
||||
@@ -193,6 +220,7 @@ export function FilePreviewModal({ isOpen, onClose, file }: FilePreviewModalProp
|
||||
const extension = file.name.split(".").pop()?.toLowerCase();
|
||||
|
||||
if (extension === "pdf") return "pdf";
|
||||
if (extension === "json") return "json";
|
||||
if (["jpg", "jpeg", "png", "gif", "webp", "svg", "bmp", "tiff"].includes(extension || "")) return "image";
|
||||
if (["mp3", "wav", "ogg", "m4a", "aac", "flac"].includes(extension || "")) return "audio";
|
||||
if (["mp4", "webm", "ogg", "mov", "avi", "mkv", "wmv", "flv", "m4v"].includes(extension || "")) return "video";
|
||||
@@ -225,7 +253,18 @@ export function FilePreviewModal({ isOpen, onClose, file }: FilePreviewModalProp
|
||||
);
|
||||
}
|
||||
|
||||
if (!previewUrl && fileType !== "video") {
|
||||
// For JSON files, we don't need previewUrl, we use jsonContent instead
|
||||
if (fileType === "json" && !jsonContent && !isLoading) {
|
||||
return (
|
||||
<div className="flex flex-col items-center justify-center h-96 gap-4">
|
||||
<FileIcon className={`h-12 w-12 ${color}`} />
|
||||
<p className="text-muted-foreground">{t("filePreview.notAvailable")}</p>
|
||||
<p className="text-sm text-muted-foreground">{t("filePreview.downloadToView")}</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (!previewUrl && fileType !== "video" && fileType !== "json") {
|
||||
return (
|
||||
<div className="flex flex-col items-center justify-center h-96 gap-4">
|
||||
<FileIcon className={`h-12 w-12 ${color}`} />
|
||||
@@ -275,6 +314,25 @@ export function FilePreviewModal({ isOpen, onClose, file }: FilePreviewModalProp
|
||||
</div>
|
||||
</ScrollArea>
|
||||
);
|
||||
case "json":
|
||||
return (
|
||||
<ScrollArea className="w-full max-h-[600px]">
|
||||
<div className="w-full border rounded-lg overflow-hidden bg-card">
|
||||
{jsonContent ? (
|
||||
<pre className="p-4 text-sm font-mono whitespace-pre-wrap break-words overflow-x-auto">
|
||||
<code className="language-json">{jsonContent}</code>
|
||||
</pre>
|
||||
) : (
|
||||
<div className="flex items-center justify-center h-32">
|
||||
<div className="flex flex-col items-center gap-2">
|
||||
<div className="animate-spin rounded-full h-6 w-6 border-b-2 border-primary" />
|
||||
<p className="text-sm text-muted-foreground">{t("filePreview.loading")}</p>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</ScrollArea>
|
||||
);
|
||||
case "image":
|
||||
return (
|
||||
<AspectRatio ratio={16 / 9} className="bg-muted">
|
||||
|
@@ -453,7 +453,33 @@ export function UploadFileModal({ isOpen, onClose, onSuccess }: UploadFileModalP
|
||||
>
|
||||
<IconX size={14} />
|
||||
</Button>
|
||||
) : upload.status === UploadStatus.SUCCESS ? null : (
|
||||
) : upload.status === UploadStatus.SUCCESS ? null : upload.status === UploadStatus.ERROR ? (
|
||||
<div className="flex gap-1">
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={() => {
|
||||
setFileUploads((prev) =>
|
||||
prev.map((u) =>
|
||||
u.id === upload.id ? { ...u, status: UploadStatus.PENDING, error: undefined } : u
|
||||
)
|
||||
);
|
||||
}}
|
||||
className="h-8 w-8 p-0"
|
||||
title={t("uploadFile.retry")}
|
||||
>
|
||||
<IconLoader size={14} />
|
||||
</Button>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={() => removeFile(upload.id)}
|
||||
className="h-8 w-8 p-0"
|
||||
>
|
||||
<IconTrash size={14} />
|
||||
</Button>
|
||||
</div>
|
||||
) : (
|
||||
<Button variant="ghost" size="sm" onClick={() => removeFile(upload.id)} className="h-8 w-8 p-0">
|
||||
<IconTrash size={14} />
|
||||
</Button>
|
||||
|
Reference in New Issue
Block a user