add using video embeds by url
This commit is contained in:
parent
f516897dad
commit
b41251966a
@ -7,7 +7,7 @@
|
||||
"@emotion/react": "^11.10.5",
|
||||
"@emotion/styled": "^11.10.5",
|
||||
"@frontend/kitui": "^1.0.82",
|
||||
"@frontend/squzanswerer": "^1.0.44",
|
||||
"@frontend/squzanswerer": "^1.0.45",
|
||||
"@mui/icons-material": "^5.10.14",
|
||||
"@mui/material": "^5.10.14",
|
||||
"@mui/x-charts": "^6.19.5",
|
||||
|
@ -12,7 +12,7 @@ export interface QuizQuestionPage extends QuizQuestionBase {
|
||||
picture: string;
|
||||
originalPicture: string;
|
||||
useImage: boolean;
|
||||
video: string;
|
||||
video: string | null;
|
||||
hint: QuestionHint;
|
||||
rule: PreviewRule;
|
||||
back: string;
|
||||
|
@ -1,8 +1,4 @@
|
||||
import type {
|
||||
QuizQuestionBase,
|
||||
QuestionBranchingRule,
|
||||
QuestionHint,
|
||||
} from "./shared";
|
||||
import type { QuizQuestionBase, QuestionBranchingRule, QuestionHint } from "./shared";
|
||||
|
||||
export interface QuizQuestionResult extends QuizQuestionBase {
|
||||
type: "result";
|
||||
@ -10,7 +6,7 @@ export interface QuizQuestionResult extends QuizQuestionBase {
|
||||
id: string;
|
||||
back: string;
|
||||
originalBack: string;
|
||||
video: string;
|
||||
video: string | null;
|
||||
innerName: string;
|
||||
text: string;
|
||||
price: [number] | [number, number];
|
||||
|
@ -1,18 +1,5 @@
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
IconButton,
|
||||
Modal,
|
||||
Typography,
|
||||
useMediaQuery,
|
||||
useTheme,
|
||||
} from "@mui/material";
|
||||
import {
|
||||
copyQuestion,
|
||||
deleteQuestion,
|
||||
deleteQuestionWithTimeout,
|
||||
updateQuestion,
|
||||
} from "@root/questions/actions";
|
||||
import { Box, Button, IconButton, Modal, Typography, useMediaQuery, useTheme } from "@mui/material";
|
||||
import { copyQuestion, deleteQuestion, deleteQuestionWithTimeout, updateQuestion } from "@root/questions/actions";
|
||||
import CustomTextField from "@ui_kit/CustomTextField";
|
||||
|
||||
import { CopyIcon } from "@icons/questionsPage/CopyIcon";
|
||||
@ -71,7 +58,7 @@ export default function PageOptions({ disableInput, question }: Props) {
|
||||
</Box>
|
||||
|
||||
<MediaSelectionAndDisplay
|
||||
resultData={question}
|
||||
question={question}
|
||||
cropAspectRatio={{ width: 1388.8, height: 793.2 }}
|
||||
/>
|
||||
</Box>
|
||||
@ -107,16 +94,17 @@ export default function PageOptions({ disableInput, question }: Props) {
|
||||
if (question.content.rule.parentId.length !== 0) {
|
||||
setOpenDelete(true);
|
||||
} else {
|
||||
deleteQuestionWithTimeout(question.id, () =>
|
||||
DeleteFunction(question.id),
|
||||
);
|
||||
deleteQuestionWithTimeout(question.id, () => DeleteFunction(question.id));
|
||||
}
|
||||
}}
|
||||
data-cy="delete-question"
|
||||
>
|
||||
<DeleteIcon color={"#4D4D4D"} />
|
||||
</IconButton>
|
||||
<Modal open={openDelete} onClose={() => setOpenDelete(false)}>
|
||||
<Modal
|
||||
open={openDelete}
|
||||
onClose={() => setOpenDelete(false)}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
position: "absolute",
|
||||
@ -128,10 +116,12 @@ export default function PageOptions({ disableInput, question }: Props) {
|
||||
background: "#FFFFFF",
|
||||
}}
|
||||
>
|
||||
<Typography variant="h6" sx={{ textAlign: "center" }}>
|
||||
Вы удаляете вопрос, участвующий в ветвлении. Все его потомки
|
||||
потеряют данные ветвления. Вы уверены, что хотите удалить
|
||||
вопрос?
|
||||
<Typography
|
||||
variant="h6"
|
||||
sx={{ textAlign: "center" }}
|
||||
>
|
||||
Вы удаляете вопрос, участвующий в ветвлении. Все его потомки потеряют данные ветвления. Вы уверены, что
|
||||
хотите удалить вопрос?
|
||||
</Typography>
|
||||
<Box
|
||||
sx={{
|
||||
@ -152,9 +142,7 @@ export default function PageOptions({ disableInput, question }: Props) {
|
||||
variant="contained"
|
||||
sx={{ minWidth: "150px" }}
|
||||
onClick={() => {
|
||||
deleteQuestionWithTimeout(question.id, () =>
|
||||
DeleteFunction(question.id),
|
||||
);
|
||||
deleteQuestionWithTimeout(question.id, () => DeleteFunction(question.id));
|
||||
}}
|
||||
>
|
||||
Подтвердить
|
||||
|
@ -1,16 +1,8 @@
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
ButtonBase,
|
||||
Modal,
|
||||
Typography,
|
||||
useTheme,
|
||||
} from "@mui/material";
|
||||
import SelectableButton from "@ui_kit/SelectableButton";
|
||||
import { Box, Button, ButtonBase, Dialog, Typography, useTheme } from "@mui/material";
|
||||
import CustomTextField from "@ui_kit/CustomTextField";
|
||||
import SelectableButton from "@ui_kit/SelectableButton";
|
||||
import { useState } from "react";
|
||||
import UploadIcon from "../../assets/icons/UploadIcon";
|
||||
|
||||
import type { DragEvent } from "react";
|
||||
|
||||
type BackgroundTypeModal = "linkVideo" | "ownVideo";
|
||||
@ -18,18 +10,12 @@ type BackgroundTypeModal = "linkVideo" | "ownVideo";
|
||||
type HelpQuestionsProps = {
|
||||
open: boolean;
|
||||
onClose: () => void;
|
||||
video: string;
|
||||
video: string | null;
|
||||
onUpload: (number: string) => void;
|
||||
};
|
||||
|
||||
export const UploadVideoModal = ({
|
||||
open,
|
||||
onClose,
|
||||
video,
|
||||
onUpload,
|
||||
}: HelpQuestionsProps) => {
|
||||
const [backgroundTypeModal, setBackgroundTypeModal] =
|
||||
useState<BackgroundTypeModal>("linkVideo");
|
||||
export default function UploadVideoModal({ open, onClose, video, onUpload }: HelpQuestionsProps) {
|
||||
const [backgroundTypeModal, setBackgroundTypeModal] = useState<BackgroundTypeModal>("linkVideo");
|
||||
const theme = useTheme();
|
||||
|
||||
const handleDrop = (event: DragEvent<HTMLDivElement>) => {
|
||||
@ -42,118 +28,102 @@ export const UploadVideoModal = ({
|
||||
};
|
||||
|
||||
return (
|
||||
<Modal
|
||||
<Dialog
|
||||
open={open}
|
||||
onClose={onClose}
|
||||
aria-labelledby="modal-modal-title"
|
||||
aria-describedby="modal-modal-description"
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
position: "absolute",
|
||||
top: "50%",
|
||||
left: "50%",
|
||||
transform: "translate(-50%, -50%)",
|
||||
maxWidth: "690px",
|
||||
bgcolor: "background.paper",
|
||||
PaperProps={{
|
||||
sx: {
|
||||
maxWidth: "640px",
|
||||
borderRadius: "12px",
|
||||
boxShadow: 24,
|
||||
p: 0,
|
||||
overflow: "hidden",
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
padding: "20px",
|
||||
background: theme.palette.background.default,
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
padding: "20px",
|
||||
background: theme.palette.background.default,
|
||||
}}
|
||||
<Typography sx={{ color: "#9A9AAF" }}>
|
||||
Видео можно вставить с любого хостинга: YouTube, Vimeo или загрузить собственное
|
||||
</Typography>
|
||||
<Button
|
||||
onClick={onClose}
|
||||
variant="contained"
|
||||
>
|
||||
<Typography sx={{ color: "#9A9AAF" }}>
|
||||
Видео можно вставить с любого хостинга: YouTube, Vimeo или загрузить
|
||||
собственное
|
||||
</Typography>
|
||||
<Button onClick={onClose} variant="contained">
|
||||
Готово
|
||||
</Button>
|
||||
</Box>
|
||||
<Box sx={{ padding: "20px", gap: "10px", display: "flex" }}>
|
||||
<SelectableButton
|
||||
isSelected={backgroundTypeModal === "linkVideo"}
|
||||
onClick={() => setBackgroundTypeModal("linkVideo")}
|
||||
sx={{ maxWidth: "170px", padding: "10px" }}
|
||||
>
|
||||
Ссылка на видео
|
||||
</SelectableButton>
|
||||
<SelectableButton
|
||||
isSelected={backgroundTypeModal === "ownVideo"}
|
||||
onClick={() => setBackgroundTypeModal("ownVideo")}
|
||||
sx={{ maxWidth: "170px", padding: "10px" }}
|
||||
>
|
||||
Загрузить свое
|
||||
</SelectableButton>
|
||||
</Box>
|
||||
{backgroundTypeModal === "linkVideo" ? (
|
||||
<Box sx={{ padding: "20px" }}>
|
||||
<Typography sx={{ paddingBottom: "15px", fontWeight: "500" }}>
|
||||
Ссылка на видео
|
||||
</Typography>
|
||||
<CustomTextField
|
||||
placeholder={"http://example.com"}
|
||||
text={video}
|
||||
onChange={({ target }) => onUpload(target.value || " ")}
|
||||
/>
|
||||
</Box>
|
||||
) : (
|
||||
<Box sx={{ padding: "20px" }}>
|
||||
<Typography sx={{ paddingBottom: "15px", fontWeight: "500" }}>
|
||||
Загрузите видео
|
||||
</Typography>
|
||||
<ButtonBase
|
||||
component="label"
|
||||
sx={{ justifyContent: "flex-start", width: "100%" }}
|
||||
>
|
||||
<input
|
||||
onChange={({ target }) => {
|
||||
if (target.files?.length) {
|
||||
onUpload(URL.createObjectURL(target.files[0] || " "));
|
||||
}
|
||||
}}
|
||||
hidden
|
||||
accept="video/*"
|
||||
multiple
|
||||
type="file"
|
||||
/>
|
||||
<Box
|
||||
onDragOver={(event: DragEvent<HTMLDivElement>) =>
|
||||
event.preventDefault()
|
||||
}
|
||||
onDrop={handleDrop}
|
||||
sx={{
|
||||
width: "580px",
|
||||
padding: "33px 33px 33px 50px",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
backgroundColor: theme.palette.background.default,
|
||||
border: `1px solid ${theme.palette.grey2.main}`,
|
||||
borderRadius: "8px",
|
||||
gap: "50px",
|
||||
}}
|
||||
>
|
||||
<UploadIcon />
|
||||
<Box sx={{ color: "#9A9AAF" }}>
|
||||
<Typography sx={{ fontWeight: "500" }}>
|
||||
Добавить видео
|
||||
</Typography>
|
||||
<Typography sx={{ fontSize: "16px" }}>
|
||||
Принимает .mp4 и .mov формат — максимум 100мб
|
||||
</Typography>
|
||||
</Box>
|
||||
</Box>
|
||||
</ButtonBase>
|
||||
</Box>
|
||||
)}
|
||||
Готово
|
||||
</Button>
|
||||
</Box>
|
||||
</Modal>
|
||||
<Box sx={{ padding: "20px", gap: "10px", display: "flex" }}>
|
||||
<SelectableButton
|
||||
isSelected={backgroundTypeModal === "linkVideo"}
|
||||
onClick={() => setBackgroundTypeModal("linkVideo")}
|
||||
sx={{ maxWidth: "170px", padding: "10px" }}
|
||||
>
|
||||
Ссылка на видео
|
||||
</SelectableButton>
|
||||
<SelectableButton
|
||||
isSelected={backgroundTypeModal === "ownVideo"}
|
||||
onClick={() => setBackgroundTypeModal("ownVideo")}
|
||||
sx={{ maxWidth: "170px", padding: "10px" }}
|
||||
>
|
||||
Загрузить свое
|
||||
</SelectableButton>
|
||||
</Box>
|
||||
{backgroundTypeModal === "linkVideo" ? (
|
||||
<Box sx={{ padding: "20px" }}>
|
||||
<Typography sx={{ paddingBottom: "15px", fontWeight: "500" }}>Ссылка на видео</Typography>
|
||||
<CustomTextField
|
||||
placeholder={"http://example.com"}
|
||||
value={video || ""}
|
||||
onChange={({ target }) => onUpload(target.value || " ")}
|
||||
/>
|
||||
</Box>
|
||||
) : (
|
||||
<Box sx={{ padding: "20px" }}>
|
||||
<Typography sx={{ paddingBottom: "15px", fontWeight: "500" }}>Загрузите видео</Typography>
|
||||
<ButtonBase
|
||||
component="label"
|
||||
sx={{ justifyContent: "flex-start", width: "100%" }}
|
||||
>
|
||||
<input
|
||||
onChange={({ target }) => {
|
||||
if (target.files?.length) {
|
||||
onUpload(URL.createObjectURL(target.files[0] || " "));
|
||||
}
|
||||
}}
|
||||
hidden
|
||||
accept="video/*"
|
||||
multiple
|
||||
type="file"
|
||||
/>
|
||||
<Box
|
||||
onDragOver={(event: DragEvent<HTMLDivElement>) => event.preventDefault()}
|
||||
onDrop={handleDrop}
|
||||
sx={{
|
||||
width: "580px",
|
||||
padding: "33px 33px 33px 50px",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
backgroundColor: theme.palette.background.default,
|
||||
border: `1px solid ${theme.palette.grey2.main}`,
|
||||
borderRadius: "8px",
|
||||
gap: "50px",
|
||||
}}
|
||||
>
|
||||
<UploadIcon />
|
||||
<Box sx={{ color: "#9A9AAF" }}>
|
||||
<Typography sx={{ fontWeight: "500" }}>Добавить видео</Typography>
|
||||
<Typography sx={{ fontSize: "16px" }}>Принимает .mp4 и .mov формат — максимум 100мб</Typography>
|
||||
</Box>
|
||||
</Box>
|
||||
</ButtonBase>
|
||||
</Box>
|
||||
)}
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
}
|
||||
|
@ -5,7 +5,7 @@ import SelectableButton from "@ui_kit/SelectableButton";
|
||||
import UploadBox from "@ui_kit/UploadBox";
|
||||
import { memo, useState } from "react";
|
||||
import UploadIcon from "../../assets/icons/UploadIcon";
|
||||
import { UploadVideoModal } from "./UploadVideoModal";
|
||||
import UploadVideoModal from "./UploadVideoModal";
|
||||
|
||||
type BackgroundType = "text" | "video";
|
||||
|
||||
@ -15,11 +15,7 @@ type HelpQuestionsProps = {
|
||||
hintText: string;
|
||||
};
|
||||
|
||||
const HelpQuestions = memo<HelpQuestionsProps>(function ({
|
||||
questionId,
|
||||
hintVideo,
|
||||
hintText,
|
||||
}) {
|
||||
const HelpQuestions = memo<HelpQuestionsProps>(function ({ questionId, hintVideo, hintText }) {
|
||||
const [open, setOpen] = useState(false);
|
||||
const [backgroundType, setBackgroundType] = useState<BackgroundType>("text");
|
||||
|
||||
@ -71,15 +67,17 @@ const HelpQuestions = memo<HelpQuestionsProps>(function ({
|
||||
</>
|
||||
) : (
|
||||
<Box>
|
||||
<Typography sx={{ paddingBottom: "15px", fontWeight: "500" }}>
|
||||
Загрузите видео
|
||||
</Typography>
|
||||
<Typography sx={{ paddingBottom: "15px", fontWeight: "500" }}>Загрузите видео</Typography>
|
||||
<ButtonBase
|
||||
onClick={() => setOpen(true)}
|
||||
sx={{ justifyContent: "flex-start" }}
|
||||
>
|
||||
{hintVideo ? (
|
||||
<video src={hintVideo} width="400" controls />
|
||||
<video
|
||||
src={hintVideo}
|
||||
width="400"
|
||||
controls
|
||||
/>
|
||||
) : (
|
||||
<>
|
||||
<UploadBox
|
||||
|
@ -1,9 +1,6 @@
|
||||
import { useState, useEffect } from "react";
|
||||
|
||||
import {
|
||||
getQuestionByContentId,
|
||||
updateQuestion,
|
||||
} from "@root/questions/actions";
|
||||
import { getQuestionByContentId, updateQuestion } from "@root/questions/actions";
|
||||
import { useCurrentQuiz } from "@root/quizes/hooks";
|
||||
|
||||
import CustomTextField from "@ui_kit/CustomTextField";
|
||||
@ -40,11 +37,7 @@ interface Props {
|
||||
resultData: QuizQuestionResult;
|
||||
}
|
||||
|
||||
export const checkEmptyData = ({
|
||||
resultData,
|
||||
}: {
|
||||
resultData: QuizQuestionResult;
|
||||
}) => {
|
||||
export const checkEmptyData = ({ resultData }: { resultData: QuizQuestionResult }) => {
|
||||
let check = true;
|
||||
if (
|
||||
resultData.title?.length > 0 ||
|
||||
@ -109,15 +102,9 @@ const InfoView = ({ resultData }: { resultData: QuizQuestionResult }) => {
|
||||
<Typography>
|
||||
{resultData?.content.rule.parentId === "line"
|
||||
? "Единый результат в конце прохождения опроса без ветвления"
|
||||
: `Заголовок вопроса, после которого появится результат: "${
|
||||
question?.title || "нет заголовка"
|
||||
}"`}
|
||||
: `Заголовок вопроса, после которого появится результат: "${question?.title || "нет заголовка"}"`}
|
||||
</Typography>
|
||||
{checkEmpty && (
|
||||
<Typography color="red">
|
||||
Вы не заполнили этот результат никакими данными
|
||||
</Typography>
|
||||
)}
|
||||
{checkEmpty && <Typography color="red">Вы не заполнили этот результат никакими данными</Typography>}
|
||||
</Paper>
|
||||
</Popover>
|
||||
</>
|
||||
@ -140,8 +127,7 @@ export const ResultCard = ({ resultContract, resultData }: Props) => {
|
||||
useEffect(() => {
|
||||
if (
|
||||
resultData.content.hint.text ||
|
||||
(quiz?.config.resultInfo.showResultForm === "after" &&
|
||||
resultData.content.redirect)
|
||||
(quiz?.config.resultInfo.showResultForm === "after" && resultData.content.redirect)
|
||||
) {
|
||||
setButtonPlus(false);
|
||||
}
|
||||
@ -167,9 +153,7 @@ export const ResultCard = ({ resultContract, resultData }: Props) => {
|
||||
<Typography sx={{ color: theme.palette.grey2.main, padding: "5px 20px" }}>
|
||||
{resultData?.content.rule.parentId === "line"
|
||||
? "Единый результат в конце прохождения опроса без ветвления"
|
||||
: `Заголовок вопроса, после которого появится результат: "${
|
||||
question?.title || "нет заголовка"
|
||||
}"`}
|
||||
: `Заголовок вопроса, после которого появится результат: "${question?.title || "нет заголовка"}"`}
|
||||
</Typography>
|
||||
<Box
|
||||
sx={{
|
||||
@ -194,16 +178,11 @@ export const ResultCard = ({ resultContract, resultData }: Props) => {
|
||||
placeholder={"Заголовок результата"}
|
||||
maxLength={200}
|
||||
onChange={({ target }: { target: HTMLInputElement }) =>
|
||||
updateQuestion(
|
||||
resultData.id,
|
||||
(question) => (question.title = target.value),
|
||||
)
|
||||
updateQuestion(resultData.id, (question) => (question.title = target.value))
|
||||
}
|
||||
sx={{
|
||||
margin: isMobile ? "10px 0" : 0,
|
||||
backgroundColor: expand
|
||||
? theme.palette.background.default
|
||||
: "transparent",
|
||||
backgroundColor: expand ? theme.palette.background.default : "transparent",
|
||||
height: "48px",
|
||||
borderRadius: "10px",
|
||||
borderWidth: "1px !important",
|
||||
@ -273,11 +252,7 @@ export const ResultCard = ({ resultContract, resultData }: Props) => {
|
||||
<Typography id={"id-copy"}>{resultData.backendId}</Typography>
|
||||
<IconButton
|
||||
edge="end"
|
||||
onClick={() =>
|
||||
navigator.clipboard.writeText(
|
||||
document.querySelector("#id-copy").innerText,
|
||||
)
|
||||
}
|
||||
onClick={() => navigator.clipboard.writeText(document.querySelector("#id-copy").innerText)}
|
||||
>
|
||||
<CopyIcon
|
||||
color={"#ffffff"}
|
||||
@ -300,10 +275,7 @@ export const ResultCard = ({ resultContract, resultData }: Props) => {
|
||||
id="headline-is-bolder"
|
||||
value={resultData.description}
|
||||
onChange={({ target }: { target: HTMLInputElement }) =>
|
||||
updateQuestion(
|
||||
resultData.id,
|
||||
(question) => (question.description = target.value),
|
||||
)
|
||||
updateQuestion(resultData.id, (question) => (question.description = target.value))
|
||||
}
|
||||
placeholder={"Заголовок пожирнее"}
|
||||
maxLength={200}
|
||||
@ -335,10 +307,7 @@ export const ResultCard = ({ resultContract, resultData }: Props) => {
|
||||
placeholder={"Заголовок результата"}
|
||||
maxLength={200}
|
||||
onChange={({ target }: { target: HTMLInputElement }) =>
|
||||
updateQuestion(
|
||||
resultData.id,
|
||||
(question) => (question.title = target.value),
|
||||
)
|
||||
updateQuestion(resultData.id, (question) => (question.title = target.value))
|
||||
}
|
||||
/>
|
||||
</Box>
|
||||
@ -350,10 +319,7 @@ export const ResultCard = ({ resultContract, resultData }: Props) => {
|
||||
if (target.value.length <= 3000) {
|
||||
setInputValue(target.value);
|
||||
}
|
||||
updateQuestion(
|
||||
resultData.id,
|
||||
(question) => (question.content.text = target.value),
|
||||
);
|
||||
updateQuestion(resultData.id, (question) => (question.content.text = target.value));
|
||||
}}
|
||||
fullWidth
|
||||
placeholder="Описание"
|
||||
@ -379,7 +345,7 @@ export const ResultCard = ({ resultContract, resultData }: Props) => {
|
||||
/>
|
||||
|
||||
<MediaSelectionAndDisplay
|
||||
resultData={resultData}
|
||||
question={resultData}
|
||||
cropAspectRatio={{ width: 305.9, height: 305.9 }}
|
||||
/>
|
||||
|
||||
@ -422,10 +388,7 @@ export const ResultCard = ({ resultContract, resultData }: Props) => {
|
||||
<IconButton
|
||||
onClick={() => {
|
||||
setButtonPlus(true);
|
||||
updateQuestion(
|
||||
resultData.id,
|
||||
(q) => (q.content.hint.text = ""),
|
||||
);
|
||||
updateQuestion(resultData.id, (q) => (q.content.hint.text = ""));
|
||||
}}
|
||||
>
|
||||
<Trash />
|
||||
@ -475,12 +438,9 @@ export const ResultCard = ({ resultContract, resultData }: Props) => {
|
||||
id="link-page-result"
|
||||
value={resultData.content.redirect}
|
||||
onChange={({ target }: { target: HTMLInputElement }) =>
|
||||
updateQuestion<QuizQuestionResult>(
|
||||
resultData.id,
|
||||
(question) => {
|
||||
question.content.redirect = target.value;
|
||||
},
|
||||
)
|
||||
updateQuestion<QuizQuestionResult>(resultData.id, (question) => {
|
||||
question.content.redirect = target.value;
|
||||
})
|
||||
}
|
||||
placeholder="https://penahub.ru"
|
||||
maxLength={200}
|
||||
|
@ -6,16 +6,7 @@ import { useCurrentQuiz } from "@root/quizes/hooks";
|
||||
import { SwitchSetting } from "../SwichResult";
|
||||
|
||||
import Info from "@icons/Info";
|
||||
import {
|
||||
Box,
|
||||
IconButton,
|
||||
Paper,
|
||||
Button,
|
||||
Typography,
|
||||
useMediaQuery,
|
||||
useTheme,
|
||||
Popover,
|
||||
} from "@mui/material";
|
||||
import { Box, IconButton, Paper, Button, Typography, useMediaQuery, useTheme, Popover } from "@mui/material";
|
||||
|
||||
import ExpandLessIconBG from "@icons/ExpandLessIconBG";
|
||||
import ExpandLessIcon from "@mui/icons-material/ExpandLess";
|
||||
@ -88,10 +79,7 @@ const InfoView = () => {
|
||||
flexDirection: "column",
|
||||
}}
|
||||
>
|
||||
<Typography>
|
||||
Oтправка письма с результатом респонденту после отображения на
|
||||
экране
|
||||
</Typography>
|
||||
<Typography>Oтправка письма с результатом респонденту после отображения на экране</Typography>
|
||||
</Paper>
|
||||
</Popover>
|
||||
</>
|
||||
@ -190,7 +178,10 @@ export const WhenCard = ({ quizExpand }: Props) => {
|
||||
}}
|
||||
>
|
||||
{whenValues.map(({ title, value, id }, index) => (
|
||||
<Box display="flex">
|
||||
<Box
|
||||
display="flex"
|
||||
key={id}
|
||||
>
|
||||
<Button
|
||||
id={id}
|
||||
onClick={() => {
|
||||
@ -201,32 +192,16 @@ export const WhenCard = ({ quizExpand }: Props) => {
|
||||
}}
|
||||
key={title}
|
||||
sx={{
|
||||
bgcolor:
|
||||
quiz?.config.resultInfo.showResultForm === value
|
||||
? " #7E2AEA"
|
||||
: "#F2F3F7",
|
||||
color:
|
||||
quiz?.config.resultInfo.showResultForm === value
|
||||
? " white"
|
||||
: "#9A9AAF",
|
||||
minWidth: isSmallMonitor
|
||||
? isMobile
|
||||
? undefined
|
||||
: "310px"
|
||||
: "auto",
|
||||
bgcolor: quiz?.config.resultInfo.showResultForm === value ? " #7E2AEA" : "#F2F3F7",
|
||||
color: quiz?.config.resultInfo.showResultForm === value ? " white" : "#9A9AAF",
|
||||
minWidth: isSmallMonitor ? (isMobile ? undefined : "310px") : "auto",
|
||||
borderRadius: "8px",
|
||||
width: isMobile ? "100%" : "220px",
|
||||
height: "44px",
|
||||
fontSize: "17px",
|
||||
border:
|
||||
quiz?.config.resultInfo.showResultForm === value
|
||||
? "none"
|
||||
: "1px solid #9A9AAF",
|
||||
border: quiz?.config.resultInfo.showResultForm === value ? "none" : "1px solid #9A9AAF",
|
||||
"&:hover": {
|
||||
backgroundColor:
|
||||
quiz?.config.resultInfo.showResultForm === value
|
||||
? "#581CA7"
|
||||
: "#7E2AEA",
|
||||
backgroundColor: quiz?.config.resultInfo.showResultForm === value ? "#581CA7" : "#7E2AEA",
|
||||
color: "white",
|
||||
},
|
||||
}}
|
||||
@ -252,32 +227,16 @@ export const WhenCard = ({ quizExpand }: Props) => {
|
||||
});
|
||||
}}
|
||||
sx={{
|
||||
bgcolor:
|
||||
quiz?.config.resultInfo.when === "email"
|
||||
? " #7E2AEA"
|
||||
: "#F2F3F7",
|
||||
color:
|
||||
quiz?.config.resultInfo.when === "email"
|
||||
? " white"
|
||||
: "#9A9AAF",
|
||||
minWidth: isSmallMonitor
|
||||
? isMobile
|
||||
? undefined
|
||||
: "310px"
|
||||
: "auto",
|
||||
bgcolor: quiz?.config.resultInfo.when === "email" ? " #7E2AEA" : "#F2F3F7",
|
||||
color: quiz?.config.resultInfo.when === "email" ? " white" : "#9A9AAF",
|
||||
minWidth: isSmallMonitor ? (isMobile ? undefined : "310px") : "auto",
|
||||
borderRadius: "8px",
|
||||
width: isMobile ? "100%" : "220px",
|
||||
height: "44px",
|
||||
fontSize: "17px",
|
||||
border:
|
||||
quiz?.config.resultInfo.when === "email"
|
||||
? "none"
|
||||
: "1px solid #9A9AAF",
|
||||
border: quiz?.config.resultInfo.when === "email" ? "none" : "1px solid #9A9AAF",
|
||||
"&:hover": {
|
||||
backgroundColor:
|
||||
quiz?.config.resultInfo.when === "email"
|
||||
? "#581CA7"
|
||||
: "#7E2AEA",
|
||||
backgroundColor: quiz?.config.resultInfo.when === "email" ? "#581CA7" : "#7E2AEA",
|
||||
color: "white",
|
||||
},
|
||||
}}
|
||||
|
@ -27,11 +27,7 @@ import {
|
||||
useMediaQuery,
|
||||
useTheme,
|
||||
} from "@mui/material";
|
||||
import {
|
||||
incrementCurrentStep,
|
||||
updateQuiz,
|
||||
uploadQuizImage,
|
||||
} from "@root/quizes/actions";
|
||||
import { incrementCurrentStep, updateQuiz, uploadQuizImage } from "@root/quizes/actions";
|
||||
import { useCurrentQuiz } from "@root/quizes/hooks";
|
||||
import CustomCheckbox from "@ui_kit/CustomCheckbox";
|
||||
import CustomTextField from "@ui_kit/CustomTextField";
|
||||
@ -47,24 +43,12 @@ import { DropZone } from "./dropZone";
|
||||
import Extra from "./extra";
|
||||
import TooltipClickInfo from "@ui_kit/Toolbars/TooltipClickInfo";
|
||||
import { VideoElement } from "./VideoElement";
|
||||
import * as React from "react";
|
||||
import UploadVideoModal from "../Questions/UploadVideoModal";
|
||||
|
||||
const designTypes = [
|
||||
[
|
||||
"standard",
|
||||
(color: string) => <LayoutStandartIcon color={color} />,
|
||||
"Standard",
|
||||
],
|
||||
[
|
||||
"expanded",
|
||||
(color: string) => <LayoutExpandedIcon color={color} />,
|
||||
"Expanded",
|
||||
],
|
||||
[
|
||||
"centered",
|
||||
(color: string) => <LayoutCenteredIcon color={color} />,
|
||||
"Centered",
|
||||
],
|
||||
["standard", (color: string) => <LayoutStandartIcon color={color} />, "Standard"],
|
||||
["expanded", (color: string) => <LayoutExpandedIcon color={color} />, "Expanded"],
|
||||
["centered", (color: string) => <LayoutCenteredIcon color={color} />, "Centered"],
|
||||
] as const;
|
||||
|
||||
export default function StartPageSettings() {
|
||||
@ -78,12 +62,28 @@ export default function StartPageSettings() {
|
||||
const [faviconUploding, setFaviconUploading] = useState<boolean>(false);
|
||||
const [backgroundUploding, setBackgroundUploading] = useState<boolean>(false);
|
||||
const [logoUploding, setLogoUploading] = useState<boolean>(false);
|
||||
const [isVideoUploadDialogOpen, setIsVideoUploadDialogOpen] = useState<boolean>(false);
|
||||
|
||||
if (!quiz) return null;
|
||||
|
||||
const MobileVersionHC = (bool: boolean) => {
|
||||
setMobileVersion(bool);
|
||||
};
|
||||
async function handleVideoUpload(videoUrl: string) {
|
||||
if (!quiz) return;
|
||||
|
||||
setBackgroundUploading(true);
|
||||
|
||||
if (videoUrl.startsWith("blob:")) {
|
||||
const videoBlob = await (await fetch(videoUrl)).blob();
|
||||
uploadQuizImage(quiz.id, videoBlob, (quiz, url) => {
|
||||
quiz.config.startpage.background.video = url;
|
||||
});
|
||||
} else {
|
||||
updateQuiz(quiz.id, (quiz) => {
|
||||
quiz.config.startpage.background.video = videoUrl;
|
||||
});
|
||||
}
|
||||
|
||||
setBackgroundUploading(false);
|
||||
}
|
||||
|
||||
const designType = quiz?.config?.startpageType;
|
||||
let cropAspectRatio:
|
||||
@ -129,6 +129,12 @@ export default function StartPageSettings() {
|
||||
|
||||
return (
|
||||
<>
|
||||
<UploadVideoModal
|
||||
open={isVideoUploadDialogOpen}
|
||||
onClose={() => setIsVideoUploadDialogOpen(false)}
|
||||
onUpload={handleVideoUpload}
|
||||
video={quiz.config.startpage.background.video}
|
||||
/>
|
||||
<Typography
|
||||
variant="h5"
|
||||
sx={{ marginTop: "60px", marginBottom: isSmallMonitor ? "0" : "40px" }}
|
||||
@ -143,25 +149,22 @@ export default function StartPageSettings() {
|
||||
fontWeight: 500,
|
||||
fontSize: "16px",
|
||||
color: formState === "design" ? "#7E2AEA" : "#7D7E86",
|
||||
borderBottom:
|
||||
formState === "design"
|
||||
? "2px solid #7E2AEA"
|
||||
: "1px solid transparent",
|
||||
borderBottom: formState === "design" ? "2px solid #7E2AEA" : "1px solid transparent",
|
||||
}}
|
||||
>
|
||||
Дизайн
|
||||
</Typography>
|
||||
</Button>
|
||||
<Button id="contentButton" onClick={() => setFormState("content")}>
|
||||
<Button
|
||||
id="contentButton"
|
||||
onClick={() => setFormState("content")}
|
||||
>
|
||||
<Typography
|
||||
sx={{
|
||||
fontWeight: 500,
|
||||
fontSize: "16px",
|
||||
color: formState === "content" ? "#7E2AEA" : "#7D7E86",
|
||||
borderBottom:
|
||||
formState === "content"
|
||||
? "2px solid #7E2AEA"
|
||||
: "1px solid transparent",
|
||||
borderBottom: formState === "content" ? "2px solid #7E2AEA" : "1px solid transparent",
|
||||
}}
|
||||
>
|
||||
Контент
|
||||
@ -222,8 +225,7 @@ export default function StartPageSettings() {
|
||||
displayEmpty
|
||||
onChange={(e) =>
|
||||
updateQuiz(quiz.id, (quiz) => {
|
||||
quiz.config.startpageType = e.target
|
||||
.value as QuizStartpageType;
|
||||
quiz.config.startpageType = e.target.value as QuizStartpageType;
|
||||
})
|
||||
}
|
||||
sx={{
|
||||
@ -280,11 +282,7 @@ export default function StartPageSettings() {
|
||||
color: theme.palette.grey2.main,
|
||||
}}
|
||||
>
|
||||
{type[1](
|
||||
type[0] === designType
|
||||
? theme.palette.orange.main
|
||||
: theme.palette.grey2.main,
|
||||
)}
|
||||
{type[1](type[0] === designType ? theme.palette.orange.main : theme.palette.grey2.main)}
|
||||
{type[2]}
|
||||
</MenuItem>
|
||||
))}
|
||||
@ -331,10 +329,7 @@ export default function StartPageSettings() {
|
||||
{quiz.config.startpage.background.type === "image" && (
|
||||
<Box
|
||||
sx={{
|
||||
display:
|
||||
quiz.config.startpage.background.type === "image"
|
||||
? "flex"
|
||||
: "none",
|
||||
display: quiz.config.startpage.background.type === "image" ? "flex" : "none",
|
||||
flexDirection: "column",
|
||||
}}
|
||||
>
|
||||
@ -368,15 +363,12 @@ export default function StartPageSettings() {
|
||||
sx={{ maxWidth: "300px" }}
|
||||
cropAspectRatio={cropAspectRatio}
|
||||
imageUrl={quiz.config.startpage.background.desktop}
|
||||
originalImageUrl={
|
||||
quiz.config.startpage.background.originalDesktop
|
||||
}
|
||||
originalImageUrl={quiz.config.startpage.background.originalDesktop}
|
||||
onImageUploadClick={async (file) => {
|
||||
setBackgroundUploading(true);
|
||||
await uploadQuizImage(quiz.id, file, (quiz, url) => {
|
||||
quiz.config.startpage.background.desktop = url;
|
||||
quiz.config.startpage.background.originalDesktop =
|
||||
url;
|
||||
quiz.config.startpage.background.originalDesktop = url;
|
||||
});
|
||||
|
||||
setBackgroundUploading(false);
|
||||
@ -426,7 +418,10 @@ export default function StartPageSettings() {
|
||||
{isMobile ? (
|
||||
<TooltipClickInfo title={"Можно загрузить видео."} />
|
||||
) : (
|
||||
<Tooltip title="Можно загрузить видео." placement="top">
|
||||
<Tooltip
|
||||
title="Можно загрузить видео."
|
||||
placement="top"
|
||||
>
|
||||
<Box>
|
||||
<InfoIcon />
|
||||
</Box>
|
||||
@ -445,7 +440,7 @@ export default function StartPageSettings() {
|
||||
) : (
|
||||
<>
|
||||
<ButtonBase
|
||||
component="label"
|
||||
onClick={() => setIsVideoUploadDialogOpen(true)}
|
||||
sx={{
|
||||
justifyContent: "center",
|
||||
height: "48px",
|
||||
@ -455,29 +450,6 @@ export default function StartPageSettings() {
|
||||
my: "20px",
|
||||
}}
|
||||
>
|
||||
<input
|
||||
onChange={async (event) => {
|
||||
setBackgroundUploading(true);
|
||||
const file = event.target.files?.[0];
|
||||
|
||||
if (file) {
|
||||
await uploadQuizImage(
|
||||
quiz.id,
|
||||
file,
|
||||
(quiz, url) => {
|
||||
quiz.config.startpage.background.video =
|
||||
url;
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
setBackgroundUploading(false);
|
||||
}}
|
||||
hidden
|
||||
accept=".mp4"
|
||||
multiple
|
||||
type="file"
|
||||
/>
|
||||
<UploadBox
|
||||
icon={<UploadIcon />}
|
||||
sx={{
|
||||
@ -559,10 +531,7 @@ export default function StartPageSettings() {
|
||||
<>
|
||||
<Box
|
||||
sx={{
|
||||
display:
|
||||
quiz.config.startpage.background.type === "image"
|
||||
? "flex"
|
||||
: "none",
|
||||
display: quiz.config.startpage.background.type === "image" ? "flex" : "none",
|
||||
flexDirection: "column",
|
||||
}}
|
||||
>
|
||||
@ -641,10 +610,7 @@ export default function StartPageSettings() {
|
||||
<>
|
||||
<Box
|
||||
sx={{
|
||||
display:
|
||||
quiz.config.startpage.background.type === "image"
|
||||
? "flex"
|
||||
: "none",
|
||||
display: quiz.config.startpage.background.type === "image" ? "flex" : "none",
|
||||
flexDirection: "column",
|
||||
}}
|
||||
>
|
||||
@ -870,22 +836,24 @@ export default function StartPageSettings() {
|
||||
maxLength={1000}
|
||||
/>
|
||||
<Extra />
|
||||
<Box sx={{display: "flex", gap: "20px", alignItems: "center"}}>
|
||||
<CustomizedSwitch
|
||||
checked={quiz.config.antifraud}
|
||||
onChange={(e) => {
|
||||
updateQuiz(quiz.id, (quiz) => {
|
||||
quiz.config.antifraud = e.target.checked;
|
||||
|
||||
})
|
||||
}}
|
||||
/>
|
||||
<Typography sx={{fontWeight: 500,
|
||||
color: theme.palette.grey3.main,}}
|
||||
>
|
||||
Включить антифрод</Typography>
|
||||
</Box>
|
||||
|
||||
<Box sx={{ display: "flex", gap: "20px", alignItems: "center" }}>
|
||||
<CustomizedSwitch
|
||||
checked={quiz.config.antifraud}
|
||||
onChange={(e) => {
|
||||
updateQuiz(quiz.id, (quiz) => {
|
||||
quiz.config.antifraud = e.target.checked;
|
||||
});
|
||||
}}
|
||||
/>
|
||||
<Typography
|
||||
sx={{
|
||||
fontWeight: 500,
|
||||
color: theme.palette.grey3.main,
|
||||
}}
|
||||
>
|
||||
Включить антифрод
|
||||
</Typography>
|
||||
</Box>
|
||||
</>
|
||||
)}
|
||||
</Box>
|
||||
|
@ -2,6 +2,7 @@ import Box from "@mui/material/Box";
|
||||
import { FC } from "react";
|
||||
import DeleteIcon from "@mui/icons-material/Delete";
|
||||
import { IconButton, SxProps, Theme } from "@mui/material";
|
||||
import { QuizVideo } from "@frontend/squzanswerer";
|
||||
|
||||
type VideoElementProps = {
|
||||
videoSrc: string;
|
||||
@ -20,12 +21,7 @@ export const VideoElement: FC<VideoElementProps> = ({
|
||||
}) => {
|
||||
return (
|
||||
<Box sx={{ position: "relative", width: `${width}px` }}>
|
||||
<video
|
||||
style={{ borderRadius: "8px" }}
|
||||
src={videoSrc}
|
||||
width={width}
|
||||
controls
|
||||
/>
|
||||
<QuizVideo videoUrl={videoSrc} />
|
||||
<IconButton
|
||||
onClick={onDeleteClick}
|
||||
sx={{
|
||||
|
@ -20,7 +20,7 @@ export const setEditQuizId = (quizId: number | null) =>
|
||||
{
|
||||
type: "setEditQuizId",
|
||||
quizId,
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
export const resetEditConfig = () =>
|
||||
@ -37,7 +37,7 @@ export const setQuizes = (quizes: RawQuiz[] | null) =>
|
||||
{
|
||||
type: "setQuizes",
|
||||
quizes,
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
const addQuiz = (quiz: Quiz) =>
|
||||
@ -48,7 +48,7 @@ const addQuiz = (quiz: Quiz) =>
|
||||
{
|
||||
type: "addQuiz",
|
||||
quiz,
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
const removeQuiz = (quizId: string) =>
|
||||
@ -62,7 +62,7 @@ const removeQuiz = (quizId: string) =>
|
||||
{
|
||||
type: "removeQuiz",
|
||||
quizId,
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
const setQuizBackendId = (quizId: string, backendId: number) =>
|
||||
@ -77,20 +77,17 @@ const setQuizBackendId = (quizId: string, backendId: number) =>
|
||||
type: "setQuizBackendId",
|
||||
quizId,
|
||||
backendId,
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
export const incrementCurrentStep = () =>
|
||||
setProducedState(
|
||||
(state) => {
|
||||
state.currentStep = Math.min(
|
||||
maxQuizSetupSteps - 1,
|
||||
state.currentStep + 1,
|
||||
);
|
||||
state.currentStep = Math.min(maxQuizSetupSteps - 1, state.currentStep + 1);
|
||||
},
|
||||
{
|
||||
type: "incrementCurrentStep",
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
export const decrementCurrentStep = () =>
|
||||
@ -100,7 +97,7 @@ export const decrementCurrentStep = () =>
|
||||
},
|
||||
{
|
||||
type: "decrementCurrentStep",
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
export const setCurrentStep = (step: number) =>
|
||||
@ -111,7 +108,7 @@ export const setCurrentStep = (step: number) =>
|
||||
{
|
||||
type: "setCurrentStep",
|
||||
step,
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
export const setQuizType = (quizId: string, quizType: QuizConfig["type"]) => {
|
||||
@ -120,10 +117,7 @@ export const setQuizType = (quizId: string, quizType: QuizConfig["type"]) => {
|
||||
});
|
||||
};
|
||||
|
||||
export const setQuizStartpageType = (
|
||||
quizId: string,
|
||||
startpageType: QuizConfig["startpageType"],
|
||||
) => {
|
||||
export const setQuizStartpageType = (quizId: string, startpageType: QuizConfig["startpageType"]) => {
|
||||
updateQuiz(quizId, (quiz) => {
|
||||
quiz.config.startpageType = startpageType;
|
||||
});
|
||||
@ -133,10 +127,7 @@ const REQUEST_DEBOUNCE = 200;
|
||||
const requestQueue = new RequestQueue();
|
||||
let requestTimeoutId: ReturnType<typeof setTimeout>;
|
||||
|
||||
export const updateQuiz = (
|
||||
quizId: string | null | undefined,
|
||||
updateFn: (quiz: Quiz) => void,
|
||||
) => {
|
||||
export const updateQuiz = (quizId: string | null | undefined, updateFn: (quiz: Quiz) => void) => {
|
||||
if (!quizId) return;
|
||||
|
||||
setProducedState(
|
||||
@ -150,7 +141,7 @@ export const updateQuiz = (
|
||||
type: "updateQuiz",
|
||||
quizId,
|
||||
updateFn: updateFn.toString(),
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
clearTimeout(requestTimeoutId);
|
||||
@ -159,9 +150,7 @@ export const updateQuiz = (
|
||||
const quiz = useQuizStore.getState().quizes.find((q) => q.id === quizId);
|
||||
if (!quiz) return;
|
||||
|
||||
const [editedQuiz, editedQuizError] = await quizApi.edit(
|
||||
quizToEditQuizRequest(quiz),
|
||||
);
|
||||
const [editedQuiz, editedQuizError] = await quizApi.edit(quizToEditQuizRequest(quiz));
|
||||
|
||||
if (editedQuizError || !editedQuiz) {
|
||||
devlog("Error editing quiz", editedQuizError, quizId);
|
||||
@ -270,22 +259,15 @@ export const copyQuiz = async (quizId: string) =>
|
||||
(state) => {
|
||||
state.quizes.unshift(newQuiz);
|
||||
},
|
||||
{ type: "addQuiz", quiz },
|
||||
{ type: "addQuiz", quiz }
|
||||
);
|
||||
});
|
||||
|
||||
export const uploadQuizImage = async (
|
||||
quizId: string,
|
||||
blob: Blob,
|
||||
updateFn: (quiz: Quiz, imageId: string) => void,
|
||||
) => {
|
||||
export const uploadQuizImage = async (quizId: string, blob: Blob, updateFn: (quiz: Quiz, imageId: string) => void) => {
|
||||
const quiz = useQuizStore.getState().quizes.find((q) => q.id === quizId);
|
||||
if (!quiz) return;
|
||||
|
||||
const [addedImages, addImagesError] = await quizApi.addImages(
|
||||
quiz.backendId,
|
||||
blob,
|
||||
);
|
||||
const [addedImages, addImagesError] = await quizApi.addImages(quiz.backendId, blob);
|
||||
|
||||
if (addImagesError || !addedImages) {
|
||||
devlog("Error uploading quiz image", addImagesError);
|
||||
@ -305,14 +287,11 @@ export const uploadQuizImage = async (
|
||||
updateQuiz(quizId, (quiz) => {
|
||||
updateFn(
|
||||
quiz,
|
||||
`https://s3.timeweb.cloud/3c580be9-cf31f296-d055-49cf-b39e-30c7959dc17b/squizimages/${quiz.qid}/${imageId}`,
|
||||
`https://s3.timeweb.cloud/3c580be9-cf31f296-d055-49cf-b39e-30c7959dc17b/squizimages/${quiz.qid}/${imageId}`
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
function setProducedState<A extends string | { type: unknown }>(
|
||||
recipe: (state: QuizStore) => void,
|
||||
action?: A,
|
||||
) {
|
||||
function setProducedState<A extends string | { type: string }>(recipe: (state: QuizStore) => void, action?: A) {
|
||||
useQuizStore.setState((state) => produce(state, recipe), false, action);
|
||||
}
|
||||
|
@ -6,19 +6,13 @@ import type { SxProps, Theme } from "@mui/material";
|
||||
|
||||
interface Props {
|
||||
sx?: SxProps<Theme>;
|
||||
imageSrc?: string;
|
||||
imageSrc?: string | null;
|
||||
onImageClick?: () => void;
|
||||
onPlusClick?: () => void;
|
||||
uploading: boolean;
|
||||
}
|
||||
|
||||
export default function AddOrEditImageButton({
|
||||
onImageClick,
|
||||
onPlusClick,
|
||||
sx,
|
||||
imageSrc,
|
||||
uploading = false,
|
||||
}: Props) {
|
||||
export default function AddOrEditImageButton({ onImageClick, onPlusClick, sx, imageSrc, uploading = false }: Props) {
|
||||
const theme = useTheme();
|
||||
const isMobile = useMediaQuery(theme.breakpoints.down(790));
|
||||
|
||||
|
@ -1,64 +1,44 @@
|
||||
import { FC, useState } from "react";
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
ButtonBase,
|
||||
Skeleton,
|
||||
Tooltip,
|
||||
Typography,
|
||||
useTheme,
|
||||
} from "@mui/material";
|
||||
import { updateQuestion, uploadQuestionImage } from "@root/questions/actions";
|
||||
import { CropModal, useCropModalState } from "@ui_kit/Modal/CropModal";
|
||||
|
||||
import AddOrEditImageButton from "@ui_kit/AddOrEditImageButton";
|
||||
import { UploadImageModal } from "../pages/Questions/UploadImage/UploadImageModal";
|
||||
import { useDisclosure } from "../utils/useDisclosure";
|
||||
import { useCurrentQuiz } from "../stores/quizes/hooks";
|
||||
import { AnyTypedQuizQuestion } from "@model/questionTypes/shared";
|
||||
import UploadBox from "@ui_kit/UploadBox";
|
||||
import UploadIcon from "@icons/UploadIcon";
|
||||
import { QuizQuestionPage } from "@/model/questionTypes/page";
|
||||
import { QuizQuestionResult } from "@/model/questionTypes/result";
|
||||
import InfoIcon from "@icons/InfoIcon";
|
||||
import UploadIcon from "@icons/UploadIcon";
|
||||
import { Box, Button, ButtonBase, Skeleton, Tooltip, Typography, useTheme } from "@mui/material";
|
||||
import { updateQuestion, uploadQuestionImage } from "@root/questions/actions";
|
||||
import AddOrEditImageButton from "@ui_kit/AddOrEditImageButton";
|
||||
import { CropModal, useCropModalState } from "@ui_kit/Modal/CropModal";
|
||||
import UploadBox from "@ui_kit/UploadBox";
|
||||
import { FC, useState } from "react";
|
||||
import { UploadImageModal } from "../pages/Questions/UploadImage/UploadImageModal";
|
||||
import { VideoElement } from "../pages/startPage/VideoElement";
|
||||
import { useCurrentQuiz } from "../stores/quizes/hooks";
|
||||
import { useDisclosure } from "../utils/useDisclosure";
|
||||
import UploadVideoModal from "@/pages/Questions/UploadVideoModal";
|
||||
|
||||
interface Iprops {
|
||||
resultData: AnyTypedQuizQuestion;
|
||||
interface Props {
|
||||
question: QuizQuestionPage | QuizQuestionResult;
|
||||
cropAspectRatio: {
|
||||
width: number;
|
||||
height: number;
|
||||
};
|
||||
}
|
||||
|
||||
export const MediaSelectionAndDisplay: FC<Iprops> = ({
|
||||
resultData,
|
||||
cropAspectRatio,
|
||||
}) => {
|
||||
export const MediaSelectionAndDisplay: FC<Props> = ({ question, cropAspectRatio }) => {
|
||||
const [pictureUploding, setPictureUploading] = useState<boolean>(false);
|
||||
const [backgroundUploding, setBackgroundUploading] = useState<boolean>(false);
|
||||
const quizQid = useCurrentQuiz()?.qid;
|
||||
const theme = useTheme();
|
||||
const {
|
||||
isCropModalOpen,
|
||||
openCropModal,
|
||||
closeCropModal,
|
||||
imageBlob,
|
||||
originalImageUrl,
|
||||
setCropModalImageBlob,
|
||||
} = useCropModalState();
|
||||
const [isImageUploadOpen, openImageUploadModal, closeImageUploadModal] =
|
||||
useDisclosure();
|
||||
const { isCropModalOpen, openCropModal, closeCropModal, imageBlob, originalImageUrl, setCropModalImageBlob } =
|
||||
useCropModalState();
|
||||
const [isImageUploadOpen, openImageUploadModal, closeImageUploadModal] = useDisclosure();
|
||||
const [isVideoUploadDialogOpen, setIsVideoUploadDialogOpen] = useState<boolean>(false);
|
||||
|
||||
async function handleImageUpload(file: File) {
|
||||
setPictureUploading(true);
|
||||
|
||||
const url = await uploadQuestionImage(
|
||||
resultData.id,
|
||||
quizQid,
|
||||
file,
|
||||
(question, url) => {
|
||||
question.content.back = url;
|
||||
question.content.originalBack = url;
|
||||
},
|
||||
);
|
||||
const url = await uploadQuestionImage(question.id, quizQid, file, (question, url) => {
|
||||
question.content.back = url;
|
||||
question.content.originalBack = url;
|
||||
});
|
||||
closeImageUploadModal();
|
||||
openCropModal(file, url);
|
||||
|
||||
@ -66,11 +46,32 @@ export const MediaSelectionAndDisplay: FC<Iprops> = ({
|
||||
}
|
||||
|
||||
function handleCropModalSaveClick(imageBlob: Blob) {
|
||||
uploadQuestionImage(resultData.id, quizQid, imageBlob, (question, url) => {
|
||||
uploadQuestionImage(question.id, quizQid, imageBlob, (question, url) => {
|
||||
question.content.back = url;
|
||||
});
|
||||
}
|
||||
|
||||
async function handleVideoUpload(videoUrl: string) {
|
||||
setBackgroundUploading(true);
|
||||
|
||||
if (videoUrl.startsWith("blob:")) {
|
||||
const videoBlob = await (await fetch(videoUrl)).blob();
|
||||
uploadQuestionImage(question.id, quizQid, videoBlob, (question, url) => {
|
||||
if (!("video" in question.content)) return;
|
||||
|
||||
question.content.video = url;
|
||||
});
|
||||
} else {
|
||||
updateQuestion(question.id, (question) => {
|
||||
if (!("video" in question.content)) return;
|
||||
|
||||
question.content.video = videoUrl;
|
||||
});
|
||||
}
|
||||
|
||||
setBackgroundUploading(false);
|
||||
}
|
||||
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
@ -87,7 +88,7 @@ export const MediaSelectionAndDisplay: FC<Iprops> = ({
|
||||
>
|
||||
<Button
|
||||
sx={{
|
||||
color: resultData.content.useImage ? "#7E2AEA" : "#9A9AAF",
|
||||
color: question.content.useImage ? "#7E2AEA" : "#9A9AAF",
|
||||
fontSize: "16px",
|
||||
"&:hover": {
|
||||
background: "none",
|
||||
@ -95,17 +96,18 @@ export const MediaSelectionAndDisplay: FC<Iprops> = ({
|
||||
}}
|
||||
variant="text"
|
||||
onClick={() =>
|
||||
updateQuestion(
|
||||
resultData.id,
|
||||
(question) => (question.content.useImage = true),
|
||||
)
|
||||
updateQuestion(question.id, (question) => {
|
||||
if (!("useImage" in question.content)) return;
|
||||
|
||||
question.content.useImage = true;
|
||||
})
|
||||
}
|
||||
>
|
||||
Изображение
|
||||
</Button>
|
||||
<Button
|
||||
sx={{
|
||||
color: resultData.content.useImage ? "#9A9AAF" : "#7E2AEA",
|
||||
color: question.content.useImage ? "#9A9AAF" : "#7E2AEA",
|
||||
fontSize: "16px",
|
||||
"&:hover": {
|
||||
background: "none",
|
||||
@ -113,45 +115,43 @@ export const MediaSelectionAndDisplay: FC<Iprops> = ({
|
||||
}}
|
||||
variant="text"
|
||||
onClick={() =>
|
||||
updateQuestion(
|
||||
resultData.id,
|
||||
(question) => (question.content.useImage = false),
|
||||
)
|
||||
updateQuestion(question.id, (question) => {
|
||||
if (!("useImage" in question.content)) return;
|
||||
|
||||
question.content.useImage = false;
|
||||
})
|
||||
}
|
||||
>
|
||||
Видео
|
||||
</Button>
|
||||
</Box>
|
||||
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
<UploadImageModal
|
||||
isOpen={isImageUploadOpen}
|
||||
onClose={closeImageUploadModal}
|
||||
handleImageChange={handleImageUpload}
|
||||
/>
|
||||
<CropModal
|
||||
isOpen={isCropModalOpen}
|
||||
imageBlob={imageBlob}
|
||||
originalImageUrl={originalImageUrl}
|
||||
setCropModalImageBlob={setCropModalImageBlob}
|
||||
onClose={closeCropModal}
|
||||
onSaveImageClick={handleCropModalSaveClick}
|
||||
onDeleteClick={() => {
|
||||
updateQuestion(question.id, (question) => {
|
||||
question.content.back = null;
|
||||
question.content.originalBack = null;
|
||||
});
|
||||
}}
|
||||
>
|
||||
<UploadImageModal
|
||||
isOpen={isImageUploadOpen}
|
||||
onClose={closeImageUploadModal}
|
||||
handleImageChange={handleImageUpload}
|
||||
/>
|
||||
<CropModal
|
||||
isOpen={isCropModalOpen}
|
||||
imageBlob={imageBlob}
|
||||
originalImageUrl={originalImageUrl}
|
||||
setCropModalImageBlob={setCropModalImageBlob}
|
||||
onClose={closeCropModal}
|
||||
onSaveImageClick={handleCropModalSaveClick}
|
||||
onDeleteClick={() => {
|
||||
updateQuestion(resultData.id, (question) => {
|
||||
question.content.back = null;
|
||||
question.content.originalBack = null;
|
||||
});
|
||||
}}
|
||||
cropAspectRatio={cropAspectRatio}
|
||||
/>
|
||||
</Box>
|
||||
|
||||
{resultData.content.useImage && (
|
||||
cropAspectRatio={cropAspectRatio}
|
||||
/>
|
||||
<UploadVideoModal
|
||||
open={isVideoUploadDialogOpen}
|
||||
onClose={() => setIsVideoUploadDialogOpen(false)}
|
||||
onUpload={handleVideoUpload}
|
||||
video={question.content.video}
|
||||
/>
|
||||
{question.content.useImage && (
|
||||
<Box
|
||||
sx={{
|
||||
cursor: "pointer",
|
||||
@ -162,14 +162,11 @@ export const MediaSelectionAndDisplay: FC<Iprops> = ({
|
||||
}}
|
||||
>
|
||||
<AddOrEditImageButton
|
||||
imageSrc={resultData.content.back}
|
||||
imageSrc={question.content.back}
|
||||
uploading={pictureUploding}
|
||||
onImageClick={() => {
|
||||
if (resultData.content.back) {
|
||||
return openCropModal(
|
||||
resultData.content.back,
|
||||
resultData.content.originalBack,
|
||||
);
|
||||
if (question.content.back) {
|
||||
return openCropModal(question.content.back, question.content.originalBack);
|
||||
}
|
||||
|
||||
openImageUploadModal();
|
||||
@ -180,9 +177,9 @@ export const MediaSelectionAndDisplay: FC<Iprops> = ({
|
||||
/>
|
||||
</Box>
|
||||
)}
|
||||
{!resultData.content.useImage && (
|
||||
{!question.content.useImage && (
|
||||
<>
|
||||
{!resultData.content.video ? (
|
||||
{!question.content.video ? (
|
||||
<>
|
||||
<Box
|
||||
sx={{
|
||||
@ -193,12 +190,11 @@ export const MediaSelectionAndDisplay: FC<Iprops> = ({
|
||||
mb: "14px",
|
||||
}}
|
||||
>
|
||||
<Typography
|
||||
sx={{ fontWeight: 500, color: theme.palette.grey3.main }}
|
||||
<Typography sx={{ fontWeight: 500, color: theme.palette.grey3.main }}>Добавить видео</Typography>
|
||||
<Tooltip
|
||||
title="Можно загрузить видео."
|
||||
placement="top"
|
||||
>
|
||||
Добавить видео
|
||||
</Typography>
|
||||
<Tooltip title="Можно загрузить видео." placement="top">
|
||||
<Box>
|
||||
<InfoIcon />
|
||||
</Box>
|
||||
@ -216,7 +212,7 @@ export const MediaSelectionAndDisplay: FC<Iprops> = ({
|
||||
) : (
|
||||
<>
|
||||
<ButtonBase
|
||||
component="label"
|
||||
onClick={() => setIsVideoUploadDialogOpen(true)}
|
||||
sx={{
|
||||
justifyContent: "center",
|
||||
height: "48px",
|
||||
@ -226,27 +222,6 @@ export const MediaSelectionAndDisplay: FC<Iprops> = ({
|
||||
my: "20px",
|
||||
}}
|
||||
>
|
||||
<input
|
||||
onChange={async (event) => {
|
||||
setBackgroundUploading(true);
|
||||
const file = event.target.files?.[0];
|
||||
if (file) {
|
||||
await uploadQuestionImage(
|
||||
resultData.id,
|
||||
quizQid,
|
||||
file,
|
||||
(question, url) => {
|
||||
question.content.video = url;
|
||||
},
|
||||
);
|
||||
}
|
||||
setBackgroundUploading(false);
|
||||
}}
|
||||
hidden
|
||||
accept=".mp4"
|
||||
multiple
|
||||
type="file"
|
||||
/>
|
||||
<UploadBox
|
||||
icon={<UploadIcon />}
|
||||
sx={{
|
||||
@ -260,10 +235,12 @@ export const MediaSelectionAndDisplay: FC<Iprops> = ({
|
||||
</>
|
||||
) : (
|
||||
<VideoElement
|
||||
videoSrc={resultData.content.video}
|
||||
videoSrc={question.content.video}
|
||||
theme={theme}
|
||||
onDeleteClick={() => {
|
||||
updateQuestion(resultData.id, (question) => {
|
||||
updateQuestion(question.id, (question) => {
|
||||
if (!("video" in question.content)) return;
|
||||
|
||||
question.content.video = null;
|
||||
});
|
||||
}}
|
||||
|
39
yarn.lock
39
yarn.lock
@ -1521,10 +1521,10 @@
|
||||
immer "^10.0.2"
|
||||
reconnecting-eventsource "^1.6.2"
|
||||
|
||||
"@frontend/squzanswerer@^1.0.44":
|
||||
version "1.0.44"
|
||||
resolved "https://penahub.gitlab.yandexcloud.net/api/v4/projects/43/packages/npm/@frontend/squzanswerer/-/@frontend/squzanswerer-1.0.44.tgz#12c19b23a1e1eff4d0cbfeffbc9ed1160c49cde2"
|
||||
integrity sha1-EsGbI6Hh7/TQy/7/vJ7RFgxJzeI=
|
||||
"@frontend/squzanswerer@^1.0.45":
|
||||
version "1.0.45"
|
||||
resolved "https://penahub.gitlab.yandexcloud.net/api/v4/projects/43/packages/npm/@frontend/squzanswerer/-/@frontend/squzanswerer-1.0.45.tgz#1124aaa099034b0b75eda7b5c91f457db47872ab"
|
||||
integrity sha1-ESSqoJkDSwt17ae1yR9FfbR4cqs=
|
||||
dependencies:
|
||||
bowser "1.9.4"
|
||||
country-flag-emoji-polyfill "^0.1.8"
|
||||
@ -10557,16 +10557,7 @@ string-natural-compare@^3.0.1:
|
||||
resolved "https://registry.yarnpkg.com/string-natural-compare/-/string-natural-compare-3.0.1.tgz#7a42d58474454963759e8e8b7ae63d71c1e7fdf4"
|
||||
integrity sha512-n3sPwynL1nwKi3WJ6AIsClwBMa0zTi54fn2oLU6ndfTSIO05xaznjSf15PcBZU6FNWbmN5Q6cxT4V5hGvB4taw==
|
||||
|
||||
"string-width-cjs@npm:string-width@^4.2.0":
|
||||
version "4.2.3"
|
||||
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
|
||||
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
|
||||
dependencies:
|
||||
emoji-regex "^8.0.0"
|
||||
is-fullwidth-code-point "^3.0.0"
|
||||
strip-ansi "^6.0.1"
|
||||
|
||||
string-width@^4.1.0, string-width@^4.2.0:
|
||||
"string-width-cjs@npm:string-width@^4.2.0", string-width@^4.1.0, string-width@^4.2.0:
|
||||
version "4.2.3"
|
||||
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
|
||||
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
|
||||
@ -10662,14 +10653,7 @@ stringify-object@^3.3.0:
|
||||
is-obj "^1.0.1"
|
||||
is-regexp "^1.0.0"
|
||||
|
||||
"strip-ansi-cjs@npm:strip-ansi@^6.0.1":
|
||||
version "6.0.1"
|
||||
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
|
||||
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
|
||||
dependencies:
|
||||
ansi-regex "^5.0.1"
|
||||
|
||||
strip-ansi@^6.0.0, strip-ansi@^6.0.1:
|
||||
"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1:
|
||||
version "6.0.1"
|
||||
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
|
||||
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
|
||||
@ -11856,7 +11840,7 @@ workbox-window@6.6.1:
|
||||
"@types/trusted-types" "^2.0.2"
|
||||
workbox-core "6.6.1"
|
||||
|
||||
"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0":
|
||||
"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0:
|
||||
version "7.0.0"
|
||||
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
|
||||
integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
|
||||
@ -11874,15 +11858,6 @@ wrap-ansi@^6.2.0:
|
||||
string-width "^4.1.0"
|
||||
strip-ansi "^6.0.0"
|
||||
|
||||
wrap-ansi@^7.0.0:
|
||||
version "7.0.0"
|
||||
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
|
||||
integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
|
||||
dependencies:
|
||||
ansi-styles "^4.0.0"
|
||||
string-width "^4.1.0"
|
||||
strip-ansi "^6.0.0"
|
||||
|
||||
wrap-ansi@^8.1.0:
|
||||
version "8.1.0"
|
||||
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214"
|
||||
|
Loading…
Reference in New Issue
Block a user