diff --git a/lib/components/ViewPublicationPage/questions/Images/ImageVariant.tsx b/lib/components/ViewPublicationPage/questions/Images/ImageVariant.tsx index 7b01ade..424c67e 100644 --- a/lib/components/ViewPublicationPage/questions/Images/ImageVariant.tsx +++ b/lib/components/ViewPublicationPage/questions/Images/ImageVariant.tsx @@ -9,6 +9,7 @@ import { useRootContainerSize } from "@contexts/RootContainerWidthContext"; import { useQuizStore } from "@/stores/useQuizStore"; import { useTranslation } from "react-i18next"; import { OwnImage } from "./OwnImage"; +import { useSnackbar } from "notistack"; type ImagesProps = { questionId: string; @@ -99,6 +100,7 @@ export const ImageVariant = ({ const { t } = useTranslation(); const isMobile = useRootContainerSize() < 450; const isTablet = useRootContainerSize() < 850; + const { enqueueSnackbar } = useSnackbar(); const canvasRef = useRef(null); @@ -171,7 +173,16 @@ export const ImageVariant = ({ {own ? ( - + { + enqueueSnackbar(errorType === "size" ? t("file is too big") : t("file type is not supported"), { + variant: "warning", + }); + }} + /> ) : ( variant.extendedText && ( void; }; -export const OwnImage = ({ imageUrl }: OwnImageProps) => { +export const OwnImage = ({ imageUrl, questionId, variantId, onValidationError }: OwnImageProps) => { const theme = useTheme(); + const { t } = useTranslation(); + const { quizId, preview } = useQuizStore(); + const { answers, updateAnswer, ownVariants, updateOwnVariant } = useQuizViewStore((state) => state); + const { enqueueSnackbar } = useSnackbar(); + const [selectedFile, setSelectedFile] = useState(null); - const [isDropzoneHighlighted, setIsDropzoneHighlighted] = useState(false); + const [isUploading, setIsUploading] = useState(false); const fileInputRef = useRef(null); - // Sync state if external imageUrl changes - useEffect(() => { - if (imageUrl) { - setSelectedFile(null); // Clear local file selection when external URL is provided + const ownVariantData = ownVariants.find((v) => v.id === variantId); + + const uploadImage = async (file: File) => { + if (isUploading) return; + if (!file) return; + + if (file.size > MAX_FILE_SIZE) { + onValidationError("size"); + return; } - }, [imageUrl]); + + const isFileTypeAccepted = ACCEPT_SEND_FILE_TYPES_MAP.picture.some((fileType) => + file.name.toLowerCase().endsWith(fileType) + ); + if (!isFileTypeAccepted) { + onValidationError("type"); + return; + } + + setIsUploading(true); + + try { + const data = await sendFile({ + questionId, + body: { + file: file, + name: file.name, + preview, + }, + qid: quizId, + }); + + const fileId = data!.data.fileIDMap[questionId]; + + // Сохраняем fileId в originalImageUrl + updateOwnVariant(variantId, "", "", fileId); + + // Для UI — локальный preview + const localImageUrl = URL.createObjectURL(file); + updateAnswer(questionId, `${file.name}|${localImageUrl}`, 0); + setSelectedFile(file); + } catch (error) { + console.error("Error uploading image:", error); + enqueueSnackbar(t("The answer was not counted")); + } finally { + setIsUploading(false); + } + }; const handleFileChange = (event: React.ChangeEvent) => { const file = event.target.files?.[0]; - if (file && file.type.startsWith("image/")) { - setSelectedFile(file); + if (file) { + uploadImage(file); } }; - const handleDrop = (event: React.DragEvent) => { - event.preventDefault(); - event.stopPropagation(); - setIsDropzoneHighlighted(false); - const file = event.dataTransfer.files?.[0]; - if (file && file.type.startsWith("image/")) { - setSelectedFile(file); - } - }; - - const handleDragOver = (event: React.DragEvent) => { - event.preventDefault(); - event.stopPropagation(); - }; - - const handleDragEnter = (event: React.DragEvent) => { - event.preventDefault(); - event.stopPropagation(); - setIsDropzoneHighlighted(true); - }; - - const handleDragLeave = (event: React.DragEvent) => { - event.preventDefault(); - event.stopPropagation(); - setIsDropzoneHighlighted(false); - }; - const handleClick = (e: React.MouseEvent) => { e.stopPropagation(); if (fileInputRef.current) { @@ -64,18 +95,28 @@ export const OwnImage = ({ imageUrl }: OwnImageProps) => { const handleRemoveImage = (e: React.MouseEvent) => { e.stopPropagation(); setSelectedFile(null); + updateAnswer(questionId, "", 0); + updateOwnVariant(variantId, "", "", ""); }; - const imageToDisplay = selectedFile ? URL.createObjectURL(selectedFile) : imageUrl; + const imageToDisplay = selectedFile + ? URL.createObjectURL(selectedFile) + : ownVariantData?.variant.originalImageUrl || imageUrl; + + if (isUploading) { + return ( + + ); + } return ( { transition: "border-color 0.3s, background-color 0.3s", overflow: "hidden", position: "relative", - "&:hover .overlay": { - opacity: 1, - }, + opacity: isUploading ? 0.7 : 1, }} > + {imageToDisplay ? ( <> @@ -107,21 +147,7 @@ export const OwnImage = ({ imageUrl }: OwnImageProps) => { style={{ width: "100%", height: "100%", objectFit: "cover" }} /> - - {selectedFile && ( + {(selectedFile || ownVariantData?.variant.originalImageUrl) && ( { zIndex: 1, backgroundColor: "rgba(0, 0, 0, 0.5)", color: "white", + height: "25px", + width: "25px", "&:hover": { backgroundColor: "rgba(0, 0, 0, 0.7)", }, @@ -150,6 +178,7 @@ export const OwnImage = ({ imageUrl }: OwnImageProps) => { opacity: 0.5, }} > + void; deleteAnswer: (questionId: string) => void; - updateOwnVariant: (id: string, answer: string, extendedText?: string) => void; + updateOwnVariant: (id: string, answer: string, extendedText?: string, originalImageUrl?: string) => void; deleteOwnVariant: (id: string) => void; setCurrentQuizStep: (step: QuizStep) => void; } @@ -90,7 +90,7 @@ export const createQuizViewStore = () => } ); }, - updateOwnVariant(id, answer, extendedText) { + updateOwnVariant(id, answer, extendedText, originalImageUrl) { set( (state) => { const index = state.ownVariants.findIndex((variant) => variant.id === id); @@ -103,7 +103,7 @@ export const createQuizViewStore = () => answer, extendedText: extendedText || "", hints: "", - originalImageUrl: "", + originalImageUrl: originalImageUrl || "", }, }); } else { @@ -111,6 +111,9 @@ export const createQuizViewStore = () => if (extendedText) { state.ownVariants[index].variant.extendedText = extendedText; } + if (originalImageUrl) { + state.ownVariants[index].variant.originalImageUrl = originalImageUrl; + } } }, false, diff --git a/lib/utils/sendQuestionAnswer.ts b/lib/utils/sendQuestionAnswer.ts index 2d34023..939e2a8 100644 --- a/lib/utils/sendQuestionAnswer.ts +++ b/lib/utils/sendQuestionAnswer.ts @@ -116,8 +116,25 @@ export async function sendQuestionAnswer( let answerString = ``; selectedVariants.forEach((e) => { if (!e.isOwn || (e.isOwn && question.content.own)) { + let imageValue = e.extendedText; + if (e.isOwn) { + // Берем fileId из ownVariants для own вариантов + const ownVariantData = ownVariants.find((v) => v.id === e.id)?.variant; + if (ownVariantData?.originalImageUrl) { + // Конструируем полный URL для own вариантов + const baseUrl = + "https://s3.timeweb.cloud/3c580be9-cf31f296-d055-49cf-b39e-30c7959dc17b/squizimages/55c25eb9-4533-4d51-9da5-54e63e8aeace/"; + // Убираем расширение файла из fileId + const fileIdWithoutExtension = ownVariantData.originalImageUrl.replace( + /\.(jpg|jpeg|png|gif|webp)$/i, + "" + ); + imageValue = baseUrl + fileIdWithoutExtension; + } + } + const body = { - Image: e.extendedText, + Image: imageValue, Description: e.isOwn ? ownAnswer : e.answer, }; answerString += `\`${JSON.stringify(body)}\`,`; @@ -135,8 +152,23 @@ export async function sendQuestionAnswer( const variant = question.content.variants.find((v) => v.id === questionAnswer.answer); if (!variant) throw new Error(`Cannot find variant with id ${questionAnswer.answer} in question ${question.id}`); + + let imageValue = variant.extendedText; + if (variant.isOwn) { + // Берем fileId из ownVariants для own вариантов + const ownVariantData = ownVariants.find((v) => v.id === variant.id)?.variant; + if (ownVariantData?.originalImageUrl) { + // Конструируем полный URL для own вариантов + const baseUrl = + "https://s3.timeweb.cloud/3c580be9-cf31f296-d055-49cf-b39e-30c7959dc17b/squizimages/55c25eb9-4533-4d51-9da5-54e63e8aeace/"; + // Убираем расширение файла из fileId + const fileIdWithoutExtension = ownVariantData.originalImageUrl.replace(/\.(jpg|jpeg|png|gif|webp)$/i, ""); + imageValue = baseUrl + fileIdWithoutExtension; + } + } + const body = { - Image: variant.extendedText, + Image: imageValue, Description: variant.answer, }; if (!body) throw new Error(`Body of answer in question ${question.id} is undefined`);