Merge branch 'dev' into 'main'
при удалении вопросов из списка и или дерева гарантируем, что default будет... See merge request frontend/squiz!67
This commit is contained in:
commit
d718b3d08e
19001
package-lock.json
generated
19001
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -40,6 +40,7 @@
|
|||||||
"react-dnd-html5-backend": "^16.0.1",
|
"react-dnd-html5-backend": "^16.0.1",
|
||||||
"react-dom": "^18.2.0",
|
"react-dom": "^18.2.0",
|
||||||
"react-easy-crop": "^5.0.0",
|
"react-easy-crop": "^5.0.0",
|
||||||
|
"react-error-boundary": "^4.0.11",
|
||||||
"react-image-crop": "^10.1.5",
|
"react-image-crop": "^10.1.5",
|
||||||
"react-image-file-resizer": "^0.4.8",
|
"react-image-file-resizer": "^0.4.8",
|
||||||
"react-rnd": "^10.4.1",
|
"react-rnd": "^10.4.1",
|
||||||
|
|||||||
@ -11,8 +11,8 @@ import ContactFormPage from "./pages/ContactFormPage/ContactFormPage";
|
|||||||
import InstallQuiz from "./pages/InstallQuiz/InstallQuiz";
|
import InstallQuiz from "./pages/InstallQuiz/InstallQuiz";
|
||||||
import Landing from "./pages/Landing/Landing";
|
import Landing from "./pages/Landing/Landing";
|
||||||
import QuestionsPage from "./pages/Questions/QuestionsPage";
|
import QuestionsPage from "./pages/Questions/QuestionsPage";
|
||||||
import { Result } from "./pages/Result/Result";
|
import { Result } from "./pages/ResultPage/Result";
|
||||||
import { Setting } from "./pages/Result/Setting";
|
import { Setting } from "./pages/ResultPage/Setting";
|
||||||
import MyQuizzesFull from "./pages/createQuize/MyQuizzesFull";
|
import MyQuizzesFull from "./pages/createQuize/MyQuizzesFull";
|
||||||
import Main from "./pages/main";
|
import Main from "./pages/main";
|
||||||
import StartPage from "./pages/startPage/StartPage";
|
import StartPage from "./pages/startPage/StartPage";
|
||||||
|
|||||||
@ -11,6 +11,7 @@ import { QUIZ_QUESTION_SELECT } from "./select";
|
|||||||
import { QUIZ_QUESTION_TEXT } from "./text";
|
import { QUIZ_QUESTION_TEXT } from "./text";
|
||||||
import { QUIZ_QUESTION_VARIANT } from "./variant";
|
import { QUIZ_QUESTION_VARIANT } from "./variant";
|
||||||
import { QUIZ_QUESTION_VARIMG } from "./varimg";
|
import { QUIZ_QUESTION_VARIMG } from "./varimg";
|
||||||
|
import { QUIZ_QUESTION_RESULT } from "./result";
|
||||||
|
|
||||||
|
|
||||||
export const defaultQuestionByType: Record<QuestionType, Omit<AnyTypedQuizQuestion, "id" | "backendId">> = {
|
export const defaultQuestionByType: Record<QuestionType, Omit<AnyTypedQuizQuestion, "id" | "backendId">> = {
|
||||||
@ -25,4 +26,5 @@ export const defaultQuestionByType: Record<QuestionType, Omit<AnyTypedQuizQuesti
|
|||||||
"text": QUIZ_QUESTION_TEXT,
|
"text": QUIZ_QUESTION_TEXT,
|
||||||
"variant": QUIZ_QUESTION_VARIANT,
|
"variant": QUIZ_QUESTION_VARIANT,
|
||||||
"varimg": QUIZ_QUESTION_VARIMG,
|
"varimg": QUIZ_QUESTION_VARIMG,
|
||||||
|
"result": QUIZ_QUESTION_RESULT,
|
||||||
} as const;
|
} as const;
|
||||||
|
|||||||
26
src/constants/result.ts
Normal file
26
src/constants/result.ts
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
import { QUIZ_QUESTION_BASE } from "./base";
|
||||||
|
|
||||||
|
import type { QuizQuestionResult } from "../model/questionTypes/result";
|
||||||
|
import { nanoid } from "nanoid";
|
||||||
|
|
||||||
|
export const QUIZ_QUESTION_RESULT: Omit<QuizQuestionResult, "id" | "backendId"> = {
|
||||||
|
...QUIZ_QUESTION_BASE,
|
||||||
|
type: "result",
|
||||||
|
content: {
|
||||||
|
...QUIZ_QUESTION_BASE.content,
|
||||||
|
multi: false,
|
||||||
|
own: false,
|
||||||
|
innerNameCheck: false,
|
||||||
|
innerName: "",
|
||||||
|
required: false,
|
||||||
|
variants: [
|
||||||
|
{
|
||||||
|
id: nanoid(),
|
||||||
|
answer: "",
|
||||||
|
extendedText: "",
|
||||||
|
hints: "",
|
||||||
|
originalImageUrl: "",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
};
|
||||||
@ -14,7 +14,8 @@ export type QuestionType =
|
|||||||
| "number"
|
| "number"
|
||||||
| "file"
|
| "file"
|
||||||
| "page"
|
| "page"
|
||||||
| "rating";
|
| "rating"
|
||||||
|
| "result";
|
||||||
|
|
||||||
/** Type that comes from server */
|
/** Type that comes from server */
|
||||||
export interface RawQuestion {
|
export interface RawQuestion {
|
||||||
|
|||||||
29
src/model/questionTypes/result.ts
Normal file
29
src/model/questionTypes/result.ts
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
import type {
|
||||||
|
QuizQuestionBase,
|
||||||
|
QuestionVariant,
|
||||||
|
QuestionHint,
|
||||||
|
PreviewRule,
|
||||||
|
} from "./shared";
|
||||||
|
|
||||||
|
export interface QuizQuestionResult extends QuizQuestionBase {
|
||||||
|
type: "result";
|
||||||
|
content: {
|
||||||
|
id: string;
|
||||||
|
/** Чекбокс "Можно несколько" */
|
||||||
|
multi: boolean;
|
||||||
|
/** Чекбокс "Вариант "свой ответ"" */
|
||||||
|
own: boolean;
|
||||||
|
/** Чекбокс "Внутреннее название вопроса" */
|
||||||
|
innerNameCheck: boolean;
|
||||||
|
/** Поле "Внутреннее название вопроса" */
|
||||||
|
innerName: string;
|
||||||
|
/** Чекбокс "Необязательный вопрос" */
|
||||||
|
required: boolean;
|
||||||
|
variants: QuestionVariant[];
|
||||||
|
hint: QuestionHint;
|
||||||
|
rule: PreviewRule;
|
||||||
|
back: string;
|
||||||
|
originalBack: string;
|
||||||
|
autofill: boolean;
|
||||||
|
};
|
||||||
|
}
|
||||||
@ -29,8 +29,6 @@ export type QuizResultsType = true | null;
|
|||||||
|
|
||||||
export interface QuizConfig {
|
export interface QuizConfig {
|
||||||
type: QuizType;
|
type: QuizType;
|
||||||
logo: string | null;
|
|
||||||
originalLogo: string | null;
|
|
||||||
noStartPage: boolean;
|
noStartPage: boolean;
|
||||||
startpageType: QuizStartpageType;
|
startpageType: QuizStartpageType;
|
||||||
results: QuizResultsType;
|
results: QuizResultsType;
|
||||||
@ -39,6 +37,10 @@ export interface QuizConfig {
|
|||||||
description: string;
|
description: string;
|
||||||
button: string;
|
button: string;
|
||||||
position: QuizStartpageAlignType;
|
position: QuizStartpageAlignType;
|
||||||
|
favIcon: string | null;
|
||||||
|
originalFavIcon: string | null;
|
||||||
|
logo: string | null;
|
||||||
|
originalLogo: string | null;
|
||||||
background: {
|
background: {
|
||||||
type: null | "image" | "video";
|
type: null | "image" | "video";
|
||||||
desktop: string | null;
|
desktop: string | null;
|
||||||
@ -61,8 +63,6 @@ export interface QuizConfig {
|
|||||||
|
|
||||||
export const defaultQuizConfig: QuizConfig = {
|
export const defaultQuizConfig: QuizConfig = {
|
||||||
type: null,
|
type: null,
|
||||||
logo: null,
|
|
||||||
originalLogo: null,
|
|
||||||
noStartPage: false,
|
noStartPage: false,
|
||||||
startpageType: null,
|
startpageType: null,
|
||||||
results: null,
|
results: null,
|
||||||
@ -71,6 +71,10 @@ export const defaultQuizConfig: QuizConfig = {
|
|||||||
description: "",
|
description: "",
|
||||||
button: "",
|
button: "",
|
||||||
position: "left",
|
position: "left",
|
||||||
|
favIcon: null,
|
||||||
|
originalFavIcon: null,
|
||||||
|
logo: null,
|
||||||
|
originalLogo: null,
|
||||||
background: {
|
background: {
|
||||||
type: null,
|
type: null,
|
||||||
desktop: null,
|
desktop: null,
|
||||||
|
|||||||
@ -6,7 +6,8 @@ import { useCurrentQuiz } from "@root/quizes/hooks";
|
|||||||
import { updateRootContentId } from "@root/quizes/actions"
|
import { updateRootContentId } from "@root/quizes/actions"
|
||||||
import { AnyTypedQuizQuestion } from "@model/questionTypes/shared"
|
import { AnyTypedQuizQuestion } from "@model/questionTypes/shared"
|
||||||
import { useQuestionsStore } from "@root/questions/store";
|
import { useQuestionsStore } from "@root/questions/store";
|
||||||
import { cleardragQuestionContentId, updateQuestion, updateOpenedModalSettingsId, getQuestionByContentId } from "@root/questions/actions";
|
import { cleardragQuestionContentId, updateQuestion, updateOpenedModalSettingsId, getQuestionByContentId, clearRuleForAll } from "@root/questions/actions";
|
||||||
|
import { withErrorBoundary } from "react-error-boundary";
|
||||||
|
|
||||||
import { storeToNodes } from "./helper";
|
import { storeToNodes } from "./helper";
|
||||||
|
|
||||||
@ -21,6 +22,7 @@ import type {
|
|||||||
} from "cytoscape";
|
} from "cytoscape";
|
||||||
import { QuestionsList } from "../SwitchBranchingPanel/QuestionsList";
|
import { QuestionsList } from "../SwitchBranchingPanel/QuestionsList";
|
||||||
import { enqueueSnackbar } from "notistack";
|
import { enqueueSnackbar } from "notistack";
|
||||||
|
import { Typography } from "@mui/material";
|
||||||
|
|
||||||
type PopperItem = {
|
type PopperItem = {
|
||||||
id: () => string;
|
id: () => string;
|
||||||
@ -111,13 +113,13 @@ interface Props {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export const CsComponent = ({
|
function CsComponent ({
|
||||||
modalQuestionParentContentId,
|
modalQuestionParentContentId,
|
||||||
modalQuestionTargetContentId,
|
modalQuestionTargetContentId,
|
||||||
setOpenedModalQuestions,
|
setOpenedModalQuestions,
|
||||||
setModalQuestionParentContentId,
|
setModalQuestionParentContentId,
|
||||||
setModalQuestionTargetContentId
|
setModalQuestionTargetContentId
|
||||||
}: Props) => {
|
}: Props) {
|
||||||
const quiz = useCurrentQuiz();
|
const quiz = useCurrentQuiz();
|
||||||
|
|
||||||
const { dragQuestionContentId, questions, desireToOpenABranchingModal } = useQuestionsStore()
|
const { dragQuestionContentId, questions, desireToOpenABranchingModal } = useQuestionsStore()
|
||||||
@ -131,7 +133,7 @@ export const CsComponent = ({
|
|||||||
const gearsContainer = useRef<HTMLDivElement | null>(null);
|
const gearsContainer = useRef<HTMLDivElement | null>(null);
|
||||||
|
|
||||||
useLayoutEffect(() => {
|
useLayoutEffect(() => {
|
||||||
const cy = cyRef?.current
|
const cy = cyRef?.current
|
||||||
if (desireToOpenABranchingModal) {
|
if (desireToOpenABranchingModal) {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
cy?.getElementById(desireToOpenABranchingModal)?.data("eroticeyeblink", true)
|
cy?.getElementById(desireToOpenABranchingModal)?.data("eroticeyeblink", true)
|
||||||
@ -142,6 +144,8 @@ const cy = cyRef?.current
|
|||||||
}, [desireToOpenABranchingModal])
|
}, [desireToOpenABranchingModal])
|
||||||
useLayoutEffect(() => {
|
useLayoutEffect(() => {
|
||||||
updateOpenedModalSettingsId()
|
updateOpenedModalSettingsId()
|
||||||
|
// updateRootContentId(quiz.id, "")
|
||||||
|
// clearRuleForAll()
|
||||||
}, [])
|
}, [])
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (modalQuestionTargetContentId.length !== 0 && modalQuestionParentContentId.length !== 0) {
|
if (modalQuestionTargetContentId.length !== 0 && modalQuestionParentContentId.length !== 0) {
|
||||||
@ -149,7 +153,6 @@ const cy = cyRef?.current
|
|||||||
}
|
}
|
||||||
setModalQuestionParentContentId("")
|
setModalQuestionParentContentId("")
|
||||||
setModalQuestionTargetContentId("")
|
setModalQuestionTargetContentId("")
|
||||||
|
|
||||||
}, [modalQuestionTargetContentId])
|
}, [modalQuestionTargetContentId])
|
||||||
|
|
||||||
const addNode = ({ parentNodeContentId, targetNodeContentId }: { parentNodeContentId: string, targetNodeContentId?: string }) => {
|
const addNode = ({ parentNodeContentId, targetNodeContentId }: { parentNodeContentId: string, targetNodeContentId?: string }) => {
|
||||||
@ -229,6 +232,7 @@ const cy = cyRef?.current
|
|||||||
question.content.rule.main = []
|
question.content.rule.main = []
|
||||||
question.content.rule.default = ""
|
question.content.rule.default = ""
|
||||||
})
|
})
|
||||||
|
clearRuleForAll()
|
||||||
} else {
|
} else {
|
||||||
const parentQuestionContentId = cy?.$('edge[target = "' + targetNodeContentId + '"]')?.toArray()?.[0]?.data()?.source
|
const parentQuestionContentId = cy?.$('edge[target = "' + targetNodeContentId + '"]')?.toArray()?.[0]?.data()?.source
|
||||||
if (targetNodeContentId && parentQuestionContentId) {
|
if (targetNodeContentId && parentQuestionContentId) {
|
||||||
@ -266,11 +270,21 @@ const cy = cyRef?.current
|
|||||||
question.content.rule.default = ""
|
question.content.rule.default = ""
|
||||||
})
|
})
|
||||||
|
|
||||||
updateQuestion(parentQuestionContentId, question => {
|
//чистим rule родителя
|
||||||
//Заменяем id удаляемого вопроса либо на id одного из оставшихся, либо на пустую строку
|
const parentQuestion = getQuestionByContentId(parentQuestionContentId)
|
||||||
if (question.content.rule.default === targetQuestionContentId) question.content.rule.default = ""
|
const newRule = {}
|
||||||
})
|
newRule.main = parentQuestion.content.rule.main.filter((data) => data.next !== targetQuestionContentId) //удаляем условия перехода от родителя к этому вопросу
|
||||||
|
newRule.parentId = parentQuestion.content.rule.parentId
|
||||||
|
newRule.default = questions.filter((q) => {
|
||||||
|
return q.content.rule.parentId === parentQuestionContentId && q.content.id !== targetQuestionContentId
|
||||||
|
})[0]?.content.id || ""
|
||||||
|
//Если этот вопрос был дефолтным у родителя - чистим дефолт
|
||||||
|
//Смотрим можем ли мы заменить id на один из main
|
||||||
|
|
||||||
|
console.log(newRule)
|
||||||
|
updateQuestion(parentQuestionContentId, (PQ) => {
|
||||||
|
PQ.content.rule = newRule
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -686,7 +700,7 @@ const cy = cyRef?.current
|
|||||||
}}
|
}}
|
||||||
// autolock
|
// autolock
|
||||||
/>
|
/>
|
||||||
{/* <button onClick={() => {
|
<button onClick={() => {
|
||||||
console.log("NODES____________________________")
|
console.log("NODES____________________________")
|
||||||
cyRef.current?.elements().forEach((ele: any) => {
|
cyRef.current?.elements().forEach((ele: any) => {
|
||||||
console.log(ele.data())
|
console.log(ele.data())
|
||||||
@ -695,7 +709,23 @@ const cy = cyRef?.current
|
|||||||
<button onClick={() => {
|
<button onClick={() => {
|
||||||
console.log("ELEMENTS____________________________")
|
console.log("ELEMENTS____________________________")
|
||||||
console.log(questions)
|
console.log(questions)
|
||||||
}}>elements</button> */}
|
}}>elements</button>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
function Clear () {
|
||||||
|
const quiz = useCurrentQuiz();
|
||||||
|
updateRootContentId(quiz.id, "")
|
||||||
|
clearRuleForAll()
|
||||||
|
return <></>
|
||||||
|
}
|
||||||
|
|
||||||
|
export default withErrorBoundary(CsComponent, {
|
||||||
|
fallback: <Clear/>,
|
||||||
|
onError: (error, info) => {
|
||||||
|
enqueueSnackbar("Дерево порвалось")
|
||||||
|
console.log(info)
|
||||||
|
console.log(error)
|
||||||
|
},
|
||||||
|
});
|
||||||
@ -1,6 +1,6 @@
|
|||||||
import { Box } from "@mui/material";
|
import { Box } from "@mui/material";
|
||||||
import { FirstNodeField } from "./FirstNodeField";
|
import { FirstNodeField } from "./FirstNodeField";
|
||||||
import { CsComponent } from "./CsComponent";
|
import CsComponent from "./CsComponent";
|
||||||
import { useQuestionsStore } from "@root/questions/store"
|
import { useQuestionsStore } from "@root/questions/store"
|
||||||
import { useCurrentQuiz } from "@root/quizes/hooks";
|
import { useCurrentQuiz } from "@root/quizes/hooks";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
|
|||||||
@ -68,7 +68,7 @@ export default function QuestionsPageCard({ question, draggableProps, isDragging
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Paper
|
<Paper
|
||||||
id={question.content.id}
|
id={question.id}
|
||||||
data-cy="quiz-question-card"
|
data-cy="quiz-question-card"
|
||||||
sx={{
|
sx={{
|
||||||
maxWidth: "796px",
|
maxWidth: "796px",
|
||||||
|
|||||||
@ -22,10 +22,10 @@ import { updateOpenBranchingPanel, updateEditSomeQuestion } from "@root/question
|
|||||||
|
|
||||||
export default function QuestionsPage() {
|
export default function QuestionsPage() {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const { openedModalSettingsId } = useQuestionsStore();
|
const { openedModalSettingsId, openBranchingPanel } = useQuestionsStore();
|
||||||
const isMobile = useMediaQuery(theme.breakpoints.down(660));
|
const isMobile = useMediaQuery(theme.breakpoints.down(660));
|
||||||
const quiz = useCurrentQuiz();
|
const quiz = useCurrentQuiz();
|
||||||
const {openBranchingPanel} = useQuestionsStore.getState()
|
console.log(quiz)
|
||||||
useLayoutEffect(() => {
|
useLayoutEffect(() => {
|
||||||
updateOpenBranchingPanel(false)
|
updateOpenBranchingPanel(false)
|
||||||
updateEditSomeQuestion()
|
updateEditSomeQuestion()
|
||||||
|
|||||||
@ -3,7 +3,7 @@ import { updateQuiz } from "@root/quizes/actions";
|
|||||||
import { useCurrentQuiz } from "@root/quizes/hooks";
|
import { useCurrentQuiz } from "@root/quizes/hooks";
|
||||||
import image from "../../assets/Rectangle 110.png";
|
import image from "../../assets/Rectangle 110.png";
|
||||||
import Info from "../../assets/icons/Info";
|
import Info from "../../assets/icons/Info";
|
||||||
import CreationFullCard from "./CreationFullCard";
|
import CreationFullCard from "./FirstEntry";
|
||||||
|
|
||||||
|
|
||||||
export const Result = () => {
|
export const Result = () => {
|
||||||
19
src/pages/ResultPage/ResultPage.tsx
Normal file
19
src/pages/ResultPage/ResultPage.tsx
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
import { useQuestionsStore } from "@root/questions/store";
|
||||||
|
import FirstEntry from "./FirstEntry"
|
||||||
|
|
||||||
|
|
||||||
|
export default function ResultPage() {
|
||||||
|
const { questions } = useQuestionsStore();
|
||||||
|
//ищём хотя бы один result
|
||||||
|
const haveResult = questions.some((question) => {
|
||||||
|
question.type === "result"
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -55,8 +55,8 @@ export const Question = ({
|
|||||||
sx={{
|
sx={{
|
||||||
minHeight: "calc(100vh - 75px)",
|
minHeight: "calc(100vh - 75px)",
|
||||||
width: "100%",
|
width: "100%",
|
||||||
maxWidth: "1000px",
|
maxWidth: "1440px",
|
||||||
padding: "20px 10px 0",
|
padding: "40px 25px 20px",
|
||||||
margin: "0 auto",
|
margin: "0 auto",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
|||||||
@ -6,11 +6,10 @@ import {
|
|||||||
FormControlLabel,
|
FormControlLabel,
|
||||||
Radio,
|
Radio,
|
||||||
useTheme,
|
useTheme,
|
||||||
useMediaQuery,
|
useMediaQuery, FormControl,
|
||||||
} from "@mui/material";
|
} from "@mui/material";
|
||||||
|
|
||||||
import { useQuizViewStore, updateAnswer } from "@root/quizView";
|
import { useQuizViewStore, updateAnswer } from "@root/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";
|
||||||
|
|
||||||
@ -92,7 +91,7 @@ export const Images = ({ currentQuestion }: ImagesProps) => {
|
|||||||
)}
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
<FormControlLabel
|
<FormControl
|
||||||
key={id}
|
key={id}
|
||||||
sx={{
|
sx={{
|
||||||
display: "block",
|
display: "block",
|
||||||
|
|||||||
@ -14,7 +14,8 @@ export const Page = ({ currentQuestion }: PageProps) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Box>
|
<Box>
|
||||||
<Typography variant="h5">{currentQuestion.title}</Typography>
|
<Typography variant="h5" sx={{ paddingBottom: "25px" }}>{currentQuestion.title}</Typography>
|
||||||
|
<Typography>{currentQuestion.content.text}</Typography>
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
display: "flex",
|
display: "flex",
|
||||||
@ -24,6 +25,8 @@ export const Page = ({ currentQuestion }: PageProps) => {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{currentQuestion.content.picture && (
|
{currentQuestion.content.picture && (
|
||||||
|
<Box sx={{borderRadius: "12px",
|
||||||
|
border: "1px solid #9A9AAF", overflow: "hidden" }}>
|
||||||
<img
|
<img
|
||||||
src={currentQuestion.content.picture}
|
src={currentQuestion.content.picture}
|
||||||
alt=""
|
alt=""
|
||||||
@ -31,10 +34,11 @@ export const Page = ({ currentQuestion }: PageProps) => {
|
|||||||
display: "block",
|
display: "block",
|
||||||
width: "100%",
|
width: "100%",
|
||||||
height: "100%",
|
height: "100%",
|
||||||
maxHeight: "80vh",
|
|
||||||
objectFit: "contain",
|
objectFit: "contain",
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
</Box>
|
||||||
|
|
||||||
)}
|
)}
|
||||||
{currentQuestion.content.video && (
|
{currentQuestion.content.video && (
|
||||||
<video
|
<video
|
||||||
|
|||||||
@ -53,20 +53,22 @@ export const Variant = ({ currentQuestion }: VariantProps) => {
|
|||||||
marginTop: "20px",
|
marginTop: "20px",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Box sx={{ display: "flex", flexDirection: "column", width: "100%" }}>
|
<Box sx={{ display: "flex", flexDirection: "row", flexWrap: "wrap", width: "100%", gap: "20px", }}>
|
||||||
{currentQuestion.content.variants.map(({ id, answer }, index) => (
|
{currentQuestion.content.variants.map(({ id, answer }, index) => (
|
||||||
<FormControlLabel
|
<FormControlLabel
|
||||||
key={id}
|
key={id}
|
||||||
sx={{
|
sx={{
|
||||||
marginBottom: "15px",
|
margin: "0",
|
||||||
borderRadius: "5px",
|
borderRadius: "12px",
|
||||||
padding: "15px",
|
padding: "15px",
|
||||||
color: theme.palette.grey2.main,
|
|
||||||
border: `1px solid ${theme.palette.grey2.main}`,
|
border: `1px solid ${theme.palette.grey2.main}`,
|
||||||
display: "flex",
|
display: "flex",
|
||||||
gap: "10px",
|
maxWidth: "685px",
|
||||||
|
justifyContent: "space-between",
|
||||||
|
width: "100%"
|
||||||
}}
|
}}
|
||||||
value={index}
|
value={index}
|
||||||
|
labelPlacement="start"
|
||||||
control={
|
control={
|
||||||
<Radio checkedIcon={<RadioCheck />} icon={<RadioIcon />} />
|
<Radio checkedIcon={<RadioCheck />} icon={<RadioIcon />} />
|
||||||
}
|
}
|
||||||
|
|||||||
@ -35,8 +35,8 @@ import UploadIcon from "../../assets/icons/UploadIcon";
|
|||||||
import ModalSizeImage from "./ModalSizeImage";
|
import ModalSizeImage from "./ModalSizeImage";
|
||||||
import SelectableIconButton from "./SelectableIconButton";
|
import SelectableIconButton from "./SelectableIconButton";
|
||||||
import { DropZone } from "./dropZone";
|
import { DropZone } from "./dropZone";
|
||||||
import DropFav from "./dropfavicon";
|
|
||||||
import Extra from "./extra";
|
import Extra from "./extra";
|
||||||
|
import { resizeFavIcon } from "@ui_kit/reactImageFileResizer";
|
||||||
|
|
||||||
|
|
||||||
const designTypes = [
|
const designTypes = [
|
||||||
@ -85,6 +85,33 @@ export default function StartPageSettings() {
|
|||||||
|
|
||||||
if (!quiz) return null; // TODO throw and catch with error boundary
|
if (!quiz) return null; // TODO throw and catch with error boundary
|
||||||
|
|
||||||
|
const favIconDropZoneElement = (
|
||||||
|
<DropZone
|
||||||
|
sx={{ height: "48px", width: "48px" }}
|
||||||
|
deleteIconSx={{ right: -40, top: -10 }}
|
||||||
|
imageUrl={quiz.config.startpage.favIcon}
|
||||||
|
originalImageUrl={quiz.config.startpage.originalFavIcon}
|
||||||
|
onImageUploadClick={async file => {
|
||||||
|
const resizedImage = await resizeFavIcon(file);
|
||||||
|
uploadQuizImage(quiz.id, resizedImage, (quiz, url) => {
|
||||||
|
quiz.config.startpage.favIcon = url;
|
||||||
|
quiz.config.startpage.originalFavIcon = url;
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
onImageSaveClick={async file => {
|
||||||
|
const resizedImage = await resizeFavIcon(file);
|
||||||
|
uploadQuizImage(quiz.id, resizedImage, (quiz, url) => {
|
||||||
|
quiz.config.startpage.favIcon = url;
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
onDeleteClick={() => {
|
||||||
|
updateQuiz(quiz.id, quiz => {
|
||||||
|
quiz.config.startpage.favIcon = null;
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Typography
|
<Typography
|
||||||
@ -304,7 +331,6 @@ export default function StartPageSettings() {
|
|||||||
</Typography>
|
</Typography>
|
||||||
<DropZone
|
<DropZone
|
||||||
text={"5 MB максимум"}
|
text={"5 MB максимум"}
|
||||||
heightImg={"110px"}
|
|
||||||
sx={{ maxWidth: "300px" }}
|
sx={{ maxWidth: "300px" }}
|
||||||
imageUrl={quiz.config.startpage.background.desktop}
|
imageUrl={quiz.config.startpage.background.desktop}
|
||||||
originalImageUrl={quiz.config.startpage.background.originalDesktop}
|
originalImageUrl={quiz.config.startpage.background.originalDesktop}
|
||||||
@ -373,7 +399,6 @@ export default function StartPageSettings() {
|
|||||||
</Typography>
|
</Typography>
|
||||||
<DropZone
|
<DropZone
|
||||||
text={"5 MB максимум"}
|
text={"5 MB максимум"}
|
||||||
heightImg={"110px"}
|
|
||||||
imageUrl={quiz.config.startpage.background.mobile}
|
imageUrl={quiz.config.startpage.background.mobile}
|
||||||
originalImageUrl={quiz.config.startpage.background.originalMobile}
|
originalImageUrl={quiz.config.startpage.background.originalMobile}
|
||||||
onImageUploadClick={file => {
|
onImageUploadClick={file => {
|
||||||
@ -476,7 +501,6 @@ export default function StartPageSettings() {
|
|||||||
</Typography>
|
</Typography>
|
||||||
<DropZone
|
<DropZone
|
||||||
text={"5 MB максимум"}
|
text={"5 MB максимум"}
|
||||||
heightImg={"110px"}
|
|
||||||
imageUrl={quiz.config.startpage.background.mobile}
|
imageUrl={quiz.config.startpage.background.mobile}
|
||||||
originalImageUrl={quiz.config.startpage.background.originalMobile}
|
originalImageUrl={quiz.config.startpage.background.originalMobile}
|
||||||
onImageUploadClick={file => {
|
onImageUploadClick={file => {
|
||||||
@ -557,24 +581,23 @@ export default function StartPageSettings() {
|
|||||||
</Typography>
|
</Typography>
|
||||||
<DropZone
|
<DropZone
|
||||||
text={"5 MB максимум"}
|
text={"5 MB максимум"}
|
||||||
heightImg={"110px"}
|
|
||||||
sx={{ maxWidth: "300px" }}
|
sx={{ maxWidth: "300px" }}
|
||||||
imageUrl={quiz.config.logo}
|
imageUrl={quiz.config.startpage.logo}
|
||||||
originalImageUrl={quiz.config.originalLogo}
|
originalImageUrl={quiz.config.startpage.originalLogo}
|
||||||
onImageUploadClick={file => {
|
onImageUploadClick={file => {
|
||||||
uploadQuizImage(quiz.id, file, (quiz, url) => {
|
uploadQuizImage(quiz.id, file, (quiz, url) => {
|
||||||
quiz.config.logo = url;
|
quiz.config.startpage.logo = url;
|
||||||
quiz.config.originalLogo = url;
|
quiz.config.startpage.originalLogo = url;
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
onImageSaveClick={file => {
|
onImageSaveClick={file => {
|
||||||
uploadQuizImage(quiz.id, file, (quiz, url) => {
|
uploadQuizImage(quiz.id, file, (quiz, url) => {
|
||||||
quiz.config.logo = url;
|
quiz.config.startpage.logo = url;
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
onDeleteClick={() => {
|
onDeleteClick={() => {
|
||||||
updateQuiz(quiz.id, quiz => {
|
updateQuiz(quiz.id, quiz => {
|
||||||
quiz.config.logo = null;
|
quiz.config.startpage.logo = null;
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
@ -597,12 +620,7 @@ export default function StartPageSettings() {
|
|||||||
gap: "10px",
|
gap: "10px",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<DropFav
|
{favIconDropZoneElement}
|
||||||
sx={{ height: "48px", width: "48px" }}
|
|
||||||
heightImg={"48px"}
|
|
||||||
widthImg={"48px"}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<Typography
|
<Typography
|
||||||
sx={{
|
sx={{
|
||||||
color: theme.palette.orange.main,
|
color: theme.palette.orange.main,
|
||||||
@ -645,24 +663,23 @@ export default function StartPageSettings() {
|
|||||||
</Typography>
|
</Typography>
|
||||||
<DropZone
|
<DropZone
|
||||||
text={"5 MB максимум"}
|
text={"5 MB максимум"}
|
||||||
heightImg={"110px"}
|
|
||||||
sx={{ maxWidth: "300px" }}
|
sx={{ maxWidth: "300px" }}
|
||||||
imageUrl={quiz.config.logo}
|
imageUrl={quiz.config.startpage.logo}
|
||||||
originalImageUrl={quiz.config.originalLogo}
|
originalImageUrl={quiz.config.startpage.originalLogo}
|
||||||
onImageUploadClick={file => {
|
onImageUploadClick={file => {
|
||||||
uploadQuizImage(quiz.id, file, (quiz, url) => {
|
uploadQuizImage(quiz.id, file, (quiz, url) => {
|
||||||
quiz.config.logo = url;
|
quiz.config.startpage.logo = url;
|
||||||
quiz.config.originalLogo = url;
|
quiz.config.startpage.originalLogo = url;
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
onImageSaveClick={file => {
|
onImageSaveClick={file => {
|
||||||
uploadQuizImage(quiz.id, file, (quiz, url) => {
|
uploadQuizImage(quiz.id, file, (quiz, url) => {
|
||||||
quiz.config.logo = url;
|
quiz.config.startpage.logo = url;
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
onDeleteClick={() => {
|
onDeleteClick={() => {
|
||||||
updateQuiz(quiz.id, quiz => {
|
updateQuiz(quiz.id, quiz => {
|
||||||
quiz.config.logo = null;
|
quiz.config.startpage.logo = null;
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
@ -685,12 +702,7 @@ export default function StartPageSettings() {
|
|||||||
gap: "10px",
|
gap: "10px",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<DropFav
|
{favIconDropZoneElement}
|
||||||
sx={{ height: "48px", width: "48px" }}
|
|
||||||
heightImg={"48px"}
|
|
||||||
widthImg={"48px"}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<Typography
|
<Typography
|
||||||
sx={{
|
sx={{
|
||||||
color: theme.palette.orange.main,
|
color: theme.palette.orange.main,
|
||||||
|
|||||||
@ -22,8 +22,7 @@ const allowedFileTypes = ["image/png", "image/jpeg", "image/gif"];
|
|||||||
interface Props {
|
interface Props {
|
||||||
text?: string;
|
text?: string;
|
||||||
sx?: SxProps<Theme>;
|
sx?: SxProps<Theme>;
|
||||||
heightImg: string;
|
deleteIconSx?: SxProps<Theme>;
|
||||||
widthImg?: string;
|
|
||||||
imageUrl: string | null;
|
imageUrl: string | null;
|
||||||
originalImageUrl: string | null;
|
originalImageUrl: string | null;
|
||||||
onImageUploadClick: (image: Blob) => void;
|
onImageUploadClick: (image: Blob) => void;
|
||||||
@ -32,7 +31,7 @@ interface Props {
|
|||||||
}
|
}
|
||||||
|
|
||||||
//Научи функцию принимать данные для валидации
|
//Научи функцию принимать данные для валидации
|
||||||
export const DropZone = ({ text, sx, heightImg, widthImg, imageUrl, originalImageUrl, onImageUploadClick, onImageSaveClick, onDeleteClick }: Props) => {
|
export const DropZone = ({ text, sx, deleteIconSx, imageUrl, originalImageUrl, onImageUploadClick, onImageSaveClick, onDeleteClick }: Props) => {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const quiz = useCurrentQuiz();
|
const quiz = useCurrentQuiz();
|
||||||
const [isDropReady, setIsDropReady] = useState<boolean>(false);
|
const [isDropReady, setIsDropReady] = useState<boolean>(false);
|
||||||
@ -103,14 +102,15 @@ export const DropZone = ({ text, sx, heightImg, widthImg, imageUrl, originalImag
|
|||||||
justifyContent: "center",
|
justifyContent: "center",
|
||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
borderRadius: "8px",
|
borderRadius: "8px",
|
||||||
|
overflow: "hidden",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{imageUrl ?
|
{imageUrl ?
|
||||||
<img
|
<img
|
||||||
height={heightImg}
|
|
||||||
width={widthImg}
|
|
||||||
src={imageUrl}
|
src={imageUrl}
|
||||||
style={{
|
style={{
|
||||||
|
width: "100%",
|
||||||
|
height: "100%",
|
||||||
objectFit: "scale-down",
|
objectFit: "scale-down",
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
@ -144,6 +144,7 @@ export const DropZone = ({ text, sx, heightImg, widthImg, imageUrl, originalImag
|
|||||||
borderRadius: "8px",
|
borderRadius: "8px",
|
||||||
borderBottomRightRadius: 0,
|
borderBottomRightRadius: 0,
|
||||||
borderTopLeftRadius: 0,
|
borderTopLeftRadius: 0,
|
||||||
|
...deleteIconSx,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<DeleteIcon />
|
<DeleteIcon />
|
||||||
|
|||||||
@ -1,274 +0,0 @@
|
|||||||
import { Box, ButtonBase, SxProps, Theme, Typography, useTheme } from "@mui/material";
|
|
||||||
import Resizer from "@ui_kit/reactImageFileResizer";
|
|
||||||
import saveAs from "file-saver";
|
|
||||||
import JSZip from "jszip";
|
|
||||||
import { enqueueSnackbar } from "notistack";
|
|
||||||
import { useEffect, useState } from "react";
|
|
||||||
import UploadIcon from "../../assets/icons/UploadIcon";
|
|
||||||
|
|
||||||
interface Props {
|
|
||||||
text?: string;
|
|
||||||
sx?: SxProps<Theme>;
|
|
||||||
heightImg: string;
|
|
||||||
widthImg?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
const imageFavicon = [
|
|
||||||
{
|
|
||||||
maxWidth: 16,
|
|
||||||
maxHeight: 16,
|
|
||||||
compressFormat: "PNG",
|
|
||||||
quality: 100,
|
|
||||||
rotation: 0,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
maxWidth: 32,
|
|
||||||
maxHeight: 32,
|
|
||||||
compressFormat: "PNG",
|
|
||||||
quality: 100,
|
|
||||||
rotation: 0,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
maxWidth: 48,
|
|
||||||
maxHeight: 48,
|
|
||||||
compressFormat: "PNG",
|
|
||||||
quality: 100,
|
|
||||||
rotation: 0,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
maxWidth: 76,
|
|
||||||
maxHeight: 76,
|
|
||||||
compressFormat: "PNG",
|
|
||||||
quality: 100,
|
|
||||||
rotation: 0,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
maxWidth: 96,
|
|
||||||
maxHeight: 96,
|
|
||||||
compressFormat: "PNG",
|
|
||||||
quality: 100,
|
|
||||||
rotation: 0,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
maxWidth: 120,
|
|
||||||
maxHeight: 120,
|
|
||||||
compressFormat: "PNG",
|
|
||||||
quality: 100,
|
|
||||||
rotation: 0,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
maxWidth: 128,
|
|
||||||
maxHeight: 128,
|
|
||||||
compressFormat: "PNG",
|
|
||||||
quality: 100,
|
|
||||||
rotation: 0,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
maxWidth: 144,
|
|
||||||
maxHeight: 144,
|
|
||||||
compressFormat: "PNG",
|
|
||||||
quality: 100,
|
|
||||||
rotation: 0,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
maxWidth: 152,
|
|
||||||
maxHeight: 152,
|
|
||||||
compressFormat: "PNG",
|
|
||||||
quality: 100,
|
|
||||||
rotation: 0,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
maxWidth: 167,
|
|
||||||
maxHeight: 167,
|
|
||||||
compressFormat: "PNG",
|
|
||||||
quality: 100,
|
|
||||||
rotation: 0,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
maxWidth: 180,
|
|
||||||
maxHeight: 180,
|
|
||||||
compressFormat: "PNG",
|
|
||||||
quality: 100,
|
|
||||||
rotation: 0,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
maxWidth: 192,
|
|
||||||
maxHeight: 192,
|
|
||||||
compressFormat: "PNG",
|
|
||||||
quality: 100,
|
|
||||||
rotation: 0,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
maxWidth: 196,
|
|
||||||
maxHeight: 196,
|
|
||||||
compressFormat: "PNG",
|
|
||||||
quality: 100,
|
|
||||||
rotation: 0,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
maxWidth: 228,
|
|
||||||
maxHeight: 288,
|
|
||||||
compressFormat: "PNG",
|
|
||||||
quality: 100,
|
|
||||||
rotation: 0,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
maxWidth: 256,
|
|
||||||
maxHeight: 256,
|
|
||||||
compressFormat: "PNG",
|
|
||||||
quality: 100,
|
|
||||||
rotation: 0,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
maxWidth: 300,
|
|
||||||
maxHeight: 300,
|
|
||||||
compressFormat: "PNG",
|
|
||||||
quality: 100,
|
|
||||||
rotation: 0,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
maxWidth: 384,
|
|
||||||
maxHeight: 384,
|
|
||||||
compressFormat: "PNG",
|
|
||||||
quality: 100,
|
|
||||||
rotation: 0,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
maxWidth: 512,
|
|
||||||
maxHeight: 512,
|
|
||||||
compressFormat: "PNG",
|
|
||||||
quality: 100,
|
|
||||||
rotation: 0,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
export default ({ text, sx, heightImg, widthImg }: Props) => {
|
|
||||||
const theme = useTheme();
|
|
||||||
const [favList, setFavList] = useState<string[]>([]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (favList.length === 18) {
|
|
||||||
|
|
||||||
const zip = new JSZip(); //создание зип архива
|
|
||||||
|
|
||||||
favList.forEach((uri, i) => {
|
|
||||||
const idx = uri.indexOf("base64,") + "base64,".length; //обработка строки картинки
|
|
||||||
const content = uri.substring(idx); //обработка строки картинки
|
|
||||||
zip.file(`fav${i}.jpg`, content, { base64: true }); //сохранение картинки в архив с именем "fav.jpg"
|
|
||||||
});
|
|
||||||
|
|
||||||
zip.generateAsync({ type: "blob" }).then(function (content) {
|
|
||||||
// скачивание архива
|
|
||||||
saveAs(content, "fav.zip"); // скачивание архива
|
|
||||||
}); // скачивание архива
|
|
||||||
}
|
|
||||||
}, [favList]);
|
|
||||||
|
|
||||||
const callback = (uri: string) => {
|
|
||||||
setFavList((old) => [...old, uri]);
|
|
||||||
};
|
|
||||||
|
|
||||||
const imgHC = (imgInp: HTMLInputElement) => {
|
|
||||||
const file = imgInp.files?.[0];
|
|
||||||
if (file) {
|
|
||||||
setFavList([]);
|
|
||||||
if (file.size < 5242880) {
|
|
||||||
setData(URL.createObjectURL(file));
|
|
||||||
imageFavicon.forEach((obj) => {
|
|
||||||
try {
|
|
||||||
Resizer.imageFileResizer(
|
|
||||||
file,
|
|
||||||
obj.maxWidth,
|
|
||||||
obj.compressFormat,
|
|
||||||
obj.quality,
|
|
||||||
obj.rotation,
|
|
||||||
callback,
|
|
||||||
"string"
|
|
||||||
);
|
|
||||||
} catch (err) {
|
|
||||||
console.log(err);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
enqueueSnackbar("Размер картинки слишком велик");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const [data, setData] = useState("");
|
|
||||||
const [ready, setReady] = useState(false);
|
|
||||||
|
|
||||||
const dragenterHC = () => {
|
|
||||||
setReady(true);
|
|
||||||
};
|
|
||||||
|
|
||||||
const dragexitHC = () => {
|
|
||||||
setReady(false);
|
|
||||||
};
|
|
||||||
|
|
||||||
const dropHC = (event: React.DragEvent<HTMLDivElement>) => {
|
|
||||||
event.preventDefault();
|
|
||||||
setReady(false);
|
|
||||||
const file = event.dataTransfer.files[0];
|
|
||||||
if (file.size < 5242880) {
|
|
||||||
setData(URL.createObjectURL(file));
|
|
||||||
} else {
|
|
||||||
enqueueSnackbar("Размер картинки слишком велик");
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const dragOverHC = (event: React.DragEvent<HTMLDivElement>) => {
|
|
||||||
event.preventDefault();
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<ButtonBase component="label" sx={{ justifyContent: "flex-start" }}>
|
|
||||||
<input onChange={(event) => imgHC(event.target)} hidden accept="image/*" multiple type="file" />
|
|
||||||
<Box
|
|
||||||
onDragEnter={dragenterHC}
|
|
||||||
onDragExit={dragexitHC}
|
|
||||||
onDrop={dropHC}
|
|
||||||
onDragOver={dragOverHC}
|
|
||||||
sx={{
|
|
||||||
width: "100%",
|
|
||||||
height: "120px",
|
|
||||||
position: "relative",
|
|
||||||
display: "flex",
|
|
||||||
justifyContent: "center",
|
|
||||||
alignItems: "center",
|
|
||||||
backgroundColor: theme.palette.background.default,
|
|
||||||
border: `1px solid ${ready ? "red" : theme.palette.grey2.main}`,
|
|
||||||
borderRadius: "8px",
|
|
||||||
opacity: data ? "0.5" : 1,
|
|
||||||
...sx,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<UploadIcon />
|
|
||||||
<Typography
|
|
||||||
sx={{
|
|
||||||
position: "absolute",
|
|
||||||
right: "10px",
|
|
||||||
bottom: "10px",
|
|
||||||
color: theme.palette.orange.main,
|
|
||||||
fontSize: "16px",
|
|
||||||
lineHeight: "19px",
|
|
||||||
textDecoration: "underline",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{text}
|
|
||||||
</Typography>
|
|
||||||
{data ? (
|
|
||||||
<img
|
|
||||||
height={heightImg}
|
|
||||||
width={widthImg}
|
|
||||||
src={data}
|
|
||||||
style={{
|
|
||||||
position: "absolute",
|
|
||||||
zIndex: "-1",
|
|
||||||
objectFit: "revert-layer",
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
) : null}
|
|
||||||
</Box>
|
|
||||||
</ButtonBase>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@ -1,3 +1,4 @@
|
|||||||
|
|
||||||
import { questionApi } from "@api/question";
|
import { questionApi } from "@api/question";
|
||||||
import { quizApi } from "@api/quiz";
|
import { quizApi } from "@api/quiz";
|
||||||
import { devlog } from "@frontend/kitui";
|
import { devlog } from "@frontend/kitui";
|
||||||
@ -13,6 +14,7 @@ import { RequestQueue } from "../../utils/requestQueue";
|
|||||||
import { updateRootContentId } from "@root/quizes/actions"
|
import { updateRootContentId } from "@root/quizes/actions"
|
||||||
import { useCurrentQuiz } from "@root/quizes/hooks"
|
import { useCurrentQuiz } from "@root/quizes/hooks"
|
||||||
import { QuestionsStore, useQuestionsStore } from "./store";
|
import { QuestionsStore, useQuestionsStore } from "./store";
|
||||||
|
import { withErrorBoundary } from "react-error-boundary";
|
||||||
|
|
||||||
|
|
||||||
export const setQuestions = (questions: RawQuestion[] | null) => setProducedState(state => {
|
export const setQuestions = (questions: RawQuestion[] | null) => setProducedState(state => {
|
||||||
@ -321,8 +323,8 @@ export const deleteQuestion = async (questionId: string, quizId: string) => requ
|
|||||||
await questionApi.delete(question.backendId);
|
await questionApi.delete(question.backendId);
|
||||||
if (question.content.rule.parentId === "root") { //удалить из стора root и очистить rule всем вопросам
|
if (question.content.rule.parentId === "root") { //удалить из стора root и очистить rule всем вопросам
|
||||||
updateRootContentId(quizId, "")
|
updateRootContentId(quizId, "")
|
||||||
clearRoleForAll()
|
clearRuleForAll()
|
||||||
} else if (question.content.rule.parentId.length > 0) { //удалить из стора вопрос из дерева и очищаем его потомков
|
} else if (question.content.rule.parentId.length > 0) { //удалить из стора вопрос из дерева и очистить его потомков
|
||||||
const clearQuestions = [] as string[]
|
const clearQuestions = [] as string[]
|
||||||
|
|
||||||
const getChildren = (parentQuestion: AnyTypedQuizQuestion) => {
|
const getChildren = (parentQuestion: AnyTypedQuizQuestion) => {
|
||||||
@ -343,8 +345,22 @@ export const deleteQuestion = async (questionId: string, quizId: string) => requ
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
}
|
//чистим rule родителя
|
||||||
|
const parentQuestion = getQuestionByContentId(question.content.rule.parentId)
|
||||||
|
const newRule = {}
|
||||||
|
newRule.main = parentQuestion.content.rule.main.filter((data) => data.next !== question.content.id) //удаляем условия перехода от родителя к этому вопросу
|
||||||
|
newRule.parentId = parentQuestion.content.rule.parentId
|
||||||
|
newRule.default = questions.filter((q) => {
|
||||||
|
return q.content.rule.parentId === question.content.rule.parentId && q.content.id !== question.content.id
|
||||||
|
})[0]?.content.id || ""
|
||||||
|
//Если этот вопрос был дефолтным у родителя - чистим дефолт
|
||||||
|
//Смотрим можем ли мы заменить id на один из main
|
||||||
|
|
||||||
|
console.log(newRule)
|
||||||
|
updateQuestion(question.content.rule.parentId, (PQ) => {
|
||||||
|
PQ.content.rule = newRule
|
||||||
|
})
|
||||||
|
}
|
||||||
removeQuestion(questionId);
|
removeQuestion(questionId);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
devlog("Error deleting question", error);
|
devlog("Error deleting question", error);
|
||||||
@ -425,7 +441,7 @@ export const updateDragQuestionContentId = (contentId?: string) => {
|
|||||||
useQuestionsStore.setState({ dragQuestionContentId: contentId ? contentId : null });
|
useQuestionsStore.setState({ dragQuestionContentId: contentId ? contentId : null });
|
||||||
}
|
}
|
||||||
|
|
||||||
export const clearRoleForAll = () => {
|
export const clearRuleForAll = () => {
|
||||||
const { questions } = useQuestionsStore.getState()
|
const { questions } = useQuestionsStore.getState()
|
||||||
questions.forEach(question => {
|
questions.forEach(question => {
|
||||||
if (question.type !== null && (question.content.rule.main.length > 0 || question.content.rule.default.length > 0 || question.content.rule.parentId.length > 0)) {
|
if (question.type !== null && (question.content.rule.main.length > 0 || question.content.rule.default.length > 0 || question.content.rule.parentId.length > 0)) {
|
||||||
@ -455,3 +471,61 @@ export const clearDesireToOpenABranchingModal = () => {
|
|||||||
export const updateEditSomeQuestion = (contentId?: string) => {
|
export const updateEditSomeQuestion = (contentId?: string) => {
|
||||||
useQuestionsStore.setState({ editSomeQuestion: contentId === undefined ? null : contentId })
|
useQuestionsStore.setState({ editSomeQuestion: contentId === undefined ? null : contentId })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const createFrontResult = (quizId: number) => setProducedState(state => {
|
||||||
|
state.questions.push({
|
||||||
|
id: nanoid(),
|
||||||
|
quizId,
|
||||||
|
type: "result",
|
||||||
|
title: "",
|
||||||
|
description: "",
|
||||||
|
deleted: false,
|
||||||
|
expanded: true,
|
||||||
|
});
|
||||||
|
}, {
|
||||||
|
type: "createFrontResult",
|
||||||
|
quizId,
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
export const createBackResult = async (
|
||||||
|
questionId: string,
|
||||||
|
type: QuestionType,
|
||||||
|
) => requestQueue.enqueue(async () => {
|
||||||
|
const question = useQuestionsStore.getState().questions.find(q => q.id === questionId);
|
||||||
|
if (!question) return;
|
||||||
|
if (question.type !== null) throw new Error("Cannot upgrade already typed question");
|
||||||
|
|
||||||
|
try {
|
||||||
|
const createdQuestion = await questionApi.create({
|
||||||
|
quiz_id: question.quizId,
|
||||||
|
type,
|
||||||
|
title: question.title,
|
||||||
|
description: question.description,
|
||||||
|
page: 0,
|
||||||
|
required: true,
|
||||||
|
content: JSON.stringify(defaultQuestionByType[type].content),
|
||||||
|
});
|
||||||
|
|
||||||
|
setProducedState(state => {
|
||||||
|
const questionIndex = state.questions.findIndex(q => q.id === questionId);
|
||||||
|
if (questionIndex !== -1) state.questions.splice(
|
||||||
|
questionIndex,
|
||||||
|
1,
|
||||||
|
rawQuestionToQuestion(createdQuestion)
|
||||||
|
);
|
||||||
|
}, {
|
||||||
|
type: "createTypedQuestion",
|
||||||
|
question,
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
devlog("Error creating question", error);
|
||||||
|
enqueueSnackbar("Не удалось создать вопрос");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export const updateResult = () => {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -112,17 +112,12 @@ export default function HeaderFull() {
|
|||||||
width: "36px",
|
width: "36px",
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<IconButton
|
<LogoutButton
|
||||||
|
onClick={handleLogoutClick}
|
||||||
sx={{
|
sx={{
|
||||||
ml: "20px",
|
ml: "20px",
|
||||||
bgcolor: "#F2F3F7",
|
|
||||||
borderRadius: "6px",
|
|
||||||
height: "36px",
|
|
||||||
width: "36px",
|
|
||||||
}}
|
}}
|
||||||
>
|
/>
|
||||||
<LogoutButton onClick={handleLogoutClick} />
|
|
||||||
</IconButton>
|
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
|
|||||||
@ -65,7 +65,7 @@ export default function QuizPreviewLayout() {
|
|||||||
>
|
>
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
p: "16px",
|
p: "40px 20px 20px",
|
||||||
whiteSpace: "break-spaces",
|
whiteSpace: "break-spaces",
|
||||||
overflowY: "auto",
|
overflowY: "auto",
|
||||||
flexGrow: 1,
|
flexGrow: 1,
|
||||||
@ -219,9 +219,9 @@ export default function QuizPreviewLayout() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function QuestionPreviewComponent({ question }: {
|
function QuestionPreviewComponent({ question }: {
|
||||||
question: AnyTypedQuizQuestion | UntypedQuizQuestion;
|
question: AnyTypedQuizQuestion | UntypedQuizQuestion | undefined;
|
||||||
}) {
|
}) {
|
||||||
if (question.type === null) return null;
|
if (!question || question.type === null) return null;
|
||||||
|
|
||||||
switch (question.type) {
|
switch (question.type) {
|
||||||
case "variant": return <Variant question={question} />;
|
case "variant": return <Variant question={question} />;
|
||||||
|
|||||||
@ -16,19 +16,22 @@ export default function Page({ question }: Props) {
|
|||||||
gap: 1,
|
gap: 1,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Typography variant="h6" data-cy="question-title">{question.title}</Typography>
|
<Typography variant="h6" data-cy="question-title" sx={{ paddingBottom: "25px" }}>{question.title}</Typography>
|
||||||
<Typography data-cy="question-text">{question.content.text}</Typography>
|
<Typography data-cy="question-text" sx={{ paddingBottom: "20px" }}>{question.content.text}</Typography>
|
||||||
{question.content.picture && (
|
{question.content.picture && (
|
||||||
|
<Box sx={{borderRadius: "12px",
|
||||||
|
border: "1px solid #9A9AAF", width: "100%", overflow: "hidden"}}>
|
||||||
<img
|
<img
|
||||||
src={question.content.picture}
|
src={question.content.picture}
|
||||||
alt=""
|
alt=""
|
||||||
style={{
|
style={{
|
||||||
width: "100%",
|
|
||||||
display: "block",
|
display: "block",
|
||||||
objectFit: "scale-down",
|
width: "100%",
|
||||||
flexGrow: 1,
|
height: "100%",
|
||||||
|
objectFit: "contain",
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
</Box>
|
||||||
)}
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -6,6 +6,7 @@ import {
|
|||||||
FormLabel,
|
FormLabel,
|
||||||
Radio,
|
Radio,
|
||||||
RadioGroup,
|
RadioGroup,
|
||||||
|
useRadioGroup,
|
||||||
Tooltip,
|
Tooltip,
|
||||||
Typography,
|
Typography,
|
||||||
} from "@mui/material";
|
} from "@mui/material";
|
||||||
@ -13,6 +14,9 @@ import {
|
|||||||
import InfoIcon from "@icons/InfoIcon";
|
import InfoIcon from "@icons/InfoIcon";
|
||||||
|
|
||||||
import type { QuizQuestionVariant } from "model/questionTypes/variant";
|
import type { QuizQuestionVariant } from "model/questionTypes/variant";
|
||||||
|
import CustomRadio from "@ui_kit/CustomRadio";
|
||||||
|
import RadioCheck from "@ui_kit/RadioCheck";
|
||||||
|
import RadioIcon from "@ui_kit/RadioIcon";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
question: QuizQuestionVariant;
|
question: QuizQuestionVariant;
|
||||||
@ -27,13 +31,17 @@ export default function Variant({ question }: Props) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<FormControl fullWidth>
|
<FormControl fullWidth>
|
||||||
<FormLabel id="quiz-question-radio-group" data-cy="question-title">
|
<FormLabel id="quiz-question-radio-group" data-cy="question-title" sx={{color: "#000000", marginBottom: "20px", fontSize: "24px", fontWeight: 500}}>
|
||||||
{question.title}
|
{question.title}
|
||||||
</FormLabel>
|
</FormLabel>
|
||||||
<RadioGroup
|
<RadioGroup
|
||||||
aria-labelledby="quiz-question-radio-group"
|
aria-labelledby="quiz-question-radio-group"
|
||||||
value={value}
|
value={value}
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
|
sx={{
|
||||||
|
flexDirection: "row",
|
||||||
|
gap: "20px"
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
{question.content.variants
|
{question.content.variants
|
||||||
.filter(({ answer }) => answer)
|
.filter(({ answer }) => answer)
|
||||||
@ -42,11 +50,21 @@ export default function Variant({ question }: Props) {
|
|||||||
key={index}
|
key={index}
|
||||||
value={variant.answer}
|
value={variant.answer}
|
||||||
data-cy="variant-answer"
|
data-cy="variant-answer"
|
||||||
|
labelPlacement="start"
|
||||||
|
sx={{borderRadius: "12px",
|
||||||
|
border: value === value ? "1px solid #7E2AEA" : "1px solid #9A9AAF",
|
||||||
|
padding: "20px",
|
||||||
|
justifyContent: "space-between",
|
||||||
|
maxWidth: "685px",
|
||||||
|
width: "100%",
|
||||||
|
margin: 0
|
||||||
|
}}
|
||||||
control={
|
control={
|
||||||
<Radio
|
<Radio
|
||||||
inputProps={{
|
inputProps={{
|
||||||
"data-cy": "variant-radio",
|
"data-cy": "variant-radio",
|
||||||
}}
|
}}
|
||||||
|
checkedIcon={<RadioCheck />} icon={<RadioIcon />}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
label={
|
label={
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { useState, ChangeEvent } from "react";
|
import { useState, ChangeEvent, useEffect } from "react";
|
||||||
import {
|
import {
|
||||||
Box,
|
Box,
|
||||||
FormControl,
|
FormControl,
|
||||||
@ -86,14 +86,13 @@ export default function Varimg({ question }: Props) {
|
|||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
border: "1px solid #E3E3E3",
|
border: "1px solid #E3E3E3",
|
||||||
maxWidth: "400px",
|
|
||||||
display: "flex",
|
display: "flex",
|
||||||
justifyContent: "center",
|
justifyContent: "center",
|
||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
borderRadius: "8px",
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{currentVariant?.extendedText ? (
|
{currentVariant ?
|
||||||
|
currentVariant.extendedText ? (
|
||||||
<img
|
<img
|
||||||
src={currentVariant.extendedText}
|
src={currentVariant.extendedText}
|
||||||
data-cy="variant-image"
|
data-cy="variant-image"
|
||||||
@ -106,12 +105,23 @@ export default function Varimg({ question }: Props) {
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<Typography p={2}>
|
<Typography p={2}>Картинка отсутствует</Typography>
|
||||||
{selectedVariantIndex === -1
|
) : question.content.back ? (
|
||||||
? "Выберите вариант"
|
<img
|
||||||
: "Картинка отсутствует"}
|
src={question.content.back}
|
||||||
</Typography>
|
data-cy="variant-image"
|
||||||
)}
|
alt="question variant"
|
||||||
|
style={{
|
||||||
|
width: "100%",
|
||||||
|
display: "block",
|
||||||
|
objectFit: "scale-down",
|
||||||
|
flexGrow: 1,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<Typography p={2}>Выберите вариант</Typography>
|
||||||
|
)
|
||||||
|
}
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -53,9 +53,9 @@ export default function QuizPreviewLayout() {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{quiz.config.startpage.background.type === "image" &&
|
{quiz.config.startpage.background.type === "image" &&
|
||||||
quiz.config.startpage.background.desktop && (
|
quiz.config.startpage.logo && (
|
||||||
<img
|
<img
|
||||||
src={quiz.config.startpage.background.desktop}
|
src={quiz.config.startpage.logo}
|
||||||
style={{
|
style={{
|
||||||
height: "30px",
|
height: "30px",
|
||||||
maxWidth: "50px",
|
maxWidth: "50px",
|
||||||
|
|||||||
@ -175,3 +175,19 @@ class Resizer {
|
|||||||
);
|
);
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export function resizeFavIcon(blob: Blob) {
|
||||||
|
return new Promise<Blob>(resolve => {
|
||||||
|
Resizer.createResizedImage(
|
||||||
|
new File([blob], "image"),
|
||||||
|
48,
|
||||||
|
"PNG",
|
||||||
|
100,
|
||||||
|
0,
|
||||||
|
async (file: Blob) => {
|
||||||
|
resolve(file);
|
||||||
|
},
|
||||||
|
"blob"
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|||||||
@ -4,8 +4,8 @@ import InstallQuiz from "../pages/InstallQuiz/InstallQuiz";
|
|||||||
import FormQuestionsPage from "../pages/Questions/Form/FormQuestionsPage";
|
import FormQuestionsPage from "../pages/Questions/Form/FormQuestionsPage";
|
||||||
import QuestionsPage from "../pages/Questions/QuestionsPage";
|
import QuestionsPage from "../pages/Questions/QuestionsPage";
|
||||||
import { QuestionsMap } from "../pages/QuestionsMap";
|
import { QuestionsMap } from "../pages/QuestionsMap";
|
||||||
import { Result } from "../pages/Result/Result";
|
import { Result } from "../pages/ResultPage/Result";
|
||||||
import { Setting } from "../pages/Result/Setting";
|
import { Setting } from "../pages/ResultPage/Setting";
|
||||||
import StartPageSettings from "../pages/startPage/StartPageSettings";
|
import StartPageSettings from "../pages/startPage/StartPageSettings";
|
||||||
import StepOne from "../pages/startPage/stepOne";
|
import StepOne from "../pages/startPage/stepOne";
|
||||||
import Steptwo from "../pages/startPage/steptwo";
|
import Steptwo from "../pages/startPage/steptwo";
|
||||||
|
|||||||
@ -8628,6 +8628,13 @@ react-easy-crop@^5.0.0:
|
|||||||
normalize-wheel "^1.0.1"
|
normalize-wheel "^1.0.1"
|
||||||
tslib "2.0.1"
|
tslib "2.0.1"
|
||||||
|
|
||||||
|
react-error-boundary@^4.0.11:
|
||||||
|
version "4.0.11"
|
||||||
|
resolved "https://registry.yarnpkg.com/react-error-boundary/-/react-error-boundary-4.0.11.tgz#36bf44de7746714725a814630282fee83a7c9a1c"
|
||||||
|
integrity sha512-U13ul67aP5DOSPNSCWQ/eO0AQEYzEFkVljULQIjMV0KlffTAhxuDoBKdO0pb/JZ8mDhMKFZ9NZi0BmLGUiNphw==
|
||||||
|
dependencies:
|
||||||
|
"@babel/runtime" "^7.12.5"
|
||||||
|
|
||||||
react-error-overlay@^6.0.11:
|
react-error-overlay@^6.0.11:
|
||||||
version "6.0.11"
|
version "6.0.11"
|
||||||
resolved "https://registry.npmjs.org/react-error-overlay/-/react-error-overlay-6.0.11.tgz"
|
resolved "https://registry.npmjs.org/react-error-overlay/-/react-error-overlay-6.0.11.tgz"
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user