fix file dragndrop

This commit is contained in:
nflnkr 2024-02-16 14:19:14 +03:00
parent 2203ad6d23
commit f6c8b3b13b
3 changed files with 284 additions and 364 deletions

@ -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 (<>
<Typography>Выбран некорректный тип файла</Typography>
</>);
case 'errorSize':
return (<>
<Typography>Файл слишком большой. Максимальный размер 50 МБ</Typography>
</>);
default:
return (<>
<Typography>Допустимые расширения файлов:</Typography>
<Typography>{
//@ts-ignore
ACCEPT_SEND_FILE_TYPES_MAP[status].join(" ")}</Typography>
</>);
}
};
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<ModalWarningType>(null);
const [isSending, setIsSending] = useState<boolean>(false);
const [isDropzoneHighlighted, setIsDropzoneHighlighted] = useState<boolean>(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<HTMLInputElement>) => {
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<HTMLDivElement>) => {
event.preventDefault();
setIsDropzoneHighlighted(false);
const file = event.dataTransfer.files[0];
uploadFile(file);
};
return (
<>
<Box>
<Typography variant="h5" color={theme.palette.text.primary} sx={{ wordBreak: "break-word" }}>{currentQuestion.title}</Typography>
<Box
sx={{
display: "flex",
flexDirection: "column",
width: "100%",
marginTop: "20px",
maxWidth: answer?.split("|")[0] ? "640px" : "600px",
}}
>
{answer?.split("|")[0] && (
<Box sx={{ display: "flex", alignItems: "center", gap: "15px" }}>
<Typography color={theme.palette.text.primary}>Вы загрузили:</Typography>
<Box
<Box>
<Typography
variant="h5"
color={theme.palette.text.primary}
sx={{ wordBreak: "break-word" }}
>{currentQuestion.title}</Typography>
<Box
sx={{
display: "flex",
flexDirection: "column",
width: "100%",
marginTop: "20px",
maxWidth: answer?.split("|")[0] ? "640px" : "600px",
}}
>
{answer?.split("|")[0] ? (
<Box sx={{ display: "flex", alignItems: "center", gap: "15px" }}>
<Typography color={theme.palette.text.primary}>Вы загрузили:</Typography>
<Box
sx={{
padding: "5px 5px 5px 16px",
backgroundColor: theme.palette.primary.main,
borderRadius: "8px",
color: "#FFFFFF",
display: "flex",
alignItems: "center",
overflow: "hidden",
gap: "15px",
}}
>
<Typography
sx={{
padding: "5px 5px 5px 16px",
backgroundColor: theme.palette.primary.main,
borderRadius: "8px",
color: "#FFFFFF",
display: "flex",
alignItems: "center",
whiteSpace: "nowrap",
textOverflow: "ellipsis",
overflow: "hidden",
gap: "15px",
}}
>
<Typography
sx={{
whiteSpace: "nowrap",
textOverflow: "ellipsis",
overflow: "hidden",
}}
>
{answer?.split("|")[0]}</Typography>
<IconButton
sx={{ p: 0 }}
onClick={async () => {
if (answer.length > 0) {
setReadySend(false)
await sendAnswer({
questionId: currentQuestion.id,
body: "",
qid: quizId,
})
}
console.log(answer)
updateAnswer(currentQuestion.id, "", 0);
setReadySend(true)
}}
>
<CloseBold />
</IconButton>
</Box>
{answer?.split("|")[0]}</Typography>
<IconButton
sx={{ p: 0 }}
onClick={async () => {
if (answer.length > 0) {
setIsSending(true);
await sendAnswer({
questionId: currentQuestion.id,
body: "",
qid: quizId,
});
}
console.log(answer);
updateAnswer(currentQuestion.id, "", 0);
setIsSending(false);
}}
>
<CloseBold />
</IconButton>
</Box>
)}
{!answer?.split("|")[0] && (
<Box sx={{
</Box>
) : (
<Box
sx={{
display: "flex",
alignItems: "center"
}}>
{
readySend ? <>
<ButtonBase component="label" sx={{ justifyContent: "flex-start", width: "100%" }}>
<input
onChange={uploadFile}
hidden
accept={ACCEPT_SEND_FILE_TYPES_MAP[currentQuestion.content.type].join(",")}
multiple
type="file"
/>
<Box
onDragOver={(event: DragEvent<HTMLDivElement>) =>
event.preventDefault()
}
}}
>
{isSending ?
<Skeleton
variant="rounded"
sx={{
width: "100%",
height: "120px",
maxWidth: "560px",
}}
/>
:
<ButtonBase
component="label"
sx={{ justifyContent: "flex-start", width: "100%" }}
>
<input
onChange={e => uploadFile(e.target.files?.[0])}
hidden
accept={ACCEPT_SEND_FILE_TYPES_MAP[currentQuestion.content.type].join(",")}
multiple
type="file"
/>
<Box
onDragEnter={() => !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",
}}
>
<UploadIcon />
<Box>
<Typography
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 #9A9AAF`,
// border: `1px solid ${theme.palette.grey2.main}`,
borderRadius: "8px",
color: "#9A9AAF",
// color: theme.palette.grey2.main,
fontWeight: 500,
}}
>
<UploadIcon />
<Box>
<Typography
sx={{
color: "#9A9AAF",
// color: theme.palette.grey2.main,
fontWeight: 500,
}}
>
{
UPLOAD_FILE_DESCRIPTIONS_MAP[currentQuestion.content.type]
.title
}
</Typography>
<Typography
sx={{
color: "#9A9AAF",
// color: theme.palette.grey2.main,
fontSize: "16px",
lineHeight: "19px",
}}
>
{
UPLOAD_FILE_DESCRIPTIONS_MAP[currentQuestion.content.type]
.description
}
</Typography>
</Box>
</Box>
</ButtonBase>
</> :
<Skeleton
variant="rounded"
sx={{
width: "100%",
height: "120px",
maxWidth: "560px",
}}
/>
}
<Info sx={{ width: "40px", height: "40px" }} color={theme.palette.primary.main} onClick={() => setStatusModal(currentQuestion.content.type)} />
</Box>
)}
{answer && currentQuestion.content.type === "picture" && (
<img
src={answer.split("|")[1]}
alt=""
style={{
marginTop: "15px",
maxWidth: "300px",
maxHeight: "300px",
}}
{UPLOAD_FILE_DESCRIPTIONS_MAP[currentQuestion.content.type].title}
</Typography>
<Typography
sx={{
color: "#9A9AAF",
// color: theme.palette.grey2.main,
fontSize: "16px",
lineHeight: "19px",
}}
>
{UPLOAD_FILE_DESCRIPTIONS_MAP[currentQuestion.content.type].description}
</Typography>
</Box>
</Box>
</ButtonBase>
}
<Info
sx={{ width: "40px", height: "40px" }}
color={theme.palette.primary.main}
onClick={() => setModalWarningType(currentQuestion.content.type)}
/>
)}
{answer && currentQuestion.content.type === "video" && (
<video
src={answer.split("|")[1]}
style={{
marginTop: "15px",
maxWidth: "300px",
maxHeight: "300px",
objectFit: "cover",
}}
/>
)}
</Box>
</Box>
)}
{answer && currentQuestion.content.type === "picture" && (
<img
src={answer.split("|")[1]}
alt=""
style={{
marginTop: "15px",
maxWidth: "300px",
maxHeight: "300px",
}}
/>
)}
{answer && currentQuestion.content.type === "video" && (
<video
src={answer.split("|")[1]}
style={{
marginTop: "15px",
maxWidth: "300px",
maxHeight: "300px",
objectFit: "cover",
}}
/>
)}
</Box>
<Modal
open={Boolean(statusModal)}
onClose={() => setStatusModal("")}
open={modalWarningType !== null}
onClose={() => setModalWarningType(null)}
>
<Box sx={{
position: 'absolute',
@ -363,10 +262,25 @@ export const File = ({ currentQuestion }: FileProps) => {
boxShadow: 24,
p: 4,
}}>
<CurrentModal status={statusModal} />
<CurrentModal status={modalWarningType} />
</Box>
</Modal>
</>
</Box>
);
};
const CurrentModal = ({ status }: { status: ModalWarningType; }) => {
switch (status) {
case null: return null;
case 'errorType': return <Typography>Выбран некорректный тип файла</Typography>;
case 'errorSize': return <Typography>Файл слишком большой. Максимальный размер 50 МБ</Typography>;
default: return (
<>
<Typography>Допустимые расширения файлов:</Typography>
<Typography>{
ACCEPT_SEND_FILE_TYPES_MAP[status].join(" ")}</Typography>
</>
);
}
};

@ -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<UploadFileType, string> = {
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<HTMLInputElement>(null);
const [file, setFile] = useState<File | null>(null);
const [acceptedType, setAcceptedType] = useState<any>(
UPLOAD_FILE_TYPES_MAP.picture
);
useEffect(() => {
setAcceptedType(UPLOAD_FILE_TYPES_MAP[question.content.type]);
}, [question.content.type]);
function handleFileChange(event: ChangeEvent<HTMLInputElement>) {
if (!event.target.files?.[0]) return setFile(null);
setFile(event.target.files[0]);
}
return (
<Box
sx={{
display: "flex",
flexDirection: "column",
alignItems: "start",
gap: 1,
}}
>
<Typography variant="h6" data-cy="question-title">{question.title}</Typography>
<Button variant="contained" onClick={() => fileInputRef.current?.click()}>
Загрузить файл
<input
ref={fileInputRef}
onChange={handleFileChange}
type="file"
accept={acceptedType}
data-cy="file-upload-input"
style={{
display: "none",
}}
/>
</Button>
{file && <Typography data-cy="chosen-file-name">Выбран файл: {file.name}</Typography>}
</Box>
);
}

@ -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<UploadFileType, { title: string; description: string; }>;
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;