сделал управление файлами
This commit is contained in:
parent
79e343374e
commit
eb498051a1
@ -95,7 +95,7 @@ async def delete_lesson(
|
|||||||
|
|
||||||
@lessons_router.get(
|
@lessons_router.get(
|
||||||
'/files/{lesson_id}/',
|
'/files/{lesson_id}/',
|
||||||
response_model=Optional[ReadLessonFile],
|
response_model=Optional[List[ReadLessonFile]],
|
||||||
summary='Get a files list by lesson ID',
|
summary='Get a files list by lesson ID',
|
||||||
description='Get a files list by lesson ID',
|
description='Get a files list by lesson ID',
|
||||||
)
|
)
|
||||||
@ -111,8 +111,6 @@ async def get_files(
|
|||||||
@lessons_router.get(
|
@lessons_router.get(
|
||||||
'/file/{file_id}/',
|
'/file/{file_id}/',
|
||||||
response_class=FileResponse,
|
response_class=FileResponse,
|
||||||
summary='Get a file by ID',
|
|
||||||
description='Get a file by ID',
|
|
||||||
)
|
)
|
||||||
async def get_file(
|
async def get_file(
|
||||||
file_id: int,
|
file_id: int,
|
||||||
|
|||||||
@ -20,15 +20,15 @@ class LessonFilesService:
|
|||||||
self.lessons_repository = LessonsRepository(db)
|
self.lessons_repository = LessonsRepository(db)
|
||||||
|
|
||||||
async def get_file_by_id(self, file_id: int) -> FileResponse:
|
async def get_file_by_id(self, file_id: int) -> FileResponse:
|
||||||
lesson_file = self.lesson_files_repository.get_by_id(file_id)
|
lesson_file = await self.lesson_files_repository.get_by_id(file_id)
|
||||||
|
|
||||||
if not lesson_file:
|
if not lesson_file:
|
||||||
raise HTTPException(404, "Файл с таким ID не найден")
|
raise HTTPException(404, "Файл с таким ID не найден")
|
||||||
|
|
||||||
return FileResponse(
|
return FileResponse(
|
||||||
lesson_file.filename,
|
lesson_file.file_path,
|
||||||
media_type=self.get_media_type(lesson_file.filename),
|
media_type=self.get_media_type(lesson_file.filename),
|
||||||
filename=os.path.basename(lesson_file.file_path),
|
filename=os.path.basename(lesson_file.filename),
|
||||||
)
|
)
|
||||||
|
|
||||||
async def get_files_list_by_lesson(self, lesson_id: int) -> List[ReadLessonFile]:
|
async def get_files_list_by_lesson(self, lesson_id: int) -> List[ReadLessonFile]:
|
||||||
@ -58,8 +58,8 @@ class LessonFilesService:
|
|||||||
file_path = await self.save_file(file, f'uploads/lessons/{lesson.id}')
|
file_path = await self.save_file(file, f'uploads/lessons/{lesson.id}')
|
||||||
|
|
||||||
lesson_file_model = LessonFile(
|
lesson_file_model = LessonFile(
|
||||||
filename=file_path,
|
filename=file.filename,
|
||||||
file_path=file.filename,
|
file_path=file_path,
|
||||||
lesson_id=lesson.id,
|
lesson_id=lesson.id,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@ -93,4 +93,5 @@ export const {
|
|||||||
useGetLessonFilesListQuery,
|
useGetLessonFilesListQuery,
|
||||||
useGetDownloadFileQuery,
|
useGetDownloadFileQuery,
|
||||||
useUploadFileMutation,
|
useUploadFileMutation,
|
||||||
|
useDeleteFileMutation,
|
||||||
} = lessonsApi;
|
} = lessonsApi;
|
||||||
|
|||||||
@ -1,7 +1,8 @@
|
|||||||
import useUpdateLessonModalForm from "./useUpdateLessonModalForm.js";
|
import useUpdateLessonModalForm from "./useUpdateLessonModalForm.js";
|
||||||
import {Button, Form, Input, InputNumber, Modal, Upload} from "antd";
|
import {Button, Divider, Form, Input, InputNumber, Modal, Popconfirm, Row, Spin, Upload} from "antd";
|
||||||
import JoditEditor from "jodit-react";
|
import JoditEditor from "jodit-react";
|
||||||
import LoadingIndicator from "../../../../Widgets/LoadingIndicator/LoadingIndicator.jsx";
|
import LoadingIndicator from "../../../../Widgets/LoadingIndicator/LoadingIndicator.jsx";
|
||||||
|
import {UploadOutlined} from "@ant-design/icons";
|
||||||
|
|
||||||
const {TextArea} = Input;
|
const {TextArea} = Input;
|
||||||
|
|
||||||
@ -16,6 +17,15 @@ const UpdateLessonModalForm = ({courseId}) => {
|
|||||||
isLoading,
|
isLoading,
|
||||||
initialContent,
|
initialContent,
|
||||||
currentLesson,
|
currentLesson,
|
||||||
|
isFilesLoading,
|
||||||
|
downloadFile,
|
||||||
|
files,
|
||||||
|
downloadingFiles,
|
||||||
|
deletingFiles,
|
||||||
|
deleteFile,
|
||||||
|
draftFiles,
|
||||||
|
handleAddFile,
|
||||||
|
handleRemoveFile,
|
||||||
} = useUpdateLessonModalForm({courseId});
|
} = useUpdateLessonModalForm({courseId});
|
||||||
|
|
||||||
if (isLoading) {
|
if (isLoading) {
|
||||||
@ -71,6 +81,57 @@ const UpdateLessonModalForm = ({courseId}) => {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|
||||||
|
{isFilesLoading ? (
|
||||||
|
<Spin/>
|
||||||
|
) : files.length > 0 ? (
|
||||||
|
files.map((file) => (
|
||||||
|
<Row key={file.id} align="middle" justify="space-between">
|
||||||
|
<span>{file.filename || "Не указан"}</span>
|
||||||
|
<div>
|
||||||
|
<Button
|
||||||
|
onClick={() => downloadFile(file.id, file.filename)}
|
||||||
|
loading={downloadingFiles[file.id] || false}
|
||||||
|
disabled={downloadingFiles[file.id] || deletingFiles[file.id] || false}
|
||||||
|
type={"dashed"}
|
||||||
|
style={{marginRight: 8}}
|
||||||
|
>
|
||||||
|
{downloadingFiles[file.id] ? "Загрузка..." : "Скачать"}
|
||||||
|
</Button>
|
||||||
|
<Popconfirm
|
||||||
|
title={"Вы уверены, что хотите удалить файл?"}
|
||||||
|
onConfirm={() => deleteFile(file.id, file.filename)}
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
loading={deletingFiles[file.id] || false}
|
||||||
|
disabled={deletingFiles[file.id] || downloadingFiles[file.id] || false}
|
||||||
|
type={"dashed"}
|
||||||
|
danger
|
||||||
|
>
|
||||||
|
{deletingFiles[file.id] ? "Удаление..." : "Удалить"}
|
||||||
|
</Button>
|
||||||
|
</Popconfirm>
|
||||||
|
</div>
|
||||||
|
<Divider/>
|
||||||
|
</Row>
|
||||||
|
))
|
||||||
|
) : (
|
||||||
|
<p>Файлы отсутствуют</p>
|
||||||
|
)}
|
||||||
|
<Form.Item name="files" label="Прикрепить файлы">
|
||||||
|
<Upload
|
||||||
|
fileList={draftFiles}
|
||||||
|
beforeUpload={(file) => {
|
||||||
|
handleAddFile(file);
|
||||||
|
return false;
|
||||||
|
}}
|
||||||
|
onRemove={(file) => handleRemoveFile(file)}
|
||||||
|
accept=".pdf,.doc,.docx,.jpg,.jpeg,.png"
|
||||||
|
multiple
|
||||||
|
>
|
||||||
|
<Button icon={<UploadOutlined/>}>Выбрать файлы</Button>
|
||||||
|
</Upload>
|
||||||
|
</Form.Item>
|
||||||
</Form>
|
</Form>
|
||||||
</Modal>
|
</Modal>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -1,8 +1,14 @@
|
|||||||
import {useDispatch, useSelector} from "react-redux";
|
import {useDispatch, useSelector} from "react-redux";
|
||||||
import {Form, notification} from "antd";
|
import {Form, notification} from "antd";
|
||||||
import {useEffect, useMemo, useRef} from "react";
|
import {useEffect, useMemo, useRef, useState} from "react";
|
||||||
import {useUpdateLessonMutation} from "../../../../../Api/lessonsApi.js";
|
import {
|
||||||
|
useDeleteFileMutation,
|
||||||
|
useGetLessonFilesListQuery,
|
||||||
|
useUpdateLessonMutation,
|
||||||
|
useUploadFileMutation
|
||||||
|
} from "../../../../../Api/lessonsApi.js";
|
||||||
import {setSelectedLessonToUpdate} from "../../../../../Redux/Slices/lessonsSlice.js";
|
import {setSelectedLessonToUpdate} from "../../../../../Redux/Slices/lessonsSlice.js";
|
||||||
|
import CONFIG from "../../../../../Core/сonfig.js";
|
||||||
|
|
||||||
|
|
||||||
const useUpdateLessonModalForm = () => {
|
const useUpdateLessonModalForm = () => {
|
||||||
@ -11,9 +17,119 @@ const useUpdateLessonModalForm = () => {
|
|||||||
const editorRef = useRef(null);
|
const editorRef = useRef(null);
|
||||||
|
|
||||||
const {selectedLessonToUpdate} = useSelector((state) => state.lessons);
|
const {selectedLessonToUpdate} = useSelector((state) => state.lessons);
|
||||||
const [updateLesson, {isLoading}] = useUpdateLessonMutation();
|
const [updateLesson, {isLoading: isLoadingUpdateLesson}] = useUpdateLessonMutation();
|
||||||
|
|
||||||
const isModalOpen = selectedLessonToUpdate !== null;
|
const isModalOpen = selectedLessonToUpdate !== null;
|
||||||
|
const [draftFiles, setDraftFiles] = useState([]);
|
||||||
|
const [uploadFile] = useUploadFileMutation();
|
||||||
|
const [deleteFileMut] = useDeleteFileMutation();
|
||||||
|
const [downloadingFiles, setDownloadingFiles] = useState({});
|
||||||
|
const [deletingFiles, setDeletingFiles] = useState({});
|
||||||
|
|
||||||
|
const {
|
||||||
|
data: currentLessonFiles = [],
|
||||||
|
isLoading: isCurrentLessonFilesLoading,
|
||||||
|
isError: isCurrentLessonFilesError
|
||||||
|
} = useGetLessonFilesListQuery(selectedLessonToUpdate?.id, {
|
||||||
|
skip: !selectedLessonToUpdate?.id
|
||||||
|
});
|
||||||
|
|
||||||
|
const downloadFile = async (fileId, fileName) => {
|
||||||
|
try {
|
||||||
|
setDownloadingFiles((prev) => ({ ...prev, [fileId]: true }));
|
||||||
|
|
||||||
|
const token = localStorage.getItem('access_token');
|
||||||
|
if (!token) {
|
||||||
|
notification.error({
|
||||||
|
title: "Ошибка",
|
||||||
|
description: "Токен не найден",
|
||||||
|
placement: "topRight",
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = await fetch(`${CONFIG.BASE_URL}/lessons/file/${fileId}/`, {
|
||||||
|
method: 'GET',
|
||||||
|
headers: {
|
||||||
|
'Authorization': `Bearer ${token}`,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
const errorText = await response.text();
|
||||||
|
notification.error({
|
||||||
|
title: "Ошибка",
|
||||||
|
description: errorText || "Не удалось скачать файл",
|
||||||
|
placement: "topRight",
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const contentType = response.headers.get('content-type');
|
||||||
|
if (!contentType || contentType.includes('text/html')) {
|
||||||
|
const errorText = await response.text();
|
||||||
|
notification.error({
|
||||||
|
title: "Ошибка",
|
||||||
|
description: errorText || "Не удалось скачать файл",
|
||||||
|
placement: "topRight",
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let safeFileName = fileName || "file";
|
||||||
|
if (!safeFileName.match(/\.[a-zA-Z0-9]+$/)) {
|
||||||
|
if (contentType.includes('application/pdf')) {
|
||||||
|
safeFileName += '.pdf';
|
||||||
|
} else if (contentType.includes('image/jpeg')) {
|
||||||
|
safeFileName += '.jpg';
|
||||||
|
} else if (contentType.includes('image/png')) {
|
||||||
|
safeFileName += '.png';
|
||||||
|
} else {
|
||||||
|
safeFileName += '.bin';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const blob = await response.blob();
|
||||||
|
const downloadUrl = window.URL.createObjectURL(blob);
|
||||||
|
const link = document.createElement("a");
|
||||||
|
link.href = downloadUrl;
|
||||||
|
link.setAttribute("download", safeFileName);
|
||||||
|
document.body.appendChild(link);
|
||||||
|
link.click();
|
||||||
|
link.remove();
|
||||||
|
window.URL.revokeObjectURL(downloadUrl);
|
||||||
|
} catch (error) {
|
||||||
|
notification.error({
|
||||||
|
title: "Ошибка",
|
||||||
|
description: error.message || "Не удалось скачать файл",
|
||||||
|
placement: "topRight",
|
||||||
|
});
|
||||||
|
} finally {
|
||||||
|
setDownloadingFiles((prev) => ({...prev, [fileId]: false}));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const deleteFile = async (fileId, fileName) => {
|
||||||
|
try {
|
||||||
|
setDeletingFiles((prev) => ({...prev, [fileId]: true}));
|
||||||
|
await deleteFileMut(fileId).unwrap();
|
||||||
|
notification.success({
|
||||||
|
title: "Файл удален",
|
||||||
|
description: `Файл ${fileName || "неизвестный"} успешно удален.`,
|
||||||
|
placement: "topRight",
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error deleting file:", error);
|
||||||
|
notification.error({
|
||||||
|
title: "Ошибка при удалении файла",
|
||||||
|
description: `Не удалось удалить файл ${fileName || "неизвестный"}: ${error.data?.detail || error.message}`,
|
||||||
|
placement: "topRight",
|
||||||
|
});
|
||||||
|
} finally {
|
||||||
|
setDeletingFiles((prev) => ({...prev, [fileId]: false}));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (selectedLessonToUpdate && isModalOpen) {
|
if (selectedLessonToUpdate && isModalOpen) {
|
||||||
@ -56,6 +172,27 @@ const useUpdateLessonModalForm = () => {
|
|||||||
lessonData,
|
lessonData,
|
||||||
}).unwrap();
|
}).unwrap();
|
||||||
|
|
||||||
|
for (const file of draftFiles) {
|
||||||
|
try {
|
||||||
|
await uploadFile({
|
||||||
|
lesson_id: selectedLessonToUpdate.id,
|
||||||
|
fileData: file,
|
||||||
|
}).unwrap();
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`Error uploading file ${file.name}:`, error);
|
||||||
|
const errorMessage = error.data?.detail
|
||||||
|
? JSON.stringify(error.data.detail, null, 2)
|
||||||
|
: JSON.stringify(error.data || error.message || "Неизвестная ошибка", null, 2);
|
||||||
|
notification.error({
|
||||||
|
title: "Ошибка загрузки файла",
|
||||||
|
description: `Не удалось загрузить файл ${file.name}: ${errorMessage}`,
|
||||||
|
placement: "topRight",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setDraftFiles([]);
|
||||||
|
|
||||||
notification.success({
|
notification.success({
|
||||||
title: "Успех",
|
title: "Успех",
|
||||||
description: "Лекция успешно обновлена!",
|
description: "Лекция успешно обновлена!",
|
||||||
@ -135,6 +272,24 @@ const useUpdateLessonModalForm = () => {
|
|||||||
[]
|
[]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const handleAddFile = (file) => {
|
||||||
|
const maxSize = 50 * 1024 * 1024; // 50 мегабайт
|
||||||
|
if (file.size > maxSize) {
|
||||||
|
notification.error({
|
||||||
|
message: "Ошибка вставки",
|
||||||
|
description: "Файл слишком большой.",
|
||||||
|
placement: "topRight",
|
||||||
|
});
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
setDraftFiles((prev) => [...prev, file]);
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleRemoveFile = (file) => {
|
||||||
|
setDraftFiles((prev) => prev.filter((f) => f.uid !== file.uid));
|
||||||
|
};
|
||||||
|
|
||||||
const initialContent = selectedLessonToUpdate?.text || "";
|
const initialContent = selectedLessonToUpdate?.text || "";
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@ -144,9 +299,19 @@ const useUpdateLessonModalForm = () => {
|
|||||||
form,
|
form,
|
||||||
joditConfig,
|
joditConfig,
|
||||||
editorRef,
|
editorRef,
|
||||||
isLoading,
|
isLoading: isLoadingUpdateLesson,
|
||||||
initialContent,
|
initialContent,
|
||||||
currentLesson: selectedLessonToUpdate,
|
currentLesson: selectedLessonToUpdate,
|
||||||
|
currentLessonFiles,
|
||||||
|
isFilesLoading: isCurrentLessonFilesLoading,
|
||||||
|
downloadFile,
|
||||||
|
files: currentLessonFiles,
|
||||||
|
downloadingFiles,
|
||||||
|
deletingFiles,
|
||||||
|
deleteFile,
|
||||||
|
draftFiles,
|
||||||
|
handleAddFile,
|
||||||
|
handleRemoveFile,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import useViewLessonModal from "./useViewLessonModal.js";
|
import useViewLessonModal from "./useViewLessonModal.js";
|
||||||
import {Avatar, Button, Col, Divider, Modal, Space, Typography} from "antd";
|
import {Avatar, Button, Col, Divider, Modal, Popconfirm, Row, Space, Spin, Typography} from "antd";
|
||||||
import {CloseOutlined, UserOutlined} from "@ant-design/icons";
|
import {CloseOutlined, UserOutlined} from "@ant-design/icons";
|
||||||
|
|
||||||
|
|
||||||
@ -10,6 +10,11 @@ const ViewLessonModal = () => {
|
|||||||
selectedLessonToView,
|
selectedLessonToView,
|
||||||
modalIsOpen,
|
modalIsOpen,
|
||||||
handleClose,
|
handleClose,
|
||||||
|
currentLessonFiles,
|
||||||
|
isCurrentLessonFilesLoading,
|
||||||
|
isCurrentLessonFilesError,
|
||||||
|
downloadFile,
|
||||||
|
downloadingFiles
|
||||||
} = useViewLessonModal();
|
} = useViewLessonModal();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -72,6 +77,30 @@ const ViewLessonModal = () => {
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
<Divider/>
|
<Divider/>
|
||||||
|
<Title level={3}>Прикрепленные файлы</Title>
|
||||||
|
{isCurrentLessonFilesLoading ? (
|
||||||
|
<Spin/>
|
||||||
|
) : currentLessonFiles.length > 0 ? (
|
||||||
|
currentLessonFiles.map((file) => (
|
||||||
|
<Row key={file.id} align="middle" justify="space-between">
|
||||||
|
<span>{file.filename || "Не указан"}</span>
|
||||||
|
<div>
|
||||||
|
<Button
|
||||||
|
onClick={() => downloadFile(file.id, file.filename)}
|
||||||
|
loading={downloadingFiles[file.id] || false}
|
||||||
|
disabled={downloadingFiles[file.id] || false}
|
||||||
|
type={"dashed"}
|
||||||
|
style={{marginRight: 8}}
|
||||||
|
>
|
||||||
|
{downloadingFiles[file.id] ? "Загрузка..." : "Скачать"}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
<Divider/>
|
||||||
|
</Row>
|
||||||
|
))
|
||||||
|
) : (
|
||||||
|
<p>Файлы отсутствуют</p>
|
||||||
|
)}
|
||||||
|
|
||||||
<div style={{textAlign: "right"}}>
|
<div style={{textAlign: "right"}}>
|
||||||
<Button onClick={handleClose}>
|
<Button onClick={handleClose}>
|
||||||
|
|||||||
@ -1,5 +1,9 @@
|
|||||||
import {useDispatch, useSelector} from "react-redux";
|
import {useDispatch, useSelector} from "react-redux";
|
||||||
import {setSelectedLessonToView} from "../../../../../Redux/Slices/lessonsSlice.js";
|
import {setSelectedLessonToView} from "../../../../../Redux/Slices/lessonsSlice.js";
|
||||||
|
import {useGetLessonFilesListQuery} from "../../../../../Api/lessonsApi.js";
|
||||||
|
import {notification} from "antd";
|
||||||
|
import CONFIG from "../../../../../Core/сonfig.js";
|
||||||
|
import {useState} from "react";
|
||||||
|
|
||||||
|
|
||||||
const useViewLessonModal = () => {
|
const useViewLessonModal = () => {
|
||||||
@ -15,10 +19,102 @@ const useViewLessonModal = () => {
|
|||||||
dispatch(setSelectedLessonToView(null));
|
dispatch(setSelectedLessonToView(null));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const {
|
||||||
|
data: currentLessonFiles = [],
|
||||||
|
isLoading: isCurrentLessonFilesLoading,
|
||||||
|
isError: isCurrentLessonFilesError
|
||||||
|
} = useGetLessonFilesListQuery(selectedLessonToView?.id, {
|
||||||
|
skip: !selectedLessonToView?.id
|
||||||
|
});
|
||||||
|
|
||||||
|
const [downloadingFiles, setDownloadingFiles] = useState({});
|
||||||
|
|
||||||
|
const downloadFile = async (fileId, fileName) => {
|
||||||
|
try {
|
||||||
|
setDownloadingFiles((prev) => ({ ...prev, [fileId]: true }));
|
||||||
|
|
||||||
|
const token = localStorage.getItem('access_token');
|
||||||
|
if (!token) {
|
||||||
|
notification.error({
|
||||||
|
title: "Ошибка",
|
||||||
|
description: "Токен не найден",
|
||||||
|
placement: "topRight",
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = await fetch(`${CONFIG.BASE_URL}/lessons/file/${fileId}/`, {
|
||||||
|
method: 'GET',
|
||||||
|
headers: {
|
||||||
|
'Authorization': `Bearer ${token}`,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
const errorText = await response.text();
|
||||||
|
notification.error({
|
||||||
|
title: "Ошибка",
|
||||||
|
description: errorText || "Не удалось скачать файл",
|
||||||
|
placement: "topRight",
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const contentType = response.headers.get('content-type');
|
||||||
|
if (!contentType || contentType.includes('text/html')) {
|
||||||
|
const errorText = await response.text();
|
||||||
|
notification.error({
|
||||||
|
title: "Ошибка",
|
||||||
|
description: errorText || "Не удалось скачать файл",
|
||||||
|
placement: "topRight",
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let safeFileName = fileName || "file";
|
||||||
|
if (!safeFileName.match(/\.[a-zA-Z0-9]+$/)) {
|
||||||
|
if (contentType.includes('application/pdf')) {
|
||||||
|
safeFileName += '.pdf';
|
||||||
|
} else if (contentType.includes('image/jpeg')) {
|
||||||
|
safeFileName += '.jpg';
|
||||||
|
} else if (contentType.includes('image/png')) {
|
||||||
|
safeFileName += '.png';
|
||||||
|
} else {
|
||||||
|
safeFileName += '.bin';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const blob = await response.blob();
|
||||||
|
const downloadUrl = window.URL.createObjectURL(blob);
|
||||||
|
const link = document.createElement("a");
|
||||||
|
link.href = downloadUrl;
|
||||||
|
link.setAttribute("download", safeFileName);
|
||||||
|
document.body.appendChild(link);
|
||||||
|
link.click();
|
||||||
|
link.remove();
|
||||||
|
window.URL.revokeObjectURL(downloadUrl);
|
||||||
|
} catch (error) {
|
||||||
|
notification.error({
|
||||||
|
title: "Ошибка",
|
||||||
|
description: error.message || "Не удалось скачать файл",
|
||||||
|
placement: "topRight",
|
||||||
|
});
|
||||||
|
} finally {
|
||||||
|
setDownloadingFiles((prev) => ({...prev, [fileId]: false}));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
selectedLessonToView,
|
selectedLessonToView,
|
||||||
modalIsOpen,
|
modalIsOpen,
|
||||||
handleClose,
|
handleClose,
|
||||||
|
currentLessonFiles,
|
||||||
|
isCurrentLessonFilesLoading,
|
||||||
|
isCurrentLessonFilesError,
|
||||||
|
downloadFile,
|
||||||
|
downloadingFiles
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user