возможность выбора своей картинки
This commit is contained in:
parent
311cdedce6
commit
b8b30a352b
@ -1,197 +1,33 @@
|
||||
import { Box, ButtonBase, IconButton, Typography, useTheme } from "@mui/material";
|
||||
import { useState, useRef } from "react";
|
||||
import CloseIcon from "@mui/icons-material/Close";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useQuizStore } from "@/stores/useQuizStore";
|
||||
import { useQuizViewStore } from "@/stores/quizView";
|
||||
import { useSnackbar } from "notistack";
|
||||
import { Skeleton } from "@mui/material";
|
||||
import UploadIcon from "@/assets/icons/UploadIcon";
|
||||
import { sendFile } from "@/api/quizRelase";
|
||||
import { ACCEPT_SEND_FILE_TYPES_MAP, MAX_FILE_SIZE } from "../../tools/fileUpload";
|
||||
import React, { forwardRef } from "react";
|
||||
import { useQuizViewStore } from "@stores/quizView";
|
||||
|
||||
// Пропсы компонента
|
||||
export type OwnVarimgImageProps = {
|
||||
imageUrl?: string;
|
||||
interface OwnVarimgImageProps {
|
||||
questionId: string;
|
||||
variantId: string;
|
||||
onValidationError: (error: "size" | "type") => void;
|
||||
};
|
||||
|
||||
export const OwnVarimgImage = ({ imageUrl, questionId, variantId, onValidationError }: OwnVarimgImageProps) => {
|
||||
const theme = useTheme();
|
||||
const { t } = useTranslation();
|
||||
const { quizId, preview } = useQuizStore();
|
||||
const { ownVariants, updateOwnVariant, updateAnswer } = useQuizViewStore((state) => state);
|
||||
const { enqueueSnackbar } = useSnackbar();
|
||||
|
||||
const [selectedFile, setSelectedFile] = useState<File | null>(null);
|
||||
const [isUploading, setIsUploading] = useState(false);
|
||||
const fileInputRef = useRef<HTMLInputElement>(null);
|
||||
|
||||
// Получаем ownVariant для этого варианта
|
||||
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;
|
||||
}
|
||||
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, name: file.name, preview },
|
||||
qid: quizId,
|
||||
});
|
||||
const fileId = data?.data.fileIDMap[questionId];
|
||||
const localImageUrl = URL.createObjectURL(file);
|
||||
// @ts-ignore
|
||||
updateOwnVariant(variantId, "", "", fileId, localImageUrl, file);
|
||||
updateAnswer(questionId, variantId, 0);
|
||||
setSelectedFile(file);
|
||||
} catch (error) {
|
||||
console.error("Error uploading image:", error);
|
||||
enqueueSnackbar(t("The answer was not counted"));
|
||||
} finally {
|
||||
setIsUploading(false);
|
||||
}
|
||||
};
|
||||
|
||||
// Обработчик выбора файла
|
||||
export const OwnVarimgImage = forwardRef<HTMLInputElement, OwnVarimgImageProps>(({ questionId, variantId }, ref) => {
|
||||
const { updateAnswer, updateOwnVariantWithFile } = useQuizViewStore((state) => state);
|
||||
|
||||
const handleFileChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const file = event.target.files?.[0];
|
||||
if (file) {
|
||||
uploadImage(file);
|
||||
}
|
||||
};
|
||||
|
||||
// Открытие диалога выбора файла
|
||||
const handleClick = (e: React.MouseEvent) => {
|
||||
e.stopPropagation();
|
||||
if (fileInputRef.current) fileInputRef.current.value = "";
|
||||
fileInputRef.current?.click();
|
||||
};
|
||||
|
||||
// Удаление изображения
|
||||
const handleRemoveImage = (e: React.MouseEvent) => {
|
||||
e.stopPropagation();
|
||||
setSelectedFile(null);
|
||||
updateOwnVariant(variantId, "", "", "", "");
|
||||
};
|
||||
|
||||
// Определяем, что показывать
|
||||
let imageToDisplay: string | null = null;
|
||||
if (selectedFile) {
|
||||
imageToDisplay = URL.createObjectURL(selectedFile);
|
||||
} else if (ownVariantData?.variant.localImageUrl) {
|
||||
// @ts-ignore
|
||||
if (ownVariantData.variant.file) {
|
||||
// @ts-ignore
|
||||
imageToDisplay = URL.createObjectURL(ownVariantData.variant.file);
|
||||
} else {
|
||||
imageToDisplay = ownVariantData.variant.localImageUrl;
|
||||
}
|
||||
} else if (imageUrl) {
|
||||
imageToDisplay = imageUrl;
|
||||
}
|
||||
|
||||
if (isUploading) {
|
||||
return (
|
||||
<Skeleton
|
||||
variant="rounded"
|
||||
sx={{ width: "100%", height: "100%", borderRadius: "12px" }}
|
||||
/>
|
||||
);
|
||||
updateOwnVariantWithFile(variantId, file);
|
||||
updateAnswer(questionId, variantId, 0);
|
||||
event.target.value = "";
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<ButtonBase
|
||||
component="div"
|
||||
onClick={handleClick}
|
||||
disabled={isUploading}
|
||||
sx={{
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
borderRadius: "12px",
|
||||
transition: "border-color 0.3s, background-color 0.3s",
|
||||
overflow: "hidden",
|
||||
position: "relative",
|
||||
opacity: isUploading ? 0.7 : 1,
|
||||
"&:hover": {
|
||||
backgroundColor: "rgba(0, 0, 0, 0.1)",
|
||||
},
|
||||
}}
|
||||
>
|
||||
<input
|
||||
type="file"
|
||||
ref={fileInputRef}
|
||||
id={`own-image-input-${variantId}`}
|
||||
ref={ref}
|
||||
style={{ display: "none" }}
|
||||
accept="image/*"
|
||||
onChange={handleFileChange}
|
||||
accept={ACCEPT_SEND_FILE_TYPES_MAP.picture.join(",")}
|
||||
hidden
|
||||
/>
|
||||
{imageToDisplay ? (
|
||||
<>
|
||||
<Box sx={{ width: "100%", height: "100%", position: "relative" }}>
|
||||
<img
|
||||
src={imageToDisplay}
|
||||
alt="Preview"
|
||||
style={{ width: "100%", height: "100%", objectFit: "cover" }}
|
||||
/>
|
||||
</Box>
|
||||
<IconButton
|
||||
onClick={handleRemoveImage}
|
||||
sx={{
|
||||
position: "absolute",
|
||||
top: 8,
|
||||
right: 8,
|
||||
zIndex: 1,
|
||||
backgroundColor: "rgba(0, 0, 0, 0.5)",
|
||||
color: "white",
|
||||
height: "25px",
|
||||
width: "25px",
|
||||
"&:hover": {
|
||||
backgroundColor: "rgba(0, 0, 0, 0.7)",
|
||||
},
|
||||
}}
|
||||
>
|
||||
<CloseIcon />
|
||||
</IconButton>
|
||||
</>
|
||||
) : (
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
opacity: 0.5,
|
||||
}}
|
||||
>
|
||||
<UploadIcon />
|
||||
<Typography
|
||||
variant="body2"
|
||||
color="text.secondary"
|
||||
sx={{ p: 2, textAlign: "center" }}
|
||||
>
|
||||
добавьте свою картинку
|
||||
</Typography>
|
||||
</Box>
|
||||
)}
|
||||
</ButtonBase>
|
||||
);
|
||||
};
|
||||
});
|
||||
|
||||
OwnVarimgImage.displayName = "OwnVarimgImage";
|
||||
|
@ -1,23 +1,12 @@
|
||||
import type { QuestionVariant, QuestionVariantWithEditedImages } from "@/model/questionTypes/shared";
|
||||
import { useQuizStore } from "@/stores/useQuizStore";
|
||||
import {
|
||||
FormControlLabel,
|
||||
TextareaAutosize,
|
||||
Radio,
|
||||
useTheme,
|
||||
Box,
|
||||
Input,
|
||||
FormControl,
|
||||
InputLabel,
|
||||
Typography,
|
||||
} from "@mui/material";
|
||||
import { FormControlLabel, TextareaAutosize, Radio, useTheme, Box, Input, Typography } from "@mui/material";
|
||||
import { useQuizViewStore } from "@stores/quizView";
|
||||
import RadioCheck from "@ui_kit/RadioCheck";
|
||||
import RadioIcon from "@ui_kit/RadioIcon";
|
||||
import { quizThemes } from "@utils/themes/Publication/themePublication";
|
||||
import { type MouseEvent } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useDebouncedCallback } from "use-debounce";
|
||||
|
||||
type VarimgVariantProps = {
|
||||
questionId: string;
|
||||
@ -175,16 +164,12 @@ export const VarimgVariant = ({
|
||||
value={index}
|
||||
onClick={sendVariant}
|
||||
label={
|
||||
variant?.isOwn ? (
|
||||
<OwnInput
|
||||
questionId={questionId}
|
||||
variant={variant}
|
||||
largeCheck={questionLargeCheck}
|
||||
ownPlaceholder={ownPlaceholder || "|"}
|
||||
/>
|
||||
) : (
|
||||
variant.answer
|
||||
)
|
||||
}
|
||||
control={
|
||||
<Radio
|
||||
@ -239,18 +224,7 @@ export const VarimgVariant = ({
|
||||
labelPlacement="start"
|
||||
value={index}
|
||||
onClick={sendVariant}
|
||||
label={
|
||||
variant?.isOwn ? (
|
||||
<OwnInput
|
||||
questionId={questionId}
|
||||
variant={variant}
|
||||
largeCheck={questionLargeCheck}
|
||||
ownPlaceholder={ownPlaceholder || "|"}
|
||||
/>
|
||||
) : (
|
||||
variant.answer
|
||||
)
|
||||
}
|
||||
label={variant.answer}
|
||||
control={
|
||||
<Radio
|
||||
checkedIcon={<RadioCheck color={theme.palette.primary.main} />}
|
||||
|
@ -1,7 +1,8 @@
|
||||
import { useEffect, useMemo, useState } from "react";
|
||||
import { useEffect, useMemo, useRef, useState } from "react";
|
||||
import { Box, ButtonBase, RadioGroup, Typography, useTheme } from "@mui/material";
|
||||
|
||||
import { VarimgVariant } from "./VarimgVariant";
|
||||
import { OwnVarimgImage } from "./OwnVarimgImage";
|
||||
|
||||
import { useQuizViewStore } from "@stores/quizView";
|
||||
import { useRootContainerSize } from "@contexts/RootContainerWidthContext";
|
||||
@ -35,9 +36,13 @@ export const Varimg = ({ currentQuestion }: VarimgProps) => {
|
||||
[currentQuestion.content.variants]
|
||||
);
|
||||
const ownVariantData = ownVariants.find((v) => v.id === answer);
|
||||
const ownImageUrl = ownVariantData?.variant.file
|
||||
const ownImageUrl = useMemo(() => {
|
||||
return ownVariantData?.variant.file
|
||||
? URL.createObjectURL(ownVariantData.variant.file)
|
||||
: ownVariantData?.variant.localImageUrl;
|
||||
}, [ownVariantData]);
|
||||
|
||||
const inputRef = useRef<HTMLInputElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (!ownVariant) {
|
||||
@ -67,15 +72,9 @@ export const Varimg = ({ currentQuestion }: VarimgProps) => {
|
||||
}
|
||||
}, [variant]);
|
||||
|
||||
const handlePlaceholderClick = () => {
|
||||
if (ownVariantInQuestion) {
|
||||
document.getElementById(`own-image-input-${ownVariantInQuestion.id}`)?.click();
|
||||
}
|
||||
};
|
||||
|
||||
const handlePreviewAreaClick = () => {
|
||||
if (ownVariantInQuestion) {
|
||||
document.getElementById(`own-image-input-${ownVariantInQuestion.id}`)?.click();
|
||||
inputRef.current?.click();
|
||||
}
|
||||
};
|
||||
|
||||
@ -140,6 +139,13 @@ export const Varimg = ({ currentQuestion }: VarimgProps) => {
|
||||
answer={answer}
|
||||
/>
|
||||
))}
|
||||
{ownVariantInQuestion && (
|
||||
<OwnVarimgImage
|
||||
ref={inputRef}
|
||||
questionId={currentQuestion.id}
|
||||
variantId={ownVariantInQuestion.id}
|
||||
/>
|
||||
)}
|
||||
</Box>
|
||||
</RadioGroup>
|
||||
<ButtonBase
|
||||
|
@ -54,6 +54,7 @@ export type QuestionVariant = {
|
||||
/** Локальный URL для предпросмотра */
|
||||
localImageUrl?: string;
|
||||
points?: number;
|
||||
file?: File;
|
||||
};
|
||||
export interface QuestionVariantWithEditedImages extends QuestionVariant {
|
||||
editedUrlImagesList?: EditedUrlImagesList | null;
|
||||
|
@ -37,6 +37,7 @@ interface QuizViewActions {
|
||||
originalImageUrl?: string,
|
||||
localImageUrl?: string
|
||||
) => void;
|
||||
updateOwnVariantWithFile: (variantId: string, file: File) => void;
|
||||
deleteOwnVariant: (id: string) => void;
|
||||
setCurrentQuizStep: (step: QuizStep) => void;
|
||||
}
|
||||
@ -134,6 +135,35 @@ export const createQuizViewStore = () =>
|
||||
}
|
||||
);
|
||||
},
|
||||
updateOwnVariantWithFile(variantId, file) {
|
||||
set(
|
||||
(state) => {
|
||||
const index = state.ownVariants.findIndex((v) => v.id === variantId);
|
||||
|
||||
if (index < 0) {
|
||||
state.ownVariants.push({
|
||||
id: variantId,
|
||||
variant: {
|
||||
id: variantId,
|
||||
answer: "",
|
||||
extendedText: "",
|
||||
hints: "",
|
||||
originalImageUrl: "",
|
||||
file: file,
|
||||
},
|
||||
});
|
||||
} else {
|
||||
state.ownVariants[index].variant.file = file;
|
||||
state.ownVariants[index].variant.localImageUrl = undefined;
|
||||
}
|
||||
},
|
||||
false,
|
||||
{
|
||||
type: "updateOwnVariantWithFile",
|
||||
variantId,
|
||||
}
|
||||
);
|
||||
},
|
||||
deleteOwnVariant(id) {
|
||||
set(
|
||||
(state) => {
|
||||
|
Loading…
Reference in New Issue
Block a user