diff --git a/lib/components/ViewPublicationPage/questions/File.tsx b/lib/components/ViewPublicationPage/questions/File.tsx index 44ccffe..8865a8a 100644 --- a/lib/components/ViewPublicationPage/questions/File.tsx +++ b/lib/components/ViewPublicationPage/questions/File.tsx @@ -13,344 +13,243 @@ import CloseBold from "@icons/CloseBold"; import UploadIcon from "@icons/UploadIcon"; import { sendAnswer, sendFile } from "@api/quizRelase"; +import { useQuizData } from "@contexts/QuizDataContext"; import Info from "@icons/Info"; -import type { UploadFileType } from "@model/questionTypes/file"; import { enqueueSnackbar } from "notistack"; -import type { DragEvent } from "react"; -import { useState, type ChangeEvent } from "react"; +import { useState } from "react"; import { useRootContainerSize } from "../../../contexts/RootContainerWidthContext"; import type { QuizQuestionFile } from "../../../model/questionTypes/file"; -import { useQuizData } from "@contexts/QuizDataContext"; +import { ACCEPT_SEND_FILE_TYPES_MAP, MAX_FILE_SIZE, UPLOAD_FILE_DESCRIPTIONS_MAP } from "../tools/fileUpload"; + +type ModalWarningType = "errorType" | "errorSize" | "picture" | "video" | "audio" | "document" | null; type FileProps = { currentQuestion: QuizQuestionFile; }; -const CurrentModal = ({ status }: { status: "errorType" | "errorSize" | "picture" | "video" | "audio" | "document" | ""; }) => { - switch (status) { - case 'errorType': - return (<> - Выбран некорректный тип файла - ); - case 'errorSize': - return (<> - Файл слишком большой. Максимальный размер 50 МБ - ); - default: - return (<> - Допустимые расширения файлов: - { - //@ts-ignore - ACCEPT_SEND_FILE_TYPES_MAP[status].join(" ")} - ); - } -}; - -const ACCEPT_SEND_FILE_TYPES_MAP = { - picture: [ - ".jpeg", - ".jpg", - ".png", - ".ico", - ".gif", - ".tiff", - ".webp", - ".eps", - ".svg" - ], - video: [ - ".mp4", - ".mov", - ".wmv", - ".avi", - ".avchd", - ".flv", - ".f4v", - ".swf", - ".mkv", - ".webm", - ".mpeg-2" - ], - audio: [ - ".aac", - ".aiff", - ".dsd", - ".flac", - ".mp3", - ".mqa", - ".ogg", - ".wav", - ".wma" - ], - document: [ - ".doc", - ".docx", - ".dotx", - ".rtf", - ".odt", - ".pdf", - ".txt", - ".xls", - ".ppt", - ".xlsx", - ".pptx", - ".pages", - ], - -}; - - -const UPLOAD_FILE_DESCRIPTIONS_MAP: Record< - UploadFileType, - { title: string; description: string; } -> = { - picture: { - title: "Добавить изображение", - description: "Принимает изображения", - }, - video: { - title: "Добавить видео", - description: "Принимает .mp4 и .mov формат — максимум 50мб", - }, - audio: { title: "Добавить аудиофайл", description: "Принимает аудиофайлы" }, - document: { title: "Добавить документ", description: "Принимает документы" }, -} as const; - export const File = ({ currentQuestion }: FileProps) => { const theme = useTheme(); const { answers } = useQuizViewStore(); const { quizId } = useQuizData(); - const [statusModal, setStatusModal] = useState<"errorType" | "errorSize" | "picture" | "video" | "audio" | "document" | "">(""); - const [readySend, setReadySend] = useState(true) + const [modalWarningType, setModalWarningType] = useState(null); + const [isSending, setIsSending] = useState(false); + const [isDropzoneHighlighted, setIsDropzoneHighlighted] = useState(false); + const isMobile = useRootContainerSize() < 500; const answer = answers.find( ({ questionId }) => questionId === currentQuestion.id )?.answer as string; - const isMobile = useRootContainerSize() < 500; - const uploadFile = async ({ target }: ChangeEvent) => { - if (readySend) { - setReadySend(false) - const file = target.files?.[0]; - if (file) { - if (file.size <= 52428800) { - //проверяем на соответствие - console.log(file.name.toLowerCase()); - if (ACCEPT_SEND_FILE_TYPES_MAP[currentQuestion.content.type].find((ednding => { - console.log(ednding); - console.log(file.name.toLowerCase().endsWith(ednding)); - return file.name.toLowerCase().endsWith(ednding); - }))) { - //Нужный формат - console.log(file); - try { + const uploadFile = async (file: File | undefined) => { + if (isSending) return; + if (!file) return; + if (file.size > MAX_FILE_SIZE) return setModalWarningType("errorSize"); - const data = await sendFile({ - questionId: currentQuestion.id, - body: { - file: file, - name: file.name - }, - qid: quizId, - }); - console.log(data); + const isFileTypeAccepted = ACCEPT_SEND_FILE_TYPES_MAP[currentQuestion.content.type].some( + fileType => file.name.toLowerCase().endsWith(fileType) + ); - await sendAnswer({ - questionId: currentQuestion.id, - body: `https://storage.yandexcloud.net/squizanswer/${quizId}/${currentQuestion.id}/${data.data.fileIDMap[currentQuestion.id]}`, - qid: quizId, - }); + if (!isFileTypeAccepted) return setModalWarningType("errorType"); - updateAnswer( - currentQuestion.id, - `${file.name}|${URL.createObjectURL(file)}`, - 0 - ); + setIsSending(true); + try { + const data = await sendFile({ + questionId: currentQuestion.id, + body: { + file: file, + name: file.name + }, + qid: quizId, + }); + console.log(data); - } catch (e) { - console.log(e); - enqueueSnackbar("ответ не был засчитан"); - } + await sendAnswer({ + questionId: currentQuestion.id, + body: `https://storage.yandexcloud.net/squizanswer/${quizId}/${currentQuestion.id}/${data.data.fileIDMap[currentQuestion.id]}`, + qid: quizId, + }); - } else { - - //неподходящий формат - setStatusModal("errorType"); - } - } else { - - setStatusModal("errorSize"); - } - - } - setReadySend(true) + updateAnswer(currentQuestion.id, `${file.name}|${URL.createObjectURL(file)}`, 0); + } catch (e) { + console.log(e); + enqueueSnackbar("ответ не был засчитан"); } + + setIsSending(false); + }; + + const onDrop = (event: React.DragEvent) => { + event.preventDefault(); + setIsDropzoneHighlighted(false); + + const file = event.dataTransfer.files[0]; + + uploadFile(file); }; return ( - <> - - {currentQuestion.title} - - {answer?.split("|")[0] && ( - - Вы загрузили: - + {currentQuestion.title} + + {answer?.split("|")[0] ? ( + + Вы загрузили: + + - - {answer?.split("|")[0]} - { - if (answer.length > 0) { - setReadySend(false) - await sendAnswer({ - questionId: currentQuestion.id, - body: "", - qid: quizId, - }) - } - console.log(answer) - updateAnswer(currentQuestion.id, "", 0); - setReadySend(true) - }} - > - - - + {answer?.split("|")[0]} + { + if (answer.length > 0) { + setIsSending(true); + await sendAnswer({ + questionId: currentQuestion.id, + body: "", + qid: quizId, + }); + } + console.log(answer); + updateAnswer(currentQuestion.id, "", 0); + setIsSending(false); + }} + > + + - )} - - {!answer?.split("|")[0] && ( - + ) : ( + - { - - readySend ? <> - - - ) => - event.preventDefault() - } + }} + > + {isSending ? + + : + + uploadFile(e.target.files?.[0])} + hidden + accept={ACCEPT_SEND_FILE_TYPES_MAP[currentQuestion.content.type].join(",")} + multiple + type="file" + /> + !answer?.split("|")[0] && setIsDropzoneHighlighted(true)} + onDragLeave={() => setIsDropzoneHighlighted(false)} + onDragOver={(e) => e.preventDefault()} + onDrop={onDrop} + sx={{ + width: "100%", + height: isMobile ? undefined : "120px", + display: "flex", + gap: "50px", + justifyContent: "flex-start", + alignItems: "center", + padding: "33px 44px 33px 55px", + backgroundColor: theme.palette.background.default, + border: `1px solid ${isDropzoneHighlighted ? "red" : "#9A9AAF"}`, + borderRadius: "8px", + }} + > + + + - - - - { - UPLOAD_FILE_DESCRIPTIONS_MAP[currentQuestion.content.type] - .title - } - - - { - UPLOAD_FILE_DESCRIPTIONS_MAP[currentQuestion.content.type] - .description - } - - - - - - - : - - } - setStatusModal(currentQuestion.content.type)} /> - - )} - {answer && currentQuestion.content.type === "picture" && ( - + + {UPLOAD_FILE_DESCRIPTIONS_MAP[currentQuestion.content.type].description} + + + + + } + setModalWarningType(currentQuestion.content.type)} /> - )} - {answer && currentQuestion.content.type === "video" && ( - + + )} + {answer && currentQuestion.content.type === "picture" && ( + + )} + {answer && currentQuestion.content.type === "video" && ( + setStatusModal("")} + open={modalWarningType !== null} + onClose={() => setModalWarningType(null)} > { boxShadow: 24, p: 4, }}> - + - + ); - +}; + +const CurrentModal = ({ status }: { status: ModalWarningType; }) => { + + switch (status) { + case null: return null; + case 'errorType': return Выбран некорректный тип файла; + case 'errorSize': return Файл слишком большой. Максимальный размер 50 МБ; + default: return ( + <> + Допустимые расширения файлов: + { + ACCEPT_SEND_FILE_TYPES_MAP[status].join(" ")} + + ); + } }; diff --git a/lib/components/ViewPublicationPage/tools/File.tsx b/lib/components/ViewPublicationPage/tools/File.tsx deleted file mode 100644 index 6598de6..0000000 --- a/lib/components/ViewPublicationPage/tools/File.tsx +++ /dev/null @@ -1,63 +0,0 @@ -import { ChangeEvent, useEffect, useRef, useState } from "react"; -import { Box, Button, Typography } from "@mui/material"; - -import type { - QuizQuestionFile, - UploadFileType, -} from "@model/questionTypes/file"; - -const UPLOAD_FILE_TYPES_MAP: Record = { - picture: "image/*", - video: "video/*", - audio: "audio/*", - document: - ".doc,.docx,application/msword,application/vnd.openxmlformats-officedocument.wordprocessingml.document,.pdf", -} as const; - -interface Props { - question: QuizQuestionFile; -} - -export default function File({ question }: Props) { - const fileInputRef = useRef(null); - const [file, setFile] = useState(null); - const [acceptedType, setAcceptedType] = useState( - UPLOAD_FILE_TYPES_MAP.picture - ); - - useEffect(() => { - setAcceptedType(UPLOAD_FILE_TYPES_MAP[question.content.type]); - }, [question.content.type]); - - function handleFileChange(event: ChangeEvent) { - if (!event.target.files?.[0]) return setFile(null); - setFile(event.target.files[0]); - } - - return ( - - {question.title} - - {file && Выбран файл: {file.name}} - - ); -} diff --git a/lib/components/ViewPublicationPage/tools/fileUpload.ts b/lib/components/ViewPublicationPage/tools/fileUpload.ts new file mode 100644 index 0000000..641b57c --- /dev/null +++ b/lib/components/ViewPublicationPage/tools/fileUpload.ts @@ -0,0 +1,69 @@ +import { UploadFileType } from "@model/questionTypes/file"; + + +export const MAX_FILE_SIZE = 5 * 2 ** 20; + +export const UPLOAD_FILE_DESCRIPTIONS_MAP = { + picture: { + title: "Добавить изображение", + description: "Принимает изображения", + }, + video: { + title: "Добавить видео", + description: "Принимает .mp4 и .mov формат — максимум 50мб", + }, + audio: { title: "Добавить аудиофайл", description: "Принимает аудиофайлы" }, + document: { title: "Добавить документ", description: "Принимает документы" }, +} as const satisfies Record; + +export const ACCEPT_SEND_FILE_TYPES_MAP = { + picture: [ + ".jpeg", + ".jpg", + ".png", + ".ico", + ".gif", + ".tiff", + ".webp", + ".eps", + ".svg" + ], + video: [ + ".mp4", + ".mov", + ".wmv", + ".avi", + ".avchd", + ".flv", + ".f4v", + ".swf", + ".mkv", + ".webm", + ".mpeg-2" + ], + audio: [ + ".aac", + ".aiff", + ".dsd", + ".flac", + ".mp3", + ".mqa", + ".ogg", + ".wav", + ".wma" + ], + document: [ + ".doc", + ".docx", + ".dotx", + ".rtf", + ".odt", + ".pdf", + ".txt", + ".xls", + ".ppt", + ".xlsx", + ".pptx", + ".pages", + ], +} as const;