возможность выбора своей картинки
This commit is contained in:
parent
311cdedce6
commit
b8b30a352b
@ -1,197 +1,33 @@
|
|||||||
import { Box, ButtonBase, IconButton, Typography, useTheme } from "@mui/material";
|
import React, { forwardRef } from "react";
|
||||||
import { useState, useRef } from "react";
|
import { useQuizViewStore } from "@stores/quizView";
|
||||||
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";
|
|
||||||
|
|
||||||
// Пропсы компонента
|
interface OwnVarimgImageProps {
|
||||||
export type OwnVarimgImageProps = {
|
|
||||||
imageUrl?: string;
|
|
||||||
questionId: string;
|
questionId: string;
|
||||||
variantId: string;
|
variantId: string;
|
||||||
onValidationError: (error: "size" | "type") => void;
|
}
|
||||||
};
|
|
||||||
|
|
||||||
export const OwnVarimgImage = ({ imageUrl, questionId, variantId, onValidationError }: OwnVarimgImageProps) => {
|
export const OwnVarimgImage = forwardRef<HTMLInputElement, OwnVarimgImageProps>(({ questionId, variantId }, ref) => {
|
||||||
const theme = useTheme();
|
const { updateAnswer, updateOwnVariantWithFile } = useQuizViewStore((state) => state);
|
||||||
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);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Обработчик выбора файла
|
|
||||||
const handleFileChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
const handleFileChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
const file = event.target.files?.[0];
|
const file = event.target.files?.[0];
|
||||||
if (file) {
|
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
|
// @ts-ignore
|
||||||
imageToDisplay = URL.createObjectURL(ownVariantData.variant.file);
|
updateOwnVariantWithFile(variantId, file);
|
||||||
} else {
|
updateAnswer(questionId, variantId, 0);
|
||||||
imageToDisplay = ownVariantData.variant.localImageUrl;
|
event.target.value = "";
|
||||||
}
|
}
|
||||||
} else if (imageUrl) {
|
};
|
||||||
imageToDisplay = imageUrl;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isUploading) {
|
|
||||||
return (
|
|
||||||
<Skeleton
|
|
||||||
variant="rounded"
|
|
||||||
sx={{ width: "100%", height: "100%", borderRadius: "12px" }}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ButtonBase
|
<input
|
||||||
component="div"
|
type="file"
|
||||||
onClick={handleClick}
|
ref={ref}
|
||||||
disabled={isUploading}
|
style={{ display: "none" }}
|
||||||
sx={{
|
accept="image/*"
|
||||||
width: "100%",
|
onChange={handleFileChange}
|
||||||
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}`}
|
|
||||||
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 type { QuestionVariant, QuestionVariantWithEditedImages } from "@/model/questionTypes/shared";
|
||||||
import { useQuizStore } from "@/stores/useQuizStore";
|
import { useQuizStore } from "@/stores/useQuizStore";
|
||||||
import {
|
import { FormControlLabel, TextareaAutosize, Radio, useTheme, Box, Input, Typography } from "@mui/material";
|
||||||
FormControlLabel,
|
|
||||||
TextareaAutosize,
|
|
||||||
Radio,
|
|
||||||
useTheme,
|
|
||||||
Box,
|
|
||||||
Input,
|
|
||||||
FormControl,
|
|
||||||
InputLabel,
|
|
||||||
Typography,
|
|
||||||
} from "@mui/material";
|
|
||||||
import { useQuizViewStore } from "@stores/quizView";
|
import { useQuizViewStore } from "@stores/quizView";
|
||||||
import RadioCheck from "@ui_kit/RadioCheck";
|
import RadioCheck from "@ui_kit/RadioCheck";
|
||||||
import RadioIcon from "@ui_kit/RadioIcon";
|
import RadioIcon from "@ui_kit/RadioIcon";
|
||||||
import { quizThemes } from "@utils/themes/Publication/themePublication";
|
import { quizThemes } from "@utils/themes/Publication/themePublication";
|
||||||
import { type MouseEvent } from "react";
|
import { type MouseEvent } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { useDebouncedCallback } from "use-debounce";
|
|
||||||
|
|
||||||
type VarimgVariantProps = {
|
type VarimgVariantProps = {
|
||||||
questionId: string;
|
questionId: string;
|
||||||
@ -175,16 +164,12 @@ export const VarimgVariant = ({
|
|||||||
value={index}
|
value={index}
|
||||||
onClick={sendVariant}
|
onClick={sendVariant}
|
||||||
label={
|
label={
|
||||||
variant?.isOwn ? (
|
<OwnInput
|
||||||
<OwnInput
|
questionId={questionId}
|
||||||
questionId={questionId}
|
variant={variant}
|
||||||
variant={variant}
|
largeCheck={questionLargeCheck}
|
||||||
largeCheck={questionLargeCheck}
|
ownPlaceholder={ownPlaceholder || "|"}
|
||||||
ownPlaceholder={ownPlaceholder || "|"}
|
/>
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
variant.answer
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
control={
|
control={
|
||||||
<Radio
|
<Radio
|
||||||
@ -239,18 +224,7 @@ export const VarimgVariant = ({
|
|||||||
labelPlacement="start"
|
labelPlacement="start"
|
||||||
value={index}
|
value={index}
|
||||||
onClick={sendVariant}
|
onClick={sendVariant}
|
||||||
label={
|
label={variant.answer}
|
||||||
variant?.isOwn ? (
|
|
||||||
<OwnInput
|
|
||||||
questionId={questionId}
|
|
||||||
variant={variant}
|
|
||||||
largeCheck={questionLargeCheck}
|
|
||||||
ownPlaceholder={ownPlaceholder || "|"}
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
variant.answer
|
|
||||||
)
|
|
||||||
}
|
|
||||||
control={
|
control={
|
||||||
<Radio
|
<Radio
|
||||||
checkedIcon={<RadioCheck color={theme.palette.primary.main} />}
|
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 { Box, ButtonBase, RadioGroup, Typography, useTheme } from "@mui/material";
|
||||||
|
|
||||||
import { VarimgVariant } from "./VarimgVariant";
|
import { VarimgVariant } from "./VarimgVariant";
|
||||||
|
import { OwnVarimgImage } from "./OwnVarimgImage";
|
||||||
|
|
||||||
import { useQuizViewStore } from "@stores/quizView";
|
import { useQuizViewStore } from "@stores/quizView";
|
||||||
import { useRootContainerSize } from "@contexts/RootContainerWidthContext";
|
import { useRootContainerSize } from "@contexts/RootContainerWidthContext";
|
||||||
@ -35,9 +36,13 @@ export const Varimg = ({ currentQuestion }: VarimgProps) => {
|
|||||||
[currentQuestion.content.variants]
|
[currentQuestion.content.variants]
|
||||||
);
|
);
|
||||||
const ownVariantData = ownVariants.find((v) => v.id === answer);
|
const ownVariantData = ownVariants.find((v) => v.id === answer);
|
||||||
const ownImageUrl = ownVariantData?.variant.file
|
const ownImageUrl = useMemo(() => {
|
||||||
? URL.createObjectURL(ownVariantData.variant.file)
|
return ownVariantData?.variant.file
|
||||||
: ownVariantData?.variant.localImageUrl;
|
? URL.createObjectURL(ownVariantData.variant.file)
|
||||||
|
: ownVariantData?.variant.localImageUrl;
|
||||||
|
}, [ownVariantData]);
|
||||||
|
|
||||||
|
const inputRef = useRef<HTMLInputElement>(null);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!ownVariant) {
|
if (!ownVariant) {
|
||||||
@ -67,15 +72,9 @@ export const Varimg = ({ currentQuestion }: VarimgProps) => {
|
|||||||
}
|
}
|
||||||
}, [variant]);
|
}, [variant]);
|
||||||
|
|
||||||
const handlePlaceholderClick = () => {
|
|
||||||
if (ownVariantInQuestion) {
|
|
||||||
document.getElementById(`own-image-input-${ownVariantInQuestion.id}`)?.click();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handlePreviewAreaClick = () => {
|
const handlePreviewAreaClick = () => {
|
||||||
if (ownVariantInQuestion) {
|
if (ownVariantInQuestion) {
|
||||||
document.getElementById(`own-image-input-${ownVariantInQuestion.id}`)?.click();
|
inputRef.current?.click();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -140,6 +139,13 @@ export const Varimg = ({ currentQuestion }: VarimgProps) => {
|
|||||||
answer={answer}
|
answer={answer}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
|
{ownVariantInQuestion && (
|
||||||
|
<OwnVarimgImage
|
||||||
|
ref={inputRef}
|
||||||
|
questionId={currentQuestion.id}
|
||||||
|
variantId={ownVariantInQuestion.id}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
</RadioGroup>
|
</RadioGroup>
|
||||||
<ButtonBase
|
<ButtonBase
|
||||||
|
@ -54,6 +54,7 @@ export type QuestionVariant = {
|
|||||||
/** Локальный URL для предпросмотра */
|
/** Локальный URL для предпросмотра */
|
||||||
localImageUrl?: string;
|
localImageUrl?: string;
|
||||||
points?: number;
|
points?: number;
|
||||||
|
file?: File;
|
||||||
};
|
};
|
||||||
export interface QuestionVariantWithEditedImages extends QuestionVariant {
|
export interface QuestionVariantWithEditedImages extends QuestionVariant {
|
||||||
editedUrlImagesList?: EditedUrlImagesList | null;
|
editedUrlImagesList?: EditedUrlImagesList | null;
|
||||||
|
@ -37,6 +37,7 @@ interface QuizViewActions {
|
|||||||
originalImageUrl?: string,
|
originalImageUrl?: string,
|
||||||
localImageUrl?: string
|
localImageUrl?: string
|
||||||
) => void;
|
) => void;
|
||||||
|
updateOwnVariantWithFile: (variantId: string, file: File) => void;
|
||||||
deleteOwnVariant: (id: string) => void;
|
deleteOwnVariant: (id: string) => void;
|
||||||
setCurrentQuizStep: (step: QuizStep) => 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) {
|
deleteOwnVariant(id) {
|
||||||
set(
|
set(
|
||||||
(state) => {
|
(state) => {
|
||||||
|
Loading…
Reference in New Issue
Block a user