From 860c883037b1facc911de50faede0d76d7b4f7fe Mon Sep 17 00:00:00 2001 From: IlyaDoronin Date: Tue, 19 Dec 2023 14:23:09 +0300 Subject: [PATCH 01/15] feat: new SliderOptions logic --- .../Questions/SliderOptions/SliderOptions.tsx | 93 +++++++++++++------ src/ui_kit/CustomNumberField.tsx | 20 +--- src/ui_kit/CustomTextField.tsx | 4 +- 3 files changed, 72 insertions(+), 45 deletions(-) diff --git a/src/pages/Questions/SliderOptions/SliderOptions.tsx b/src/pages/Questions/SliderOptions/SliderOptions.tsx index bcc23b7d..a449d69c 100644 --- a/src/pages/Questions/SliderOptions/SliderOptions.tsx +++ b/src/pages/Questions/SliderOptions/SliderOptions.tsx @@ -1,4 +1,4 @@ -import { useState } from "react"; +import { useEffect, useState } from "react"; import { Box, Typography, useMediaQuery, useTheme } from "@mui/material"; import ButtonsOptions from "../ButtonsOptions"; import CustomNumberField from "@ui_kit/CustomNumberField"; @@ -16,6 +16,22 @@ export default function SliderOptions({ question }: Props) { const isMobile = useMediaQuery(theme.breakpoints.down(790)); const [switchState, setSwitchState] = useState("setting"); const [stepError, setStepError] = useState(""); + const [startError, setStartError] = useState(false); + + useEffect(() => { + const min = Number(question.content.range.split("—")[0]); + const max = Number(question.content.range.split("—")[1]); + const start = Number(question.content.start); + console.log(min, max, start); + + if (start < min || start > max) { + setStartError(true); + } + + if (start >= min && start <= max) { + setStartError(false); + } + }, [question.content.range, question.content.start]); const SSHC = (data: string) => { setSwitchState(data); @@ -44,10 +60,19 @@ export default function SliderOptions({ question }: Props) { marginRight: isMobile ? "10px" : "0px", }} > - + Выбор значения из диапазона - + { if (question.type !== "number") return; - question.content.range = `${target.value}—${question.content.range.split("—")[1]}`; + question.content.range = `${target.value}—${ + question.content.range.split("—")[1] + }`; }); }} onBlur={({ target }) => { - const start = question.content.start; const min = Number(target.value); const max = Number(question.content.range.split("—")[1]); @@ -70,15 +96,9 @@ export default function SliderOptions({ question }: Props) { updateQuestion(question.id, (question) => { if (question.type !== "number") return; - question.content.range = `${max - 1 >= 0 ? max - 1 : 0}—${question.content.range.split("—")[1]}`; - }); - } - - if (start < min) { - updateQuestion(question.id, (question) => { - if (question.type !== "number") return; - - question.content.start = min; + question.content.range = `${max - 1 >= 0 ? max - 1 : 0}—${ + question.content.range.split("—")[1] + }`; }); } }} @@ -94,11 +114,12 @@ export default function SliderOptions({ question }: Props) { updateQuestion(question.id, (question) => { if (question.type !== "number") return; - question.content.range = `${question.content.range.split("—")[0]}—${target.value}`; + question.content.range = `${ + question.content.range.split("—")[0] + }—${target.value}`; }); }} onBlur={({ target }) => { - const start = question.content.start; const step = question.content.step; const min = Number(question.content.range.split("—")[0]); const max = Number(target.value); @@ -108,15 +129,9 @@ export default function SliderOptions({ question }: Props) { updateQuestion(question.id, (question) => { if (question.type !== "number") return; - question.content.range = `${min}—${min + 1 >= 100 ? 100 : min + 1}`; - }); - } - - if (start > max) { - updateQuestion(question.id, (question) => { - if (question.type !== "number") return; - - question.content.start = max; + question.content.range = `${min}—${ + min + 1 >= 100 ? 100 : min + 1 + }`; }); } @@ -128,7 +143,11 @@ export default function SliderOptions({ question }: Props) { }); if (range % step) { - setStepError(`Шаг должен делить без остатка диапазон ${max} - ${min} = ${max - min}`); + setStepError( + `Шаг должен делить без остатка диапазон ${max} - ${min} = ${ + max - min + }` + ); } else { setStepError(""); } @@ -147,7 +166,14 @@ export default function SliderOptions({ question }: Props) { }} > - + Начальное значение { updateQuestion(question.id, (question) => { if (question.type !== "number") return; @@ -205,7 +232,11 @@ export default function SliderOptions({ question }: Props) { } if (range % step) { - setStepError(`Шаг должен делить без остатка диапазон ${max} - ${min} = ${max - min}`); + setStepError( + `Шаг должен делить без остатка диапазон ${max} - ${min} = ${ + max - min + }` + ); } else { setStepError(""); } @@ -214,7 +245,11 @@ export default function SliderOptions({ question }: Props) { - + ); diff --git a/src/ui_kit/CustomNumberField.tsx b/src/ui_kit/CustomNumberField.tsx index 8797d74f..9a443fc7 100644 --- a/src/ui_kit/CustomNumberField.tsx +++ b/src/ui_kit/CustomNumberField.tsx @@ -9,6 +9,7 @@ interface CustomNumberFieldProps { onKeyDown?: (event: KeyboardEvent) => void; onBlur?: (event: FocusEvent) => void; error?: string; + emptyError?: boolean; value: string; sx?: SxProps; min?: number; @@ -20,6 +21,7 @@ export default function CustomNumberField({ value, sx, error, + emptyError, onChange, onKeyDown, onBlur, @@ -34,32 +36,20 @@ export default function CustomNumberField({ inputValue.match(/^\d*$/) || (inputValue[0] === "-" && inputValue.slice(1).match(/^\d*$/)) ) { + console.log("inputValue", inputValue); onChange?.({ ...event, target: { ...event.target, value: inputValue } }); } }; - const onInputBlur = (event: FocusEvent) => { - const inputValue = event.target.value; - - if (Number(inputValue) < min) { - onChange?.({ ...event, target: { ...event.target, value: String(min) } }); - } - - if (Number(inputValue) > max) { - onChange?.({ ...event, target: { ...event.target, value: String(max) } }); - } - - onBlur?.(event); - }; - return ( ); diff --git a/src/ui_kit/CustomTextField.tsx b/src/ui_kit/CustomTextField.tsx index 34dc98b8..8ce1f705 100755 --- a/src/ui_kit/CustomTextField.tsx +++ b/src/ui_kit/CustomTextField.tsx @@ -7,6 +7,7 @@ interface CustomTextFieldProps { placeholder: string; value?: string; error?: string; + emptyError?: boolean; onChange?: (event: ChangeEvent) => void; onKeyDown?: (event: KeyboardEvent) => void; onBlur?: (event: FocusEvent) => void; @@ -25,6 +26,7 @@ export default function CustomTextField({ text, sx, error, + emptyError, InputProps, maxLength = 200, }: CustomTextFieldProps) { @@ -62,7 +64,7 @@ export default function CustomTextField({ value={inputValue} placeholder={placeholder} onChange={handleInputChange} - error={!!error} + error={!!error || emptyError} label={error} onFocus={handleInputFocus} onBlur={handleInputBlur} From d179e0cecaa78693cdb3f1b840f509bcd5123fa8 Mon Sep 17 00:00:00 2001 From: IlyaDoronin Date: Tue, 19 Dec 2023 15:31:01 +0300 Subject: [PATCH 02/15] fix: min max value --- .../Questions/SliderOptions/SliderOptions.tsx | 45 +++++++++---------- src/ui_kit/CustomNumberField.tsx | 17 ++++++- 2 files changed, 36 insertions(+), 26 deletions(-) diff --git a/src/pages/Questions/SliderOptions/SliderOptions.tsx b/src/pages/Questions/SliderOptions/SliderOptions.tsx index a449d69c..b178d82d 100644 --- a/src/pages/Questions/SliderOptions/SliderOptions.tsx +++ b/src/pages/Questions/SliderOptions/SliderOptions.tsx @@ -17,12 +17,13 @@ export default function SliderOptions({ question }: Props) { const [switchState, setSwitchState] = useState("setting"); const [stepError, setStepError] = useState(""); const [startError, setStartError] = useState(false); + const [minError, setMinError] = useState(false); + const [maxError, setMaxError] = useState(false); useEffect(() => { const min = Number(question.content.range.split("—")[0]); const max = Number(question.content.range.split("—")[1]); const start = Number(question.content.start); - console.log(min, max, start); if (start < min || start > max) { setStartError(true); @@ -79,7 +80,11 @@ export default function SliderOptions({ question }: Props) { min={0} max={99999999999} value={question.content.range.split("—")[0]} + emptyError={minError} onChange={({ target }) => { + const min = Number(target.value); + const max = Number(question.content.range.split("—")[1]); + updateQuestion(question.id, (question) => { if (question.type !== "number") return; @@ -87,19 +92,12 @@ export default function SliderOptions({ question }: Props) { question.content.range.split("—")[1] }`; }); - }} - onBlur={({ target }) => { - const min = Number(target.value); - const max = Number(question.content.range.split("—")[1]); if (min >= max) { - updateQuestion(question.id, (question) => { - if (question.type !== "number") return; - - question.content.range = `${max - 1 >= 0 ? max - 1 : 0}—${ - question.content.range.split("—")[1] - }`; - }); + setMinError(true); + } else { + setMinError(false); + setMaxError(false); } }} /> @@ -110,7 +108,11 @@ export default function SliderOptions({ question }: Props) { min={0} max={100000000000} value={question.content.range.split("—")[1]} + emptyError={maxError} onChange={({ target }) => { + const min = Number(question.content.range.split("—")[0]); + const max = Number(target.value); + updateQuestion(question.id, (question) => { if (question.type !== "number") return; @@ -118,6 +120,13 @@ export default function SliderOptions({ question }: Props) { question.content.range.split("—")[0] }—${target.value}`; }); + + if (max <= min) { + setMaxError(true); + } else { + setMaxError(false); + setMinError(false); + } }} onBlur={({ target }) => { const step = question.content.step; @@ -125,16 +134,6 @@ export default function SliderOptions({ question }: Props) { const max = Number(target.value); const range = max - min; - if (max <= min) { - updateQuestion(question.id, (question) => { - if (question.type !== "number") return; - - question.content.range = `${min}—${ - min + 1 >= 100 ? 100 : min + 1 - }`; - }); - } - if (step > max) { updateQuestion(question.id, (question) => { if (question.type !== "number") return; @@ -179,8 +178,6 @@ export default function SliderOptions({ question }: Props) { { diff --git a/src/ui_kit/CustomNumberField.tsx b/src/ui_kit/CustomNumberField.tsx index 9a443fc7..c74be2eb 100644 --- a/src/ui_kit/CustomNumberField.tsx +++ b/src/ui_kit/CustomNumberField.tsx @@ -36,11 +36,24 @@ export default function CustomNumberField({ inputValue.match(/^\d*$/) || (inputValue[0] === "-" && inputValue.slice(1).match(/^\d*$/)) ) { - console.log("inputValue", inputValue); onChange?.({ ...event, target: { ...event.target, value: inputValue } }); } }; + const onInputBlur = (event: FocusEvent) => { + const inputValue = event.target.value; + + if (Number(inputValue) < min) { + onChange?.({ ...event, target: { ...event.target, value: String(min) } }); + } + + if (Number(inputValue) > max) { + onChange?.({ ...event, target: { ...event.target, value: String(max) } }); + } + + onBlur?.(event); + }; + return ( ); From 223ec98bc44cc2d0ad4d227762333a584998d798 Mon Sep 17 00:00:00 2001 From: IlyaDoronin Date: Tue, 19 Dec 2023 16:15:42 +0300 Subject: [PATCH 03/15] feat: stepError logic --- .../Questions/SliderOptions/SliderOptions.tsx | 37 ++++++++----------- 1 file changed, 15 insertions(+), 22 deletions(-) diff --git a/src/pages/Questions/SliderOptions/SliderOptions.tsx b/src/pages/Questions/SliderOptions/SliderOptions.tsx index b178d82d..d1fa632d 100644 --- a/src/pages/Questions/SliderOptions/SliderOptions.tsx +++ b/src/pages/Questions/SliderOptions/SliderOptions.tsx @@ -34,6 +34,21 @@ export default function SliderOptions({ question }: Props) { } }, [question.content.range, question.content.start]); + useEffect(() => { + const min = Number(question.content.range.split("—")[0]); + const max = Number(question.content.range.split("—")[1]); + const step = Number(question.content.step); + const range = max - min; + + if (range % step) { + setStepError( + `Шаг должен делить без остатка диапазон ${max} - ${min} = ${max - min}` + ); + } else { + setStepError(""); + } + }, [question]); + const SSHC = (data: string) => { setSwitchState(data); }; @@ -132,7 +147,6 @@ export default function SliderOptions({ question }: Props) { const step = question.content.step; const min = Number(question.content.range.split("—")[0]); const max = Number(target.value); - const range = max - min; if (step > max) { updateQuestion(question.id, (question) => { @@ -140,16 +154,6 @@ export default function SliderOptions({ question }: Props) { question.content.step = min; }); - - if (range % step) { - setStepError( - `Шаг должен делить без остатка диапазон ${max} - ${min} = ${ - max - min - }` - ); - } else { - setStepError(""); - } } }} /> @@ -217,7 +221,6 @@ export default function SliderOptions({ question }: Props) { onBlur={({ target }) => { const min = Number(question.content.range.split("—")[0]); const max = Number(question.content.range.split("—")[1]); - const range = max - min; const step = Number(target.value); if (step > max) { @@ -227,16 +230,6 @@ export default function SliderOptions({ question }: Props) { question.content.step = max; }); } - - if (range % step) { - setStepError( - `Шаг должен делить без остатка диапазон ${max} - ${min} = ${ - max - min - }` - ); - } else { - setStepError(""); - } }} /> From 6056d32fd8b21ff71a3a7ab64a333c48a49dd9eb Mon Sep 17 00:00:00 2001 From: IlyaDoronin Date: Tue, 19 Dec 2023 16:40:46 +0300 Subject: [PATCH 04/15] fix: step --- .../Questions/SliderOptions/SliderOptions.tsx | 5 ++- .../ViewPublicationPage/questions/Number.tsx | 34 ++++++++++++------- 2 files changed, 23 insertions(+), 16 deletions(-) diff --git a/src/pages/Questions/SliderOptions/SliderOptions.tsx b/src/pages/Questions/SliderOptions/SliderOptions.tsx index d1fa632d..4a7225fe 100644 --- a/src/pages/Questions/SliderOptions/SliderOptions.tsx +++ b/src/pages/Questions/SliderOptions/SliderOptions.tsx @@ -206,8 +206,8 @@ export default function SliderOptions({ question }: Props) { { - const min = Number(question.content.range.split("—")[0]); const max = Number(question.content.range.split("—")[1]); const step = Number(target.value); diff --git a/src/pages/ViewPublicationPage/questions/Number.tsx b/src/pages/ViewPublicationPage/questions/Number.tsx index 28936e83..6056beab 100644 --- a/src/pages/ViewPublicationPage/questions/Number.tsx +++ b/src/pages/ViewPublicationPage/questions/Number.tsx @@ -18,21 +18,29 @@ export const Number = ({ currentQuestion }: NumberProps) => { const [maxRange, setMaxRange] = useState("100000000000"); const theme = useTheme(); const { answers } = useQuizViewStore(); - const updateMinRangeDebounced = useDebouncedCallback((value, crowded = false) => { - if (crowded) { - setMinRange(maxRange); - } + const updateMinRangeDebounced = useDebouncedCallback( + (value, crowded = false) => { + if (crowded) { + setMinRange(maxRange); + } - updateAnswer(currentQuestion.content.id, value); - }, 1000); - const updateMaxRangeDebounced = useDebouncedCallback((value, crowded = false) => { - if (crowded) { - setMaxRange(minRange); - } + updateAnswer(currentQuestion.content.id, value); + }, + 1000 + ); + const updateMaxRangeDebounced = useDebouncedCallback( + (value, crowded = false) => { + if (crowded) { + setMaxRange(minRange); + } - updateAnswer(currentQuestion.content.id, value); - }, 1000); - const answer = answers.find(({ questionId }) => questionId === currentQuestion.content.id)?.answer as string; + updateAnswer(currentQuestion.content.id, value); + }, + 1000 + ); + const answer = answers.find( + ({ questionId }) => questionId === currentQuestion.content.id + )?.answer as string; const min = window.Number(currentQuestion.content.range.split("—")[0]); const max = window.Number(currentQuestion.content.range.split("—")[1]); From fa2dedeed668749b87ddc115efed6b8fbac55eac Mon Sep 17 00:00:00 2001 From: ArtChaos189 Date: Wed, 20 Dec 2023 02:08:33 +0300 Subject: [PATCH 05/15] - fix --- src/model/questionTypes/page.ts | 7 +- .../AnswerDraggableList/AnswerItem.tsx | 291 +++--- .../Questions/AnswerDraggableList/index.tsx | 68 +- .../BranchingQuestionsModal.tsx | 157 +-- .../DraggableList/QuestionPageCard.tsx | 937 +++++++++--------- src/pages/Questions/DropDown/DropDown.tsx | 15 +- src/pages/Questions/Emoji/Emoji.tsx | 422 ++++---- .../OptionsPicture/OptionsPicture.tsx | 282 +++--- .../Questions/PageOptions/PageOptions.tsx | 467 +++++---- src/pages/Questions/UploadVideoModal.tsx | 58 +- .../Questions/answerOptions/AnswerOptions.tsx | 153 ++- src/pages/ResultPage/ResultSettings.tsx | 67 +- src/pages/ResultPage/SettingForm.tsx | 38 +- src/pages/ResultPage/cards/ResultCard.tsx | 332 +++---- src/pages/createQuize/QuizCard.tsx | 7 +- src/utils/hooks/useAddAnswer.ts | 17 + 16 files changed, 1584 insertions(+), 1734 deletions(-) create mode 100644 src/utils/hooks/useAddAnswer.ts diff --git a/src/model/questionTypes/page.ts b/src/model/questionTypes/page.ts index d422ba95..ba6eb261 100644 --- a/src/model/questionTypes/page.ts +++ b/src/model/questionTypes/page.ts @@ -1,8 +1,4 @@ -import type { - QuizQuestionBase, - QuestionHint, - PreviewRule, -} from "./shared"; +import type { QuizQuestionBase, QuestionHint, PreviewRule } from "./shared"; export interface QuizQuestionPage extends QuizQuestionBase { type: "page"; @@ -15,6 +11,7 @@ export interface QuizQuestionPage extends QuizQuestionBase { text: string; picture: string; originalPicture: string; + useImage: boolean; video: string; hint: QuestionHint; rule: PreviewRule; diff --git a/src/pages/Questions/AnswerDraggableList/AnswerItem.tsx b/src/pages/Questions/AnswerDraggableList/AnswerItem.tsx index 8eca8d32..841728a8 100644 --- a/src/pages/Questions/AnswerDraggableList/AnswerItem.tsx +++ b/src/pages/Questions/AnswerDraggableList/AnswerItem.tsx @@ -1,16 +1,16 @@ import { MessageIcon } from "@icons/messagIcon"; import { PointsIcon } from "@icons/questionsPage/PointsIcon"; import { DeleteIcon } from "@icons/questionsPage/deleteIcon"; -import {TextareaAutosize} from "@mui/base/TextareaAutosize"; +import { TextareaAutosize } from "@mui/base/TextareaAutosize"; import { - Box, - FormControl, - IconButton, - InputAdornment, - Popover, - TextField, - useMediaQuery, - useTheme, + Box, + FormControl, + IconButton, + InputAdornment, + Popover, + TextField, + useMediaQuery, + useTheme, } from "@mui/material"; import { addQuestionVariant, deleteQuestionVariant, setQuestionVariantField } from "@root/questions/actions"; import type { KeyboardEvent, ReactNode } from "react"; @@ -18,158 +18,147 @@ import { useState } from "react"; import { Draggable } from "react-beautiful-dnd"; import type { QuestionVariant } from "../../../model/questionTypes/shared"; import { useDebouncedCallback } from "use-debounce"; - +import { enqueueSnackbar } from "notistack"; type AnswerItemProps = { - index: number; - questionId: string; - variant: QuestionVariant; - largeCheck: boolean; - additionalContent?: ReactNode; - additionalMobile?: ReactNode; + index: number; + questionId: string; + variant: QuestionVariant; + largeCheck: boolean; + disableKeyDown?: boolean; + additionalContent?: ReactNode; + additionalMobile?: ReactNode; }; export const AnswerItem = ({ - index, - variant, - questionId, - largeCheck, - additionalContent, - additionalMobile, + index, + variant, + questionId, + largeCheck, + additionalContent, + additionalMobile, + disableKeyDown, }: AnswerItemProps) => { - const theme = useTheme(); - const isTablet = useMediaQuery(theme.breakpoints.down(790)); - const [isOpen, setIsOpen] = useState(false); - const [anchorEl, setAnchorEl] = useState(null); + const theme = useTheme(); + const isTablet = useMediaQuery(theme.breakpoints.down(790)); + const [isOpen, setIsOpen] = useState(false); + const [anchorEl, setAnchorEl] = useState(null); - const setQuestionVariantAnswer = useDebouncedCallback((value) => { - setQuestionVariantField(questionId, variant.id, "answer", value); - }, 200); + const setQuestionVariantAnswer = useDebouncedCallback((value) => { + setQuestionVariantField(questionId, variant.id, "answer", value); + }, 200); - const handleClick = (event: React.MouseEvent) => { - setAnchorEl(event.currentTarget); - setIsOpen(true); - }; + const handleClick = (event: React.MouseEvent) => { + setAnchorEl(event.currentTarget); + setIsOpen(true); + }; - const handleClose = () => { - setIsOpen(false); - }; + const handleClose = () => { + setIsOpen(false); + }; - return ( - - {(provided) => ( - - + {(provided) => ( + + + { + setQuestionVariantAnswer(target.value || " "); + }} + onKeyDown={(event: KeyboardEvent) => { + if (disableKeyDown) { + enqueueSnackbar("100 максимальное количество вопросов"); + } else if (event.code === "Enter" && !largeCheck) { + addQuestionVariant(questionId); + } + }} + InputProps={{ + startAdornment: ( + <> + + + + {additionalContent} + + ), + endAdornment: ( + + + + + - { - setQuestionVariantAnswer(target.value || " "); - }} - onKeyDown={(event: KeyboardEvent) => { - if (event.code === "Enter" && !largeCheck) { - addQuestionVariant(questionId); - } - }} - InputProps={{ - startAdornment: ( - <> - - - - {additionalContent} - - ), - endAdornment: ( - - - - - - setQuestionVariantAnswer(e.target.value || " ")} - onKeyDown={( - event: KeyboardEvent - ) => event.stopPropagation()} - /> - - deleteQuestionVariant(questionId, variant.id)} - > - - - - ), - }} - sx={{ - "& .MuiInputBase-root": { - padding: additionalContent ? "5px 13px" : "13px", - borderRadius: "10px", - background: "#ffffff", - "& input.MuiInputBase-input": { - height: "22px", - }, - "& textarea.MuiInputBase-input": { - marginTop: "1px", - }, - "& .MuiOutlinedInput-notchedOutline": { - border: "none", - }, - }, - }} - inputProps={{ - sx: { fontSize: "18px", lineHeight: "21px", py: 0, ml: "13px" }, - "data-cy": "quiz-variant-question-answer", - }} - /> - {additionalMobile} - - - )} - - - ); + setQuestionVariantAnswer(e.target.value || " ")} + onKeyDown={(event: KeyboardEvent) => event.stopPropagation()} + /> + + deleteQuestionVariant(questionId, variant.id)}> + + + + ), + }} + sx={{ + "& .MuiInputBase-root": { + padding: additionalContent ? "5px 13px" : "13px", + borderRadius: "10px", + background: "#ffffff", + "& input.MuiInputBase-input": { + height: "22px", + }, + "& textarea.MuiInputBase-input": { + marginTop: "1px", + }, + "& .MuiOutlinedInput-notchedOutline": { + border: "none", + }, + }, + }} + inputProps={{ + sx: { fontSize: "18px", lineHeight: "21px", py: 0, ml: "13px" }, + "data-cy": "quiz-variant-question-answer", + }} + /> + {additionalMobile} + + + )} + + ); }; diff --git a/src/pages/Questions/AnswerDraggableList/index.tsx b/src/pages/Questions/AnswerDraggableList/index.tsx index c36e8918..e41fa188 100644 --- a/src/pages/Questions/AnswerDraggableList/index.tsx +++ b/src/pages/Questions/AnswerDraggableList/index.tsx @@ -6,44 +6,40 @@ import { DragDropContext, Droppable } from "react-beautiful-dnd"; import type { QuestionVariant, QuizQuestionsWithVariants } from "../../../model/questionTypes/shared"; import { AnswerItem } from "./AnswerItem"; - type AnswerDraggableListProps = { - question: QuizQuestionsWithVariants; - additionalContent?: (variant: QuestionVariant, index: number) => ReactNode; - additionalMobile?: (variant: QuestionVariant, index: number) => ReactNode; + question: QuizQuestionsWithVariants; + additionalContent?: (variant: QuestionVariant, index: number) => ReactNode; + additionalMobile?: (variant: QuestionVariant, index: number) => ReactNode; }; -export const AnswerDraggableList = ({ - question, - additionalContent, - additionalMobile, -}: AnswerDraggableListProps) => { - const onDragEnd = ({ destination, source }: DropResult) => { - if (destination) { - reorderQuestionVariants(question.id, source.index, destination.index); - } - }; +export const AnswerDraggableList = ({ question, additionalContent, additionalMobile }: AnswerDraggableListProps) => { + const onDragEnd = ({ destination, source }: DropResult) => { + if (destination) { + reorderQuestionVariants(question.id, source.index, destination.index); + } + }; - return ( - - - {(provided) => ( - - {question.content.variants.map((variant, index) => ( - - ))} - {provided.placeholder} - - )} - - - ); + return ( + + + {(provided) => ( + + {question.content.variants.map((variant, index) => ( + = 100} + questionId={question.id} + largeCheck={"largeCheck" in question.content ? question.content.largeCheck : false} + variant={variant} + additionalContent={additionalContent?.(variant, index)} + additionalMobile={additionalMobile?.(variant, index)} + /> + ))} + {provided.placeholder} + + )} + + + ); }; diff --git a/src/pages/Questions/BranchingModal/BranchingQuestionsModal.tsx b/src/pages/Questions/BranchingModal/BranchingQuestionsModal.tsx index 1a77ec92..70ae0da0 100644 --- a/src/pages/Questions/BranchingModal/BranchingQuestionsModal.tsx +++ b/src/pages/Questions/BranchingModal/BranchingQuestionsModal.tsx @@ -13,7 +13,7 @@ import { useTheme, Checkbox, } from "@mui/material"; -import { AnyTypedQuizQuestion, createBranchingRuleMain } from "../../../model/questionTypes/shared" +import { AnyTypedQuizQuestion, createBranchingRuleMain } from "../../../model/questionTypes/shared"; import { Select } from "../Select"; import RadioCheck from "@ui_kit/RadioCheck"; @@ -27,45 +27,51 @@ import { updateOpenedModalSettingsId } from "@root/uiTools/actions"; import { useUiTools } from "@root/uiTools/store"; import { enqueueSnackbar } from "notistack"; - export default function BranchingQuestions() { const theme = useTheme(); const { openedModalSettingsId } = useUiTools(); - const [targetQuestion, setTargetQuestion] = useState(getQuestionById(openedModalSettingsId) || getQuestionByContentId(openedModalSettingsId)) - const [parentQuestion, setParentQuestion] = useState(getQuestionByContentId(targetQuestion?.content.rule.parentId)) + const [targetQuestion, setTargetQuestion] = useState( + getQuestionById(openedModalSettingsId) || getQuestionByContentId(openedModalSettingsId) + ); + const [parentQuestion, setParentQuestion] = useState( + getQuestionByContentId(targetQuestion?.content.rule.parentId) + ); useLayoutEffect(() => { - if (parentQuestion === null) return + if (parentQuestion === null) return; if (parentQuestion.content.rule.main.length === 0) { - let mutate = JSON.parse(JSON.stringify(parentQuestion)) - mutate.content.rule.main = [{ - next: targetQuestion.content.id, - or: true, - rules: [{ - question: parentQuestion.content.id, - answers: [] - }] - }] - setParentQuestion(mutate) + let mutate = JSON.parse(JSON.stringify(parentQuestion)); + mutate.content.rule.main = [ + { + next: targetQuestion.content.id, + or: true, + rules: [ + { + question: parentQuestion.content.id, + answers: [], + }, + ], + }, + ]; + setParentQuestion(mutate); } - }) + }); if (targetQuestion === null || parentQuestion === null) { - enqueueSnackbar("Невозможно найти данные ветвления для этого вопроса") - return <> + enqueueSnackbar("Невозможно найти данные ветвления для этого вопроса"); + return <>; } const saveData = () => { if (parentQuestion !== null) { - updateQuestion(parentQuestion.content.id, question => question.content = parentQuestion.content) + updateQuestion(parentQuestion.content.id, (question) => (question.content = parentQuestion.content)); } - handleClose() - - } + handleClose(); + }; const handleClose = () => { - updateOpenedModalSettingsId() + updateOpenedModalSettingsId(); }; return ( @@ -100,44 +106,50 @@ export default function BranchingQuestions() { {targetQuestion.title} - + - - { - parentQuestion.content.rule.main.length ? - parentQuestion.content.rule.main.map((e: any, i: number) => { - if (e.next === targetQuestion.content.id) { - return - } else { - <> - } - }) - : - - } - + {parentQuestion.content.rule.main.length ? ( + parentQuestion.content.rule.main.map((e: any, i: number) => { + if (e.next === targetQuestion.content.id) { + return ( + + ); + } else { + <>; + } + }) + ) : ( + + )} - { - const mutate = JSON.parse(JSON.stringify(parentQuestion)) - mutate.content.rule.main.push(createBranchingRuleMain(targetQuestion.content.id, parentQuestion.content.id)) - setParentQuestion(mutate) + const mutate = JSON.parse(JSON.stringify(parentQuestion)); + mutate.content.rule.main.push( + createBranchingRuleMain(targetQuestion.content.id, parentQuestion.content.id) + ); + setParentQuestion(mutate); }} > Добавить условие - - { - let mutate = JSON.parse(JSON.stringify(parentQuestion)) - mutate.content.rule.default = parentQuestion.content.rule.default === targetQuestion.content.id ? "" : targetQuestion.content.id - setParentQuestion(mutate) - }} - />} label="Следующий вопрос по-умолчанию" /> - + { + let mutate = JSON.parse(JSON.stringify(parentQuestion)); + mutate.content.rule.default = + parentQuestion.content.rule.default === targetQuestion.content.id + ? "" + : targetQuestion.content.id; + setParentQuestion(mutate); + }} + /> + } + label="Следующий вопрос по-умолчанию" + /> - - - diff --git a/src/pages/Questions/DraggableList/QuestionPageCard.tsx b/src/pages/Questions/DraggableList/QuestionPageCard.tsx index cf589a46..c76a8395 100644 --- a/src/pages/Questions/DraggableList/QuestionPageCard.tsx +++ b/src/pages/Questions/DraggableList/QuestionPageCard.tsx @@ -18,18 +18,31 @@ import RatingIcon from "@icons/questionsPage/rating"; import Slider from "@icons/questionsPage/slider"; import ExpandLessIcon from "@mui/icons-material/ExpandLess"; import { - Box, Button, - Checkbox, - FormControl, - FormControlLabel, - IconButton, - InputAdornment, Modal, - Paper, - TextField, Typography, - useMediaQuery, - useTheme, + Box, + Button, + Checkbox, + FormControl, + FormControlLabel, + IconButton, + InputAdornment, + Modal, + Paper, + TextField, + Typography, + useMediaQuery, + useTheme, } from "@mui/material"; -import { copyQuestion, createUntypedQuestion, deleteQuestion, clearRuleForAll, toggleExpandQuestion, updateQuestion, updateUntypedQuestion, getQuestionByContentId, deleteQuestionWithTimeout } from "@root/questions/actions"; +import { + copyQuestion, + createUntypedQuestion, + deleteQuestion, + clearRuleForAll, + toggleExpandQuestion, + updateQuestion, + updateUntypedQuestion, + getQuestionByContentId, + deleteQuestionWithTimeout, +} from "@root/questions/actions"; import { updateRootContentId } from "@root/quizes/actions"; import { useRef, useState } from "react"; import type { DraggableProvidedDragHandleProps } from "react-beautiful-dnd"; @@ -44,493 +57,449 @@ import { useCurrentQuiz } from "@root/quizes/hooks"; import { useQuestionsStore } from "@root/questions/store"; interface Props { - question: AnyTypedQuizQuestion | UntypedQuizQuestion; - draggableProps: DraggableProvidedDragHandleProps | null | undefined; - isDragging: boolean; - index: number; + question: AnyTypedQuizQuestion | UntypedQuizQuestion; + draggableProps: DraggableProvidedDragHandleProps | null | undefined; + isDragging: boolean; + index: number; } export default function QuestionsPageCard({ question, draggableProps, isDragging, index }: Props) { - const { questions } = useQuestionsStore(); - const [plusVisible, setPlusVisible] = useState(false); - const [open, setOpen] = useState(false); - const [openDelete, setOpenDelete] = useState(false); - const theme = useTheme(); - const isTablet = useMediaQuery(theme.breakpoints.down(1000)); - const isMobile = useMediaQuery(theme.breakpoints.down(790)); - const anchorRef = useRef(null); - const quiz = useCurrentQuiz(); + const maxLengthTextField = 225; - const setTitle = useDebouncedCallback((title) => { - const updateQuestionFn = question.type === null ? updateUntypedQuestion : updateQuestion; + const { questions } = useQuestionsStore(); + const [plusVisible, setPlusVisible] = useState(false); + const [isTextFieldtActive, setIsTextFieldtActive] = useState(false); + const [open, setOpen] = useState(false); + const [openDelete, setOpenDelete] = useState(false); + const theme = useTheme(); + const isTablet = useMediaQuery(theme.breakpoints.down(1000)); + const isMobile = useMediaQuery(theme.breakpoints.down(790)); + const anchorRef = useRef(null); + const quiz = useCurrentQuiz(); - updateQuestionFn(question.id, question => { - question.title = title; + const setTitle = useDebouncedCallback((title) => { + const updateQuestionFn = question.type === null ? updateUntypedQuestion : updateQuestion; + + updateQuestionFn(question.id, (question) => { + question.title = title; + }); + }, 200); + + const deleteFn = () => { + if (question.type !== null) { + if (question.content.rule.parentId === "root") { + //удалить из стора root и очистить rule всем вопросам + updateRootContentId(quiz.id, ""); + clearRuleForAll(); + deleteQuestion(question.id); + questions.forEach((q) => { + if (q.type === "result") { + deleteQuestion(q.id); + } }); - }, 200); + } else if (question.content.rule.parentId.length > 0) { + //удалить из стора вопрос из дерева и очистить его потомков + const clearQuestions = [] as string[]; - const deleteFn = () => { - if (question.type !== null) { - if (question.content.rule.parentId === "root") { //удалить из стора root и очистить rule всем вопросам - updateRootContentId(quiz.id, ""); - clearRuleForAll(); - deleteQuestion(question.id); - questions.forEach(q => { - if (q.type === "result") { - deleteQuestion(q.id); - } - }); - } else if (question.content.rule.parentId.length > 0) { //удалить из стора вопрос из дерева и очистить его потомков - const clearQuestions = [] as string[]; - - //записываем потомков , а их результаты удаляем - const getChildren = (parentQuestion: AnyTypedQuizQuestion) => { - questions.forEach((targetQuestion) => { - if (targetQuestion.content.rule.parentId === parentQuestion.content.id) {//если у вопроса совпал родитель с родителем => он потомок, в кучу его - if (targetQuestion.type === "result") { - deleteQuestion(targetQuestion.id); - } else { - if (!clearQuestions.includes(targetQuestion.content.id)) clearQuestions.push(targetQuestion.content.id); - getChildren(targetQuestion); //и ищем его потомков - } - } - }); - }; - getChildren(question); - //чистим потомков от инфы ветвления - clearQuestions.forEach((id) => { - updateQuestion(id, question => { - question.content.rule.parentId = ""; - question.content.rule.main = []; - question.content.rule.default = ""; - }); - }); - - //чистим 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 = parentQuestion.content.rule.parentId === question.content.id ? "" : parentQuestion.content.rule.parentId; - newRule.children = [...parentQuestion.content.rule.children].splice(parentQuestion.content.rule.children.indexOf(question.content.id), 1); - - updateQuestion(question.content.rule.parentId, (PQ) => { - PQ.content.rule = newRule; - }); - deleteQuestion(question.id); + //записываем потомков , а их результаты удаляем + const getChildren = (parentQuestion: AnyTypedQuizQuestion) => { + questions.forEach((targetQuestion) => { + if (targetQuestion.content.rule.parentId === parentQuestion.content.id) { + //если у вопроса совпал родитель с родителем => он потомок, в кучу его + if (targetQuestion.type === "result") { + deleteQuestion(targetQuestion.id); + } else { + if (!clearQuestions.includes(targetQuestion.content.id)) clearQuestions.push(targetQuestion.content.id); + getChildren(targetQuestion); //и ищем его потомков + } } + }); + }; + getChildren(question); + //чистим потомков от инфы ветвления + clearQuestions.forEach((id) => { + updateQuestion(id, (question) => { + question.content.rule.parentId = ""; + question.content.rule.main = []; + question.content.rule.default = ""; + }); + }); + //чистим 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 = + parentQuestion.content.rule.parentId === question.content.id ? "" : parentQuestion.content.rule.parentId; + newRule.children = [...parentQuestion.content.rule.children].splice( + parentQuestion.content.rule.children.indexOf(question.content.id), + 1 + ); - deleteQuestion(question.id); - } else { - console.log("удаляю безтипогово"); - deleteQuestion(question.id); - } - }; + updateQuestion(question.content.rule.parentId, (PQ) => { + PQ.content.rule = newRule; + }); + deleteQuestion(question.id); + } - return ( - <> - - - { + setIsTextFieldtActive(true); + }; + + const handleInputBlur = (event: React.FocusEvent) => { + setIsTextFieldtActive(false); + }; + + return ( + <> + + + + setTitle(target.value || " ")} + onFocus={handleInputFocus} + onBlur={handleInputBlur} + inputProps={{ + maxLength: maxLengthTextField, + }} + InputProps={{ + startAdornment: ( + + setOpen((isOpened) => !isOpened)} > - setTitle(target.value || " ")} - InputProps={{ - startAdornment: ( - - setOpen((isOpened) => !isOpened)} - > - {IconAndrom(question.expanded, question.type)} - - setOpen(false)} - anchorRef={anchorRef} - question={question} - questionType={question.type} - /> - - ), - }} - sx={{ - margin: isMobile ? "10px 0" : 0, - "& .MuiInputBase-root": { - color: "#000000", - backgroundColor: question.expanded - ? theme.palette.background.default - : "transparent", - height: "48px", - borderRadius: "10px", - ".MuiOutlinedInput-notchedOutline": { - borderWidth: "1px !important", - border: !question.expanded ? "none" : null, - }, - "& .MuiInputBase-input::placeholder": { - color: "#4D4D4D", - opacity: 0.8, - }, - }, - }} - inputProps={{ - sx: { - fontSize: "18px", - lineHeight: "21px", - py: 0, - paddingLeft: question.type === null ? 0 : "18px", - }, - "data-cy": "quiz-question-title", - }} - /> - - - toggleExpandQuestion(question.id)} - > - {question.expanded ? ( - - ) : ( - - )} - - {question.expanded ? ( - <> - ) : ( - - - } - checkedIcon={} - /> - } - label={""} - sx={{ - color: theme.palette.grey2.main, - ml: "-9px", - mr: 0, - userSelect: "none", - }} - /> - copyQuestion(question.id, question.quizId)} - > - - - { - if(question.content.rule.parentId.length !== 0) { - setOpenDelete(true) - } else { - deleteQuestionWithTimeout(question.id, deleteFn); - } - - }} - data-cy="delete-question" - > - - - setOpenDelete(false)}> - - - Вы удаляете вопрос, участвующий в ветвлении. Все его потомки потеряют данные ветвления. Вы уверены, что хотите удалить вопрос? - - - - - - - - - )} - {question.type !== null && - - {question.page + 1} - - } - - - - - - {question.expanded && ( - - {question.type === null ? ( - - ) : ( - - )} - - )} - - setPlusVisible(true)} - onMouseLeave={() => setPlusVisible(false)} - sx={{ - maxWidth: "825px", - display: "flex", - alignItems: "center", - height: "40px", - cursor: "pointer", - }} - > - createUntypedQuestion(question.quizId, question.id)} - sx={{ - display: plusVisible && !isDragging ? "flex" : "none", - width: "100%", - alignItems: "center", - columnGap: "10px", - }} - data-cy="create-question" - > - + setOpen(false)} + anchorRef={anchorRef} + question={question} + questionType={question.type} /> - - - - - ); + + ), + endAdornment: isTextFieldtActive && question.title.length >= maxLengthTextField - 7 && ( + + {question.title.length} + / + {maxLengthTextField} + + ), + }} + sx={{ + margin: isMobile ? "10px 0" : 0, + "& .MuiInputBase-root": { + color: "#000000", + backgroundColor: question.expanded ? theme.palette.background.default : "transparent", + height: "48px", + borderRadius: "10px", + ".MuiOutlinedInput-notchedOutline": { + borderWidth: "1px !important", + border: !question.expanded ? "none" : null, + }, + "& .MuiInputBase-input::placeholder": { + color: "#4D4D4D", + opacity: 0.8, + }, + }, + }} + /> + + + toggleExpandQuestion(question.id)} + > + {question.expanded ? ( + + ) : ( + + )} + + {question.expanded ? ( + <> + ) : ( + + + } + checkedIcon={} + /> + } + label={""} + sx={{ + color: theme.palette.grey2.main, + ml: "-9px", + mr: 0, + userSelect: "none", + }} + /> + copyQuestion(question.id, question.quizId)}> + + + { + if (question.content.rule.parentId.length !== 0) { + setOpenDelete(true); + } else { + deleteQuestionWithTimeout(question.id, deleteFn); + } + }} + data-cy="delete-question" + > + + + setOpenDelete(false)}> + + + Вы удаляете вопрос, участвующий в ветвлении. Все его потомки потеряют данные ветвления. Вы + уверены, что хотите удалить вопрос? + + + + + + + + + )} + {question.type !== null && ( + + {question.page + 1} + + )} + + + + + + {question.expanded && ( + + {question.type === null ? ( + + ) : ( + + )} + + )} + + setPlusVisible(true)} + onMouseLeave={() => setPlusVisible(false)} + sx={{ + maxWidth: "825px", + display: "flex", + alignItems: "center", + height: "40px", + cursor: "pointer", + }} + > + createUntypedQuestion(question.quizId, question.id)} + sx={{ + display: plusVisible && !isDragging ? "flex" : "none", + width: "100%", + alignItems: "center", + columnGap: "10px", + }} + data-cy="create-question" + > + + + + + + ); } const IconAndrom = (isExpanded: boolean, questionType: QuestionType | null) => { - switch (questionType) { - case "variant": - return ( - - ); - case "images": - return ( - - ); - case "varimg": - return ( - - ); - case "emoji": - return ( - - ); - case "text": - return ( - - ); - case "select": - return ( - - ); - case "date": - return ( - - ); - case "number": - return ( - - ); - case "file": - return ( - - ); - case "page": - return ( - - ); - case "rating": - return ( - - ); - default: - return <>; - } + switch (questionType) { + case "variant": + return ; + case "images": + return ; + case "varimg": + return ; + case "emoji": + return ; + case "text": + return ; + case "select": + return ; + case "date": + return ; + case "number": + return ; + case "file": + return ; + case "page": + return ; + case "rating": + return ; + default: + return <>; + } }; diff --git a/src/pages/Questions/DropDown/DropDown.tsx b/src/pages/Questions/DropDown/DropDown.tsx index f5e035be..ceab1763 100644 --- a/src/pages/Questions/DropDown/DropDown.tsx +++ b/src/pages/Questions/DropDown/DropDown.tsx @@ -4,22 +4,23 @@ import { AnswerDraggableList } from "../AnswerDraggableList"; import { EnterIcon } from "../../../assets/icons/questionsPage/enterIcon"; import SwitchDropDown from "./switchDropDown"; import ButtonsOptions from "../ButtonsOptions"; -import type { QuizQuestionSelect } from "../../../model/questionTypes/select"; import { addQuestionVariant } from "@root/questions/actions"; +import { useAddAnswer } from "../../../utils/hooks/useAddAnswer"; - +import type { QuizQuestionSelect } from "../../../model/questionTypes/select"; interface Props { question: QuizQuestionSelect; } export default function DropDown({ question }: Props) { + const onClickAddAnAnswer = useAddAnswer(); const [switchState, setSwitchState] = useState("setting"); const theme = useTheme(); const isMobile = useMediaQuery(theme.breakpoints.down(790)); const SSHC = (data: string) => { setSwitchState(data); - }; + }; return ( <> @@ -60,7 +61,7 @@ export default function DropDown({ question }: Props) { mr: "4px", height: "19px", }} - onClick={() => addQuestionVariant(question.id)} + onClick={() => onClickAddAnAnswer(question)} > Добавьте ответ @@ -87,11 +88,7 @@ export default function DropDown({ question }: Props) { )} - + ); diff --git a/src/pages/Questions/Emoji/Emoji.tsx b/src/pages/Questions/Emoji/Emoji.tsx index ff389b5e..7c9c9bd1 100644 --- a/src/pages/Questions/Emoji/Emoji.tsx +++ b/src/pages/Questions/Emoji/Emoji.tsx @@ -1,14 +1,7 @@ import { EmojiIcons } from "@icons/EmojiIocns"; import AddEmoji from "@icons/questionsPage/addEmoji"; import PlusImage from "@icons/questionsPage/plus"; -import { - Box, - Link, - Popover, - Typography, - useMediaQuery, - useTheme, -} from "@mui/material"; +import { Box, Link, Popover, Typography, useMediaQuery, useTheme } from "@mui/material"; import { addQuestionVariant, updateQuestion } from "@root/questions/actions"; import { EmojiPicker } from "@ui_kit/EmojiPicker"; import { useState } from "react"; @@ -17,225 +10,220 @@ import type { QuizQuestionEmoji } from "../../../model/questionTypes/emoji"; import { AnswerDraggableList } from "../AnswerDraggableList"; import ButtonsOptions from "../ButtonsOptions"; import SwitchEmoji from "./switchEmoji"; - +import { useAddAnswer } from "../../../utils/hooks/useAddAnswer"; interface Props { - question: QuizQuestionEmoji; + question: QuizQuestionEmoji; } export default function Emoji({ question }: Props) { - const [switchState, setSwitchState] = useState("setting"); - const [open, setOpen] = useState(false); - const [anchorElement, setAnchorElement] = useState( - null - ); - const [selectedVariant, setSelectedVariant] = useState(null); - const theme = useTheme(); - const isMobile = useMediaQuery(theme.breakpoints.down(790)); - const isTablet = useMediaQuery(theme.breakpoints.down(1000)); + const [switchState, setSwitchState] = useState("setting"); + const onClickAddAnAnswer = useAddAnswer(); + const [open, setOpen] = useState(false); + const [anchorElement, setAnchorElement] = useState(null); + const [selectedVariant, setSelectedVariant] = useState(null); + const theme = useTheme(); + const isMobile = useMediaQuery(theme.breakpoints.down(790)); + const isTablet = useMediaQuery(theme.breakpoints.down(1000)); - const SSHC = (data: string) => { - setSwitchState(data); - }; + const SSHC = (data: string) => { + setSwitchState(data); + }; - return ( - <> - - ( - <> - {!isTablet && ( - - { - setAnchorElement(currentTarget); - setSelectedVariant(variant.id); - setOpen(true); - }} - > - - {variant.extendedText ? ( - - - {variant.extendedText} - - - - - - ) : ( - - )} - - - - )} - - )} - additionalMobile={(variant) => ( - <> - {isTablet && ( - { - setAnchorElement(currentTarget); - setSelectedVariant(variant.id); - setOpen(true); - }} - sx={{ - display: "flex", - alignItems: "center", - m: "8px", - position: "relative", - }} - > - - {variant.extendedText ? ( - - {variant.extendedText} - - ) : ( - - )} - - + - - - )} - - )} - /> - event.stopPropagation()} - onClose={() => setOpen(false)} - anchorOrigin={{ - vertical: "bottom", - horizontal: "right", + return ( + <> + + ( + <> + {!isTablet && ( + + { + setAnchorElement(currentTarget); + setSelectedVariant(variant.id); + setOpen(true); }} - sx={{ - ".MuiPaper-root.MuiPaper-rounded": { - borderRadius: "10px", - }, - }} - > - { - setOpen(false); - updateQuestion(question.id, question => { - if (question.type !== "emoji") return; - - const variant = question.content.variants.find(v => v.id === selectedVariant); - if (!variant) return; - - variant.extendedText = native; - }); - }} - /> - - + - addQuestionVariant(question.id)} + gap: "5px", + }} > - Добавьте ответ - - {!isTablet && ( - <> - - или нажмите Enter - - - - )} + {variant.extendedText ? ( + + + {variant.extendedText} + + + + + + ) : ( + + )} + + - - - - - ); + )} + + )} + additionalMobile={(variant) => ( + <> + {isTablet && ( + { + setAnchorElement(currentTarget); + setSelectedVariant(variant.id); + setOpen(true); + }} + sx={{ + display: "flex", + alignItems: "center", + m: "8px", + position: "relative", + }} + > + + {variant.extendedText ? ( + + {variant.extendedText} + + ) : ( + + )} + + + + + + )} + + )} + /> + event.stopPropagation()} + onClose={() => setOpen(false)} + anchorOrigin={{ + vertical: "bottom", + horizontal: "right", + }} + sx={{ + ".MuiPaper-root.MuiPaper-rounded": { + borderRadius: "10px", + }, + }} + > + { + setOpen(false); + updateQuestion(question.id, (question) => { + if (question.type !== "emoji") return; + + const variant = question.content.variants.find((v) => v.id === selectedVariant); + if (!variant) return; + + variant.extendedText = native; + }); + }} + /> + + + onClickAddAnAnswer(question)} + > + Добавьте ответ + + {!isTablet && ( + <> + + или нажмите Enter + + + + )} + + + + + + ); } diff --git a/src/pages/Questions/OptionsPicture/OptionsPicture.tsx b/src/pages/Questions/OptionsPicture/OptionsPicture.tsx index 1b262c64..a8a9923d 100644 --- a/src/pages/Questions/OptionsPicture/OptionsPicture.tsx +++ b/src/pages/Questions/OptionsPicture/OptionsPicture.tsx @@ -1,10 +1,4 @@ -import { - Box, - Link, - Typography, - useMediaQuery, - useTheme -} from "@mui/material"; +import { Box, Link, Typography, useMediaQuery, useTheme } from "@mui/material"; import { addQuestionVariant, uploadQuestionImage } from "@root/questions/actions"; import AddOrEditImageButton from "@ui_kit/AddOrEditImageButton"; import { CropModal, useCropModalState } from "@ui_kit/Modal/CropModal"; @@ -17,165 +11,153 @@ import { UploadImageModal } from "../UploadImage/UploadImageModal"; import SwitchAnswerOptionsPict from "./switchOptionsPict"; import { useCurrentQuiz } from "@root/quizes/hooks"; import { useDisclosure } from "../../../utils/useDisclosure"; - +import { useAddAnswer } from "../../../utils/hooks/useAddAnswer"; interface Props { - question: QuizQuestionImages; + question: QuizQuestionImages; } export default function OptionsPicture({ question }: Props) { - const theme = useTheme(); - const quizQid = useCurrentQuiz()?.qid; - const [selectedVariantId, setSelectedVariantId] = useState(null); - const [switchState, setSwitchState] = useState("setting"); - const isMobile = useMediaQuery(theme.breakpoints.down(790)); - const [isImageUploadOpen, openImageUploadModal, closeImageUploadModal] = useDisclosure(); - const { - isCropModalOpen, - openCropModal, - closeCropModal, - imageBlob, - originalImageUrl, - setCropModalImageBlob, - } = useCropModalState(); + const theme = useTheme(); + const onClickAddAnAnswer = useAddAnswer(); + const quizQid = useCurrentQuiz()?.qid; + const [selectedVariantId, setSelectedVariantId] = useState(null); + const [switchState, setSwitchState] = useState("setting"); + const isMobile = useMediaQuery(theme.breakpoints.down(790)); + const [isImageUploadOpen, openImageUploadModal, closeImageUploadModal] = useDisclosure(); + const { isCropModalOpen, openCropModal, closeCropModal, imageBlob, originalImageUrl, setCropModalImageBlob } = + useCropModalState(); - const SSHC = (data: string) => { - setSwitchState(data); - }; + const SSHC = (data: string) => { + setSwitchState(data); + }; - const handleImageUpload = async (file: File) => { - if (!selectedVariantId) return; + const handleImageUpload = async (file: File) => { + if (!selectedVariantId) return; - const url = await uploadQuestionImage(question.id, quizQid, file, (question, url) => { - if (!("variants" in question.content)) return; + const url = await uploadQuestionImage(question.id, quizQid, file, (question, url) => { + if (!("variants" in question.content)) return; - const variant = question.content.variants.find(variant => variant.id === selectedVariantId); - if (!variant) return; + const variant = question.content.variants.find((variant) => variant.id === selectedVariantId); + if (!variant) return; - variant.extendedText = url; - variant.originalImageUrl = url; - }); - closeImageUploadModal(); - openCropModal(file, url); - }; + variant.extendedText = url; + variant.originalImageUrl = url; + }); + closeImageUploadModal(); + openCropModal(file, url); + }; - function handleCropModalSaveClick(imageBlob: Blob) { - if (!selectedVariantId) return; + function handleCropModalSaveClick(imageBlob: Blob) { + if (!selectedVariantId) return; - uploadQuestionImage(question.id, quizQid, imageBlob, (question, url) => { - if (!("variants" in question.content)) return; + uploadQuestionImage(question.id, quizQid, imageBlob, (question, url) => { + if (!("variants" in question.content)) return; - const variant = question.content.variants.find(variant => variant.id === selectedVariantId); - if (!variant) return; + const variant = question.content.variants.find((variant) => variant.id === selectedVariantId); + if (!variant) return; - variant.extendedText = url; - }); - } + variant.extendedText = url; + }); + } - return ( - <> - - ( - <> - {!isMobile && ( - { - setSelectedVariantId(variant.id); - if (variant.extendedText) { - return openCropModal( - variant.extendedText, - variant.originalImageUrl - ); - } + return ( + <> + + ( + <> + {!isMobile && ( + { + setSelectedVariantId(variant.id); + if (variant.extendedText) { + return openCropModal(variant.extendedText, variant.originalImageUrl); + } - openImageUploadModal(); - }} - onPlusClick={() => { - setSelectedVariantId(variant.id); - openImageUploadModal(); - }} - sx={{ mx: "10px" }} - /> - )} - - )} - additionalMobile={(variant) => ( - <> - {isMobile && ( - { - setSelectedVariantId(variant.id); - if (variant.extendedText) { - return openCropModal( - variant.extendedText, - variant.originalImageUrl - ); - } - - openImageUploadModal(); - }} - onPlusClick={() => { - setSelectedVariantId(variant.id); - openImageUploadModal(); - }} - sx={{ m: "8px", width: "auto" }} - /> - )} - - )} + openImageUploadModal(); + }} + onPlusClick={() => { + setSelectedVariantId(variant.id); + openImageUploadModal(); + }} + sx={{ mx: "10px" }} /> - - - - addQuestionVariant(question.id)} - > - Добавьте ответ - - {isMobile ? null : ( - <> - - или нажмите Enter - - - - )} - - - - - + )} + + )} + additionalMobile={(variant) => ( + <> + {isMobile && ( + { + setSelectedVariantId(variant.id); + if (variant.extendedText) { + return openCropModal(variant.extendedText, variant.originalImageUrl); + } - ); + openImageUploadModal(); + }} + onPlusClick={() => { + setSelectedVariantId(variant.id); + openImageUploadModal(); + }} + sx={{ m: "8px", width: "auto" }} + /> + )} + + )} + /> + + + + onClickAddAnAnswer(question)} + > + Добавьте ответ + + {isMobile ? null : ( + <> + + или нажмите Enter + + + + )} + + + + + + ); } diff --git a/src/pages/Questions/PageOptions/PageOptions.tsx b/src/pages/Questions/PageOptions/PageOptions.tsx index 6e79a496..b7878072 100644 --- a/src/pages/Questions/PageOptions/PageOptions.tsx +++ b/src/pages/Questions/PageOptions/PageOptions.tsx @@ -1,5 +1,5 @@ import { VideofileIcon } from "@icons/questionsPage/VideofileIcon"; -import { Box, Typography, useMediaQuery, useTheme } from "@mui/material"; +import { Box, Button, Typography, useMediaQuery, useTheme } from "@mui/material"; import { updateQuestion, uploadQuestionImage } from "@root/questions/actions"; import { useCurrentQuiz } from "@root/quizes/hooks"; import AddOrEditImageButton from "@ui_kit/AddOrEditImageButton"; @@ -14,253 +14,252 @@ import { UploadVideoModal } from "../UploadVideoModal"; import SwitchPageOptions from "./switchPageOptions"; import { useDisclosure } from "../../../utils/useDisclosure"; - type Props = { - disableInput?: boolean; - question: QuizQuestionPage; + disableInput?: boolean; + question: QuizQuestionPage; }; export default function PageOptions({ disableInput, question }: Props) { - const [openVideoModal, setOpenVideoModal] = useState(false); - const [switchState, setSwitchState] = useState("setting"); - const theme = useTheme(); - const isTablet = useMediaQuery(theme.breakpoints.down(980)); - const isFigmaTablet = useMediaQuery(theme.breakpoints.down(990)); - const isMobile = useMediaQuery(theme.breakpoints.down(780)); - const quizQid = useCurrentQuiz()?.qid; - const { - isCropModalOpen, - openCropModal, - closeCropModal, - imageBlob, - originalImageUrl, - setCropModalImageBlob, - } = useCropModalState(); - const [isImageUploadOpen, openImageUploadModal, closeImageUploadModal] = useDisclosure(); + const [openVideoModal, setOpenVideoModal] = useState(false); + const [switchState, setSwitchState] = useState("setting"); + const theme = useTheme(); + const isTablet = useMediaQuery(theme.breakpoints.down(980)); + const isFigmaTablet = useMediaQuery(theme.breakpoints.down(990)); + const isMobile = useMediaQuery(theme.breakpoints.down(780)); + const quizQid = useCurrentQuiz()?.qid; + const { isCropModalOpen, openCropModal, closeCropModal, imageBlob, originalImageUrl, setCropModalImageBlob } = + useCropModalState(); + const [isImageUploadOpen, openImageUploadModal, closeImageUploadModal] = useDisclosure(); - const setText = useDebouncedCallback((value) => { - updateQuestion(question.id, question => { - if (question.type !== "page") return; + const setText = useDebouncedCallback((value) => { + updateQuestion(question.id, (question) => { + if (question.type !== "page") return; - question.content.text = value; - }); - }, 200); + question.content.text = value; + }); + }, 200); - const SSHC = (data: string) => { - setSwitchState(data); - }; + const SSHC = (data: string) => { + setSwitchState(data); + }; - async function handleImageUpload(file: File) { - const url = await uploadQuestionImage(question.id, quizQid, file, (question, url) => { - if (question.type !== "page") return; + async function handleImageUpload(file: File) { + const url = await uploadQuestionImage(question.id, quizQid, file, (question, url) => { + if (question.type !== "page") return; - question.content.picture = url; - question.content.originalPicture = url; - }); - closeImageUploadModal(); - openCropModal(file, url); - } + question.content.picture = url; + question.content.originalPicture = url; + }); + closeImageUploadModal(); + openCropModal(file, url); + } - function handleCropModalSaveClick(imageBlob: Blob) { - uploadQuestionImage(question.id, quizQid, imageBlob, (question, url) => { - if (question.type !== "page") return; + function handleCropModalSaveClick(imageBlob: Blob) { + uploadQuestionImage(question.id, quizQid, imageBlob, (question, url) => { + if (question.type !== "page") return; - question.content.picture = url; - }); - } + question.content.picture = url; + }); + } - return ( - <> - + + + setText(target.value)} + /> + + + + + { + if (question.content.picture) { + return openCropModal(question.content.picture, question.content.originalPicture); + } + + openImageUploadModal(); + }} + onPlusClick={() => { + openImageUploadModal(); + }} + /> + + + updateQuestion(question.id, (question) => ((question as QuizQuestionPage).content.useImage = true)) + } > - - setText(target.value)} - /> - - + Изображение + + + + + или + + {isMobile ? ( + - - { - if (question.content.picture) { - return openCropModal( - question.content.picture, - question.content.originalPicture - ); - } - - openImageUploadModal(); - }} - onPlusClick={() => { - openImageUploadModal(); - }} - /> - - - Изображение - - - - - или - - {isMobile ? ( - - - - - - + - - - ) : ( - - - - - - + - - - )} - - - Видео - - - setOpenVideoModal(false)} - video={question.content.video} - onUpload={(url) => { - updateQuestion(question.id, question => { - if (question.type !== "page") return; - - question.content.video = url; - }); - }} - /> + - - - - - ); + + + + + + ) : ( + + + + + setOpenVideoModal(true)} + style={{ + display: "flex", + alignItems: "center", + justifyContent: "center", + background: "#7E2AEA", + height: "100%", + width: "25px", + color: "white", + fontSize: "15px", + }} + > + + + + + )} + + + updateQuestion(question.id, (question) => ((question as QuizQuestionPage).content.useImage = false)) + } + > + Видео + + + setOpenVideoModal(false)} + video={question.content.video} + onUpload={(url) => { + updateQuestion(question.id, (question) => { + if (question.type !== "page") return; + + question.content.video = url; + }); + }} + /> + + + + + + ); } diff --git a/src/pages/Questions/UploadVideoModal.tsx b/src/pages/Questions/UploadVideoModal.tsx index d26dc7f4..2426e42f 100644 --- a/src/pages/Questions/UploadVideoModal.tsx +++ b/src/pages/Questions/UploadVideoModal.tsx @@ -1,11 +1,4 @@ -import { - Box, - Button, - ButtonBase, - Modal, - Typography, - useTheme, -} from "@mui/material"; +import { Box, Button, ButtonBase, Modal, Typography, useTheme } from "@mui/material"; import SelectableButton from "@ui_kit/SelectableButton"; import CustomTextField from "@ui_kit/CustomTextField"; import { useState } from "react"; @@ -22,14 +15,8 @@ type HelpQuestionsProps = { onUpload: (number: string) => void; }; -export const UploadVideoModal = ({ - open, - onClose, - video, - onUpload, -}: HelpQuestionsProps) => { - const [backgroundTypeModal, setBackgroundTypeModal] = - useState("linkVideo"); +export const UploadVideoModal = ({ open, onClose, video, onUpload }: HelpQuestionsProps) => { + const [backgroundTypeModal, setBackgroundTypeModal] = useState("linkVideo"); const theme = useTheme(); const handleDrop = (event: DragEvent) => { @@ -42,12 +29,7 @@ export const UploadVideoModal = ({ }; return ( - + - Видео можно вставить с любого хостинга: YouTube, Vimeo или загрузить - собственное + Видео можно вставить с любого хостинга: YouTube, Vimeo или загрузить собственное - + {backgroundTypeModal === "linkVideo" ? ( - - Ссылка на видео - + Ссылка на видео ) : ( - - Загрузите видео - - + Загрузите видео + { if (target.files?.length) { @@ -123,9 +99,7 @@ export const UploadVideoModal = ({ type="file" /> ) => - event.preventDefault() - } + onDragOver={(event: DragEvent) => event.preventDefault()} onDrop={handleDrop} sx={{ width: "580px", @@ -140,12 +114,8 @@ export const UploadVideoModal = ({ > - - Добавить видео - - - Принимает .mp4 и .mov формат — максимум 100мб - + Добавить видео + Принимает .mp4 и .mov формат — максимум 100мб diff --git a/src/pages/Questions/answerOptions/AnswerOptions.tsx b/src/pages/Questions/answerOptions/AnswerOptions.tsx index 8df98d4b..31648df5 100755 --- a/src/pages/Questions/answerOptions/AnswerOptions.tsx +++ b/src/pages/Questions/answerOptions/AnswerOptions.tsx @@ -6,90 +6,87 @@ import ButtonsOptionsAndPict from "../ButtonsOptionsAndPict"; import SwitchAnswerOptions from "./switchAnswerOptions"; import type { QuizQuestionVariant } from "../../../model/questionTypes/variant"; import { addQuestionVariant } from "@root/questions/actions"; - +import { useAddAnswer } from "../../../utils/hooks/useAddAnswer"; interface Props { - question: QuizQuestionVariant; + question: QuizQuestionVariant; } export default function AnswerOptions({ question }: Props) { - const [switchState, setSwitchState] = useState("setting"); - const theme = useTheme(); - const isMobile = useMediaQuery(theme.breakpoints.down(790)); + const onClickAddAnAnswer = useAddAnswer(); + const [switchState, setSwitchState] = useState("setting"); + const theme = useTheme(); + const isMobile = useMediaQuery(theme.breakpoints.down(790)); - const SSHC = (data: string) => { - setSwitchState(data); - }; + const SSHC = (data: string) => { + setSwitchState(data); + }; - return ( - <> - - {question.content.variants.length === 0 ? ( - - Добавьте ответ - - ) : ( - - )} + return ( + <> + + {question.content.variants.length === 0 ? ( + + Добавьте ответ + + ) : ( + + )} - - addQuestionVariant(question.id)} - > - Добавьте ответ - - {isMobile ? null : ( - <> - - или нажмите Enter - - - - )} - - - - - - ); + + onClickAddAnAnswer(question)} + > + Добавьте ответ + + {isMobile ? null : ( + <> + + или нажмите Enter + + + + )} + + + + + + ); } diff --git a/src/pages/ResultPage/ResultSettings.tsx b/src/pages/ResultPage/ResultSettings.tsx index 4bf2cb0a..e912f0d8 100644 --- a/src/pages/ResultPage/ResultSettings.tsx +++ b/src/pages/ResultPage/ResultSettings.tsx @@ -12,45 +12,48 @@ import { useEffect, useRef, useState } from "react"; import { WhenCard } from "./cards/WhenCard"; import { ResultCard, checkEmptyData } from "./cards/ResultCard"; import { EmailSettingsCard } from "./cards/EmailSettingsCard"; -import { useCurrentQuiz } from "@root/quizes/hooks" +import { useCurrentQuiz } from "@root/quizes/hooks"; import { useQuestionsStore } from "@root/questions/store"; import { createFrontResult, deleteQuestion } from "@root/questions/actions"; import { QuizQuestionResult } from "@model/questionTypes/result"; export const ResultSettings = () => { - const { questions } = useQuestionsStore() - const quiz = useCurrentQuiz() - const results = useQuestionsStore().questions.filter((q): q is QuizQuestionResult => q.type === "result") - const [quizExpand, setQuizExpand] = useState(true) - const [resultContract, setResultContract] = useState(true) + const { questions } = useQuestionsStore(); + const quiz = useCurrentQuiz(); + const results = useQuestionsStore().questions.filter((q): q is QuizQuestionResult => q.type === "result"); + const [quizExpand, setQuizExpand] = useState(true); + const [resultContract, setResultContract] = useState(true); const isReadyToLeaveRef = useRef(true); - useEffect(function calcIsReadyToLeave(){ - let isReadyToLeave = true; - results.forEach((result) => { - if (checkEmptyData({ resultData: result })) { - isReadyToLeave = false; - } - }); - isReadyToLeaveRef.current = isReadyToLeave; - }, [results]) + useEffect( + function calcIsReadyToLeave() { + let isReadyToLeave = true; + results.forEach((result) => { + if (checkEmptyData({ resultData: result })) { + isReadyToLeave = false; + } + }); + isReadyToLeaveRef.current = isReadyToLeave; + }, + [results] + ); useEffect(() => { - return () => { - if (isReadyToLeaveRef.current === false) alert("Пожалуйста, проверьте, что вы заполнили все результаты"); - }; + return () => { + if (isReadyToLeaveRef.current === false) alert("Пожалуйста, проверьте, что вы заполнили все результаты"); + }; }, []); return ( - - - Настройки результатов - + + Настройки результатов - {quiz.config.resultInfo.when === "email" && } - - + Создайте результат @@ -105,9 +104,9 @@ export const ResultSettings = () => { - { - results.map((resultQuestion) => ) - } + {results.map((resultQuestion) => ( + + ))} { Показывать результат - + { ))} {typeActive === "e-mail" ? ( - + ) : ( <> - + - + )} - + Создайте результат diff --git a/src/pages/ResultPage/cards/ResultCard.tsx b/src/pages/ResultPage/cards/ResultCard.tsx index c180a376..0ac6298b 100644 --- a/src/pages/ResultPage/cards/ResultCard.tsx +++ b/src/pages/ResultPage/cards/ResultCard.tsx @@ -1,8 +1,7 @@ import * as React from "react"; -import { getQuestionByContentId, updateQuestion, uploadQuestionImage } from "@root/questions/actions" -import { useCurrentQuiz } from "@root/quizes/hooks" - +import { getQuestionByContentId, updateQuestion, uploadQuestionImage } from "@root/questions/actions"; +import { useCurrentQuiz } from "@root/quizes/hooks"; import CustomTextField from "@ui_kit/CustomTextField"; import { CropModal, useCropModalState } from "@ui_kit/Modal/CropModal"; @@ -20,7 +19,7 @@ import { useMediaQuery, useTheme, FormControl, - Popover + Popover, } from "@mui/material"; import MiniButtonSetting from "@ui_kit/MiniButtonSetting"; @@ -38,7 +37,7 @@ interface Props { } export const checkEmptyData = ({ resultData }: { resultData: QuizQuestionResult }) => { - let check = true + let check = true; if ( resultData.title.length > 0 || resultData.description.length > 0 || @@ -47,14 +46,15 @@ export const checkEmptyData = ({ resultData }: { resultData: QuizQuestionResult resultData.content.innerName.length > 0 || resultData.content.text.length > 0 || resultData.content.video.length > 0 || - resultData.content.hint.text.length > 0 - ) check = false - return check -} + resultData.content.hint.text.length > 0 + ) + check = false; + return check; +}; const InfoView = ({ resultData }: { resultData: QuizQuestionResult }) => { - const checkEmpty = checkEmptyData({ resultData }) - const question = getQuestionByContentId(resultData.content.rule.parentId) + const checkEmpty = checkEmptyData({ resultData }); + const question = getQuestionByContentId(resultData.content.rule.parentId); const [anchorEl, setAnchorEl] = React.useState(null); const handleClick = (event: React.MouseEvent) => { @@ -66,20 +66,18 @@ const InfoView = ({ resultData }: { resultData: QuizQuestionResult }) => { }; const open = Boolean(anchorEl); - const id = open ? 'simple-popover' : undefined; + const id = open ? "simple-popover" : undefined; return ( <> { anchorEl={anchorEl} onClose={handleClose} anchorOrigin={{ - vertical: 'bottom', - horizontal: 'left', + vertical: "bottom", + horizontal: "left", }} > - {resultData?.content.rule.parentId === "line" ? "Единый результат в конце прохождения опросника без ветвления" - : - `Заголовок вопроса, после которого появится результат: "${question?.title || "нет заголовка"}"` - } - + {resultData?.content.rule.parentId === "line" + ? "Единый результат в конце прохождения опросника без ветвления" + : `Заголовок вопроса, после которого появится результат: "${question?.title || "нет заголовка"}"`} - {checkEmpty && - - Вы не заполнили этот результат никакими данными - - } - + {checkEmpty && Вы не заполнили этот результат никакими данными} - ) -} + ); +}; export const ResultCard = ({ resultContract, resultData }: Props) => { - console.log("resultData", resultData) + console.log("resultData", resultData); + + console.log(resultData.content.video); const quizQid = useCurrentQuiz()?.qid; const theme = useTheme(); @@ -128,30 +121,20 @@ export const ResultCard = ({ resultContract, resultData }: Props) => { const isMobile = useMediaQuery(theme.breakpoints.down(790)); const isTablet = useMediaQuery(theme.breakpoints.down(800)); - const [expand, setExpand] = React.useState(true) - const [resultCardSettings, setResultCardSettings] = React.useState(false) - const [buttonPlus, setButtonPlus] = React.useState(true) + const [expand, setExpand] = React.useState(true); + const [resultCardSettings, setResultCardSettings] = React.useState(false); + const [buttonPlus, setButtonPlus] = React.useState(true); React.useEffect(() => { - setExpand(true) - }, [resultContract]) + setExpand(true); + }, [resultContract]); - - const { - isCropModalOpen, - openCropModal, - closeCropModal, - imageBlob, - originalImageUrl, - setCropModalImageBlob, - } = useCropModalState(); + const { isCropModalOpen, openCropModal, closeCropModal, imageBlob, originalImageUrl, setCropModalImageBlob } = + useCropModalState(); const [isImageUploadOpen, openImageUploadModal, closeImageUploadModal] = useDisclosure(); - - async function handleImageUpload(file: File) { const url = await uploadQuestionImage(resultData.id, quizQid, file, (question, url) => { - question.content.back = url; question.content.originalBack = url; }); @@ -165,9 +148,6 @@ export const ResultCard = ({ resultContract, resultData }: Props) => { }); } - - - return ( { backgroundColor: expand ? "white" : "#EEE4FC", border: expand ? "none" : "1px solid #9A9AAF", boxShadow: "0px 10px 30px #e7e7e7", - m: "20px 0" + m: "20px 0", }} > { flexDirection: isMobile ? "column" : null, justifyContent: "space-between", minHeight: "40px", - }} > { updateQuestion(resultData.id, question => question.title = target.value)} + onChange={({ target }: { target: HTMLInputElement }) => + updateQuestion(resultData.id, (question) => (question.title = target.value)) + } sx={{ margin: isMobile ? "10px 0" : 0, "& .MuiInputBase-root": { color: "#000000", - backgroundColor: expand - ? theme.palette.background.default - : "transparent", + backgroundColor: expand ? theme.palette.background.default : "transparent", height: "48px", borderRadius: "10px", ".MuiOutlinedInput-notchedOutline": { @@ -292,7 +271,10 @@ export const ResultCard = ({ resultContract, resultData }: Props) => { updateQuestion(resultData.id, question => question.title = target.value)} /> + onChange={({ target }: { target: HTMLInputElement }) => + updateQuestion(resultData.id, (question) => (question.title = target.value)) + } + /> { - - updateQuestion(resultData.id, (question) => question.description = target.value)} + onChange={({ target }: { target: HTMLInputElement }) => + updateQuestion(resultData.id, (question) => (question.description = target.value)) + } placeholder={"Заголовок пожирнее"} sx={{ borderRadius: "8px", @@ -324,9 +306,10 @@ export const ResultCard = ({ resultContract, resultData }: Props) => { updateQuestion(resultData.id, (question) => question.content.text = target.value)} + onChange={({ target }: { target: HTMLInputElement }) => + updateQuestion(resultData.id, (question) => (question.content.text = target.value)) + } fullWidth placeholder="Описание" multiline @@ -350,14 +333,12 @@ export const ResultCard = ({ resultContract, resultData }: Props) => { }} /> - - { }, }} variant="text" - onClick={() => updateQuestion(resultData.id, (question) => question.content.useImage = true)} + onClick={() => updateQuestion(resultData.id, (question) => (question.content.useImage = true))} > Изображение @@ -387,7 +368,7 @@ export const ResultCard = ({ resultContract, resultData }: Props) => { }, }} variant="text" - onClick={() => updateQuestion(resultData.id, (question) => question.content.useImage = false)} + onClick={() => updateQuestion(resultData.id, (question) => (question.content.useImage = false))} > Видео @@ -414,25 +395,21 @@ export const ResultCard = ({ resultContract, resultData }: Props) => { /> - { - resultData.content.useImage && + {resultData.content.useImage && ( { if (resultData.content.back) { - return openCropModal( - resultData.content.back, - resultData.content.originalBack - ); + return openCropModal(resultData.content.back, resultData.content.originalBack); } openImageUploadModal(); @@ -442,106 +419,99 @@ export const ResultCard = ({ resultContract, resultData }: Props) => { }} /> - } - { - !resultData.content.useImage && + )} + {!resultData.content.useImage && ( updateQuestion(resultData.id, q => { - q.content.video = e.target.value; - })} + onChange={(e) => + updateQuestion(resultData.id, (q) => { + q.content.video = e.target.value; + }) + } /> - } + )} - - - { - buttonPlus ? - - : - - - - Призыв к действию - - { - setButtonPlus(true) - updateQuestion(resultData.id, (q) => q.content.hint.text = "") - }} - > - - - - - updateQuestion(resultData.id, (question) => question.content.hint.text = target.value)} - fullWidth - placeholder="Например: узнать подробнее" - sx={{ - "& .MuiInputBase-root": { - backgroundColor: "#F2F3F7", - width: "409px", - height: "48px", - borderRadius: "8px", - }, + {buttonPlus ? ( + + ) : ( + + + + Призыв к действию + + { + setButtonPlus(true); + updateQuestion(resultData.id, (q) => (q.content.hint.text = "")); }} - inputProps={{ - sx: { - height: "85px", - borderRadius: "10px", - fontSize: "18px", - lineHeight: "21px", - py: 0, - }, - }} - /> + > + + - } - - - - - - + + updateQuestion(resultData.id, (question) => (question.content.hint.text = target.value)) + } + fullWidth + placeholder="Например: узнать подробнее" + sx={{ + "& .MuiInputBase-root": { + backgroundColor: "#F2F3F7", + width: "409px", + height: "48px", + borderRadius: "8px", + }, + }} + inputProps={{ + sx: { + height: "85px", + borderRadius: "10px", + fontSize: "18px", + lineHeight: "21px", + py: 0, + }, + }} + /> + + )} { > { - setResultCardSettings(!resultCardSettings) + setResultCardSettings(!resultCardSettings); }} sx={{ - backgroundColor: - resultCardSettings - ? theme.palette.brightPurple.main - : "transparent", - color: - resultCardSettings ? "#ffffff" : theme.palette.grey3.main, + backgroundColor: resultCardSettings ? theme.palette.brightPurple.main : "transparent", + color: resultCardSettings ? "#ffffff" : theme.palette.grey3.main, "&:hover": { backgroundColor: resultCardSettings ? "#581CA7" : "#7E2AEA", - color: "white" - } + color: "white", + }, }} > - + {!isTablet && "Настройки"} - { - resultCardSettings && + {resultCardSettings && ( updateQuestion(resultData.id, (question) => question.content.innerName = target.value)} + onChange={({ target }: { target: HTMLInputElement }) => + updateQuestion(resultData.id, (question) => (question.content.innerName = target.value)) + } /> - } + )} - ) - } - - ) -} + )} + + ); +}; diff --git a/src/pages/createQuize/QuizCard.tsx b/src/pages/createQuize/QuizCard.tsx index 5ef59b0e..40dba8a8 100755 --- a/src/pages/createQuize/QuizCard.tsx +++ b/src/pages/createQuize/QuizCard.tsx @@ -46,7 +46,12 @@ export default function QuizCard({ quiz, openCount = 0, applicationCount = 0, co 0px 2.76726px 8.55082px rgba(210, 208, 225, 0.0674749)`, }} > - {quiz.name} + + {quiz.name} + { + const { enqueueSnackbar } = useSnackbar(); + + const onClickAddAnAnswer = (question: QuizQuestionsWithVariants) => { + if (question.content.variants.length >= 10) { + enqueueSnackbar("100 максимальное количество вопросов"); + } else { + addQuestionVariant(question.id); + } + }; + + return onClickAddAnAnswer; +}; From 2e7c4d4de621e51cd5ed6a426abb8a477a5920c9 Mon Sep 17 00:00:00 2001 From: IlyaDoronin Date: Wed, 20 Dec 2023 13:46:38 +0300 Subject: [PATCH 06/15] refactor: Cytoscape components --- .../Questions/BranchingMap/CsComponent.tsx | 1257 +++++++++-------- .../Questions/BranchingMap/FirstNodeField.tsx | 164 +-- .../Questions/BranchingMap/hooks/usePopper.ts | 482 +++++++ .../BranchingMap/hooks/useRemoveNode.ts | 211 +++ src/pages/Questions/BranchingMap/index.tsx | 2 - .../Questions/BranchingMap/stylesheet.ts | 53 + .../BranchingQuestionsModal/index.tsx | 2 - src/stores/uiTools/store.ts | 8 +- 8 files changed, 1474 insertions(+), 705 deletions(-) create mode 100644 src/pages/Questions/BranchingMap/hooks/usePopper.ts create mode 100644 src/pages/Questions/BranchingMap/hooks/useRemoveNode.ts create mode 100644 src/pages/Questions/BranchingMap/stylesheet.ts diff --git a/src/pages/Questions/BranchingMap/CsComponent.tsx b/src/pages/Questions/BranchingMap/CsComponent.tsx index 30db6972..a66cbd02 100644 --- a/src/pages/Questions/BranchingMap/CsComponent.tsx +++ b/src/pages/Questions/BranchingMap/CsComponent.tsx @@ -4,15 +4,25 @@ import { Button } from "@mui/material"; import CytoscapeComponent from "react-cytoscapejs"; import popper from "cytoscape-popper"; import { useCurrentQuiz } from "@root/quizes/hooks"; -import { updateRootContentId } from "@root/quizes/actions" -import { AnyTypedQuizQuestion } from "@model/questionTypes/shared" +import { updateRootContentId } from "@root/quizes/actions"; +import { AnyTypedQuizQuestion } from "@model/questionTypes/shared"; import { useQuestionsStore } from "@root/questions/store"; -import { deleteQuestion, updateQuestion, getQuestionByContentId, clearRuleForAll, createFrontResult } from "@root/questions/actions"; -import { updateOpenedModalSettingsId, } from "@root/uiTools/actions"; +import { + deleteQuestion, + updateQuestion, + getQuestionByContentId, + clearRuleForAll, + createFrontResult, +} from "@root/questions/actions"; +import { updateOpenedModalSettingsId } from "@root/uiTools/actions"; import { cleardragQuestionContentId } from "@root/uiTools/actions"; import { withErrorBoundary } from "react-error-boundary"; +import { useRemoveNode } from "./hooks/useRemoveNode"; +import { usePopper } from "./hooks/usePopper"; + import { storeToNodes } from "./helper"; +import { stylesheet } from "./stylesheet"; import "./styles.css"; @@ -22,111 +32,61 @@ import type { NodeSingular, AbstractEventObject, ElementDefinition, + LayoutEventObject, } from "cytoscape"; import { enqueueSnackbar } from "notistack"; import { useUiTools } from "@root/uiTools/store"; -type PopperItem = { - id: () => string; -}; +// type PopperItem = { +// id: () => string; +// }; -type Modifier = { - name: string; - options: unknown; -}; +// type Modifier = { +// name: string; +// options: unknown; +// }; -type PopperConfig = { - popper: { - placement: string; - modifiers?: Modifier[]; - }; - content: (items: PopperItem[]) => void; -}; +// type PopperConfig = { +// popper: { +// placement: string; +// modifiers?: Modifier[]; +// }; +// content: (items: PopperItem[]) => void; +// }; -type Popper = { - update: () => Promise; - setOptions: (modifiers: { modifiers?: Modifier[] }) => void; -}; +// type Popper = { +// update: () => Promise; +// setOptions: (modifiers: { modifiers?: Modifier[] }) => void; +// }; -type NodeSingularWithPopper = NodeSingular & { - popper: (config: PopperConfig) => Popper; -}; - -const stylesheet: Stylesheet[] = [ - { - selector: "node", - style: { - shape: "round-rectangle", - width: 130, - height: 130, - backgroundColor: "#FFFFFF", - label: "data(label)", - "font-size": "16", - color: "#4D4D4D", - "text-halign": "center", - "text-valign": "center", - "text-wrap": "wrap", - "text-max-width": "80", - }, - }, - { - selector: "[?eroticeyeblink]", - style: { - "border-width": "4px", - "border-style": "solid", - "border-color": "#7e2aea", - }, - }, - { - selector: ".multiline-auto", - style: { - "text-wrap": "wrap", - "text-max-width": "80", - }, - }, - { - selector: "edge", - style: { - width: 30, - "line-color": "#DEDFE7", - "curve-style": "taxi", - "taxi-direction": "horizontal", - "taxi-turn": 60, - }, - }, - { - selector: ":selected", - style: { - "border-style": "solid", - "border-width": 1.5, - "border-color": "#9A9AAF", - }, - }, -]; +// type NodeSingularWithPopper = NodeSingular & { +// popper: (config: PopperConfig) => Popper; +// }; Cytoscape.use(popper); -interface Props { +type CsComponentProps = { modalQuestionParentContentId: string; modalQuestionTargetContentId: string; setOpenedModalQuestions: (open: boolean) => void; setModalQuestionParentContentId: (id: string) => void; setModalQuestionTargetContentId: (id: string) => void; -} - +}; function CsComponent({ modalQuestionParentContentId, modalQuestionTargetContentId, setOpenedModalQuestions, setModalQuestionParentContentId, - setModalQuestionTargetContentId -}: Props) { + setModalQuestionTargetContentId, +}: CsComponentProps) { const quiz = useCurrentQuiz(); - const { dragQuestionContentId, desireToOpenABranchingModal } = useUiTools() - const trashQuestions = useQuestionsStore().questions - const questions = trashQuestions.filter((question) => question.type !== "result" && question.type !== null) + const { dragQuestionContentId, desireToOpenABranchingModal } = useUiTools(); + const trashQuestions = useQuestionsStore().questions; + const questions = trashQuestions.filter( + (question) => question.type !== "result" && question.type !== null + ); const [startCreate, setStartCreate] = useState(""); const [startRemove, setStartRemove] = useState(""); @@ -136,354 +96,421 @@ function CsComponent({ const crossesContainer = useRef(null); const gearsContainer = useRef(null); + // const { layoutOptions } = usePopper({ + // layoutsContainer, + // plusesContainer, + // crossesContainer, + // gearsContainer, + // setModalQuestionParentContentId, + // setOpenedModalQuestions, + // setStartCreate, + // setStartRemove, + // }); + const layoutOptions = {}; + const removeNode = () => {}; + // const { removeNode } = useRemoveNode({ + // cyRef, + // layoutOptions, + // layoutsContainer, + // plusesContainer, + // crossesContainer, + // gearsContainer, + // }); + useLayoutEffect(() => { - const cy = cyRef?.current + const cy = cyRef?.current; if (desireToOpenABranchingModal) { setTimeout(() => { - cy?.getElementById(desireToOpenABranchingModal)?.data("eroticeyeblink", true) - }, 250) + cy?.getElementById(desireToOpenABranchingModal)?.data( + "eroticeyeblink", + true + ); + }, 250); } else { - cy?.elements().data("eroticeyeblink", false) + cy?.elements().data("eroticeyeblink", false); } - }, [desireToOpenABranchingModal]) + }, [desireToOpenABranchingModal]); useLayoutEffect(() => { - updateOpenedModalSettingsId() - // updateRootContentId(quiz.id, "") - // clearRuleForAll() - }, []) + updateOpenedModalSettingsId(); + // updateRootContentId(quiz.id, "") + // clearRuleForAll() + }, []); useEffect(() => { - if (modalQuestionTargetContentId.length !== 0 && modalQuestionParentContentId.length !== 0) { - addNode({ parentNodeContentId: modalQuestionParentContentId, targetNodeContentId: modalQuestionTargetContentId }) + if ( + modalQuestionTargetContentId.length !== 0 && + modalQuestionParentContentId.length !== 0 + ) { + addNode({ + parentNodeContentId: modalQuestionParentContentId, + targetNodeContentId: modalQuestionTargetContentId, + }); } - setModalQuestionParentContentId("") - setModalQuestionTargetContentId("") - }, [modalQuestionTargetContentId]) - const addNode = ({ parentNodeContentId, targetNodeContentId }: { parentNodeContentId: string, targetNodeContentId?: string }) => { - + setModalQuestionParentContentId(""); + setModalQuestionTargetContentId(""); + }, [modalQuestionTargetContentId]); + + const addNode = ({ + parentNodeContentId, + targetNodeContentId, + }: { + parentNodeContentId: string; + targetNodeContentId?: string; + }) => { //запрещаем работу родителя-ребенка если это один и тот же вопрос - if (parentNodeContentId === targetNodeContentId) return + if (parentNodeContentId === targetNodeContentId) return; - - const cy = cyRef?.current - const parentNodeChildren = cy?.$('edge[source = "' + parentNodeContentId + '"]')?.length + const cy = cyRef?.current; + const parentNodeChildren = cy?.$( + 'edge[source = "' + parentNodeContentId + '"]' + )?.length; //если есть инфо о выбранном вопросе из модалки - берём родителя из инфо модалки. Иначе из значения дропа - const targetQuestion = { ...getQuestionByContentId(targetNodeContentId || dragQuestionContentId) } as AnyTypedQuizQuestion - if (Object.keys(targetQuestion).length !== 0 && parentNodeContentId && parentNodeChildren !== undefined) { - clearDataAfterAddNode({ parentNodeContentId, targetQuestion, parentNodeChildren }) - cy?.data('changed', true) - createFrontResult(quiz.backendId, targetQuestion.content.id) + const targetQuestion = { + ...getQuestionByContentId(targetNodeContentId || dragQuestionContentId), + } as AnyTypedQuizQuestion; + if ( + Object.keys(targetQuestion).length !== 0 && + parentNodeContentId && + parentNodeChildren !== undefined + ) { + clearDataAfterAddNode({ + parentNodeContentId, + targetQuestion, + parentNodeChildren, + }); + cy?.data("changed", true); + if (quiz) { + createFrontResult(quiz.backendId, targetQuestion.content.id); + } const es = cy?.add([ { data: { id: targetQuestion.content.id, - label: targetQuestion.title === "" || targetQuestion.title === " " ? "noname" : targetQuestion.title - } + label: + targetQuestion.title === "" || targetQuestion.title === " " + ? "noname" + : targetQuestion.title, + }, }, { data: { source: parentNodeContentId, - target: targetQuestion.content.id - } - } - ]) - cy?.layout(lyopts).run() - console.log(es) - cy?.center(es) + target: targetQuestion.content.id, + }, + }, + ]); + cy?.layout(layoutOptions).run(); + console.log(es); + cy?.center(es); } else { - enqueueSnackbar("Добавляемый вопрос не найден") + enqueueSnackbar("Добавляемый вопрос не найден"); } - } - - const clearDataAfterAddNode = ({ parentNodeContentId, targetQuestion, parentNodeChildren }: { parentNodeContentId: string, targetQuestion: AnyTypedQuizQuestion, parentNodeChildren: number }) => { - - const parentQuestion = { ...getQuestionByContentId(parentNodeContentId) } as AnyTypedQuizQuestion + }; + const clearDataAfterAddNode = ({ + parentNodeContentId, + targetQuestion, + parentNodeChildren, + }: { + parentNodeContentId: string; + targetQuestion: AnyTypedQuizQuestion; + parentNodeChildren: number; + }) => { + const parentQuestion = { + ...getQuestionByContentId(parentNodeContentId), + } as AnyTypedQuizQuestion; //смотрим не добавлен ли родителю result. Если да - убираем его. Веточкам result не нужен trashQuestions.forEach((targetQuestion) => { - if (targetQuestion.type === "result" && targetQuestion.content.rule.parentId === parentQuestion.content.id) { - console.log('deleteQ', targetQuestion.id) + if ( + targetQuestion.type === "result" && + targetQuestion.content.rule.parentId === parentQuestion.content.id + ) { + console.log("deleteQ", targetQuestion.id); deleteQuestion(targetQuestion.id); } - }) - + }); //предупреждаем добавленный вопрос о том, кто его родитель - updateQuestion(targetQuestion.content.id, question => { - question.content.rule.parentId = parentNodeContentId - question.content.rule.main = [] - }) + updateQuestion(targetQuestion.content.id, (question) => { + question.content.rule.parentId = parentNodeContentId; + question.content.rule.main = []; + }); //предупреждаем родителя о новом потомке (если он ещё не знает о нём) - if (!parentQuestion.content.rule.children.includes(targetQuestion.content.id)) updateQuestion(parentNodeContentId, question => { - question.content.rule.children = [...question.content.rule.children, targetQuestion.content.id] - }) - + if ( + !parentQuestion.content.rule.children.includes(targetQuestion.content.id) + ) + updateQuestion(parentNodeContentId, (question) => { + question.content.rule.children = [ + ...question.content.rule.children, + targetQuestion.content.id, + ]; + }); //Если детей больше 1 - предупреждаем стор вопросов об открытии модалки ветвления if (parentQuestion.content.rule.children >= 1) { - updateOpenedModalSettingsId(targetQuestion.content.id) + updateOpenedModalSettingsId(targetQuestion.content.id); } - } + }; + // const removeNode = ({ targetNodeContentId }: { targetNodeContentId: string }) => { + // console.log("старт удаление") + // const deleteNodes: string[] = [] + // const deleteEdges: any = [] + // const cy = cyRef?.current - const removeNode = ({ targetNodeContentId }: { targetNodeContentId: string }) => { - console.log("старт удаление") - const deleteNodes = [] as string[] - const deleteEdges: any = [] - const cy = cyRef?.current + // const findChildrenToDelete = (node) => { - const findChildrenToDelete = (node) => { + // //Узнаём грани, идущие от этой ноды + // cy?.$('edge[source = "' + node.id() + '"]')?.toArray().forEach((edge) => { + // const edgeData = edge.data() - //Узнаём грани, идущие от этой ноды - cy?.$('edge[source = "' + node.id() + '"]')?.toArray().forEach((edge) => { - const edgeData = edge.data() + // //записываем id грани для дальнейшего удаления + // deleteEdges.push(edge) + // //ищем ноду на конце грани, записываем её ID для дальнейшего удаления + // const targetNode = cy?.$("#" + edgeData.target) + // deleteNodes.push(targetNode.data().id) + // //вызываем функцию для анализа потомков уже у этой ноды + // findChildrenToDelete(targetNode) + // }) - //записываем id грани для дальнейшего удаления - deleteEdges.push(edge) - //ищем ноду на конце грани, записываем её ID для дальнейшего удаления - const targetNode = cy?.$("#" + edgeData.target) - deleteNodes.push(targetNode.data().id) - //вызываем функцию для анализа потомков уже у этой ноды - findChildrenToDelete(targetNode) - }) + // } + // findChildrenToDelete(cy?.getElementById(targetNodeContentId)) - } - findChildrenToDelete(cy?.getElementById(targetNodeContentId)) + // const targetQuestion = getQuestionByContentId(targetNodeContentId) - const targetQuestion = getQuestionByContentId(targetNodeContentId) + // if (targetQuestion.content.rule.parentId === "root" && quiz) { + // updateRootContentId(quiz?.id, "") + // updateQuestion(targetNodeContentId, question => { + // question.content.rule.parentId = "" + // question.content.rule.main = [] + // question.content.rule.children = [] + // question.content.rule.default = "" + // }) + // trashQuestions.forEach(q => { + // if (q.type === "result") { + // deleteQuestion(q.id); + // } + // }); + // clearRuleForAll() - if (targetQuestion.content.rule.parentId === "root" && quiz) { - updateRootContentId(quiz?.id, "") - updateQuestion(targetNodeContentId, question => { - question.content.rule.parentId = "" - question.content.rule.main = [] - question.content.rule.children = [] - question.content.rule.default = "" - }) - trashQuestions.forEach(q => { - if (q.type === "result") { - deleteQuestion(q.id); - } - }); - clearRuleForAll() + // } else { - } else { + // const parentQuestionContentId = cy?.$('edge[target = "' + targetNodeContentId + '"]')?.toArray()?.[0]?.data()?.source + // if (targetNodeContentId && parentQuestionContentId) { + // if (cy?.edges(`[source="${parentQuestionContentId}"]`).length === 0) + // createFrontResult(quiz.backendId, parentQuestionContentId) + // clearDataAfterRemoveNode({ targetQuestionContentId: targetNodeContentId, parentQuestionContentId }) + // cy?.remove(cy?.$('#' + targetNodeContentId)).layout(lyopts).run() + // } - const parentQuestionContentId = cy?.$('edge[target = "' + targetNodeContentId + '"]')?.toArray()?.[0]?.data()?.source - if (targetNodeContentId && parentQuestionContentId) { -if (cy?.edges(`[source="${parentQuestionContentId}"]`).length === 0) - createFrontResult(quiz.backendId, parentQuestionContentId) - clearDataAfterRemoveNode({ targetQuestionContentId: targetNodeContentId, parentQuestionContentId }) - cy?.remove(cy?.$('#' + targetNodeContentId)).layout(lyopts).run() - } + // } - } + // //После всех манипуляций удаляем грани и ноды из CS Чистим rule потомков на беке - //После всех манипуляций удаляем грани и ноды из CS Чистим rule потомков на беке + // deleteNodes.forEach((nodeId) => {//Ноды + // cy?.remove(cy?.$("#" + nodeId)) + // removeButtons(nodeId) + // updateQuestion(nodeId, question => { + // question.content.rule.parentId = "" + // question.content.rule.main = [] + // question.content.rule.default = "" + // question.content.rule.children = [] + // }) - deleteNodes.forEach((nodeId) => {//Ноды - cy?.remove(cy?.$("#" + nodeId)) - removeButtons(nodeId) - updateQuestion(nodeId, question => { - question.content.rule.parentId = "" - question.content.rule.main = [] - question.content.rule.default = "" - question.content.rule.children = [] - }) + // }) + // deleteEdges.forEach((edge: any) => {//Грани + // cy?.remove(edge) + // }) - }) + // removeButtons(targetNodeContentId) + // cy?.data('changed', true) + // cy?.layout(lyopts).run() - deleteEdges.forEach((edge: any) => {//Грани - cy?.remove(edge) - }) + // //удаляем result всех потомков + // trashQuestions.forEach((qr) => { + // if (qr.type === "result") { + // if (deleteNodes.includes(qr.content.rule.parentId) || qr.content.rule.parentId === targetQuestion.content.id) { + // deleteQuestion(qr.id); + // } + // } + // }) + // } - removeButtons(targetNodeContentId) - cy?.data('changed', true) - cy?.layout(lyopts).run() + // const clearDataAfterRemoveNode = ({ targetQuestionContentId, parentQuestionContentId }: { targetQuestionContentId: string, parentQuestionContentId: string }) => { - //удаляем result всех потомков - trashQuestions.forEach((qr) => { - if (qr.type === "result") { - if (deleteNodes.includes(qr.content.rule.parentId) || qr.content.rule.parentId === targetQuestion.content.id) { - deleteQuestion(qr.id); - } - } - }) - } - - - const clearDataAfterRemoveNode = ({ targetQuestionContentId, parentQuestionContentId }: { targetQuestionContentId: string, parentQuestionContentId: string }) => { - - - - updateQuestion(targetQuestionContentId, question => { - question.content.rule.parentId = "" - question.content.rule.children = [] - question.content.rule.main = [] - question.content.rule.default = "" - }) - - - //чистим rule родителя - const parentQuestion = getQuestionByContentId(parentQuestionContentId) - const newRule = {} - const newChildren = [...parentQuestion.content.rule.children] - newChildren.splice(parentQuestion.content.rule.children.indexOf(targetQuestionContentId), 1); - newRule.main = parentQuestion.content.rule.main.filter((data) => data.next !== targetQuestionContentId) //удаляем условия перехода от родителя к этому вопросу - newRule.parentId = parentQuestion.content.rule.parentId - newRule.default = parentQuestion.content.rule.default === targetQuestionContentId ? "" : parentQuestion.content.rule.default - newRule.children = newChildren - - updateQuestion(parentQuestionContentId, (PQ) => { - PQ.content.rule = newRule - }) - } + // updateQuestion(targetQuestionContentId, question => { + // question.content.rule.parentId = "" + // question.content.rule.children = [] + // question.content.rule.main = [] + // question.content.rule.default = "" + // }) + // //чистим rule родителя + // const parentQuestion = getQuestionByContentId(parentQuestionContentId) + // const newRule = {} + // const newChildren = [...parentQuestion.content.rule.children] + // newChildren.splice(parentQuestion.content.rule.children.indexOf(targetQuestionContentId), 1); + // newRule.main = parentQuestion.content.rule.main.filter((data) => data.next !== targetQuestionContentId) //удаляем условия перехода от родителя к этому вопросу + // newRule.parentId = parentQuestion.content.rule.parentId + // newRule.default = parentQuestion.content.rule.default === targetQuestionContentId ? "" : parentQuestion.content.rule.default + // newRule.children = newChildren + // updateQuestion(parentQuestionContentId, (PQ) => { + // PQ.content.rule = newRule + // }) + // } useEffect(() => { if (startCreate) { addNode({ parentNodeContentId: startCreate }); - cleardragQuestionContentId() + cleardragQuestionContentId(); setStartCreate(""); } }, [startCreate]); useEffect(() => { if (startRemove) { - removeNode({ targetNodeContentId: startRemove }); + removeNode(startRemove); setStartRemove(""); } }, [startRemove]); + // const readyLO = (e) => { + // if (e.cy.data('firstNode') === 'nonroot') { + // e.cy.data('firstNode', 'root') + // e.cy.nodes().sort((a, b) => (a.data('root') ? 1 : -1)).layout(lyopts).run() - const readyLO = (e) => { - if (e.cy.data('firstNode') === 'nonroot') { - e.cy.data('firstNode', 'root') - e.cy.nodes().sort((a, b) => (a.data('root') ? 1 : -1)).layout(lyopts).run() + // } else { - } else { + // e.cy.data('changed', false) + // e.cy.removeData('firstNode') + // } + // //удаляем иконки + // e.cy.nodes().forEach((ele: any) => { + // const data = ele.data() + // data.id && removeButtons(data.id); + // }) + // initialPopperIcons(e) + // } - e.cy.data('changed', false) - e.cy.removeData('firstNode') - } + // const lyopts = { + // name: 'preset', - //удаляем иконки - e.cy.nodes().forEach((ele: any) => { - const data = ele.data() - data.id && removeButtons(data.id); - }) - initialPopperIcons(e) - } + // positions: (e) => { + // if (!e.cy().data('changed')) { + // return e.data('oldPos') + // } + // const id = e.id() + // const incomming = e.cy().edges(`[target="${id}"]`) + // const layer = 0 + // e.removeData('lastChild') - const lyopts = { - name: 'preset', + // if (incomming.length === 0) { + // if (e.cy().data('firstNode') === undefined) + // e.cy().data('firstNode', 'root') + // e.data('root', true) + // const children = e.cy().edges(`[source="${id}"]`).targets() + // e.data('layer', layer) + // e.data('children', children.length) + // const queue = [] + // children.forEach(n => { + // queue.push({ task: n, layer: layer + 1 }) + // }) + // while (queue.length) { + // const task = queue.pop() + // task.task.data('layer', task.layer) + // task.task.removeData('subtreeWidth') + // const children = e.cy().edges(`[source="${task.task.id()}"]`).targets() + // task.task.data('children', children.length) + // if (children.length !== 0) { + // children.forEach(n => queue.push({ task: n, layer: task.layer + 1 })) + // } + // } + // queue.push({ parent: e, children: children }) + // while (queue.length) { + // const task = queue.pop() + // if (task.children.length === 0) { + // task.parent.data('subtreeWidth', task.parent.height() + 50) + // continue + // } + // const unprocessed = task?.children.filter(e => { + // return (e.data('subtreeWidth') === undefined) + // }) + // if (unprocessed.length !== 0) { + // queue.push(task) + // unprocessed.forEach(t => { + // queue.push({ parent: t, children: t.cy().edges(`[source="${t.id()}"]`).targets() }) + // }) + // continue + // } - positions: (e) => { - if (!e.cy().data('changed')) { - return e.data('oldPos') - } - const id = e.id() - const incomming = e.cy().edges(`[target="${id}"]`) - const layer = 0 - e.removeData('lastChild') + // task?.parent.data('subtreeWidth', task.children.reduce((p, n) => p + n.data('subtreeWidth'), 0)) + // } - if (incomming.length === 0) { - if (e.cy().data('firstNode') === undefined) - e.cy().data('firstNode', 'root') - e.data('root', true) - const children = e.cy().edges(`[source="${id}"]`).targets() - e.data('layer', layer) - e.data('children', children.length) - const queue = [] - children.forEach(n => { - queue.push({ task: n, layer: layer + 1 }) - }) - while (queue.length) { - const task = queue.pop() - task.task.data('layer', task.layer) - task.task.removeData('subtreeWidth') - const children = e.cy().edges(`[source="${task.task.id()}"]`).targets() - task.task.data('children', children.length) - if (children.length !== 0) { - children.forEach(n => queue.push({ task: n, layer: task.layer + 1 })) - } - } - queue.push({ parent: e, children: children }) - while (queue.length) { - const task = queue.pop() - if (task.children.length === 0) { - task.parent.data('subtreeWidth', task.parent.height() + 50) - continue - } - const unprocessed = task?.children.filter(e => { - return (e.data('subtreeWidth') === undefined) - }) - if (unprocessed.length !== 0) { - queue.push(task) - unprocessed.forEach(t => { - queue.push({ parent: t, children: t.cy().edges(`[source="${t.id()}"]`).targets() }) - }) - continue - } + // const pos = { x: 0, y: 0 } + // e.data('oldPos', pos) - task?.parent.data('subtreeWidth', task.children.reduce((p, n) => p + n.data('subtreeWidth'), 0)) - } + // queue.push({ task: children, parent: e }) + // while (queue.length) { + // const task = queue.pop() + // const oldPos = task.parent.data('oldPos') + // let yoffset = oldPos.y - task.parent.data('subtreeWidth') / 2 + // task.task.forEach(n => { + // const width = n.data('subtreeWidth') - const pos = { x: 0, y: 0 } - e.data('oldPos', pos) + // n.data('oldPos', { x: 250 * n.data('layer'), y: yoffset + width / 2 }) + // yoffset += width + // queue.push({ task: n.cy().edges(`[source="${n.id()}"]`).targets(), parent: n }) + // }) + // } + // e.cy().data('changed', false) + // return pos + // } else { - queue.push({ task: children, parent: e }) - while (queue.length) { - const task = queue.pop() - const oldPos = task.parent.data('oldPos') - let yoffset = oldPos.y - task.parent.data('subtreeWidth') / 2 - task.task.forEach(n => { - const width = n.data('subtreeWidth') - - n.data('oldPos', { x: 250 * n.data('layer'), y: yoffset + width / 2 }) - yoffset += width - queue.push({ task: n.cy().edges(`[source="${n.id()}"]`).targets(), parent: n }) - }) - } - e.cy().data('changed', false) - return pos - } else { - - const opos = e.data('oldPos') - if (opos) { - return opos - } - } - }, // map of (node id) => (position obj); or function(node){ return somPos; } - zoom: undefined, // the zoom level to set (prob want fit = false if set) - pan: true, // the pan level to set (prob want fit = false if set) - fit: false, // whether to fit to viewport - padding: 30, // padding on fit - animate: false, // whether to transition the node positions - animationDuration: 500, // duration of animation in ms if enabled - animationEasing: undefined, // easing of animation if enabled - animateFilter: function (node, i) { return false; }, // a function that determines whether the node should be animated. All nodes animated by default on animate enabled. Non-animated nodes are positioned immediately when the layout starts - ready: readyLO, // callback on layoutready - transform: function (node, position) { return position; } // transform a given node position. Useful for changing flow direction in discrete layouts - } + // const opos = e.data('oldPos') + // if (opos) { + // return opos + // } + // } + // }, // map of (node id) => (position obj); or function(node){ return somPos; } + // zoom: undefined, // the zoom level to set (prob want fit = false if set) + // pan: true, // the pan level to set (prob want fit = false if set) + // fit: false, // whether to fit to viewport + // padding: 30, // padding on fit + // animate: false, // whether to transition the node positions + // animationDuration: 500, // duration of animation in ms if enabled + // animationEasing: undefined, // easing of animation if enabled + // animateFilter: function (node, i) { return false; }, // a function that determines whether the node should be animated. All nodes animated by default on animate enabled. Non-animated nodes are positioned immediately when the layout starts + // ready: readyLO, // callback on layoutready + // transform: function (node, position) { return position; } // transform a given node position. Useful for changing flow direction in discrete layouts + // } useEffect(() => { - document.querySelector("#root")?.addEventListener("mouseup", cleardragQuestionContentId); + document + .querySelector("#root") + ?.addEventListener("mouseup", cleardragQuestionContentId); const cy = cyRef.current; - const eles = cy?.add(storeToNodes(questions.filter((question: AnyTypedQuizQuestion) => (question.type !== "result" && question.type !== null)))) - cy.data('changed', true) + const eles = cy?.add( + storeToNodes( + questions.filter( + (question) => question.type && question.type !== "result" + ) as AnyTypedQuizQuestion[] + ) + ); + cy?.data("changed", true); // cy.data('changed', true) - const elecs = eles.layout(lyopts).run() - cy?.on('add', () => cy.data('changed', true)) - cy?.fit() + const elecs = eles?.layout(layoutOptions).run(); + cy?.on("add", () => cy.data("changed", true)); + cy?.fit(); //cy?.layout().run() return () => { - document.querySelector("#root")?.removeEventListener("mouseup", cleardragQuestionContentId); + document + .querySelector("#root") + ?.removeEventListener("mouseup", cleardragQuestionContentId); layoutsContainer.current?.remove(); plusesContainer.current?.remove(); crossesContainer.current?.remove(); @@ -491,298 +518,292 @@ if (cy?.edges(`[source="${parentQuestionContentId}"]`).length === 0) }; }, []); + // const removeButtons = (id: string) => { + // layoutsContainer.current + // ?.querySelector(`.popper-layout[data-id='${id}']`) + // ?.remove(); + // plusesContainer.current + // ?.querySelector(`.popper-plus[data-id='${id}']`) + // ?.remove(); + // crossesContainer.current + // ?.querySelector(`.popper-cross[data-id='${id}']`) + // ?.remove(); + // gearsContainer.current + // ?.querySelector(`.popper-gear[data-id='${id}']`) + // ?.remove(); + // }; - const removeButtons = (id: string) => { - layoutsContainer.current - ?.querySelector(`.popper-layout[data-id='${id}']`) - ?.remove(); - plusesContainer.current - ?.querySelector(`.popper-plus[data-id='${id}']`) - ?.remove(); - crossesContainer.current - ?.querySelector(`.popper-cross[data-id='${id}']`) - ?.remove(); - gearsContainer.current - ?.querySelector(`.popper-gear[data-id='${id}']`) - ?.remove(); - }; + // const initialPopperIcons = ({ cy }: LayoutEventObject) => { + // const container = + // (document.body.querySelector( + // ".__________cytoscape_container" + // ) as HTMLDivElement) || null; + // if (!container) { + // return; + // } - const initialPopperIcons = (e) => { - const cy = e.cy + // container.style.overflow = "hidden"; - const container = - (document.body.querySelector( - ".__________cytoscape_container" - ) as HTMLDivElement) || null; + // if (!plusesContainer.current) { + // plusesContainer.current = document.createElement("div"); + // plusesContainer.current.setAttribute("id", "popper-pluses"); + // container.append(plusesContainer.current); + // } + // if (!crossesContainer.current) { + // crossesContainer.current = document.createElement("div"); + // crossesContainer.current.setAttribute("id", "popper-crosses"); + // container.append(crossesContainer.current); + // } + // if (!gearsContainer.current) { + // gearsContainer.current = document.createElement("div"); + // gearsContainer.current.setAttribute("id", "popper-gears"); + // container.append(gearsContainer.current); + // } + // if (!layoutsContainer.current) { + // layoutsContainer.current = document.createElement("div"); + // layoutsContainer.current.setAttribute("id", "popper-layouts"); + // container.append(layoutsContainer.current); + // } - if (!container) { - return; - } + // const ext = cy.extent(); + // const nodesInView = cy.nodes().filter((n) => { + // const bb = n.boundingBox(); + // return ( + // bb.x2 > ext.x1 && bb.x1 < ext.x2 && bb.y2 > ext.y1 && bb.y1 < ext.y2 + // ); + // }); - container.style.overflow = "hidden"; + // nodesInView.toArray()?.forEach((item) => { + // const node = item as NodeSingularWithPopper; - if (!plusesContainer.current) { - plusesContainer.current = document.createElement("div"); - plusesContainer.current.setAttribute("id", "popper-pluses"); - container.append(plusesContainer.current); - } - if (!crossesContainer.current) { - crossesContainer.current = document.createElement("div"); - crossesContainer.current.setAttribute("id", "popper-crosses"); - container.append(crossesContainer.current); - } - if (!gearsContainer.current) { - gearsContainer.current = document.createElement("div"); - gearsContainer.current.setAttribute("id", "popper-gears"); - container.append(gearsContainer.current); - } - if (!layoutsContainer.current) { - layoutsContainer.current = document.createElement("div"); - layoutsContainer.current.setAttribute("id", "popper-layouts"); - container.append(layoutsContainer.current); - } + // const layoutsPopper = node.popper({ + // popper: { + // placement: "left", + // modifiers: [{ name: "flip", options: { boundary: node } }], + // }, + // content: ([item]) => { + // const itemId = item.id(); + // const itemElement = layoutsContainer.current?.querySelector( + // `.popper-layout[data-id='${itemId}']` + // ); + // if (itemElement) { + // return itemElement; + // } - const ext = cy.extent() - const nodesInView = cy.nodes().filter(n => { - const bb = n.boundingBox() - return bb.x2 > ext.x1 && bb.x1 < ext.x2 && bb.y2 > ext.y1 && bb.y1 < ext.y2 - }) + // const layoutElement = document.createElement("div"); + // layoutElement.style.zIndex = "0"; + // layoutElement.classList.add("popper-layout"); + // layoutElement.setAttribute("data-id", item.id()); + // layoutElement.addEventListener("mouseup", () => { + // //Узнаём грани, идущие от этой ноды + // setModalQuestionParentContentId(item.id()); + // setOpenedModalQuestions(true); + // }); + // layoutsContainer.current?.appendChild(layoutElement); - nodesInView - .toArray() - ?.forEach((item) => { - const node = item as NodeSingularWithPopper; + // return layoutElement; + // }, + // }); - const layoutsPopper = node.popper({ - popper: { - placement: "left", - modifiers: [{ name: "flip", options: { boundary: node } }], - }, - content: ([item]) => { - const itemId = item.id(); - const itemElement = layoutsContainer.current?.querySelector( - `.popper-layout[data-id='${itemId}']` - ); - if (itemElement) { - return itemElement; - } + // const plusesPopper = node.popper({ + // popper: { + // placement: "right", + // modifiers: [{ name: "flip", options: { boundary: node } }], + // }, + // content: ([item]) => { + // const itemId = item.id(); + // const itemElement = plusesContainer.current?.querySelector( + // `.popper-plus[data-id='${itemId}']` + // ); + // if (itemElement) { + // return itemElement; + // } - const layoutElement = document.createElement("div"); - layoutElement.style.zIndex = "0" - layoutElement.classList.add("popper-layout"); - layoutElement.setAttribute("data-id", item.id()); - layoutElement.addEventListener("mouseup", () => { - //Узнаём грани, идущие от этой ноды - setModalQuestionParentContentId(item.id()) - setOpenedModalQuestions(true) - }); - layoutsContainer.current?.appendChild(layoutElement); + // const plusElement = document.createElement("div"); + // plusElement.classList.add("popper-plus"); + // plusElement.setAttribute("data-id", item.id()); + // plusElement.style.zIndex = "1"; + // plusElement.addEventListener("mouseup", () => { + // setStartCreate(node.id()); + // }); - return layoutElement; - }, - }); + // plusesContainer.current?.appendChild(plusElement); - const plusesPopper = node.popper({ - popper: { - placement: "right", - modifiers: [{ name: "flip", options: { boundary: node } }], - }, - content: ([item]) => { - const itemId = item.id(); - const itemElement = plusesContainer.current?.querySelector( - `.popper-plus[data-id='${itemId}']` - ); - if (itemElement) { - return itemElement; - } + // return plusElement; + // }, + // }); - const plusElement = document.createElement("div"); - plusElement.classList.add("popper-plus"); - plusElement.setAttribute("data-id", item.id()); - plusElement.style.zIndex = "1" - plusElement.addEventListener("mouseup", () => { - setStartCreate(node.id()); - }); + // const crossesPopper = node.popper({ + // popper: { + // placement: "top-end", + // modifiers: [{ name: "flip", options: { boundary: node } }], + // }, + // content: ([item]) => { + // const itemId = item.id(); + // const itemElement = crossesContainer.current?.querySelector( + // `.popper-cross[data-id='${itemId}']` + // ); + // if (itemElement) { + // return itemElement; + // } - plusesContainer.current?.appendChild(plusElement); + // const crossElement = document.createElement("div"); + // crossElement.classList.add("popper-cross"); + // crossElement.setAttribute("data-id", item.id()); + // crossElement.style.zIndex = "2"; + // crossesContainer.current?.appendChild(crossElement); + // crossElement.addEventListener("mouseup", () => { + // setStartRemove(node.id()); + // }); - return plusElement; - }, - }); + // return crossElement; + // }, + // }); + // let gearsPopper: Popper | null = null; + // if (node.data().root !== true) { + // gearsPopper = node.popper({ + // popper: { + // placement: "left", + // modifiers: [{ name: "flip", options: { boundary: node } }], + // }, + // content: ([item]) => { + // const itemId = item.id(); - const crossesPopper = node.popper({ - popper: { - placement: "top-end", - modifiers: [{ name: "flip", options: { boundary: node } }], - }, - content: ([item]) => { - const itemId = item.id(); - const itemElement = crossesContainer.current?.querySelector( - `.popper-cross[data-id='${itemId}']` - ); - if (itemElement) { - return itemElement; - } + // const itemElement = gearsContainer.current?.querySelector( + // `.popper-gear[data-id='${itemId}']` + // ); + // if (itemElement) { + // return itemElement; + // } - const crossElement = document.createElement("div"); - crossElement.classList.add("popper-cross"); - crossElement.setAttribute("data-id", item.id()); - crossElement.style.zIndex = "2" - crossesContainer.current?.appendChild(crossElement); - crossElement.addEventListener("mouseup", () => { - setStartRemove(node.id()) + // const gearElement = document.createElement("div"); + // gearElement.classList.add("popper-gear"); + // gearElement.setAttribute("data-id", item.id()); + // gearElement.style.zIndex = "1"; + // gearsContainer.current?.appendChild(gearElement); + // gearElement.addEventListener("mouseup", (e) => { + // console.log("up"); + // updateOpenedModalSettingsId(item.id()); + // }); + // return gearElement; + // }, + // }); + // } + // const update = async () => { + // await plusesPopper.update(); + // await crossesPopper.update(); + // await gearsPopper?.update(); + // await layoutsPopper.update(); + // }; - } - ); + // const onZoom = (event: AbstractEventObject) => { + // const zoom = event.cy.zoom(); - return crossElement; - }, - }); -let gearsPopper = null - if (node.data().root !== true) { - gearsPopper = node.popper({ - popper: { - placement: "left", - modifiers: [{ name: "flip", options: { boundary: node } }], - }, - content: ([item]) => { - const itemId = item.id(); + // //update(); - const itemElement = gearsContainer.current?.querySelector( - `.popper-gear[data-id='${itemId}']` - ); - if (itemElement) { - return itemElement; - } + // crossesPopper.setOptions({ + // modifiers: [ + // { name: "flip", options: { boundary: node } }, + // { name: "offset", options: { offset: [-5 * zoom, -30 * zoom] } }, + // ], + // }); - const gearElement = document.createElement("div"); - gearElement.classList.add("popper-gear"); - gearElement.setAttribute("data-id", item.id()); - gearElement.style.zIndex = "1" - gearsContainer.current?.appendChild(gearElement); - gearElement.addEventListener("mouseup", (e) => { - console.log("up") - updateOpenedModalSettingsId(item.id()) - }); + // layoutsPopper.setOptions({ + // modifiers: [ + // { name: "flip", options: { boundary: node } }, + // { name: "offset", options: { offset: [0, -130 * zoom] } }, + // ], + // }); + // plusesPopper.setOptions({ + // modifiers: [ + // { name: "flip", options: { boundary: node } }, + // { name: "offset", options: { offset: [0, 0 * zoom] } }, + // ], + // }); + // gearsPopper?.setOptions({ + // modifiers: [ + // { name: "flip", options: { boundary: node } }, + // { name: "offset", options: { offset: [0, 0] } }, + // ], + // }); - return gearElement; - }, - }); - } - const update = async () => { - await plusesPopper.update(); - await crossesPopper.update(); - await gearsPopper?.update(); - await layoutsPopper.update(); - }; + // layoutsContainer.current + // ?.querySelectorAll("#popper-layouts > .popper-layout") + // .forEach((item) => { + // const element = item as HTMLDivElement; + // element.style.width = `${130 * zoom}px`; + // element.style.height = `${130 * zoom}px`; + // }); - const onZoom = (event: AbstractEventObject) => { - const zoom = event.cy.zoom(); + // plusesContainer.current + // ?.querySelectorAll("#popper-pluses > .popper-plus") + // .forEach((item) => { + // const element = item as HTMLDivElement; + // element.style.width = `${40 * zoom}px`; + // element.style.height = `${40 * zoom}px`; + // element.style.fontSize = `${40 * zoom}px`; + // element.style.borderRadius = `${6 * zoom}px`; + // }); - //update(); + // crossesContainer.current + // ?.querySelectorAll("#popper-crosses > .popper-cross") + // .forEach((item) => { + // const element = item as HTMLDivElement; + // element.style.width = `${24 * zoom}px`; + // element.style.height = `${24 * zoom}px`; + // element.style.fontSize = `${24 * zoom}px`; + // element.style.borderRadius = `${6 * zoom}px`; + // }); - crossesPopper.setOptions({ - modifiers: [ - { name: "flip", options: { boundary: node } }, - { name: "offset", options: { offset: [-5 * zoom, -30 * zoom] } }, - ], - }); + // gearsContainer?.current + // ?.querySelectorAll("#popper-gears > .popper-gear") + // .forEach((item) => { + // const element = item as HTMLDivElement; + // element.style.width = `${60 * zoom}px`; + // element.style.height = `${40 * zoom}px`; + // }); + // }; - layoutsPopper.setOptions({ - modifiers: [ - { name: "flip", options: { boundary: node } }, - { name: "offset", options: { offset: [0, -130 * zoom] } }, - ], - }); - plusesPopper.setOptions({ - modifiers: [ - { name: "flip", options: { boundary: node } }, - { name: "offset", options: { offset: [0, 0 * zoom] } }, - ], - }); - gearsPopper?.setOptions({ - modifiers: [ - { name: "flip", options: { boundary: node } }, - { name: "offset", options: { offset: [0, 0] } }, - ], - }); + // //node?.on("position", update); + // let pressed = false; + // let hide = false; + // cy?.on("mousedown", () => { + // pressed = true; + // }); + // cy?.on("mouseup", () => { + // pressed = false; + // hide = false; - layoutsContainer.current - ?.querySelectorAll("#popper-layouts > .popper-layout") - .forEach((item) => { - const element = item as HTMLDivElement; - element.style.width = `${130 * zoom}px`; - element.style.height = `${130 * zoom}px`; - }); + // const gc = gearsContainer.current; + // if (gc) gc.style.display = "block"; + // const pc = plusesContainer.current; + // const xc = crossesContainer.current; + // const lc = layoutsContainer.current; + // if (pc) pc.style.display = "block"; + // if (xc) xc.style.display = "block"; + // if (lc) lc.style.display = "block"; + // update(); + // }); - plusesContainer.current - ?.querySelectorAll("#popper-pluses > .popper-plus") - .forEach((item) => { - const element = item as HTMLDivElement; - element.style.width = `${40 * zoom}px`; - element.style.height = `${40 * zoom}px`; - element.style.fontSize = `${40 * zoom}px`; - element.style.borderRadius = `${6 * zoom}px`; - }); + // cy?.on("mousemove", () => { + // if (pressed && !hide) { + // hide = true; + // const gc = gearsContainer.current; + // if (gc) gc.style.display = "none"; + // const pc = plusesContainer.current; + // const xc = crossesContainer.current; + // const lc = layoutsContainer.current; + // if (pc) pc.style.display = "none"; + // if (xc) xc.style.display = "none"; + // if (lc) lc.style.display = "block"; + // } + // }); - crossesContainer.current - ?.querySelectorAll("#popper-crosses > .popper-cross") - .forEach((item) => { - const element = item as HTMLDivElement; - element.style.width = `${24 * zoom}px`; - element.style.height = `${24 * zoom}px`; - element.style.fontSize = `${24 * zoom}px`; - element.style.borderRadius = `${6 * zoom}px`; - }); - - gearsContainer?.current - ?.querySelectorAll("#popper-gears > .popper-gear") - .forEach((item) => { - const element = item as HTMLDivElement; - element.style.width = `${60 * zoom}px`; - element.style.height = `${40 * zoom}px`; - }); - }; - - //node?.on("position", update); - let pressed = false - let hide = false - cy?.on('mousedown', () => { pressed = true }) - cy?.on('mouseup', () => { - pressed = false - hide = false - - - const gc = gearsContainer.current - if (gc) gc.style.display = 'block' - const pc = plusesContainer.current - const xc = crossesContainer.current - const lc = layoutsContainer.current - if (pc) pc.style.display = 'block' - if (xc) xc.style.display = 'block' - if (lc) lc.style.display = 'block' - update() - }) - - cy?.on('mousemove', () => { - if (pressed && !hide) { - hide = true - const gc = gearsContainer.current - if (gc) gc.style.display = 'none' - const pc = plusesContainer.current - const xc = crossesContainer.current - const lc = layoutsContainer.current - if (pc) pc.style.display = 'none' - if (xc) xc.style.display = 'none' - if (lc) lc.style.display = 'block' - } - }); - - cy?.on("zoom render", onZoom); - }); - }; + // cy?.on("zoom render", onZoom); + // }); + // }; return ( <> @@ -796,9 +817,7 @@ let gearsPopper = null }} variant="text" onClick={() => { - - cyRef.current?.fit() - + cyRef.current?.fit(); }} > Выровнять @@ -809,7 +828,7 @@ let gearsPopper = null // elements={createGraphElements(tree, quiz)} style={{ height: "480px", background: "#F2F3F7" }} stylesheet={stylesheet} - layout={(lyopts)} + layout={layoutOptions} cy={(cy) => { cyRef.current = cy; }} @@ -827,20 +846,22 @@ let gearsPopper = null }}>elements */} ); -}; +} function Clear() { const quiz = useCurrentQuiz(); - updateRootContentId(quiz.id, "") - clearRuleForAll() - return <> + if (quiz) { + updateRootContentId(quiz.id, ""); + } + clearRuleForAll(); + return <>; } export default withErrorBoundary(CsComponent, { fallback: , onError: (error, info) => { - enqueueSnackbar("Дерево порвалось") - console.log(info) - console.log(error) + enqueueSnackbar("Дерево порвалось"); + console.log(info); + console.log(error); }, }); diff --git a/src/pages/Questions/BranchingMap/FirstNodeField.tsx b/src/pages/Questions/BranchingMap/FirstNodeField.tsx index 5bae72c1..f2e68a3a 100644 --- a/src/pages/Questions/BranchingMap/FirstNodeField.tsx +++ b/src/pages/Questions/BranchingMap/FirstNodeField.tsx @@ -1,94 +1,98 @@ -import { Box } from "@mui/material" +import { Box } from "@mui/material"; import { useEffect, useRef, useLayoutEffect } from "react"; -import { deleteQuestion, clearRuleForAll, updateQuestion } from "@root/questions/actions" -import { updateOpenedModalSettingsId } from "@root/uiTools/actions" -import { updateRootContentId } from "@root/quizes/actions" -import { useCurrentQuiz } from "@root/quizes/hooks" -import { useQuestionsStore } from "@root/questions/store" +import { + deleteQuestion, + clearRuleForAll, + updateQuestion, +} from "@root/questions/actions"; +import { updateOpenedModalSettingsId } from "@root/uiTools/actions"; +import { updateRootContentId } from "@root/quizes/actions"; +import { useCurrentQuiz } from "@root/quizes/hooks"; +import { useQuestionsStore } from "@root/questions/store"; import { enqueueSnackbar } from "notistack"; import { useUiTools } from "@root/uiTools/store"; interface Props { - setOpenedModalQuestions: (open: boolean) => void; - modalQuestionTargetContentId: string; + setOpenedModalQuestions: (open: boolean) => void; + modalQuestionTargetContentId: string; } -export const FirstNodeField = ({ setOpenedModalQuestions, modalQuestionTargetContentId }: Props) => { - const quiz = useCurrentQuiz(); - - - useLayoutEffect(() => { - updateOpenedModalSettingsId() - console.log("first render firstComponent") - updateRootContentId(quiz.id, "") - clearRuleForAll() - }, []) - - - const { questions } = useQuestionsStore() - const { dragQuestionContentId } = useUiTools() - const Container = useRef(null); - - const modalOpen = () => setOpenedModalQuestions(true) - - const newRootNode = () => { - if (quiz) { - if (dragQuestionContentId) { - updateRootContentId(quiz?.id, dragQuestionContentId) - updateQuestion(dragQuestionContentId, (question) => question.content.rule.parentId = "root") - //если были результаты - удалить - questions.forEach((q) => { - if (q.type === 'result') deleteQuestion(q.id) - }) - - } - } else { - enqueueSnackbar("Нет информации о взятом опроснике") - } +export const FirstNodeField = ({ + setOpenedModalQuestions, + modalQuestionTargetContentId, +}: Props) => { + const quiz = useCurrentQuiz(); + useLayoutEffect(() => { + updateOpenedModalSettingsId(); + console.log("first render firstComponent"); + if (quiz) { + updateRootContentId(quiz.id, ""); } + clearRuleForAll(); + }, []); - useEffect(() => { - Container.current?.addEventListener("mouseup", newRootNode) - Container.current?.addEventListener("click", modalOpen) - return () => { - Container.current?.removeEventListener("mouseup", newRootNode) - Container.current?.removeEventListener("click", modalOpen) - } - }, [dragQuestionContentId]) + const { questions } = useQuestionsStore(); + const { dragQuestionContentId } = useUiTools(); + const Container = useRef(null); - useEffect(() => { - if (quiz) { + const modalOpen = () => setOpenedModalQuestions(true); - if (modalQuestionTargetContentId) { - updateRootContentId(quiz?.id, modalQuestionTargetContentId) - updateQuestion(modalQuestionTargetContentId, (question) => question.content.rule.parentId = "root") - //если были результаты - удалить - questions.forEach((q) => { - if (q.type === 'result') deleteQuestion(q.id) - }) - } - } else { - enqueueSnackbar("Нет информации о взятом опроснике") - } + const newRootNode = () => { + if (quiz && dragQuestionContentId) { + updateRootContentId(quiz?.id, dragQuestionContentId); + updateQuestion( + dragQuestionContentId, + (question) => (question.content.rule.parentId = "root") + ); + //если были результаты - удалить + questions.forEach((q) => { + if (q.type === "result") deleteQuestion(q.id); + }); + } else { + enqueueSnackbar("Нет информации о взятом опроснике"); + } + }; - }, [modalQuestionTargetContentId]) + useEffect(() => { + Container.current?.addEventListener("mouseup", newRootNode); + Container.current?.addEventListener("click", modalOpen); + return () => { + Container.current?.removeEventListener("mouseup", newRootNode); + Container.current?.removeEventListener("click", modalOpen); + }; + }, [dragQuestionContentId]); + useEffect(() => { + if (quiz && modalQuestionTargetContentId) { + updateRootContentId(quiz?.id, modalQuestionTargetContentId); + updateQuestion( + modalQuestionTargetContentId, + (question) => (question.content.rule.parentId = "root") + ); + //если были результаты - удалить + questions.forEach((q) => { + if (q.type === "result") deleteQuestion(q.id); + }); + } else { + enqueueSnackbar("Нет информации о взятом опроснике"); + } + }, [modalQuestionTargetContentId]); - return ( - - + - - ) -} \ No newline at end of file + return ( + + + + + ); +}; diff --git a/src/pages/Questions/BranchingMap/hooks/usePopper.ts b/src/pages/Questions/BranchingMap/hooks/usePopper.ts new file mode 100644 index 00000000..957beece --- /dev/null +++ b/src/pages/Questions/BranchingMap/hooks/usePopper.ts @@ -0,0 +1,482 @@ +import { updateOpenedModalSettingsId } from "@root/uiTools/actions"; + +import type { MutableRefObject } from "react"; +import type { + PresetLayoutOptions, + LayoutEventObject, + NodeSingular, + NodePositionMap, + NodePositionFunction, + AbstractEventObject, +} from "cytoscape"; + +type usePopperArgs = { + layoutsContainer: MutableRefObject; + plusesContainer: MutableRefObject; + crossesContainer: MutableRefObject; + gearsContainer: MutableRefObject; + setModalQuestionParentContentId: (id: string) => void; + setOpenedModalQuestions: (open: boolean) => void; + setStartCreate: (id: string) => void; + setStartRemove: (id: string) => void; +}; + +type PopperItem = { + id: () => string; +}; + +type Modifier = { + name: string; + options: unknown; +}; + +type PopperConfig = { + popper: { + placement: string; + modifiers?: Modifier[]; + }; + content: (items: PopperItem[]) => void; +}; + +type Popper = { + update: () => Promise; + setOptions: (modifiers: { modifiers?: Modifier[] }) => void; +}; + +type NodeSingularWithPopper = NodeSingular & { + popper: (config: PopperConfig) => Popper; +}; + +export const usePopper = ({ + layoutsContainer, + plusesContainer, + crossesContainer, + gearsContainer, + setModalQuestionParentContentId, + setOpenedModalQuestions, + setStartCreate, + setStartRemove, +}: usePopperArgs) => { + const removeButtons = (id: string) => { + layoutsContainer.current + ?.querySelector(`.popper-layout[data-id='${id}']`) + ?.remove(); + plusesContainer.current + ?.querySelector(`.popper-plus[data-id='${id}']`) + ?.remove(); + crossesContainer.current + ?.querySelector(`.popper-cross[data-id='${id}']`) + ?.remove(); + gearsContainer.current + ?.querySelector(`.popper-gear[data-id='${id}']`) + ?.remove(); + }; + + const initialPopperIcons = ({ cy }: LayoutEventObject) => { + const container = + (document.body.querySelector( + ".__________cytoscape_container" + ) as HTMLDivElement) || null; + + if (!container) { + return; + } + + container.style.overflow = "hidden"; + + if (!plusesContainer.current) { + plusesContainer.current = document.createElement("div"); + plusesContainer.current.setAttribute("id", "popper-pluses"); + container.append(plusesContainer.current); + } + if (!crossesContainer.current) { + crossesContainer.current = document.createElement("div"); + crossesContainer.current.setAttribute("id", "popper-crosses"); + container.append(crossesContainer.current); + } + if (!gearsContainer.current) { + gearsContainer.current = document.createElement("div"); + gearsContainer.current.setAttribute("id", "popper-gears"); + container.append(gearsContainer.current); + } + if (!layoutsContainer.current) { + layoutsContainer.current = document.createElement("div"); + layoutsContainer.current.setAttribute("id", "popper-layouts"); + container.append(layoutsContainer.current); + } + + const ext = cy.extent(); + const nodesInView = cy.nodes().filter((n) => { + const bb = n.boundingBox(); + return ( + bb.x2 > ext.x1 && bb.x1 < ext.x2 && bb.y2 > ext.y1 && bb.y1 < ext.y2 + ); + }); + + nodesInView.toArray()?.forEach((item) => { + const node = item as NodeSingularWithPopper; + + const layoutsPopper = node.popper({ + popper: { + placement: "left", + modifiers: [{ name: "flip", options: { boundary: node } }], + }, + content: ([item]) => { + const itemId = item.id(); + const itemElement = layoutsContainer.current?.querySelector( + `.popper-layout[data-id='${itemId}']` + ); + if (itemElement) { + return itemElement; + } + + const layoutElement = document.createElement("div"); + layoutElement.style.zIndex = "0"; + layoutElement.classList.add("popper-layout"); + layoutElement.setAttribute("data-id", item.id()); + layoutElement.addEventListener("mouseup", () => { + //Узнаём грани, идущие от этой ноды + setModalQuestionParentContentId(item.id()); + setOpenedModalQuestions(true); + }); + layoutsContainer.current?.appendChild(layoutElement); + + return layoutElement; + }, + }); + + const plusesPopper = node.popper({ + popper: { + placement: "right", + modifiers: [{ name: "flip", options: { boundary: node } }], + }, + content: ([item]) => { + const itemId = item.id(); + const itemElement = plusesContainer.current?.querySelector( + `.popper-plus[data-id='${itemId}']` + ); + if (itemElement) { + return itemElement; + } + + const plusElement = document.createElement("div"); + plusElement.classList.add("popper-plus"); + plusElement.setAttribute("data-id", item.id()); + plusElement.style.zIndex = "1"; + plusElement.addEventListener("mouseup", () => { + setStartCreate(node.id()); + }); + + plusesContainer.current?.appendChild(plusElement); + + return plusElement; + }, + }); + + const crossesPopper = node.popper({ + popper: { + placement: "top-end", + modifiers: [{ name: "flip", options: { boundary: node } }], + }, + content: ([item]) => { + const itemId = item.id(); + const itemElement = crossesContainer.current?.querySelector( + `.popper-cross[data-id='${itemId}']` + ); + if (itemElement) { + return itemElement; + } + + const crossElement = document.createElement("div"); + crossElement.classList.add("popper-cross"); + crossElement.setAttribute("data-id", item.id()); + crossElement.style.zIndex = "2"; + crossesContainer.current?.appendChild(crossElement); + crossElement.addEventListener("mouseup", () => { + setStartRemove(node.id()); + }); + + return crossElement; + }, + }); + let gearsPopper: Popper | null = null; + if (node.data().root !== true) { + gearsPopper = node.popper({ + popper: { + placement: "left", + modifiers: [{ name: "flip", options: { boundary: node } }], + }, + content: ([item]) => { + const itemId = item.id(); + + const itemElement = gearsContainer.current?.querySelector( + `.popper-gear[data-id='${itemId}']` + ); + if (itemElement) { + return itemElement; + } + + const gearElement = document.createElement("div"); + gearElement.classList.add("popper-gear"); + gearElement.setAttribute("data-id", item.id()); + gearElement.style.zIndex = "1"; + gearsContainer.current?.appendChild(gearElement); + gearElement.addEventListener("mouseup", (e) => { + console.log("up"); + updateOpenedModalSettingsId(item.id()); + }); + + return gearElement; + }, + }); + } + const update = async () => { + await plusesPopper.update(); + await crossesPopper.update(); + await gearsPopper?.update(); + await layoutsPopper.update(); + }; + + const onZoom = (event: AbstractEventObject) => { + const zoom = event.cy.zoom(); + + //update(); + + crossesPopper.setOptions({ + modifiers: [ + { name: "flip", options: { boundary: node } }, + { name: "offset", options: { offset: [-5 * zoom, -30 * zoom] } }, + ], + }); + + layoutsPopper.setOptions({ + modifiers: [ + { name: "flip", options: { boundary: node } }, + { name: "offset", options: { offset: [0, -130 * zoom] } }, + ], + }); + plusesPopper.setOptions({ + modifiers: [ + { name: "flip", options: { boundary: node } }, + { name: "offset", options: { offset: [0, 0 * zoom] } }, + ], + }); + gearsPopper?.setOptions({ + modifiers: [ + { name: "flip", options: { boundary: node } }, + { name: "offset", options: { offset: [0, 0] } }, + ], + }); + + layoutsContainer.current + ?.querySelectorAll("#popper-layouts > .popper-layout") + .forEach((item) => { + const element = item as HTMLDivElement; + element.style.width = `${130 * zoom}px`; + element.style.height = `${130 * zoom}px`; + }); + + plusesContainer.current + ?.querySelectorAll("#popper-pluses > .popper-plus") + .forEach((item) => { + const element = item as HTMLDivElement; + element.style.width = `${40 * zoom}px`; + element.style.height = `${40 * zoom}px`; + element.style.fontSize = `${40 * zoom}px`; + element.style.borderRadius = `${6 * zoom}px`; + }); + + crossesContainer.current + ?.querySelectorAll("#popper-crosses > .popper-cross") + .forEach((item) => { + const element = item as HTMLDivElement; + element.style.width = `${24 * zoom}px`; + element.style.height = `${24 * zoom}px`; + element.style.fontSize = `${24 * zoom}px`; + element.style.borderRadius = `${6 * zoom}px`; + }); + + gearsContainer?.current + ?.querySelectorAll("#popper-gears > .popper-gear") + .forEach((item) => { + const element = item as HTMLDivElement; + element.style.width = `${60 * zoom}px`; + element.style.height = `${40 * zoom}px`; + }); + }; + + //node?.on("position", update); + let pressed = false; + let hide = false; + cy?.on("mousedown", () => { + pressed = true; + }); + cy?.on("mouseup", () => { + pressed = false; + hide = false; + + const gc = gearsContainer.current; + if (gc) gc.style.display = "block"; + const pc = plusesContainer.current; + const xc = crossesContainer.current; + const lc = layoutsContainer.current; + if (pc) pc.style.display = "block"; + if (xc) xc.style.display = "block"; + if (lc) lc.style.display = "block"; + update(); + }); + + cy?.on("mousemove", () => { + if (pressed && !hide) { + hide = true; + const gc = gearsContainer.current; + if (gc) gc.style.display = "none"; + const pc = plusesContainer.current; + const xc = crossesContainer.current; + const lc = layoutsContainer.current; + if (pc) pc.style.display = "none"; + if (xc) xc.style.display = "none"; + if (lc) lc.style.display = "block"; + } + }); + + cy?.on("zoom render", onZoom); + }); + }; + + const readyLO = (event: LayoutEventObject) => { + if (event.cy.data("firstNode") === "nonroot") { + event.cy.data("firstNode", "root"); + event.cy + .nodes() + .sort((a, b) => (a.data("root") ? 1 : -1)) + .layout(layoutOptions) + .run(); + } else { + event.cy.data("changed", false); + event.cy.removeData("firstNode"); + } + + //удаляем иконки + event.cy.nodes().forEach((ele: any) => { + const data = ele.data(); + data.id && removeButtons(data.id); + }); + + initialPopperIcons(event); + }; + + const layoutOptions: PresetLayoutOptions = { + name: "preset", + + positions: (node) => { + if (!node.cy().data("changed")) { + return node.data("oldPos"); + } + const id = node.id(); + const incomming = node.cy().edges(`[target="${id}"]`); + const layer = 0; + node.removeData("lastChild"); + + if (incomming.length === 0) { + if (node.cy().data("firstNode") === undefined) + node.cy().data("firstNode", "root"); + node.data("root", true); + const children = node.cy().edges(`[source="${id}"]`).targets(); + node.data("layer", layer); + node.data("children", children.length); + const queue = []; + children.forEach((n) => { + queue.push({ task: n, layer: layer + 1 }); + }); + while (queue.length) { + const task = queue.pop(); + task.task.data("layer", task.layer); + task.task.removeData("subtreeWidth"); + const children = e + .cy() + .edges(`[source="${task.task.id()}"]`) + .targets(); + task.task.data("children", children.length); + if (children.length !== 0) { + children.forEach((n) => + queue.push({ task: n, layer: task.layer + 1 }) + ); + } + } + queue.push({ parent: e, children: children }); + while (queue.length) { + const task = queue.pop(); + if (task.children.length === 0) { + task.parent.data("subtreeWidth", task.parent.height() + 50); + continue; + } + const unprocessed = task?.children.filter((e) => { + return node.data("subtreeWidth") === undefined; + }); + if (unprocessed.length !== 0) { + queue.push(task); + unprocessed.forEach((t) => { + queue.push({ + parent: t, + children: t.cy().edges(`[source="${t.id()}"]`).targets(), + }); + }); + continue; + } + + task?.parent.data( + "subtreeWidth", + task.children.reduce((p, n) => p + n.data("subtreeWidth"), 0) + ); + } + + const pos = { x: 0, y: 0 }; + node.data("oldPos", pos); + + queue.push({ task: children, parent: e }); + while (queue.length) { + const task = queue.pop(); + const oldPos = task.parent.data("oldPos"); + let yoffset = oldPos.y - task.parent.data("subtreeWidth") / 2; + task.task.forEach((n) => { + const width = n.data("subtreeWidth"); + + n.data("oldPos", { + x: 250 * n.data("layer"), + y: yoffset + width / 2, + }); + yoffset += width; + queue.push({ + task: n.cy().edges(`[source="${n.id()}"]`).targets(), + parent: n, + }); + }); + } + node.cy().data("changed", false); + return pos; + } else { + const opos = node.data("oldPos"); + if (opos) { + return opos; + } + } + }, // map of (node id) => (position obj); or function(node){ return somPos; } + zoom: undefined, // the zoom level to set (prob want fit = false if set) + pan: 1, // the pan level to set (prob want fit = false if set) + fit: false, // whether to fit to viewport + padding: 30, // padding on fit + animate: false, // whether to transition the node positions + animationDuration: 500, // duration of animation in ms if enabled + animationEasing: undefined, // easing of animation if enabled + animateFilter: function (node, i) { + return false; + }, // a function that determines whether the node should be animated. All nodes animated by default on animate enabled. Non-animated nodes are positioned immediately when the layout starts + ready: readyLO, // callback on layoutready + transform: function (node, position) { + return position; + }, // transform a given node position. Useful for changing flow direction in discrete layouts + }; + + return { layoutOptions }; +}; diff --git a/src/pages/Questions/BranchingMap/hooks/useRemoveNode.ts b/src/pages/Questions/BranchingMap/hooks/useRemoveNode.ts new file mode 100644 index 00000000..05586b95 --- /dev/null +++ b/src/pages/Questions/BranchingMap/hooks/useRemoveNode.ts @@ -0,0 +1,211 @@ +import { + deleteQuestion, + updateQuestion, + getQuestionByContentId, + clearRuleForAll, + createFrontResult, +} from "@root/questions/actions"; +import { useQuestionsStore } from "@root/questions/store"; +import { useCurrentQuiz } from "@root/quizes/hooks"; +import { updateRootContentId } from "@root/quizes/actions"; + +import type { MutableRefObject } from "react"; +import type { + Core, + CollectionReturnValue, + PresetLayoutOptions, +} from "cytoscape"; +import type { + QuestionBranchingRule, + QuestionBranchingRuleMain, +} from "../../../../model/questionTypes/shared"; + +type UseRemoveNodeArgs = { + cyRef: MutableRefObject; + layoutOptions: PresetLayoutOptions; + layoutsContainer: MutableRefObject; + plusesContainer: MutableRefObject; + crossesContainer: MutableRefObject; + gearsContainer: MutableRefObject; +}; + +export const useRemoveNode = ({ + cyRef, + layoutOptions, + layoutsContainer, + plusesContainer, + crossesContainer, + gearsContainer, +}: UseRemoveNodeArgs) => { + const { questions: trashQuestions } = useQuestionsStore(); + const quiz = useCurrentQuiz(); + + const removeButtons = (id: string) => { + layoutsContainer.current + ?.querySelector(`.popper-layout[data-id='${id}']`) + ?.remove(); + plusesContainer.current + ?.querySelector(`.popper-plus[data-id='${id}']`) + ?.remove(); + crossesContainer.current + ?.querySelector(`.popper-cross[data-id='${id}']`) + ?.remove(); + gearsContainer.current + ?.querySelector(`.popper-gear[data-id='${id}']`) + ?.remove(); + }; + + const clearDataAfterRemoveNode = ({ + targetQuestionContentId, + parentQuestionContentId, + }: { + targetQuestionContentId: string; + parentQuestionContentId: string; + }) => { + updateQuestion(targetQuestionContentId, (question) => { + question.content.rule.parentId = ""; + question.content.rule.children = []; + question.content.rule.main = []; + question.content.rule.default = ""; + }); + + //чистим rule родителя + const parentQuestion = getQuestionByContentId(parentQuestionContentId); + + if (!parentQuestion?.type) { + return; + } + + const newChildren = [...parentQuestion.content.rule.children]; + newChildren.splice( + parentQuestion.content.rule.children.indexOf(targetQuestionContentId), + 1 + ); + + const newRule: QuestionBranchingRule = { + children: newChildren, + default: + parentQuestion.content.rule.default === targetQuestionContentId + ? "" + : parentQuestion.content.rule.default, + //удаляем условия перехода от родителя к этому вопросу, + main: parentQuestion.content.rule.main.filter( + (data: QuestionBranchingRuleMain) => + data.next !== targetQuestionContentId + ), + parentId: parentQuestion.content.rule.parentId, + }; + + updateQuestion(parentQuestionContentId, (PQ) => { + PQ.content.rule = newRule; + }); + }; + + const removeNode = (targetNodeContentId: string) => { + const deleteNodes: string[] = []; + const deleteEdges: any = []; + const cy = cyRef?.current; + + const findChildrenToDelete = (node: CollectionReturnValue) => { + //Узнаём грани, идущие от этой ноды + cy?.$('edge[source = "' + node.id() + '"]') + ?.toArray() + .forEach((edge) => { + const edgeData = edge.data(); + + //записываем id грани для дальнейшего удаления + deleteEdges.push(edge); + //ищем ноду на конце грани, записываем её ID для дальнейшего удаления + const targetNode = cy?.$("#" + edgeData.target); + deleteNodes.push(targetNode.data().id); + //вызываем функцию для анализа потомков уже у этой ноды + findChildrenToDelete(targetNode); + }); + }; + + const elementToDelete = cy?.getElementById(targetNodeContentId); + + if (elementToDelete) { + findChildrenToDelete(elementToDelete); + } + + const targetQuestion = getQuestionByContentId(targetNodeContentId); + + if ( + targetQuestion?.type && + targetQuestion.content.rule.parentId === "root" && + quiz + ) { + updateRootContentId(quiz?.id, ""); + updateQuestion(targetNodeContentId, (question) => { + question.content.rule.parentId = ""; + question.content.rule.main = []; + question.content.rule.children = []; + question.content.rule.default = ""; + }); + trashQuestions.forEach((q) => { + if (q.type === "result") { + deleteQuestion(q.id); + } + }); + clearRuleForAll(); + } else { + const parentQuestionContentId = cy + ?.$('edge[target = "' + targetNodeContentId + '"]') + ?.toArray()?.[0] + ?.data()?.source; + if (targetNodeContentId && parentQuestionContentId) { + if ( + quiz && + cy?.edges(`[source="${parentQuestionContentId}"]`).length === 0 + ) { + createFrontResult(quiz.backendId, parentQuestionContentId); + } + clearDataAfterRemoveNode({ + targetQuestionContentId: targetNodeContentId, + parentQuestionContentId, + }); + cy?.remove(cy?.$("#" + targetNodeContentId)) + .layout(layoutOptions) + .run(); + } + } + + //После всех манипуляций удаляем грани и ноды из CS Чистим rule потомков на беке + + deleteNodes.forEach((nodeId) => { + //Ноды + cy?.remove(cy?.$("#" + nodeId)); + removeButtons(nodeId); + updateQuestion(nodeId, (question) => { + question.content.rule.parentId = ""; + question.content.rule.main = []; + question.content.rule.default = ""; + question.content.rule.children = []; + }); + }); + + deleteEdges.forEach((edge: any) => { + //Грани + cy?.remove(edge); + }); + + removeButtons(targetNodeContentId); + cy?.data("changed", true); + cy?.layout(layoutOptions).run(); + + //удаляем result всех потомков + trashQuestions.forEach((qr) => { + if ( + qr.type === "result" && + (deleteNodes.includes(qr.content.rule.parentId || "") || + (targetQuestion?.type && + qr.content.rule.parentId === targetQuestion.content.id)) + ) { + deleteQuestion(qr.id); + } + }); + }; + + return { removeNode }; +}; diff --git a/src/pages/Questions/BranchingMap/index.tsx b/src/pages/Questions/BranchingMap/index.tsx index 6517a19e..39b22789 100644 --- a/src/pages/Questions/BranchingMap/index.tsx +++ b/src/pages/Questions/BranchingMap/index.tsx @@ -14,8 +14,6 @@ export const BranchingMap = () => { const [modalQuestionTargetContentId, setModalQuestionTargetContentId] = useState("") const [openedModalQuestions, setOpenedModalQuestions] = useState(false) - - return ( void; setOpenedModalQuestions: (open: boolean) => void; - setModalQuestionParentContentId: (open: string) => void; } export const BranchingQuestionsModal = ({ openedModalQuestions, setOpenedModalQuestions, setModalQuestionTargetContentId, - setModalQuestionParentContentId, }: Props) => { const trashQuestions = useQuestionsStore().questions; const questions = trashQuestions.filter( diff --git a/src/stores/uiTools/store.ts b/src/stores/uiTools/store.ts index 2a102999..98ce7918 100644 --- a/src/stores/uiTools/store.ts +++ b/src/stores/uiTools/store.ts @@ -8,14 +8,16 @@ export type UiTools = { openBranchingPanel: boolean; desireToOpenABranchingModal: string | null; editSomeQuestion: string | null; + lastDeletionNodeTime: number | null; }; const initialState: UiTools = { - openedModalSettingsId: null as null, + openedModalSettingsId: null, dragQuestionContentId: null, openBranchingPanel: false, - desireToOpenABranchingModal: null as null, - editSomeQuestion: null as null, + desireToOpenABranchingModal: null, + editSomeQuestion: null, + lastDeletionNodeTime: null }; export const useUiTools = create()( From 7e6a5b774e2879a681744b6a83941612fc495c10 Mon Sep 17 00:00:00 2001 From: IlyaDoronin Date: Wed, 20 Dec 2023 13:55:38 +0300 Subject: [PATCH 07/15] fix: rendering error --- .../Questions/BranchingMap/CsComponent.tsx | 38 +++++++++---------- .../Questions/BranchingMap/hooks/usePopper.ts | 10 ++--- 2 files changed, 23 insertions(+), 25 deletions(-) diff --git a/src/pages/Questions/BranchingMap/CsComponent.tsx b/src/pages/Questions/BranchingMap/CsComponent.tsx index a66cbd02..947ca5a8 100644 --- a/src/pages/Questions/BranchingMap/CsComponent.tsx +++ b/src/pages/Questions/BranchingMap/CsComponent.tsx @@ -96,26 +96,24 @@ function CsComponent({ const crossesContainer = useRef(null); const gearsContainer = useRef(null); - // const { layoutOptions } = usePopper({ - // layoutsContainer, - // plusesContainer, - // crossesContainer, - // gearsContainer, - // setModalQuestionParentContentId, - // setOpenedModalQuestions, - // setStartCreate, - // setStartRemove, - // }); - const layoutOptions = {}; - const removeNode = () => {}; - // const { removeNode } = useRemoveNode({ - // cyRef, - // layoutOptions, - // layoutsContainer, - // plusesContainer, - // crossesContainer, - // gearsContainer, - // }); + const { layoutOptions } = usePopper({ + layoutsContainer, + plusesContainer, + crossesContainer, + gearsContainer, + setModalQuestionParentContentId, + setOpenedModalQuestions, + setStartCreate, + setStartRemove, + }); + const { removeNode } = useRemoveNode({ + cyRef, + layoutOptions, + layoutsContainer, + plusesContainer, + crossesContainer, + gearsContainer, + }); useLayoutEffect(() => { const cy = cyRef?.current; diff --git a/src/pages/Questions/BranchingMap/hooks/usePopper.ts b/src/pages/Questions/BranchingMap/hooks/usePopper.ts index 957beece..c420dca7 100644 --- a/src/pages/Questions/BranchingMap/hooks/usePopper.ts +++ b/src/pages/Questions/BranchingMap/hooks/usePopper.ts @@ -221,7 +221,7 @@ export const usePopper = ({ gearElement.setAttribute("data-id", item.id()); gearElement.style.zIndex = "1"; gearsContainer.current?.appendChild(gearElement); - gearElement.addEventListener("mouseup", (e) => { + gearElement.addEventListener("mouseup", () => { console.log("up"); updateOpenedModalSettingsId(item.id()); }); @@ -393,7 +393,7 @@ export const usePopper = ({ const task = queue.pop(); task.task.data("layer", task.layer); task.task.removeData("subtreeWidth"); - const children = e + const children = node .cy() .edges(`[source="${task.task.id()}"]`) .targets(); @@ -404,14 +404,14 @@ export const usePopper = ({ ); } } - queue.push({ parent: e, children: children }); + queue.push({ parent: node, children: children }); while (queue.length) { const task = queue.pop(); if (task.children.length === 0) { task.parent.data("subtreeWidth", task.parent.height() + 50); continue; } - const unprocessed = task?.children.filter((e) => { + const unprocessed = task?.children.filter((node) => { return node.data("subtreeWidth") === undefined; }); if (unprocessed.length !== 0) { @@ -434,7 +434,7 @@ export const usePopper = ({ const pos = { x: 0, y: 0 }; node.data("oldPos", pos); - queue.push({ task: children, parent: e }); + queue.push({ task: children, parent: node }); while (queue.length) { const task = queue.pop(); const oldPos = task.parent.data("oldPos"); From 6dbc2dcd01ead9ff0b91485a809712646a7cdb95 Mon Sep 17 00:00:00 2001 From: nflnkr Date: Wed, 20 Dec 2023 14:26:40 +0300 Subject: [PATCH 08/15] fix auth modal links --- src/pages/Landing/HeaderLanding.tsx | 8 +- src/pages/auth/Signin.tsx | 354 +++++++++++++------------ src/pages/auth/Signup.tsx | 385 ++++++++++++++-------------- src/pages/startPage/Restore.tsx | 334 ++++++++++++------------ 4 files changed, 554 insertions(+), 527 deletions(-) diff --git a/src/pages/Landing/HeaderLanding.tsx b/src/pages/Landing/HeaderLanding.tsx index 6eb1381c..95105a06 100644 --- a/src/pages/Landing/HeaderLanding.tsx +++ b/src/pages/Landing/HeaderLanding.tsx @@ -15,13 +15,9 @@ export default function Component() { const theme = useTheme(); const isMobile = useMediaQuery(theme.breakpoints.down(600)); const isTablet = useMediaQuery(theme.breakpoints.down(1000)); - const [select, setSelect] = React.useState(0); const userId = useUserStore((state) => state.userId); - const navigate = useNavigate(); const location = useLocation(); - const onClick = () => (userId ? navigate("/list") : navigate("/signin")); - return ( */} - {/* + + + + + + + + Вход в личный кабинет + + + + + {/* Забыли пароль? */} - - Вы еще не присоединились? - - - Регистрация - - - Забыли пароль - - - - - - ); + + Вы еще не присоединились? + + + Регистрация + + + Забыли пароль + + + + + + ); } diff --git a/src/pages/auth/Signup.tsx b/src/pages/auth/Signup.tsx index c0e1f93b..3f1a0262 100644 --- a/src/pages/auth/Signup.tsx +++ b/src/pages/auth/Signup.tsx @@ -12,210 +12,215 @@ import { Link as RouterLink, useLocation, useNavigate } from "react-router-dom"; import { object, ref, string } from "yup"; interface Values { - email: string; - password: string; - repeatPassword: string; + email: string; + password: string; + repeatPassword: string; } const initialValues: Values = { - email: "", - password: "", - repeatPassword: "", + email: "", + password: "", + repeatPassword: "", }; const validationSchema = object({ - email: string().required("Поле обязательно").email("Введите корректный email"), - password: string() - .min(8, "Минимум 8 символов") - .matches(/^[.,:;-_+\d\w]+$/, "Некорректные символы") - .required("Поле обязательно"), - repeatPassword: string() - .oneOf([ref("password"), undefined], "Пароли не совпадают") - .required("Повторите пароль"), + email: string().required("Поле обязательно").email("Введите корректный email"), + password: string() + .min(8, "Минимум 8 символов") + .matches(/^[.,:;-_+\d\w]+$/, "Некорректные символы") + .required("Поле обязательно"), + repeatPassword: string() + .oneOf([ref("password"), undefined], "Пароли не совпадают") + .required("Повторите пароль"), }); export default function SignupDialog() { - const [isDialogOpen, setIsDialogOpen] = useState(true); - const user = useUserStore((state) => state.user); - const theme = useTheme(); - const upMd = useMediaQuery(theme.breakpoints.up("md")); - const location = useLocation(); + const [isDialogOpen, setIsDialogOpen] = useState(true); + const user = useUserStore((state) => state.user); + const theme = useTheme(); + const upMd = useMediaQuery(theme.breakpoints.up("md")); + const location = useLocation(); - const navigate = useNavigate(); - const formik = useFormik({ - initialValues, - validationSchema, - onSubmit: async (values, formikHelpers) => { - const [registerResponse, registerError] = await register(values.email.trim(), values.password.trim(), "+7"); + const navigate = useNavigate(); + const formik = useFormik({ + initialValues, + validationSchema, + onSubmit: async (values, formikHelpers) => { + const [registerResponse, registerError] = await register(values.email.trim(), values.password.trim(), "+7"); - formikHelpers.setSubmitting(false); + formikHelpers.setSubmitting(false); - if (registerError) { - return enqueueSnackbar(registerError); - } + if (registerError) { + return enqueueSnackbar(registerError); + } - if (registerResponse) { - setUserId(registerResponse._id); - } - }, - }); - - useEffect( - function redirectIfSignedIn() { - if (user) navigate("/list", { replace: true }); - }, - [navigate, user] - ); - - function handleClose() { - setIsDialogOpen(false); - setTimeout(() => navigate("/"), theme.transitions.duration.leavingScreen); - } - - return ( - - - - - - - - - - Регистрация - - - - - + }); - navigate("/"), theme.transitions.duration.leavingScreen); + } + + return ( + - Вход в личный кабинет - - - Забыли пароль - - - - ); + + + + + + + + + Регистрация + + + + + + + + Вход в личный кабинет + + + Забыли пароль + + + + ); } diff --git a/src/pages/startPage/Restore.tsx b/src/pages/startPage/Restore.tsx index 31cc8ad3..2769338a 100644 --- a/src/pages/startPage/Restore.tsx +++ b/src/pages/startPage/Restore.tsx @@ -1,183 +1,195 @@ import { FC, useState } from "react"; import CloseIcon from "@mui/icons-material/Close"; -import { Box, Button, Dialog, IconButton, Typography, useMediaQuery, useTheme } from "@mui/material"; +import { Box, Button, Dialog, IconButton, Link, Typography, useMediaQuery, useTheme } from "@mui/material"; import InputTextfield from "@ui_kit/InputTextfield"; import PasswordInput from "@ui_kit/passwordInput"; import { useFormik } from "formik"; import { object, ref, string } from "yup"; import Logotip from "../Landing/images/icons/QuizLogo"; -import { useNavigate } from "react-router-dom"; +import { useNavigate, Link as RouterLink, useLocation } from "react-router-dom"; interface Values { - email: string; - password: string; - repeatPassword: string; + email: string; + password: string; + repeatPassword: string; } const initialValues: Values = { - email: "", - password: "", - repeatPassword: "", + email: "", + password: "", + repeatPassword: "", }; const validationSchema = object({ - email: string().required("Поле обязательно").email("Введите корректный email"), - password: string() - .min(8, "Минимум 8 символов") - .matches(/^[.,:;-_+\d\w]+$/, "Некорректные символы") - .required("Поле обязательно"), - repeatPassword: string() - .oneOf([ref("password"), undefined], "Пароли не совпадают") - .required("Повторите пароль"), + email: string().required("Поле обязательно").email("Введите корректный email"), + password: string() + .min(8, "Минимум 8 символов") + .matches(/^[.,:;-_+\d\w]+$/, "Некорректные символы") + .required("Поле обязательно"), + repeatPassword: string() + .oneOf([ref("password"), undefined], "Пароли не совпадают") + .required("Повторите пароль"), }); export const Restore: FC = () => { - const [isDialogOpen, setIsDialogOpen] = useState(true); - const navigate = useNavigate(); - const theme = useTheme(); - const upMd = useMediaQuery(theme.breakpoints.up("md")); + const [isDialogOpen, setIsDialogOpen] = useState(true); + const navigate = useNavigate(); + const theme = useTheme(); + const upMd = useMediaQuery(theme.breakpoints.up("md")); + const location = useLocation(); - const formik = useFormik({ - initialValues, - validationSchema, - onSubmit: (values) => { - }, - }); - - function handleClose() { - setIsDialogOpen(false); - setTimeout(() => navigate("/"), theme.transitions.duration.leavingScreen); - } - - return ( - ({ + initialValues, + validationSchema, + onSubmit: (values) => { }, - }} - slotProps={{ - backdrop: { - style: { - backgroundColor: "rgb(0 0 0 / 0.7)", - }, - }, - }} - > - - navigate("/"), theme.transitions.duration.leavingScreen); + } + + return ( + - - - - - - - Восстановление пароля - - - - - - - - ); + + + + + + + + + Восстановление пароля + + + + + + + У меня уже есть аккаунт + + + + ); }; From ddc73f47a258c76fe282c1c9db3bf6e1e38aa74a Mon Sep 17 00:00:00 2001 From: IlyaDoronin Date: Wed, 20 Dec 2023 15:34:07 +0300 Subject: [PATCH 09/15] refactor: comments and imports --- .../Questions/BranchingMap/CsComponent.tsx | 551 +----------------- .../Questions/BranchingMap/hooks/usePopper.ts | 2 - 2 files changed, 6 insertions(+), 547 deletions(-) diff --git a/src/pages/Questions/BranchingMap/CsComponent.tsx b/src/pages/Questions/BranchingMap/CsComponent.tsx index 947ca5a8..ae97e350 100644 --- a/src/pages/Questions/BranchingMap/CsComponent.tsx +++ b/src/pages/Questions/BranchingMap/CsComponent.tsx @@ -1,12 +1,16 @@ import { useEffect, useLayoutEffect, useRef, useState } from "react"; import Cytoscape from "cytoscape"; -import { Button } from "@mui/material"; import CytoscapeComponent from "react-cytoscapejs"; import popper from "cytoscape-popper"; +import { Button } from "@mui/material"; +import { withErrorBoundary } from "react-error-boundary"; +import { enqueueSnackbar } from "notistack"; + import { useCurrentQuiz } from "@root/quizes/hooks"; import { updateRootContentId } from "@root/quizes/actions"; import { AnyTypedQuizQuestion } from "@model/questionTypes/shared"; import { useQuestionsStore } from "@root/questions/store"; +import { useUiTools } from "@root/uiTools/store"; import { deleteQuestion, updateQuestion, @@ -16,7 +20,6 @@ import { } from "@root/questions/actions"; import { updateOpenedModalSettingsId } from "@root/uiTools/actions"; import { cleardragQuestionContentId } from "@root/uiTools/actions"; -import { withErrorBoundary } from "react-error-boundary"; import { useRemoveNode } from "./hooks/useRemoveNode"; import { usePopper } from "./hooks/usePopper"; @@ -26,42 +29,7 @@ import { stylesheet } from "./stylesheet"; import "./styles.css"; -import type { - Stylesheet, - Core, - NodeSingular, - AbstractEventObject, - ElementDefinition, - LayoutEventObject, -} from "cytoscape"; -import { enqueueSnackbar } from "notistack"; -import { useUiTools } from "@root/uiTools/store"; - -// type PopperItem = { -// id: () => string; -// }; - -// type Modifier = { -// name: string; -// options: unknown; -// }; - -// type PopperConfig = { -// popper: { -// placement: string; -// modifiers?: Modifier[]; -// }; -// content: (items: PopperItem[]) => void; -// }; - -// type Popper = { -// update: () => Promise; -// setOptions: (modifiers: { modifiers?: Modifier[] }) => void; -// }; - -// type NodeSingularWithPopper = NodeSingular & { -// popper: (config: PopperConfig) => Popper; -// }; +import type { Core } from "cytoscape"; Cytoscape.use(popper); @@ -251,115 +219,6 @@ function CsComponent({ } }; - // const removeNode = ({ targetNodeContentId }: { targetNodeContentId: string }) => { - // console.log("старт удаление") - // const deleteNodes: string[] = [] - // const deleteEdges: any = [] - // const cy = cyRef?.current - - // const findChildrenToDelete = (node) => { - - // //Узнаём грани, идущие от этой ноды - // cy?.$('edge[source = "' + node.id() + '"]')?.toArray().forEach((edge) => { - // const edgeData = edge.data() - - // //записываем id грани для дальнейшего удаления - // deleteEdges.push(edge) - // //ищем ноду на конце грани, записываем её ID для дальнейшего удаления - // const targetNode = cy?.$("#" + edgeData.target) - // deleteNodes.push(targetNode.data().id) - // //вызываем функцию для анализа потомков уже у этой ноды - // findChildrenToDelete(targetNode) - // }) - - // } - // findChildrenToDelete(cy?.getElementById(targetNodeContentId)) - - // const targetQuestion = getQuestionByContentId(targetNodeContentId) - - // if (targetQuestion.content.rule.parentId === "root" && quiz) { - // updateRootContentId(quiz?.id, "") - // updateQuestion(targetNodeContentId, question => { - // question.content.rule.parentId = "" - // question.content.rule.main = [] - // question.content.rule.children = [] - // question.content.rule.default = "" - // }) - // trashQuestions.forEach(q => { - // if (q.type === "result") { - // deleteQuestion(q.id); - // } - // }); - // clearRuleForAll() - - // } else { - - // const parentQuestionContentId = cy?.$('edge[target = "' + targetNodeContentId + '"]')?.toArray()?.[0]?.data()?.source - // if (targetNodeContentId && parentQuestionContentId) { - // if (cy?.edges(`[source="${parentQuestionContentId}"]`).length === 0) - // createFrontResult(quiz.backendId, parentQuestionContentId) - // clearDataAfterRemoveNode({ targetQuestionContentId: targetNodeContentId, parentQuestionContentId }) - // cy?.remove(cy?.$('#' + targetNodeContentId)).layout(lyopts).run() - // } - - // } - - // //После всех манипуляций удаляем грани и ноды из CS Чистим rule потомков на беке - - // deleteNodes.forEach((nodeId) => {//Ноды - // cy?.remove(cy?.$("#" + nodeId)) - // removeButtons(nodeId) - // updateQuestion(nodeId, question => { - // question.content.rule.parentId = "" - // question.content.rule.main = [] - // question.content.rule.default = "" - // question.content.rule.children = [] - // }) - - // }) - - // deleteEdges.forEach((edge: any) => {//Грани - // cy?.remove(edge) - // }) - - // removeButtons(targetNodeContentId) - // cy?.data('changed', true) - // cy?.layout(lyopts).run() - - // //удаляем result всех потомков - // trashQuestions.forEach((qr) => { - // if (qr.type === "result") { - // if (deleteNodes.includes(qr.content.rule.parentId) || qr.content.rule.parentId === targetQuestion.content.id) { - // deleteQuestion(qr.id); - // } - // } - // }) - // } - - // const clearDataAfterRemoveNode = ({ targetQuestionContentId, parentQuestionContentId }: { targetQuestionContentId: string, parentQuestionContentId: string }) => { - - // updateQuestion(targetQuestionContentId, question => { - // question.content.rule.parentId = "" - // question.content.rule.children = [] - // question.content.rule.main = [] - // question.content.rule.default = "" - // }) - - // //чистим rule родителя - // const parentQuestion = getQuestionByContentId(parentQuestionContentId) - // const newRule = {} - // const newChildren = [...parentQuestion.content.rule.children] - // newChildren.splice(parentQuestion.content.rule.children.indexOf(targetQuestionContentId), 1); - // newRule.main = parentQuestion.content.rule.main.filter((data) => data.next !== targetQuestionContentId) //удаляем условия перехода от родителя к этому вопросу - // newRule.parentId = parentQuestion.content.rule.parentId - // newRule.default = parentQuestion.content.rule.default === targetQuestionContentId ? "" : parentQuestion.content.rule.default - // newRule.children = newChildren - - // updateQuestion(parentQuestionContentId, (PQ) => { - // PQ.content.rule = newRule - // }) - // } - useEffect(() => { if (startCreate) { addNode({ parentNodeContentId: startCreate }); @@ -375,117 +234,6 @@ function CsComponent({ } }, [startRemove]); - // const readyLO = (e) => { - // if (e.cy.data('firstNode') === 'nonroot') { - // e.cy.data('firstNode', 'root') - // e.cy.nodes().sort((a, b) => (a.data('root') ? 1 : -1)).layout(lyopts).run() - - // } else { - - // e.cy.data('changed', false) - // e.cy.removeData('firstNode') - // } - - // //удаляем иконки - // e.cy.nodes().forEach((ele: any) => { - // const data = ele.data() - // data.id && removeButtons(data.id); - // }) - // initialPopperIcons(e) - // } - - // const lyopts = { - // name: 'preset', - - // positions: (e) => { - // if (!e.cy().data('changed')) { - // return e.data('oldPos') - // } - // const id = e.id() - // const incomming = e.cy().edges(`[target="${id}"]`) - // const layer = 0 - // e.removeData('lastChild') - - // if (incomming.length === 0) { - // if (e.cy().data('firstNode') === undefined) - // e.cy().data('firstNode', 'root') - // e.data('root', true) - // const children = e.cy().edges(`[source="${id}"]`).targets() - // e.data('layer', layer) - // e.data('children', children.length) - // const queue = [] - // children.forEach(n => { - // queue.push({ task: n, layer: layer + 1 }) - // }) - // while (queue.length) { - // const task = queue.pop() - // task.task.data('layer', task.layer) - // task.task.removeData('subtreeWidth') - // const children = e.cy().edges(`[source="${task.task.id()}"]`).targets() - // task.task.data('children', children.length) - // if (children.length !== 0) { - // children.forEach(n => queue.push({ task: n, layer: task.layer + 1 })) - // } - // } - // queue.push({ parent: e, children: children }) - // while (queue.length) { - // const task = queue.pop() - // if (task.children.length === 0) { - // task.parent.data('subtreeWidth', task.parent.height() + 50) - // continue - // } - // const unprocessed = task?.children.filter(e => { - // return (e.data('subtreeWidth') === undefined) - // }) - // if (unprocessed.length !== 0) { - // queue.push(task) - // unprocessed.forEach(t => { - // queue.push({ parent: t, children: t.cy().edges(`[source="${t.id()}"]`).targets() }) - // }) - // continue - // } - - // task?.parent.data('subtreeWidth', task.children.reduce((p, n) => p + n.data('subtreeWidth'), 0)) - // } - - // const pos = { x: 0, y: 0 } - // e.data('oldPos', pos) - - // queue.push({ task: children, parent: e }) - // while (queue.length) { - // const task = queue.pop() - // const oldPos = task.parent.data('oldPos') - // let yoffset = oldPos.y - task.parent.data('subtreeWidth') / 2 - // task.task.forEach(n => { - // const width = n.data('subtreeWidth') - - // n.data('oldPos', { x: 250 * n.data('layer'), y: yoffset + width / 2 }) - // yoffset += width - // queue.push({ task: n.cy().edges(`[source="${n.id()}"]`).targets(), parent: n }) - // }) - // } - // e.cy().data('changed', false) - // return pos - // } else { - - // const opos = e.data('oldPos') - // if (opos) { - // return opos - // } - // } - // }, // map of (node id) => (position obj); or function(node){ return somPos; } - // zoom: undefined, // the zoom level to set (prob want fit = false if set) - // pan: true, // the pan level to set (prob want fit = false if set) - // fit: false, // whether to fit to viewport - // padding: 30, // padding on fit - // animate: false, // whether to transition the node positions - // animationDuration: 500, // duration of animation in ms if enabled - // animationEasing: undefined, // easing of animation if enabled - // animateFilter: function (node, i) { return false; }, // a function that determines whether the node should be animated. All nodes animated by default on animate enabled. Non-animated nodes are positioned immediately when the layout starts - // ready: readyLO, // callback on layoutready - // transform: function (node, position) { return position; } // transform a given node position. Useful for changing flow direction in discrete layouts - // } - useEffect(() => { document .querySelector("#root") @@ -516,293 +264,6 @@ function CsComponent({ }; }, []); - // const removeButtons = (id: string) => { - // layoutsContainer.current - // ?.querySelector(`.popper-layout[data-id='${id}']`) - // ?.remove(); - // plusesContainer.current - // ?.querySelector(`.popper-plus[data-id='${id}']`) - // ?.remove(); - // crossesContainer.current - // ?.querySelector(`.popper-cross[data-id='${id}']`) - // ?.remove(); - // gearsContainer.current - // ?.querySelector(`.popper-gear[data-id='${id}']`) - // ?.remove(); - // }; - - // const initialPopperIcons = ({ cy }: LayoutEventObject) => { - // const container = - // (document.body.querySelector( - // ".__________cytoscape_container" - // ) as HTMLDivElement) || null; - - // if (!container) { - // return; - // } - - // container.style.overflow = "hidden"; - - // if (!plusesContainer.current) { - // plusesContainer.current = document.createElement("div"); - // plusesContainer.current.setAttribute("id", "popper-pluses"); - // container.append(plusesContainer.current); - // } - // if (!crossesContainer.current) { - // crossesContainer.current = document.createElement("div"); - // crossesContainer.current.setAttribute("id", "popper-crosses"); - // container.append(crossesContainer.current); - // } - // if (!gearsContainer.current) { - // gearsContainer.current = document.createElement("div"); - // gearsContainer.current.setAttribute("id", "popper-gears"); - // container.append(gearsContainer.current); - // } - // if (!layoutsContainer.current) { - // layoutsContainer.current = document.createElement("div"); - // layoutsContainer.current.setAttribute("id", "popper-layouts"); - // container.append(layoutsContainer.current); - // } - - // const ext = cy.extent(); - // const nodesInView = cy.nodes().filter((n) => { - // const bb = n.boundingBox(); - // return ( - // bb.x2 > ext.x1 && bb.x1 < ext.x2 && bb.y2 > ext.y1 && bb.y1 < ext.y2 - // ); - // }); - - // nodesInView.toArray()?.forEach((item) => { - // const node = item as NodeSingularWithPopper; - - // const layoutsPopper = node.popper({ - // popper: { - // placement: "left", - // modifiers: [{ name: "flip", options: { boundary: node } }], - // }, - // content: ([item]) => { - // const itemId = item.id(); - // const itemElement = layoutsContainer.current?.querySelector( - // `.popper-layout[data-id='${itemId}']` - // ); - // if (itemElement) { - // return itemElement; - // } - - // const layoutElement = document.createElement("div"); - // layoutElement.style.zIndex = "0"; - // layoutElement.classList.add("popper-layout"); - // layoutElement.setAttribute("data-id", item.id()); - // layoutElement.addEventListener("mouseup", () => { - // //Узнаём грани, идущие от этой ноды - // setModalQuestionParentContentId(item.id()); - // setOpenedModalQuestions(true); - // }); - // layoutsContainer.current?.appendChild(layoutElement); - - // return layoutElement; - // }, - // }); - - // const plusesPopper = node.popper({ - // popper: { - // placement: "right", - // modifiers: [{ name: "flip", options: { boundary: node } }], - // }, - // content: ([item]) => { - // const itemId = item.id(); - // const itemElement = plusesContainer.current?.querySelector( - // `.popper-plus[data-id='${itemId}']` - // ); - // if (itemElement) { - // return itemElement; - // } - - // const plusElement = document.createElement("div"); - // plusElement.classList.add("popper-plus"); - // plusElement.setAttribute("data-id", item.id()); - // plusElement.style.zIndex = "1"; - // plusElement.addEventListener("mouseup", () => { - // setStartCreate(node.id()); - // }); - - // plusesContainer.current?.appendChild(plusElement); - - // return plusElement; - // }, - // }); - - // const crossesPopper = node.popper({ - // popper: { - // placement: "top-end", - // modifiers: [{ name: "flip", options: { boundary: node } }], - // }, - // content: ([item]) => { - // const itemId = item.id(); - // const itemElement = crossesContainer.current?.querySelector( - // `.popper-cross[data-id='${itemId}']` - // ); - // if (itemElement) { - // return itemElement; - // } - - // const crossElement = document.createElement("div"); - // crossElement.classList.add("popper-cross"); - // crossElement.setAttribute("data-id", item.id()); - // crossElement.style.zIndex = "2"; - // crossesContainer.current?.appendChild(crossElement); - // crossElement.addEventListener("mouseup", () => { - // setStartRemove(node.id()); - // }); - - // return crossElement; - // }, - // }); - // let gearsPopper: Popper | null = null; - // if (node.data().root !== true) { - // gearsPopper = node.popper({ - // popper: { - // placement: "left", - // modifiers: [{ name: "flip", options: { boundary: node } }], - // }, - // content: ([item]) => { - // const itemId = item.id(); - - // const itemElement = gearsContainer.current?.querySelector( - // `.popper-gear[data-id='${itemId}']` - // ); - // if (itemElement) { - // return itemElement; - // } - - // const gearElement = document.createElement("div"); - // gearElement.classList.add("popper-gear"); - // gearElement.setAttribute("data-id", item.id()); - // gearElement.style.zIndex = "1"; - // gearsContainer.current?.appendChild(gearElement); - // gearElement.addEventListener("mouseup", (e) => { - // console.log("up"); - // updateOpenedModalSettingsId(item.id()); - // }); - - // return gearElement; - // }, - // }); - // } - // const update = async () => { - // await plusesPopper.update(); - // await crossesPopper.update(); - // await gearsPopper?.update(); - // await layoutsPopper.update(); - // }; - - // const onZoom = (event: AbstractEventObject) => { - // const zoom = event.cy.zoom(); - - // //update(); - - // crossesPopper.setOptions({ - // modifiers: [ - // { name: "flip", options: { boundary: node } }, - // { name: "offset", options: { offset: [-5 * zoom, -30 * zoom] } }, - // ], - // }); - - // layoutsPopper.setOptions({ - // modifiers: [ - // { name: "flip", options: { boundary: node } }, - // { name: "offset", options: { offset: [0, -130 * zoom] } }, - // ], - // }); - // plusesPopper.setOptions({ - // modifiers: [ - // { name: "flip", options: { boundary: node } }, - // { name: "offset", options: { offset: [0, 0 * zoom] } }, - // ], - // }); - // gearsPopper?.setOptions({ - // modifiers: [ - // { name: "flip", options: { boundary: node } }, - // { name: "offset", options: { offset: [0, 0] } }, - // ], - // }); - - // layoutsContainer.current - // ?.querySelectorAll("#popper-layouts > .popper-layout") - // .forEach((item) => { - // const element = item as HTMLDivElement; - // element.style.width = `${130 * zoom}px`; - // element.style.height = `${130 * zoom}px`; - // }); - - // plusesContainer.current - // ?.querySelectorAll("#popper-pluses > .popper-plus") - // .forEach((item) => { - // const element = item as HTMLDivElement; - // element.style.width = `${40 * zoom}px`; - // element.style.height = `${40 * zoom}px`; - // element.style.fontSize = `${40 * zoom}px`; - // element.style.borderRadius = `${6 * zoom}px`; - // }); - - // crossesContainer.current - // ?.querySelectorAll("#popper-crosses > .popper-cross") - // .forEach((item) => { - // const element = item as HTMLDivElement; - // element.style.width = `${24 * zoom}px`; - // element.style.height = `${24 * zoom}px`; - // element.style.fontSize = `${24 * zoom}px`; - // element.style.borderRadius = `${6 * zoom}px`; - // }); - - // gearsContainer?.current - // ?.querySelectorAll("#popper-gears > .popper-gear") - // .forEach((item) => { - // const element = item as HTMLDivElement; - // element.style.width = `${60 * zoom}px`; - // element.style.height = `${40 * zoom}px`; - // }); - // }; - - // //node?.on("position", update); - // let pressed = false; - // let hide = false; - // cy?.on("mousedown", () => { - // pressed = true; - // }); - // cy?.on("mouseup", () => { - // pressed = false; - // hide = false; - - // const gc = gearsContainer.current; - // if (gc) gc.style.display = "block"; - // const pc = plusesContainer.current; - // const xc = crossesContainer.current; - // const lc = layoutsContainer.current; - // if (pc) pc.style.display = "block"; - // if (xc) xc.style.display = "block"; - // if (lc) lc.style.display = "block"; - // update(); - // }); - - // cy?.on("mousemove", () => { - // if (pressed && !hide) { - // hide = true; - // const gc = gearsContainer.current; - // if (gc) gc.style.display = "none"; - // const pc = plusesContainer.current; - // const xc = crossesContainer.current; - // const lc = layoutsContainer.current; - // if (pc) pc.style.display = "none"; - // if (xc) xc.style.display = "none"; - // if (lc) lc.style.display = "block"; - // } - // }); - - // cy?.on("zoom render", onZoom); - // }); - // }; - return ( <> + + + + + + ); +}; diff --git a/src/stores/uiTools/actions.ts b/src/stores/uiTools/actions.ts index 03c2a534..c88c487f 100644 --- a/src/stores/uiTools/actions.ts +++ b/src/stores/uiTools/actions.ts @@ -31,4 +31,5 @@ export const updateEditSomeQuestion = (contentId?: string) => { }; -export const updateOpenedModalSettingsId = (id?: string) => useUiTools.setState({ openedModalSettingsId: id ? id : null }); \ No newline at end of file +export const updateOpenedModalSettingsId = (id?: string) => useUiTools.setState({ openedModalSettingsId: id ? id : null }); +export const updateDeleteId = (deleteNodeId: string | null = null) => useUiTools.setState({ deleteNodeId }); diff --git a/src/stores/uiTools/store.ts b/src/stores/uiTools/store.ts index 98ce7918..800fe74b 100644 --- a/src/stores/uiTools/store.ts +++ b/src/stores/uiTools/store.ts @@ -1,32 +1,30 @@ import { create } from "zustand"; import { devtools } from "zustand/middleware"; - export type UiTools = { - openedModalSettingsId: string | null; - dragQuestionContentId: string | null; - openBranchingPanel: boolean; - desireToOpenABranchingModal: string | null; - editSomeQuestion: string | null; - lastDeletionNodeTime: number | null; + openedModalSettingsId: string | null; + dragQuestionContentId: string | null; + openBranchingPanel: boolean; + desireToOpenABranchingModal: string | null; + editSomeQuestion: string | null; + lastDeletionNodeTime: number | null; + deleteNodeId: string | null; }; const initialState: UiTools = { - openedModalSettingsId: null, - dragQuestionContentId: null, - openBranchingPanel: false, - desireToOpenABranchingModal: null, - editSomeQuestion: null, - lastDeletionNodeTime: null + openedModalSettingsId: null, + dragQuestionContentId: null, + openBranchingPanel: false, + desireToOpenABranchingModal: null, + editSomeQuestion: null, + lastDeletionNodeTime: null, + deleteNodeId: null, }; export const useUiTools = create()( - devtools( - () => initialState, - { - name: "UiTools", - enabled: process.env.NODE_ENV === "development", - trace: process.env.NODE_ENV === "development", - } - ) + devtools(() => initialState, { + name: "UiTools", + enabled: process.env.NODE_ENV === "development", + trace: process.env.NODE_ENV === "development", + }) ); From 1d90754f1e401fda043cef0bd62045176d703c9f Mon Sep 17 00:00:00 2001 From: Nastya Date: Wed, 20 Dec 2023 20:31:51 +0300 Subject: [PATCH 12/15] =?UTF-8?q?merge=20=D1=86=D0=B8=D1=82=D0=BE=D1=81?= =?UTF-8?q?=D0=BA=D0=B5=D0=B9=D0=BF=20=D0=BA=D0=BE=D0=BC=D0=BF=D0=BE=D0=BD?= =?UTF-8?q?=D0=B5=D0=BD=D1=82=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Questions/BranchingMap/CsComponent.tsx | 147 ++++-------------- 1 file changed, 28 insertions(+), 119 deletions(-) diff --git a/src/pages/Questions/BranchingMap/CsComponent.tsx b/src/pages/Questions/BranchingMap/CsComponent.tsx index 819b9bac..203fcd58 100644 --- a/src/pages/Questions/BranchingMap/CsComponent.tsx +++ b/src/pages/Questions/BranchingMap/CsComponent.tsx @@ -13,6 +13,10 @@ import { cleardragQuestionContentId } from "@root/uiTools/actions"; import { withErrorBoundary } from "react-error-boundary"; import { ProblemIcon } from "@ui_kit/ProblemIcon"; +import { useRemoveNode } from "./hooks/useRemoveNode"; +import { usePopper } from "./hooks/usePopper"; +import { updateDeleteId } from "@root/uiTools/actions"; + import { storeToNodes } from "./helper"; import "./styles.css"; @@ -107,7 +111,7 @@ const stylesheet: Stylesheet[] = [ Cytoscape.use(popper); -interface Props { +interface CsComponentProps { modalQuestionParentContentId: string; modalQuestionTargetContentId: string; setOpenedModalQuestions: (open: boolean) => void; @@ -123,7 +127,7 @@ function CsComponent({ setOpenedModalQuestions, setModalQuestionParentContentId, setModalQuestionTargetContentId -}: Props) { +}: CsComponentProps) { const quiz = useCurrentQuiz(); const { dragQuestionContentId, desireToOpenABranchingModal, canCreatePublic } = useUiTools() @@ -138,6 +142,25 @@ function CsComponent({ const crossesContainer = useRef(null); const gearsContainer = useRef(null); + const { layoutOptions } = usePopper({ + layoutsContainer, + plusesContainer, + crossesContainer, + gearsContainer, + setModalQuestionParentContentId, + setOpenedModalQuestions, + setStartCreate, + setStartRemove, + }); + const { removeNode } = useRemoveNode({ + cyRef, + layoutOptions, + layoutsContainer, + plusesContainer, + crossesContainer, + gearsContainer, + }); + useEffect(() => { return () => { @@ -196,7 +219,7 @@ function CsComponent({ } } ]) - cy?.layout(lyopts).run() + cy?.layout(layoutOptions).run() cy?.center(es) } else { enqueueSnackbar("Добавляемый вопрос не найден") @@ -234,121 +257,6 @@ function CsComponent({ } - const removeNode = ({ targetNodeContentId }: { targetNodeContentId: string }) => { - const deleteNodes = [] as string[] - const deleteEdges: any = [] - const cy = cyRef?.current - - const findChildrenToDelete = (node) => { - - //Узнаём грани, идущие от этой ноды - cy?.$('edge[source = "' + node.id() + '"]')?.toArray().forEach((edge) => { - const edgeData = edge.data() - - //записываем id грани для дальнейшего удаления - deleteEdges.push(edge) - //ищем ноду на конце грани, записываем её ID для дальнейшего удаления - const targetNode = cy?.$("#" + edgeData.target) - deleteNodes.push(targetNode.data().id) - //вызываем функцию для анализа потомков уже у этой ноды - findChildrenToDelete(targetNode) - }) - - } - findChildrenToDelete(cy?.getElementById(targetNodeContentId)) - - const targetQuestion = getQuestionByContentId(targetNodeContentId) - - if (targetQuestion.content.rule.parentId === "root" && quiz) { - updateRootContentId(quiz?.id, "") - updateQuestion(targetNodeContentId, question => { - question.content.rule.parentId = "" - question.content.rule.main = [] - question.content.rule.children = [] - question.content.rule.default = "" - }) - trashQuestions.forEach(q => { - if (q.type === "result") { - deleteQuestion(q.id); - } - }); - clearRuleForAll() - - } else { - - const parentQuestionContentId = cy?.$('edge[target = "' + targetNodeContentId + '"]')?.toArray()?.[0]?.data()?.source - if (targetNodeContentId && parentQuestionContentId) { - if (cy?.edges(`[source="${parentQuestionContentId}"]`).length === 0) - createFrontResult(quiz.backendId, parentQuestionContentId) - clearDataAfterRemoveNode({ targetQuestionContentId: targetNodeContentId, parentQuestionContentId }) - cy?.remove(cy?.$('#' + targetNodeContentId)).layout(lyopts).run() - } - - } - - //После всех манипуляций удаляем грани и ноды из CS Чистим rule потомков на беке - - deleteNodes.forEach((nodeId) => {//Ноды - cy?.remove(cy?.$("#" + nodeId)) - removeButtons(nodeId) - updateQuestion(nodeId, question => { - question.content.rule.parentId = "" - question.content.rule.main = [] - question.content.rule.default = "" - question.content.rule.children = [] - }) - - - }) - - deleteEdges.forEach((edge: any) => {//Грани - cy?.remove(edge) - }) - - removeButtons(targetNodeContentId) - cy?.data('changed', true) - cy?.layout(lyopts).run() - - //удаляем result всех потомков - trashQuestions.forEach((qr) => { - if (qr.type === "result") { - if (deleteNodes.includes(qr.content.rule.parentId) || qr.content.rule.parentId === targetQuestion.content.id) { - deleteQuestion(qr.id); - } - } - }) - } - - - const clearDataAfterRemoveNode = ({ targetQuestionContentId, parentQuestionContentId }: { targetQuestionContentId: string, parentQuestionContentId: string }) => { - - - - updateQuestion(targetQuestionContentId, question => { - question.content.rule.parentId = "" - question.content.rule.children = [] - question.content.rule.main = [] - question.content.rule.default = "" - }) - - - //чистим rule родителя - const parentQuestion = getQuestionByContentId(parentQuestionContentId) - const newRule = {} - const newChildren = [...parentQuestion.content.rule.children] - newChildren.splice(parentQuestion.content.rule.children.indexOf(targetQuestionContentId), 1); - newRule.main = parentQuestion.content.rule.main.filter((data) => data.next !== targetQuestionContentId) //удаляем условия перехода от родителя к этому вопросу - newRule.parentId = parentQuestion.content.rule.parentId - newRule.default = parentQuestion.content.rule.default === targetQuestionContentId ? "" : parentQuestion.content.rule.default - newRule.children = newChildren - - updateQuestion(parentQuestionContentId, (PQ) => { - PQ.content.rule = newRule - }) - } - - - useEffect(() => { if (startCreate) { addNode({ parentNodeContentId: startCreate }); @@ -359,12 +267,13 @@ function CsComponent({ useEffect(() => { if (startRemove) { - removeNode({ targetNodeContentId: startRemove }); + updateDeleteId(startRemove); setStartRemove(""); } }, [startRemove]); + const readyLO = (e) => { if (e.cy.data('firstNode') === 'nonroot') { e.cy.data('firstNode', 'root') From c382f6b7ab6dd769454ab8c5e399b7ee4ae26613 Mon Sep 17 00:00:00 2001 From: Nastya Date: Thu, 21 Dec 2023 11:29:51 +0300 Subject: [PATCH 13/15] =?UTF-8?q?=D0=BF=D1=80=D0=B8=D0=BC=D0=B5=D1=80?= =?UTF-8?q?=D0=BD=D0=BE=20=D1=80=D0=B0=D1=81=D0=BA=D0=B8=D0=B4=D0=B0=D0=BD?= =?UTF-8?q?=D0=BD=D0=B0=D1=8F=20=D0=BB=D0=BE=D0=B3=D0=B8=D0=BA=D0=B0=20?= =?UTF-8?q?=D1=80=D0=B5=D0=B7=D1=83=D0=BB=D1=82=D0=BE=D0=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/constants/result.ts | 3 +- src/model/questionTypes/result.ts | 1 + .../Questions/BranchingMap/CsComponent.tsx | 8 +- .../Questions/BranchingMap/FirstNodeField.tsx | 13 +-- .../BranchingMap/hooks/useRemoveNode.ts | 8 +- src/pages/Questions/ButtonsOptions.tsx | 11 +- src/pages/Questions/ButtonsOptionsAndPict.tsx | 9 +- .../DraggableList/QuestionPageCard.tsx | 11 +- src/pages/ResultPage/FirstEntry.tsx | 57 ---------- src/pages/ResultPage/ResultPage.tsx | 28 +++-- src/pages/ResultPage/ResultSettings.tsx | 4 +- src/pages/startPage/EditPage.tsx | 50 +++++---- src/stores/questions/actions.ts | 105 ++++++++---------- src/stores/quizes/actions.ts | 43 +++++-- 14 files changed, 151 insertions(+), 200 deletions(-) diff --git a/src/constants/result.ts b/src/constants/result.ts index b9f40159..1e1d5c35 100644 --- a/src/constants/result.ts +++ b/src/constants/result.ts @@ -12,6 +12,7 @@ export const QUIZ_QUESTION_RESULT: Omit innerName: "", text: "", price: [0], - useImage: true + useImage: true, + usage: true }, }; diff --git a/src/model/questionTypes/result.ts b/src/model/questionTypes/result.ts index 7636bd36..0437b2ce 100644 --- a/src/model/questionTypes/result.ts +++ b/src/model/questionTypes/result.ts @@ -18,5 +18,6 @@ export interface QuizQuestionResult extends QuizQuestionBase { rule: QuestionBranchingRule, hint: QuestionHint; autofill: boolean; + usage: boolean }; } diff --git a/src/pages/Questions/BranchingMap/CsComponent.tsx b/src/pages/Questions/BranchingMap/CsComponent.tsx index dd6777fc..b3cd0c55 100644 --- a/src/pages/Questions/BranchingMap/CsComponent.tsx +++ b/src/pages/Questions/BranchingMap/CsComponent.tsx @@ -16,7 +16,7 @@ import { updateQuestion, getQuestionByContentId, clearRuleForAll, - createFrontResult, + createResult, } from "@root/questions/actions"; import { updateModalInfoWhyCantCreate, @@ -130,7 +130,7 @@ function CsComponent({ if (Object.keys(targetQuestion).length !== 0 && parentNodeContentId && parentNodeChildren !== undefined) { clearDataAfterAddNode({ parentNodeContentId, targetQuestion, parentNodeChildren }) cy?.data('changed', true) - createFrontResult(quiz?.backendId, targetQuestion.content.id) + createResult(quiz?.backendId, targetQuestion.content.id) const es = cy?.add([ { data: { @@ -157,10 +157,10 @@ function CsComponent({ const parentQuestion = { ...getQuestionByContentId(parentNodeContentId) } as AnyTypedQuizQuestion - //смотрим не добавлен ли родителю result. Если да - убираем его. Веточкам result не нужен + //смотрим не добавлен ли родителю result. Если да - делаем его неактивным. Веточкам result не нужен trashQuestions.forEach((targetQuestion) => { if (targetQuestion.type === "result" && targetQuestion.content.rule.parentId === parentQuestion.content.id) { - deleteQuestion(targetQuestion.id); + updateQuestion(targetQuestion.id, (q) => q.content.usage = false); } }) diff --git a/src/pages/Questions/BranchingMap/FirstNodeField.tsx b/src/pages/Questions/BranchingMap/FirstNodeField.tsx index 849efb23..ba14fc18 100644 --- a/src/pages/Questions/BranchingMap/FirstNodeField.tsx +++ b/src/pages/Questions/BranchingMap/FirstNodeField.tsx @@ -1,6 +1,6 @@ import { Box } from "@mui/material" import { useEffect, useRef, useLayoutEffect } from "react"; -import { deleteQuestion, clearRuleForAll, updateQuestion } from "@root/questions/actions" +import { deleteQuestion, clearRuleForAll, updateQuestion, createResult } from "@root/questions/actions" import { updateOpenedModalSettingsId } from "@root/uiTools/actions" import { updateRootContentId } from "@root/quizes/actions" import { useCurrentQuiz } from "@root/quizes/hooks" @@ -34,11 +34,7 @@ export const FirstNodeField = ({ setOpenedModalQuestions, modalQuestionTargetCon if (dragQuestionContentId) { updateRootContentId(quiz?.id, dragQuestionContentId) updateQuestion(dragQuestionContentId, (question) => question.content.rule.parentId = "root") - //если были результаты - удалить - questions.forEach((q) => { - if (q.type === 'result') deleteQuestion(q.id) - }) - + createResult(quiz?.backendId, dragQuestionContentId) } } else { enqueueSnackbar("Нет информации о взятом опроснике") @@ -61,10 +57,7 @@ export const FirstNodeField = ({ setOpenedModalQuestions, modalQuestionTargetCon if (modalQuestionTargetContentId) { updateRootContentId(quiz?.id, modalQuestionTargetContentId) updateQuestion(modalQuestionTargetContentId, (question) => question.content.rule.parentId = "root") - //если были результаты - удалить - questions.forEach((q) => { - if (q.type === 'result') deleteQuestion(q.id) - }) + createResult(quiz?.backendId, modalQuestionTargetContentId) } } else { enqueueSnackbar("Нет информации о взятом опроснике") diff --git a/src/pages/Questions/BranchingMap/hooks/useRemoveNode.ts b/src/pages/Questions/BranchingMap/hooks/useRemoveNode.ts index 05586b95..ccea4ac5 100644 --- a/src/pages/Questions/BranchingMap/hooks/useRemoveNode.ts +++ b/src/pages/Questions/BranchingMap/hooks/useRemoveNode.ts @@ -3,7 +3,6 @@ import { updateQuestion, getQuestionByContentId, clearRuleForAll, - createFrontResult, } from "@root/questions/actions"; import { useQuestionsStore } from "@root/questions/store"; import { useCurrentQuiz } from "@root/quizes/hooks"; @@ -143,11 +142,6 @@ export const useRemoveNode = ({ question.content.rule.children = []; question.content.rule.default = ""; }); - trashQuestions.forEach((q) => { - if (q.type === "result") { - deleteQuestion(q.id); - } - }); clearRuleForAll(); } else { const parentQuestionContentId = cy @@ -159,7 +153,7 @@ export const useRemoveNode = ({ quiz && cy?.edges(`[source="${parentQuestionContentId}"]`).length === 0 ) { - createFrontResult(quiz.backendId, parentQuestionContentId); + //createFrontResult(quiz.backendId, parentQuestionContentId); } clearDataAfterRemoveNode({ targetQuestionContentId: targetNodeContentId, diff --git a/src/pages/Questions/ButtonsOptions.tsx b/src/pages/Questions/ButtonsOptions.tsx index 66cdef0a..d827fad4 100644 --- a/src/pages/Questions/ButtonsOptions.tsx +++ b/src/pages/Questions/ButtonsOptions.tsx @@ -57,11 +57,6 @@ export default function ButtonsOptions({ if (question.content.rule.parentId === "root") { //удалить из стора root и очистить rule всем вопросам updateRootContentId(quiz.id, ""); clearRuleForAll(); - questions.forEach(q => { - if (q.type === "result") { - deleteQuestion(q.id); - } - }); deleteQuestion(question.id); } else if (question.content.rule.parentId.length > 0) { //удалить из стора вопрос из дерева и очистить его потомков const clearQuestions = [] as string[]; @@ -69,10 +64,8 @@ export default function ButtonsOptions({ //записываем потомков , а их результаты удаляем const getChildren = (parentQuestion: AnyTypedQuizQuestion) => { questions.forEach((targetQuestion) => { - if (targetQuestion.content.rule.parentId === parentQuestion.content.id) {//если у вопроса совпал родитель с родителем => он потомок, в кучу его - if (targetQuestion.type === "result") { - deleteQuestion(targetQuestion.id); - } else { + if (targetQuestion.type !== null && targetQuestion.content.rule.parentId === parentQuestion.content.id) {//если у вопроса совпал родитель с родителем => он потомок, в кучу его + if (targetQuestion.type !== "result" && targetQuestion.type !== null) { if (!clearQuestions.includes(targetQuestion.content.id)) clearQuestions.push(targetQuestion.content.id); getChildren(targetQuestion); //и ищем его потомков } diff --git a/src/pages/Questions/ButtonsOptionsAndPict.tsx b/src/pages/Questions/ButtonsOptionsAndPict.tsx index e4fbae65..528932fe 100644 --- a/src/pages/Questions/ButtonsOptionsAndPict.tsx +++ b/src/pages/Questions/ButtonsOptionsAndPict.tsx @@ -58,11 +58,6 @@ export default function ButtonsOptionsAndPict({ if (question.content.rule.parentId === "root") { //удалить из стора root и очистить rule всем вопросам updateRootContentId(quiz.id, ""); clearRuleForAll(); - questions.forEach(q => { - if (q.type === "result") { - deleteQuestion(q.id); - } - }); deleteQuestion(question.id); } else if (question.content.rule.parentId.length > 0) { //удалить из стора вопрос из дерева и очистить его потомков const clearQuestions = [] as string[]; @@ -71,9 +66,7 @@ export default function ButtonsOptionsAndPict({ const getChildren = (parentQuestion: AnyTypedQuizQuestion) => { questions.forEach((targetQuestion) => { if (targetQuestion.content.rule.parentId === parentQuestion.content.id) {//если у вопроса совпал родитель с родителем => он потомок, в кучу его - if (targetQuestion.type === "result") { - deleteQuestion(targetQuestion.id); - } else { + if (targetQuestion.type !== null && targetQuestion.type !== "result") { if (!clearQuestions.includes(targetQuestion.content.id)) clearQuestions.push(targetQuestion.content.id); getChildren(targetQuestion); //и ищем его потомков } diff --git a/src/pages/Questions/DraggableList/QuestionPageCard.tsx b/src/pages/Questions/DraggableList/QuestionPageCard.tsx index 5da33ac3..38343340 100644 --- a/src/pages/Questions/DraggableList/QuestionPageCard.tsx +++ b/src/pages/Questions/DraggableList/QuestionPageCard.tsx @@ -93,21 +93,14 @@ const maxLengthTextField = 225; updateRootContentId(quiz.id, ""); clearRuleForAll(); deleteQuestion(question.id); - questions.forEach(q => { - if (q.type === "result") { - deleteQuestion(q.id); - } - }); } else if (question.content.rule.parentId.length > 0) { //удалить из стора вопрос из дерева и очистить его потомков const clearQuestions = [] as string[]; //записываем потомков , а их результаты удаляем const getChildren = (parentQuestion: AnyTypedQuizQuestion) => { questions.forEach((targetQuestion) => { - if (targetQuestion.content.rule.parentId === parentQuestion.content.id) {//если у вопроса совпал родитель с родителем => он потомок, в кучу его - if (targetQuestion.type === "result") { - deleteQuestion(targetQuestion.id); - } else { + if (targetQuestion.type !== null && targetQuestion.content.rule.parentId === parentQuestion.content.id) {//если у вопроса совпал родитель с родителем => он потомок, в кучу его + if (targetQuestion.type !== null && targetQuestion.type !== "result") { if (!clearQuestions.includes(targetQuestion.content.id)) clearQuestions.push(targetQuestion.content.id); getChildren(targetQuestion); //и ищем его потомков } diff --git a/src/pages/ResultPage/FirstEntry.tsx b/src/pages/ResultPage/FirstEntry.tsx index b11eb41a..2960f3cb 100644 --- a/src/pages/ResultPage/FirstEntry.tsx +++ b/src/pages/ResultPage/FirstEntry.tsx @@ -1,38 +1,10 @@ -import { ResultSettings } from "./ResultSettings"; -import { createFrontResult } from "@root/questions/actions"; -import { useQuestionsStore } from "@root/questions/store"; -import { useCurrentQuiz } from "@root/quizes/hooks"; import { Box, Typography, useTheme, useMediaQuery, Button } from "@mui/material"; import image from "../../assets/Rectangle 110.png"; -import { enqueueSnackbar } from "notistack"; -import { AnyTypedQuizQuestion } from "@model/questionTypes/shared"; -import ArrowLeft from "../../assets/icons/questionsPage/arrowLeft"; -import { decrementCurrentStep } from "@root/quizes/actions"; export const FirstEntry = () => { const theme = useTheme(); - const quiz = useCurrentQuiz(); - const { questions } = useQuestionsStore(); const isSmallMonitor = useMediaQuery(theme.breakpoints.down(1250)); - const create = () => { - if (quiz?.config.haveRoot) { - questions - .filter((question: AnyTypedQuizQuestion) => { - return ( - question.type !== null && - question.content.rule.parentId.length !== 0 && - question.content.rule.children.length === 0 - ); - }) - .forEach((question) => { - createFrontResult(quiz.backendId, question.content.id); - }); - } else { - createFrontResult(quiz.backendId, "line"); - } - }; - return ( <> { }} /> - - - - - ); }; diff --git a/src/pages/ResultPage/ResultPage.tsx b/src/pages/ResultPage/ResultPage.tsx index 06eb39bb..00808381 100644 --- a/src/pages/ResultPage/ResultPage.tsx +++ b/src/pages/ResultPage/ResultPage.tsx @@ -1,15 +1,27 @@ -import { useQuestionsStore } from "@root/questions/store"; import { FirstEntry } from "./FirstEntry" import { ResultSettings } from "./ResultSettings" +import { decrementCurrentStep } from "@root/quizes/actions"; +import { Box, Button } from "@mui/material"; +import ArrowLeft from "@icons/questionsPage/arrowLeft" export const ResultPage = () => { - const { questions } = useQuestionsStore(); - //ищём хотя бы один result - const haveResult = questions.some((question) => question.type === "result") return ( - haveResult ? - - : + <> - ); + + + + + + ) } \ No newline at end of file diff --git a/src/pages/ResultPage/ResultSettings.tsx b/src/pages/ResultPage/ResultSettings.tsx index e912f0d8..157f424d 100644 --- a/src/pages/ResultPage/ResultSettings.tsx +++ b/src/pages/ResultPage/ResultSettings.tsx @@ -14,7 +14,7 @@ import { ResultCard, checkEmptyData } from "./cards/ResultCard"; import { EmailSettingsCard } from "./cards/EmailSettingsCard"; import { useCurrentQuiz } from "@root/quizes/hooks"; import { useQuestionsStore } from "@root/questions/store"; -import { createFrontResult, deleteQuestion } from "@root/questions/actions"; +import { deleteQuestion } from "@root/questions/actions"; import { QuizQuestionResult } from "@model/questionTypes/result"; export const ResultSettings = () => { @@ -25,6 +25,8 @@ export const ResultSettings = () => { const [resultContract, setResultContract] = useState(true); const isReadyToLeaveRef = useRef(true); + console.log(quiz) + console.log(results) useEffect( function calcIsReadyToLeave() { let isReadyToLeave = true; diff --git a/src/pages/startPage/EditPage.tsx b/src/pages/startPage/EditPage.tsx index 11f091bb..3e8dd957 100755 --- a/src/pages/startPage/EditPage.tsx +++ b/src/pages/startPage/EditPage.tsx @@ -32,7 +32,7 @@ import { Link, useNavigate } from "react-router-dom"; import useSWR from "swr"; import { useDebouncedCallback } from "use-debounce"; import { SidebarMobile } from "./Sidebar/SidebarMobile"; -import { cleanQuestions, setQuestions } from "@root/questions/actions"; +import { cleanQuestions, createResult, setQuestions } from "@root/questions/actions"; import { updateOpenBranchingPanel, updateCanCreatePublic, updateModalInfoWhyCantCreate } from "@root/uiTools/actions"; import { BranchingPanel } from "../Questions/BranchingPanel"; import { useQuestionsStore } from "@root/questions/store"; @@ -59,10 +59,14 @@ export default function EditPage() { const questions = await questionApi.getList({ quiz_id: editQuizId }); setQuestions(questions); + if (questions === null || !questions.find(q => q.type !== null && q.content?.rule.parentId === "line")) createResult(quiz?.backendId, "line") }; getData(); }, []); +console.log(quiz) +console.log(questions) + const { openBranchingPanel, whyCantCreatePublic, canCreatePublic } = useUiTools(); const theme = useTheme(); const navigate = useNavigate(); @@ -78,10 +82,12 @@ export default function EditPage() { }, [navigate, editQuizId]); useEffect( - () => () => { - resetEditConfig(); - cleanQuestions(); - updateModalInfoWhyCantCreate(false) + () => { + return () => { + resetEditConfig(); + cleanQuestions(); + updateModalInfoWhyCantCreate(false) + } }, [] ); @@ -123,7 +129,7 @@ export default function EditPage() { useEffect(() => { updateQuestionHint(questions) }, [questions]); - + async function handleLogoutClick() { const [, logoutError] = await logout(); @@ -376,11 +382,11 @@ export default function EditPage() { height: "34px", minWidth: "130px", }} - onClick={() => Object.keys(whyCantCreatePublic).length === 0 ? () => {} : updateModalInfoWhyCantCreate(true)} + onClick={() => Object.keys(whyCantCreatePublic).length === 0 ? () => { } : updateModalInfoWhyCantCreate(true)} > Тестовый просмотр - : + : - {quiz?.status === "start" && https://hbpn.link/{quiz.qid} - } - - + {quiz?.status === "start" ? "Стоп" : "Старт"} + + {quiz?.status === "start" && https://hbpn.link/{quiz.qid} + } + + ); diff --git a/src/stores/questions/actions.ts b/src/stores/questions/actions.ts index c59e00db..456f4566 100644 --- a/src/stores/questions/actions.ts +++ b/src/stores/questions/actions.ts @@ -15,6 +15,7 @@ import { useCurrentQuiz } from "@root/quizes/hooks"; import { QuestionsStore, useQuestionsStore } from "./store"; import { useUiTools } from "../uiTools/store"; import { withErrorBoundary } from "react-error-boundary"; +import { QuizQuestionResult } from "@model/questionTypes/result"; export const setQuestions = (questions: RawQuestion[] | null) => setProducedState(state => { @@ -481,7 +482,12 @@ export const getQuestionByContentId = (questionContentId: string | null) => { export const clearRuleForAll = () => { const { questions } = useQuestionsStore.getState(); 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) + && question.type !== "result") { + updateQuestion(question.content.id, question => { question.content.rule.parentId = ""; question.content.rule.main = []; @@ -491,63 +497,48 @@ export const clearRuleForAll = () => { }); }; - -export const createFrontResult = (quizId: number, parentContentId?: string) => setProducedState(state => { - const frontId = nanoid(); - const content = JSON.parse(JSON.stringify(defaultQuestionByType["result"].content)); - content.id = frontId; - if (parentContentId) content.rule.parentId = parentContentId; - state.questions.push({ - id: frontId, - quizId, - type: "result", - title: "", - description: "", - deleted: false, - expanded: true, - page: 101, - required: true, - content - }); -}, { - type: "createFrontResult", - quizId, -}); - - -export const createBackResult = async ( - questionId: string, - type: QuestionType, +export const createResult = async ( + quizId: number, + parentContentId?: string ) => requestQueue.enqueue(async () => { - const question = useQuestionsStore.getState().questions.find(q => q.id === questionId); - if (!question) return; - if (question.type !== "result") 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: "createBackResult", - question, - }); - } catch (error) { - devlog("Error creating question", error); - enqueueSnackbar("Не удалось создать вопрос"); + if (!quizId || !parentContentId) { + console.error("Нет данных для создания результата. quizId: ", quizId, ", quizId: ", parentContentId) } + + //Мы получили запрос на создание резулта. Анализируем существует ли такой. Если да - просто делаем его активным + const question = useQuestionsStore.getState().questions.find(q=> q.type !== null && q?.content.rule.parentContentId === parentContentId) + + if (question) {//существует, делаем активным + + updateQuestion(question.id, (q) => { + q.content.usage = true + }) + + } else {//не существует, создаём + const content = JSON.parse(JSON.stringify(defaultQuestionByType["result"].content)); + content.rule.parentId = parentContentId; + + try { + const createdQuestion:RawQuestion = await questionApi.create({ + quiz_id: quizId, + type: "result", + title: "", + description: "", + page: 101, + required: true, + content: JSON.stringify(content), + }); + + setProducedState(state => { + state.questions.push(rawQuestionToQuestion(createdQuestion)) + }, { + type: "createBackResult", + createdQuestion, + }); + } catch (error) { + devlog("Error creating question", error); + enqueueSnackbar("Не удалось создать вопрос"); + } + } }); diff --git a/src/stores/quizes/actions.ts b/src/stores/quizes/actions.ts index e0367bfd..fd718a1d 100644 --- a/src/stores/quizes/actions.ts +++ b/src/stores/quizes/actions.ts @@ -9,8 +9,9 @@ import { NavigateFunction } from "react-router-dom"; import { isAxiosCanceledError } from "../../utils/isAxiosCanceledError"; import { RequestQueue } from "../../utils/requestQueue"; import { QuizStore, useQuizStore } from "./store"; -import { createUntypedQuestion } from "@root/questions/actions"; +import { createUntypedQuestion, updateQuestion } from "@root/questions/actions"; import { useCurrentQuiz } from "./hooks" +import { useQuestionsStore } from "@root/questions/store"; export const setEditQuizId = (quizId: number | null) => setProducedState(state => { @@ -176,12 +177,40 @@ export const deleteQuiz = async (quizId: string) => requestQueue.enqueue(async ( enqueueSnackbar(`Не удалось удалить квиз. ${message}`); } }); -export const updateRootContentId = (quizId: string, id:string) => updateQuiz( - quizId, - quiz => { - quiz.config.haveRoot = id - }, -); +export const updateRootContentId = (quizId: string, id: string) => { + + if (id.length === 0) {//дерева больше не существует, все результаты неактивны кроме результата линейности + useQuestionsStore.getState().questions.forEach((q) => { + if (q.type !== null && q.type === "result") { + if (q.content.rule.parentId === "line" && q.content.usage === false) { + updateQuestion(q.id, (q) => { + q.content.usage === true + }) + } else { + updateQuestion(q.id, (q) => { + q.content.usage === false + }) + } + } + }) + } else { //было создано дерево, результат линейности неактивен + useQuestionsStore.getState().questions.forEach((q) => { + if (q.type !== null && q.content.rule.parentId === "line") { + updateQuestion(q.id, (q) => { + q.content.usage === false + }) + } + + }) + } + + updateQuiz( + quizId, + quiz => { + quiz.config.haveRoot = id + }, + ); +} // TODO copy quiz From ec53992a131ff61f17ad5844e8931c2b801fe556 Mon Sep 17 00:00:00 2001 From: nflnkr Date: Thu, 21 Dec 2023 12:34:56 +0300 Subject: [PATCH 14/15] fix redirect location state --- src/pages/Landing/HeaderLanding.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/Landing/HeaderLanding.tsx b/src/pages/Landing/HeaderLanding.tsx index 95105a06..94444268 100644 --- a/src/pages/Landing/HeaderLanding.tsx +++ b/src/pages/Landing/HeaderLanding.tsx @@ -66,7 +66,7 @@ export default function Component() { {/**/} + + ) diff --git a/src/pages/ViewPublicationPage/ContactForm.tsx b/src/pages/ViewPublicationPage/ContactForm.tsx index ddef90fd..728c7ba5 100644 --- a/src/pages/ViewPublicationPage/ContactForm.tsx +++ b/src/pages/ViewPublicationPage/ContactForm.tsx @@ -6,6 +6,7 @@ import TextIcon from "@icons/ContactFormIcon/TextIcon"; import AddressIcon from "@icons/ContactFormIcon/AddressIcon"; import { useCurrentQuiz } from "@root/quizes/hooks"; +import { NameplateLogo } from "@icons/NameplateLogo"; import CustomCheckbox from "@ui_kit/CustomCheckbox"; import { useState } from "react"; import { useQuestionsStore } from "@root/questions/store"; @@ -71,19 +72,19 @@ export const ContactForm = ({ }} > {quiz?.config.formContact.title || "Заполните форму, чтобы получить результаты теста"} - + { quiz?.config.formContact.desc && - - {quiz?.config.formContact.desc} - + + {quiz?.config.formContact.desc} + } @@ -138,6 +139,16 @@ export const ContactForm = ({ + + + Сделано на PenaQuiz + @@ -154,14 +165,14 @@ const Inputs = () => { if (FC.used) someUsed.push() return }) - + if (someUsed.length) { return <>{someUsed} } else { return <> - {Icons[0]} - {Icons[1]} - {Icons[2]} + {Icons[0]} + {Icons[1]} + {Icons[2]} } } diff --git a/src/pages/ViewPublicationPage/Footer.tsx b/src/pages/ViewPublicationPage/Footer.tsx index 8a1f2dea..6833117d 100644 --- a/src/pages/ViewPublicationPage/Footer.tsx +++ b/src/pages/ViewPublicationPage/Footer.tsx @@ -8,7 +8,7 @@ import { useQuestionsStore } from "@root/questions/store"; import type { AnyTypedQuizQuestion, QuizQuestionBase } from "../../model/questionTypes/shared"; import { getQuestionByContentId } from "@root/questions/actions"; import { enqueueSnackbar } from "notistack"; -import { NameplateLogo } from "@icons/NameplateLogo"; +import { NameplateLogoFQ } from "@icons/NameplateLogoFQ"; type FooterProps = { setCurrentQuestion: (step: AnyTypedQuizQuestion) => void; @@ -189,22 +189,11 @@ export const Footer = ({ setCurrentQuestion, question, setShowContactForm, setSh position: "relative", padding: "15px 0", borderTop: `1px solid ${theme.palette.grey[400]}`, + height: '75px', + + display: "flex" }} > - - - Сделано на PenaQuiz - + { QUESTIONS_MAP[currentQuestion.type as Exclude]; return ( - + {!showContactForm && !showResultForm && ( { @@ -100,31 +101,59 @@ export const ResultForm = ({ - { - quiz?.config.resultInfo.when === "before" && + - + + Сделано на PenaQuiz + + - } + + { + quiz?.config.resultInfo.when === "before" && + <> + + + + + } + + + ); };