From 69e76397f5e899ca6519d6f2eeea3ee92b3490b8 Mon Sep 17 00:00:00 2001 From: nflnkr Date: Sun, 25 Feb 2024 14:12:41 +0300 Subject: [PATCH 1/6] fix broken memoization of question card component --- src/pages/Questions/ButtonsOptions.tsx | 26 ++++---------- .../ButtonsOptions/ButtonsOptions.tsx | 32 +++++------------ src/pages/Questions/ButtonsOptionsAndPict.tsx | 28 ++++----------- .../DraggableList/DraggableListItem.tsx | 9 +++-- .../DraggableList/QuestionPageCard.tsx | 34 ++++++++----------- .../Questions/PageOptions/PageOptions.tsx | 20 ++++------- .../Questions/QuestionSwitchWindowTool.tsx | 19 ++++------- src/pages/main.tsx | 8 ++--- src/stores/quizes/hooks.ts | 8 +++++ src/utils/deleteFunc.ts | 12 ++++--- src/utils/deleteTimeoutedQuestions.ts | 18 +++------- 11 files changed, 78 insertions(+), 136 deletions(-) diff --git a/src/pages/Questions/ButtonsOptions.tsx b/src/pages/Questions/ButtonsOptions.tsx index dd91ec0d..615e4be1 100644 --- a/src/pages/Questions/ButtonsOptions.tsx +++ b/src/pages/Questions/ButtonsOptions.tsx @@ -1,6 +1,3 @@ -import { DoubleArrowRight } from "@icons/questionsPage/DoubleArrowRight"; -import { DoubleTick } from "@icons/questionsPage/DoubleTick"; -import { VectorQuestions } from "@icons/questionsPage/VectorQuestions"; import { DeleteIcon } from "@icons/questionsPage/deleteIcon"; import type { SxProps } from "@mui/material"; import { @@ -8,7 +5,6 @@ import { Button, IconButton, Modal, - Tooltip, Typography, useMediaQuery, useTheme, @@ -17,27 +13,17 @@ import { copyQuestion, deleteQuestion, deleteQuestionWithTimeout, - clearRuleForAll, - updateQuestion, - getQuestionByContentId, } from "@root/questions/actions"; +import { useQuestionsStore } from "@root/questions/store"; +import { useCurrentQuiz } from "@root/quizes/hooks"; import { updateDesireToOpenABranchingModal } from "@root/uiTools/actions"; import MiniButtonSetting from "@ui_kit/MiniButtonSetting"; +import { DeleteFunction } from "@utils/deleteFunc"; +import { useState } from "react"; import { CopyIcon } from "../../assets/icons/questionsPage/CopyIcon"; import Branching from "../../assets/icons/questionsPage/branching"; -import Clue from "../../assets/icons/questionsPage/clue"; -import { HideIcon } from "../../assets/icons/questionsPage/hideIcon"; import SettingIcon from "../../assets/icons/questionsPage/settingIcon"; import type { AnyTypedQuizQuestion } from "../../model/questionTypes/shared"; -import { useCurrentQuiz } from "@root/quizes/hooks"; -import { enqueueSnackbar } from "notistack"; -import { useQuestionsStore } from "@root/questions/store"; -import { updateOpenedModalSettingsId } from "@root/uiTools/actions"; -import { updateRootContentId } from "@root/quizes/actions"; -import { useUiTools } from "@root/uiTools/store"; -import { useState } from "react"; -import { updateSomeWorkBackend } from "@root/uiTools/actions"; -import { DeleteFunction } from "@utils/deleteFunc"; interface Props { switchState: string; @@ -306,7 +292,7 @@ export default function ButtonsOptions({ setOpenDelete(true); } else { deleteQuestionWithTimeout(question.id, () => - DeleteFunction(questions, question, quiz), + DeleteFunction(question), ); } }} @@ -351,7 +337,7 @@ export default function ButtonsOptions({ sx={{ minWidth: "150px" }} onClick={() => { deleteQuestionWithTimeout(question.id, () => - DeleteFunction(questions, question, quiz), + DeleteFunction(question), ); }} > diff --git a/src/pages/Questions/ButtonsOptions/ButtonsOptions.tsx b/src/pages/Questions/ButtonsOptions/ButtonsOptions.tsx index 88501fdc..4d3c7fec 100644 --- a/src/pages/Questions/ButtonsOptions/ButtonsOptions.tsx +++ b/src/pages/Questions/ButtonsOptions/ButtonsOptions.tsx @@ -1,6 +1,3 @@ -import { DoubleArrowRight } from "@icons/questionsPage/DoubleArrowRight"; -import { DoubleTick } from "@icons/questionsPage/DoubleTick"; -import { VectorQuestions } from "@icons/questionsPage/VectorQuestions"; import { DeleteIcon } from "@icons/questionsPage/deleteIcon"; import type { SxProps } from "@mui/material"; import { @@ -8,7 +5,6 @@ import { Button, IconButton, Modal, - Tooltip, Typography, useMediaQuery, useTheme, @@ -17,27 +13,17 @@ import { copyQuestion, deleteQuestion, deleteQuestionWithTimeout, - clearRuleForAll, - updateQuestion, - getQuestionByContentId, } from "@root/questions/actions"; +import { useQuestionsStore } from "@root/questions/store"; +import { useCurrentQuiz } from "@root/quizes/hooks"; import { updateDesireToOpenABranchingModal } from "@root/uiTools/actions"; import MiniButtonSetting from "@ui_kit/MiniButtonSetting"; -import { CopyIcon } from "../../assets/icons/questionsPage/CopyIcon"; -import Branching from "../../assets/icons/questionsPage/branching"; -import Clue from "../../assets/icons/questionsPage/clue"; -import { HideIcon } from "../../assets/icons/questionsPage/hideIcon"; -import SettingIcon from "../../assets/icons/questionsPage/settingIcon"; -import type { AnyTypedQuizQuestion } from "../../model/questionTypes/shared"; -import { useCurrentQuiz } from "@root/quizes/hooks"; -import { enqueueSnackbar } from "notistack"; -import { useQuestionsStore } from "@root/questions/store"; -import { updateOpenedModalSettingsId } from "@root/uiTools/actions"; -import { updateRootContentId } from "@root/quizes/actions"; -import { useUiTools } from "@root/uiTools/store"; -import { useState } from "react"; -import { updateSomeWorkBackend } from "@root/uiTools/actions"; import { DeleteFunction } from "@utils/deleteFunc"; +import { useState } from "react"; +import { CopyIcon } from "../../../assets/icons/questionsPage/CopyIcon"; +import Branching from "../../../assets/icons/questionsPage/branching"; +import SettingIcon from "../../../assets/icons/questionsPage/settingIcon"; +import type { AnyTypedQuizQuestion } from "../../../model/questionTypes/shared"; interface Props { switchState: string; @@ -213,7 +199,7 @@ export default function ButtonsOptions({ setOpenDelete(true); } else { deleteQuestionWithTimeout(question.id, () => - DeleteFunction(questions, question, quiz), + DeleteFunction(question), ); } }} @@ -258,7 +244,7 @@ export default function ButtonsOptions({ sx={{ minWidth: "150px" }} onClick={() => { deleteQuestionWithTimeout(question.id, () => - DeleteFunction(questions, question, quiz), + DeleteFunction(question), ); }} > diff --git a/src/pages/Questions/ButtonsOptionsAndPict.tsx b/src/pages/Questions/ButtonsOptionsAndPict.tsx index e0157b8d..566fcb0b 100644 --- a/src/pages/Questions/ButtonsOptionsAndPict.tsx +++ b/src/pages/Questions/ButtonsOptionsAndPict.tsx @@ -1,13 +1,10 @@ -import { DoubleArrowRight } from "@icons/questionsPage/DoubleArrowRight"; -import { DoubleTick } from "@icons/questionsPage/DoubleTick"; -import { VectorQuestions } from "@icons/questionsPage/VectorQuestions"; +import { QuizQuestionVariant } from "@model/questionTypes/variant"; import { QuizQuestionVarImg } from "@model/questionTypes/varimg"; import { Box, Button, IconButton, Modal, - Tooltip, Typography, useMediaQuery, useTheme, @@ -15,31 +12,20 @@ import { import { copyQuestion, deleteQuestion, - updateQuestion, - clearRuleForAll, - getQuestionByContentId, deleteQuestionWithTimeout, } from "@root/questions/actions"; +import { useQuestionsStore } from "@root/questions/store"; +import { useCurrentQuiz } from "@root/quizes/hooks"; +import { updateDesireToOpenABranchingModal } from "@root/uiTools/actions"; import MiniButtonSetting from "@ui_kit/MiniButtonSetting"; import { ReallyChangingModal } from "@ui_kit/Modal/ReallyChangingModal/ReallyChangingModal"; +import { DeleteFunction } from "@utils/deleteFunc"; import { useEffect, useState } from "react"; import { CopyIcon } from "../../assets/icons/questionsPage/CopyIcon"; import Branching from "../../assets/icons/questionsPage/branching"; -import Clue from "../../assets/icons/questionsPage/clue"; import { DeleteIcon } from "../../assets/icons/questionsPage/deleteIcon"; -import { HideIcon } from "../../assets/icons/questionsPage/hideIcon"; import ImgIcon from "../../assets/icons/questionsPage/imgIcon"; import SettingIcon from "../../assets/icons/questionsPage/settingIcon"; -import { QuizQuestionVariant } from "@model/questionTypes/variant"; -import { updateOpenedModalSettingsId } from "@root/questions/actions"; -import { updateDesireToOpenABranchingModal } from "@root/uiTools/actions"; -import { useQuestionsStore } from "@root/questions/store"; -import { enqueueSnackbar } from "notistack"; -import { useCurrentQuiz } from "@root/quizes/hooks"; -import { updateRootContentId } from "@root/quizes/actions"; -import { AnyTypedQuizQuestion } from "@model/questionTypes/shared"; -import { updateSomeWorkBackend } from "@root/uiTools/actions"; -import { DeleteFunction } from "@utils/deleteFunc"; interface Props { switchState: string; @@ -337,7 +323,7 @@ export default function ButtonsOptionsAndPict({ setOpenDelete(true); } else { deleteQuestionWithTimeout(question.id, () => - DeleteFunction(questions, question, quiz), + DeleteFunction(question), ); } }} @@ -382,7 +368,7 @@ export default function ButtonsOptionsAndPict({ sx={{ minWidth: "150px" }} onClick={() => { deleteQuestionWithTimeout(question.id, () => - DeleteFunction(questions, question, quiz), + DeleteFunction(question), ); }} > diff --git a/src/pages/Questions/DraggableList/DraggableListItem.tsx b/src/pages/Questions/DraggableList/DraggableListItem.tsx index e03a3e79..0cf7a525 100644 --- a/src/pages/Questions/DraggableList/DraggableListItem.tsx +++ b/src/pages/Questions/DraggableList/DraggableListItem.tsx @@ -3,13 +3,12 @@ import { UntypedQuizQuestion, } from "@model/questionTypes/shared"; import { Box, ListItem, Typography, useTheme } from "@mui/material"; +import { cancelQuestionDeletion } from "@root/questions/actions"; +import { updateEditSomeQuestion } from "@root/uiTools/actions"; +import { useUiTools } from "@root/uiTools/store"; import { memo, useEffect } from "react"; import { Draggable } from "react-beautiful-dnd"; import QuestionsPageCard from "./QuestionPageCard"; -import { cancelQuestionDeletion } from "@root/questions/actions"; -import { updateEditSomeQuestion } from "@root/uiTools/actions"; -import { useQuestionsStore } from "@root/questions/store"; -import { useUiTools } from "@root/uiTools/store"; type Props = { question: AnyTypedQuizQuestion | UntypedQuizQuestion; @@ -27,7 +26,7 @@ function DraggableListItem({ setOpenBranchingPage, }: Props) { const theme = useTheme(); - const { editSomeQuestion } = useUiTools(); + const editSomeQuestion = useUiTools((state) => state.editSomeQuestion); useEffect(() => { let counter = 0; diff --git a/src/pages/Questions/DraggableList/QuestionPageCard.tsx b/src/pages/Questions/DraggableList/QuestionPageCard.tsx index 076f1592..ed12631f 100644 --- a/src/pages/Questions/DraggableList/QuestionPageCard.tsx +++ b/src/pages/Questions/DraggableList/QuestionPageCard.tsx @@ -13,6 +13,7 @@ import OptionsPict from "@icons/questionsPage/options_pict"; import Page from "@icons/questionsPage/page"; import RatingIcon from "@icons/questionsPage/rating"; import Slider from "@icons/questionsPage/slider"; +import { QuestionType } from "@model/question/question"; import ExpandLessIcon from "@mui/icons-material/ExpandLess"; import { Box, @@ -21,8 +22,9 @@ import { IconButton, InputAdornment, Modal, + TextField as MuiTextField, Paper, - TextField, + TextFieldProps, Typography, useMediaQuery, useTheme, @@ -31,26 +33,24 @@ import { copyQuestion, createUntypedQuestion, deleteQuestion, + deleteQuestionWithTimeout, toggleExpandQuestion, updateQuestion, updateUntypedQuestion, - deleteQuestionWithTimeout, } from "@root/questions/actions"; -import { useRef, useState } from "react"; +import { DeleteFunction } from "@utils/deleteFunc"; +import { FC, useRef, useState } from "react"; import type { DraggableProvidedDragHandleProps } from "react-beautiful-dnd"; -import { useDebouncedCallback } from "use-debounce"; import { ReactComponent as PlusIcon } from "../../../assets/icons/plus.svg"; import type { AnyTypedQuizQuestion, UntypedQuizQuestion, } from "../../../model/questionTypes/shared"; import SwitchQuestionsPage from "../SwitchQuestionsPage"; -import { ChooseAnswerModal } from "./ChooseAnswerModal"; import TypeQuestions from "../TypeQuestions"; -import { QuestionType } from "@model/question/question"; -import { useCurrentQuiz } from "@root/quizes/hooks"; -import { useQuestionsStore } from "@root/questions/store"; -import { DeleteFunction } from "@utils/deleteFunc"; +import { ChooseAnswerModal } from "./ChooseAnswerModal"; + +const TextField = MuiTextField as unknown as FC; interface Props { question: AnyTypedQuizQuestion | UntypedQuizQuestion; @@ -71,7 +71,6 @@ export default function QuestionsPageCard({ }: Props) { const maxLengthTextField = 225; - const { questions } = useQuestionsStore(); const [plusVisible, setPlusVisible] = useState(false); const [open, setOpen] = useState(false); @@ -82,16 +81,15 @@ export default function QuestionsPageCard({ const isTablet = useMediaQuery(theme.breakpoints.down(1000)); const isMobile = useMediaQuery(theme.breakpoints.down(790)); const anchorRef = useRef(null); - const quiz = useCurrentQuiz(); - const setTitle = useDebouncedCallback((title) => { + const setTitle = (title: string) => { const updateQuestionFn = question.type === null ? updateUntypedQuestion : updateQuestion; updateQuestionFn(question.id, (question) => { question.title = title; }); - }, 200); + }; const handleInputFocus = () => { setIsTextFieldtActive(true); @@ -134,11 +132,9 @@ export default function QuestionsPageCard({ > - setTitle(target.value || " ") - } + onChange={({ target }) => setTitle(target.value || " ")} onFocus={handleInputFocus} onBlur={handleInputBlur} inputProps={{ @@ -303,7 +299,7 @@ export default function QuestionsPageCard({ setOpenDelete(true); } else { deleteQuestionWithTimeout(question.id, () => - DeleteFunction(questions, question, quiz), + DeleteFunction(question), ); } }} @@ -350,7 +346,7 @@ export default function QuestionsPageCard({ sx={{ minWidth: "150px" }} onClick={() => { deleteQuestionWithTimeout(question.id, () => - DeleteFunction(questions, question, quiz), + DeleteFunction(question), ); }} > diff --git a/src/pages/Questions/PageOptions/PageOptions.tsx b/src/pages/Questions/PageOptions/PageOptions.tsx index ca17c920..fb656b71 100644 --- a/src/pages/Questions/PageOptions/PageOptions.tsx +++ b/src/pages/Questions/PageOptions/PageOptions.tsx @@ -15,18 +15,14 @@ import { } from "@root/questions/actions"; import CustomTextField from "@ui_kit/CustomTextField"; +import { CopyIcon } from "@icons/questionsPage/CopyIcon"; +import { DeleteIcon } from "@icons/questionsPage/deleteIcon"; +import { updateDesireToOpenABranchingModal } from "@root/uiTools/actions"; +import { MediaSelectionAndDisplay } from "@ui_kit/MediaSelectionAndDisplay"; +import { DeleteFunction } from "@utils/deleteFunc"; import { useState } from "react"; import { useDebouncedCallback } from "use-debounce"; import type { QuizQuestionPage } from "../../../model/questionTypes/page"; -import ButtonsOptions from "../ButtonsOptions"; -import SwitchPageOptions from "./switchPageOptions"; -import { MediaSelectionAndDisplay } from "@ui_kit/MediaSelectionAndDisplay"; -import { CopyIcon } from "@icons/questionsPage/CopyIcon"; -import { DeleteFunction } from "@utils/deleteFunc"; -import { DeleteIcon } from "@icons/questionsPage/deleteIcon"; -import { useCurrentQuiz } from "@root/quizes/hooks"; -import { useQuestionsStore } from "@root/questions/store"; -import { updateDesireToOpenABranchingModal } from "@root/uiTools/actions"; type Props = { disableInput?: boolean; @@ -50,8 +46,6 @@ export default function PageOptions({ disableInput, question }: Props) { }); }, 200); - const quiz = useCurrentQuiz(); - const { questions } = useQuestionsStore.getState(); const [openDelete, setOpenDelete] = useState(false); const openedModal = () => { @@ -126,7 +120,7 @@ export default function PageOptions({ disableInput, question }: Props) { setOpenDelete(true); } else { deleteQuestionWithTimeout(question.id, () => - DeleteFunction(questions, question, quiz), + DeleteFunction(question), ); } }} @@ -171,7 +165,7 @@ export default function PageOptions({ disableInput, question }: Props) { sx={{ minWidth: "150px" }} onClick={() => { deleteQuestionWithTimeout(question.id, () => - DeleteFunction(questions, question, quiz), + DeleteFunction(question), ); }} > diff --git a/src/pages/Questions/QuestionSwitchWindowTool.tsx b/src/pages/Questions/QuestionSwitchWindowTool.tsx index b0b4ab3c..0f2dbab8 100644 --- a/src/pages/Questions/QuestionSwitchWindowTool.tsx +++ b/src/pages/Questions/QuestionSwitchWindowTool.tsx @@ -1,14 +1,9 @@ -import { useEffect, useLayoutEffect } from "react"; import { Box, useMediaQuery, useTheme } from "@mui/material"; +import { deleteTimeoutedQuestions } from "@utils/deleteTimeoutedQuestions"; +import { useCallback } from "react"; +import { BranchingMap } from "./BranchingMap"; import { DraggableList } from "./DraggableList"; import { SwitchBranchingPanel } from "./SwitchBranchingPanel"; -import { BranchingMap } from "./BranchingMap"; -import { useQuestionsStore } from "@root/questions/store"; -import { useUiTools } from "@root/uiTools/store"; -import { useQuestions } from "@root/questions/hooks"; -import { useCurrentQuiz } from "@root/quizes/hooks"; - -import { deleteTimeoutedQuestions } from "@utils/deleteTimeoutedQuestions"; interface Props { openBranchingPage: boolean; @@ -21,17 +16,15 @@ export const QuestionSwitchWindowTool = ({ setOpenBranchingPage, widthMain, }: Props) => { - const { questions } = useQuestionsStore.getState(); const theme = useTheme(); const isMobile = useMediaQuery(theme.breakpoints.down(600)); - const quiz = useCurrentQuiz(); - const openBranchingPageHC = () => { + const openBranchingPageHC = useCallback(() => { if (!openBranchingPage) { - deleteTimeoutedQuestions(questions, quiz); + deleteTimeoutedQuestions(); } setOpenBranchingPage(!openBranchingPage); - }; + }, [openBranchingPage, setOpenBranchingPage]); return ( (0); const [openBranchingPage, setOpenBranchingPage] = useState(false); - const openBranchingPageHC = () => { + const openBranchingPageHC = useCallback(() => { if (!openBranchingPage) { - deleteTimeoutedQuestions(questions, quiz); + deleteTimeoutedQuestions(); } setOpenBranchingPage((old) => !old); - }; + }, [openBranchingPage, setOpenBranchingPage]); const isConditionMet = [1].includes(currentStep) && quizConfig?.type !== "form"; diff --git a/src/stores/quizes/hooks.ts b/src/stores/quizes/hooks.ts index f27bbe0d..b95ec60d 100644 --- a/src/stores/quizes/hooks.ts +++ b/src/stores/quizes/hooks.ts @@ -34,3 +34,11 @@ export function useCurrentQuiz() { return quiz; } + +export function getCurrentQuiz() { + const { quizes, editQuizId } = useQuizStore.getState(); + + const quiz = quizes.find((q) => q.backendId === editQuizId); + + return quiz; +} diff --git a/src/utils/deleteFunc.ts b/src/utils/deleteFunc.ts index e250975b..273a256e 100644 --- a/src/utils/deleteFunc.ts +++ b/src/utils/deleteFunc.ts @@ -6,15 +6,17 @@ import { getQuestionByContentId, updateQuestion, } from "@root/questions/actions"; +import { useQuestionsStore } from "@root/questions/store"; import { updateRootContentId } from "@root/quizes/actions"; +import { getCurrentQuiz } from "@root/quizes/hooks"; //Всё здесь нужно сделать последовательно. И пусть весь мир ждёт. -export const DeleteFunction = async ( - questions: any, - question: any, - quiz: any, -) => { +export const DeleteFunction = async (question: any) => { + const questions = useQuestionsStore.getState().questions; + const quiz = getCurrentQuiz(); + if (!quiz) throw new Error("Quiz is null"); + if (question.type !== null) { if (question.content.rule.parentId === "root") { //удалить из стора root и очистить rule всем вопросам diff --git a/src/utils/deleteTimeoutedQuestions.ts b/src/utils/deleteTimeoutedQuestions.ts index f712381b..6deb3820 100644 --- a/src/utils/deleteTimeoutedQuestions.ts +++ b/src/utils/deleteTimeoutedQuestions.ts @@ -1,17 +1,11 @@ -import { - AnyTypedQuizQuestion, - UntypedQuizQuestion, -} from "@model/questionTypes/shared"; -import { Quiz } from "@model/quiz/quiz"; +import { AnyTypedQuizQuestion } from "@model/questionTypes/shared"; +import { useQuestionsStore } from "@root/questions/store"; import { updateSomeWorkBackend } from "@root/uiTools/actions"; import { DeleteFunction } from "@utils/deleteFunc"; -type allQuestionsTypes = AnyTypedQuizQuestion | UntypedQuizQuestion; +export const deleteTimeoutedQuestions = async () => { + const questions = useQuestionsStore.getState().questions; -export const deleteTimeoutedQuestions = async ( - questions: allQuestionsTypes[], - quiz: Quiz | undefined, -) => { const questionsForDeletion = questions.filter( ({ type, deleted }) => type && type !== "result" && deleted, ) as AnyTypedQuizQuestion[]; @@ -19,9 +13,7 @@ export const deleteTimeoutedQuestions = async ( updateSomeWorkBackend(true); await Promise.allSettled( - questionsForDeletion.map((question) => - DeleteFunction(questions, question, quiz), - ), + questionsForDeletion.map((question) => DeleteFunction(question)), ); updateSomeWorkBackend(false); From 3f82b7d97a7260e65f735ddbcbab2cf62ebd45de Mon Sep 17 00:00:00 2001 From: nflnkr Date: Mon, 26 Feb 2024 12:45:56 +0300 Subject: [PATCH 2/6] fix textfield ts(2590) --- .../Form/FormDraggableList/QuestionPageCard.tsx | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/pages/Questions/Form/FormDraggableList/QuestionPageCard.tsx b/src/pages/Questions/Form/FormDraggableList/QuestionPageCard.tsx index 8361ff5d..49886313 100644 --- a/src/pages/Questions/Form/FormDraggableList/QuestionPageCard.tsx +++ b/src/pages/Questions/Form/FormDraggableList/QuestionPageCard.tsx @@ -19,10 +19,11 @@ import { IconButton, InputAdornment, Paper, - TextField, + TextField as MuiTextField, Typography, useMediaQuery, useTheme, + TextFieldProps, } from "@mui/material"; import { copyQuestion, @@ -33,7 +34,7 @@ import { updateUntypedQuestion, } from "@root/questions/actions"; import CustomTextField from "@ui_kit/CustomTextField"; -import { useRef, useState } from "react"; +import { FC, useRef, useState } from "react"; import type { DraggableProvidedDragHandleProps } from "react-beautiful-dnd"; import { useDebouncedCallback } from "use-debounce"; import type { @@ -55,6 +56,8 @@ import { SignalCellularNullOutlined, } from "@mui/icons-material"; +const TextField = MuiTextField as unknown as FC; + interface Props { question: AnyTypedQuizQuestion | UntypedQuizQuestion; questionIndex: number; @@ -136,8 +139,7 @@ export default function QuestionsPageCard({ placeholder={`Заголовок ${questionIndex + 1} вопроса`} defaultValue={question.title} onChange={({ target }) => { - if ((target.value, toString().length <= 225)) - setTitle(target.value); + if (target.value.length <= 225) setTitle(target.value); }} onFocus={handleInputFocus} onBlur={handleInputBlur} From 4f68ddfad5c185005ddee1f03b38dd9c6564a9f1 Mon Sep 17 00:00:00 2001 From: nflnkr Date: Mon, 26 Feb 2024 12:51:33 +0300 Subject: [PATCH 3/6] request queue dedupes requests by id --- src/pages/main.tsx | 1 - src/stores/questions.ts | 18 +++++------ src/stores/questions/actions.ts | 31 ++++++------------ src/stores/quizes/actions.ts | 11 +++---- src/stores/results/actions.ts | 57 +++++++++++++++++---------------- src/utils/requestQueue.ts | 10 ++++-- 6 files changed, 60 insertions(+), 68 deletions(-) diff --git a/src/pages/main.tsx b/src/pages/main.tsx index 9fd82a9f..d5bcf8d8 100755 --- a/src/pages/main.tsx +++ b/src/pages/main.tsx @@ -35,7 +35,6 @@ export default function Main({ sidebar, header, footer, Page }: Props) { const theme = useTheme(); const quiz = useCurrentQuiz(); const quizConfig = quiz?.config; - const { questions } = useQuestionsStore(); const { editQuizId } = useQuizStore(); const currentStep = useQuizStore((state) => state.currentStep); const { isTestServer } = useDomainDefine(); diff --git a/src/stores/questions.ts b/src/stores/questions.ts index 1c19d6ee..810d2712 100644 --- a/src/stores/questions.ts +++ b/src/stores/questions.ts @@ -380,15 +380,15 @@ export const findQuestionById = (quizId: number) => { let found = null; questionStore .getState() - ["listQuestions"][quizId].some( - (quiz: AnyTypedQuizQuestion, index: number) => { - if (quiz.backendId === quizId) { - found = { quiz, index }; - return true; - } - return false; - }, - ); + [ + "listQuestions" + ][quizId].some((quiz: AnyTypedQuizQuestion, index: number) => { + if (quiz.backendId === quizId) { + found = { quiz, index }; + return true; + } + return false; + }); return found; }; diff --git a/src/stores/questions/actions.ts b/src/stores/questions/actions.ts index 4cee7725..9d577920 100644 --- a/src/stores/questions/actions.ts +++ b/src/stores/questions/actions.ts @@ -13,21 +13,14 @@ import { UntypedQuizQuestion, createQuestionVariant, } from "@model/questionTypes/shared"; -import { defaultQuestionByType } from "../../constants/default"; import { produce } from "immer"; import { nanoid } from "nanoid"; import { enqueueSnackbar } from "notistack"; +import { defaultQuestionByType } from "../../constants/default"; import { isAxiosCanceledError } from "../../utils/isAxiosCanceledError"; -import { RequestQueue } from "../../utils/requestQueue"; -import { updateRootContentId } from "@root/quizes/actions"; -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"; import { replaceEmptyLinesToSpace } from "../../utils/replaceEmptyLinesToSpace"; -import { useQuizPreviewStore } from "@root/quizPreview"; -import { useQuizStore } from "@root/quizes/store"; +import { RequestQueue } from "../../utils/requestQueue"; +import { QuestionsStore, useQuestionsStore } from "./store"; export const setQuestions = (questions: RawQuestion[] | null | undefined) => setProducedState( @@ -244,9 +237,7 @@ export const cancelQuestionDeletion = (questionId: string) => }, ); -const REQUEST_DEBOUNCE = 200; const requestQueue = new RequestQueue(); -let requestTimeoutId: ReturnType; export const updateQuestion = async ( questionId: string, @@ -275,8 +266,6 @@ export const updateQuestion = async ( }, ); - // clearTimeout(requestTimeoutId); - const request = async () => { const q = useQuestionsStore.getState().questions.find((q) => q.id === questionId) || @@ -321,9 +310,7 @@ export const updateQuestion = async ( return; } - // requestTimeoutId = setTimeout(() => { - requestQueue.enqueue(request); - // }, REQUEST_DEBOUNCE); + requestQueue.enqueue(`updateQuestion-${questionId}`, request); }; export const addQuestionVariant = (questionId: string) => { @@ -453,7 +440,7 @@ export const createTypedQuestion = async ( questionId: string, type: QuestionType, ) => - requestQueue.enqueue(async () => { + requestQueue.enqueue(`createTypedQuestion-${questionId}`, async () => { const questions = useQuestionsStore.getState().questions; const question = questions.find((q) => q.id === questionId); if (!question) return; @@ -501,7 +488,7 @@ export const createTypedQuestion = async ( }); export const deleteQuestion = async (questionId: string) => - requestQueue.enqueue(async () => { + requestQueue.enqueue(`deleteQuestion-${questionId}`, async () => { const question = useQuestionsStore .getState() .questions.find((q) => q.id === questionId); @@ -525,7 +512,7 @@ export const deleteQuestion = async (questionId: string) => }); export const copyQuestion = async (questionId: string, quizId: number) => - requestQueue.enqueue(async () => { + requestQueue.enqueue(`copyQuestion-${quizId}-${questionId}`, async () => { const question = useQuestionsStore .getState() .questions.find((q) => q.id === questionId); @@ -585,7 +572,7 @@ export const copyQuestion = async (questionId: string, quizId: number) => } }); -function setProducedState( +function setProducedState( recipe: (state: QuestionsStore) => void, action?: A, ) { @@ -635,7 +622,7 @@ export const createResult = async ( quizId: number | null | undefined, parentContentId?: string, ) => - requestQueue.enqueue(async () => { + requestQueue.enqueue(`createResult-${quizId}`, async () => { if (!quizId || !parentContentId) { console.error( "Нет данных для создания результата. quizId: ", diff --git a/src/stores/quizes/actions.ts b/src/stores/quizes/actions.ts index b703f97e..05279454 100644 --- a/src/stores/quizes/actions.ts +++ b/src/stores/quizes/actions.ts @@ -3,15 +3,14 @@ import { devlog, getMessageFromFetchError } from "@frontend/kitui"; import { quizToEditQuizRequest } from "@model/quiz/edit"; import { Quiz, RawQuiz, rawQuizToQuiz } from "@model/quiz/quiz"; import { QuizConfig, maxQuizSetupSteps } from "@model/quizSettings"; +import { createUntypedQuestion, updateQuestion } from "@root/questions/actions"; +import { useQuestionsStore } from "@root/questions/store"; import { produce } from "immer"; import { enqueueSnackbar } from "notistack"; import { NavigateFunction } from "react-router-dom"; import { isAxiosCanceledError } from "../../utils/isAxiosCanceledError"; import { RequestQueue } from "../../utils/requestQueue"; import { QuizStore, useQuizStore } from "./store"; -import { createUntypedQuestion, updateQuestion } from "@root/questions/actions"; -import { useCurrentQuiz } from "./hooks"; -import { useQuestionsStore } from "@root/questions/store"; export const setEditQuizId = (quizId: number | null) => setProducedState( @@ -157,7 +156,7 @@ export const updateQuiz = ( clearTimeout(requestTimeoutId); requestTimeoutId = setTimeout(async () => { requestQueue - .enqueue(async () => { + .enqueue(`updateQuiz-${quizId}`, async () => { const quiz = useQuizStore .getState() .quizes.find((q) => q.id === quizId); @@ -178,7 +177,7 @@ export const updateQuiz = ( }; export const createQuiz = async (navigate: NavigateFunction) => - requestQueue.enqueue(async () => { + requestQueue.enqueue("createQuiz", async () => { try { const rawQuiz = await quizApi.create(); const quiz = rawQuizToQuiz(rawQuiz); @@ -196,7 +195,7 @@ export const createQuiz = async (navigate: NavigateFunction) => }); export const deleteQuiz = async (quizId: string) => - requestQueue.enqueue(async () => { + requestQueue.enqueue(`deleteQuiz-${quizId}`, async () => { const quiz = useQuizStore.getState().quizes.find((q) => q.id === quizId); if (!quiz) return; diff --git a/src/stores/results/actions.ts b/src/stores/results/actions.ts index 62e6fe61..551639c4 100644 --- a/src/stores/results/actions.ts +++ b/src/stores/results/actions.ts @@ -33,7 +33,7 @@ const removeResult = (resultId: string) => }); export const deleteResult = async (resultId: number) => - requestQueue.enqueue(async () => { + requestQueue.enqueue(`deleteResult-${resultId}`, async () => { const result = useResultStore .getState() .results.find((r) => r.id === resultId); @@ -54,32 +54,35 @@ export const obsolescenceResult = async ( resultId: string, editQuizId: number, ) => - requestQueue.enqueue(async () => { - const result = useResultStore - .getState() - .results.find((r) => r.id === resultId); - if (!result) return; - if (result.new === false) return; - let lossDebouncer: null | ReturnType = null; - let lossId: string[] = [] as string[]; - if (!lossId.includes(resultId)) lossId.push(resultId); - if (typeof lossDebouncer === "number") clearTimeout(lossDebouncer); - lossDebouncer = setTimeout(async () => { - //стреляем на лишение новизны - try { - await resultApi.obsolescence(lossId); - //сбрасываем массив - lossId = []; - } catch (error) { - devlog("Error", error); + requestQueue.enqueue( + `obsolescenceResult-${resultId}-${editQuizId}`, + async () => { + const result = useResultStore + .getState() + .results.find((r) => r.id === resultId); + if (!result) return; + if (result.new === false) return; + let lossDebouncer: null | ReturnType = null; + let lossId: string[] = [] as string[]; + if (!lossId.includes(resultId)) lossId.push(resultId); + if (typeof lossDebouncer === "number") clearTimeout(lossDebouncer); + lossDebouncer = setTimeout(async () => { + //стреляем на лишение новизны + try { + await resultApi.obsolescence(lossId); + //сбрасываем массив + lossId = []; + } catch (error) { + devlog("Error", error); - const message = getMessageFromFetchError(error) ?? ""; - enqueueSnackbar(`Ошибка. ${message}`); - } - }, 3000); - const resultList = await resultApi.getList(editQuizId); - setResults(resultList); - }); + const message = getMessageFromFetchError(error) ?? ""; + enqueueSnackbar(`Ошибка. ${message}`); + } + }, 3000); + const resultList = await resultApi.getList(editQuizId); + setResults(resultList); + }, + ); export const answerResultListExport = async ( editQuizId: number, @@ -111,7 +114,7 @@ export const answerResultListExport = async ( download(); }; -function setProducedState( +function setProducedState( recipe: (state: ResultStore) => void, action?: A, ) { diff --git a/src/utils/requestQueue.ts b/src/utils/requestQueue.ts index 5fd1c67a..49a5f6b8 100644 --- a/src/utils/requestQueue.ts +++ b/src/utils/requestQueue.ts @@ -1,14 +1,15 @@ -export class RequestQueue { +export class RequestQueue { private pendingPromise = false; private items: Array<{ + id: IdType; action: () => Promise; resolve: (value: T) => void; reject: (reason?: any) => void; }> = []; - enqueue(action: () => Promise) { + enqueue(id: IdType, action: () => Promise) { return new Promise((resolve, reject) => { - this.items.push({ action, resolve, reject }); + this.items.push({ action, resolve, reject, id }); this.dequeue(); }); } @@ -19,6 +20,9 @@ export class RequestQueue { const item = this.items.shift(); if (!item) return; + // remove tasks with same id since they are outdated + this.items = this.items.filter((i) => i.id !== item.id); + try { this.pendingPromise = true; const payload = await item.action(); From 9a4f4e8a6689aea5e42f2c21ea83d1bc5da8744d Mon Sep 17 00:00:00 2001 From: nflnkr Date: Mon, 26 Feb 2024 12:52:52 +0300 Subject: [PATCH 4/6] add questions state rollback on request error --- src/stores/questions/actions.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/stores/questions/actions.ts b/src/stores/questions/actions.ts index 9d577920..b8a7b6fe 100644 --- a/src/stores/questions/actions.ts +++ b/src/stores/questions/actions.ts @@ -267,6 +267,8 @@ export const updateQuestion = async ( ); const request = async () => { + const rollbackQuestions = useQuestionsStore.getState(); + const q = useQuestionsStore.getState().questions.find((q) => q.id === questionId) || useQuestionsStore @@ -300,6 +302,8 @@ export const updateQuestion = async ( } catch (error) { if (isAxiosCanceledError(error)) return; + useQuestionsStore.setState(rollbackQuestions); + devlog("Error editing question", { error, questionId }); enqueueSnackbar("Не удалось сохранить вопрос"); } From bf1d150954ebcae4f65017abc9a35f9ac018bb5b Mon Sep 17 00:00:00 2001 From: nflnkr Date: Mon, 26 Feb 2024 14:37:54 +0300 Subject: [PATCH 5/6] fix rollback --- src/stores/questions/actions.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/stores/questions/actions.ts b/src/stores/questions/actions.ts index b8a7b6fe..99202212 100644 --- a/src/stores/questions/actions.ts +++ b/src/stores/questions/actions.ts @@ -238,12 +238,15 @@ export const cancelQuestionDeletion = (questionId: string) => ); const requestQueue = new RequestQueue(); +let rollbackQuestions: ReturnType; export const updateQuestion = async ( questionId: string, updateFn: (question: T) => void, skipQueue = false, ) => { + if (!rollbackQuestions) rollbackQuestions = useQuestionsStore.getState(); + setProducedState( (state) => { const question = @@ -267,8 +270,6 @@ export const updateQuestion = async ( ); const request = async () => { - const rollbackQuestions = useQuestionsStore.getState(); - const q = useQuestionsStore.getState().questions.find((q) => q.id === questionId) || useQuestionsStore @@ -282,6 +283,7 @@ export const updateQuestion = async ( const response = await questionApi.edit( questionToEditQuestionRequest(replaceEmptyLinesToSpace(q)), ); + rollbackQuestions = useQuestionsStore.getState(); //Если мы делаем листочек веточкой - удаляем созданный к нему результ const questionResult = useQuestionsStore From 312f3665015241d95cf110c9f7d7ecf12eb0b827 Mon Sep 17 00:00:00 2001 From: nflnkr Date: Mon, 26 Feb 2024 18:56:07 +0300 Subject: [PATCH 6/6] inputs receive value from state remove unused code --- .../AnswerDraggableList/AnswerItem.tsx | 34 +++----- .../Questions/DataOptions/settingData.tsx | 17 +--- .../Questions/DropDown/settingDropDown.tsx | 26 ++---- src/pages/Questions/Emoji/settingEmoji.tsx | 18 +---- .../FormDraggableList/QuestionPageCard.tsx | 6 +- .../SettingOptionsAndPict.tsx | 20 +---- .../OptionsPicture/settingOpytionsPict.tsx | 20 +---- .../Questions/OwnTextField/OwnTextField.tsx | 12 ++- .../OwnTextField/settingTextField.tsx | 23 +----- .../Questions/PageOptions/PageOptions.tsx | 18 +---- .../PageOptions/SettingPageOptions.tsx | 17 +--- .../Questions/RatingOptions/RatingOptions.tsx | 79 ++++++++----------- .../Questions/RatingOptions/settingRating.tsx | 10 +-- .../Questions/SliderOptions/settingSlider.tsx | 17 +--- src/pages/Questions/SwitchQuestionsPage.tsx | 3 + .../Questions/UploadFile/settingUpload.tsx | 17 +--- .../answerOptions/responseSettings.tsx | 17 +--- src/pages/Questions/helpQuestions.tsx | 7 +- src/pages/startPage/EditPage.tsx | 2 +- 19 files changed, 86 insertions(+), 277 deletions(-) diff --git a/src/pages/Questions/AnswerDraggableList/AnswerItem.tsx b/src/pages/Questions/AnswerDraggableList/AnswerItem.tsx index 3b1bfd92..67dcba5a 100644 --- a/src/pages/Questions/AnswerDraggableList/AnswerItem.tsx +++ b/src/pages/Questions/AnswerDraggableList/AnswerItem.tsx @@ -1,29 +1,24 @@ -import { MessageIcon } from "@icons/messagIcon"; import { PointsIcon } from "@icons/questionsPage/PointsIcon"; import { DeleteIcon } from "@icons/questionsPage/deleteIcon"; -import { TextareaAutosize } from "@mui/base/TextareaAutosize"; import { Box, FormControl, IconButton, InputAdornment, - Popover, TextField as MuiTextField, + TextFieldProps, useMediaQuery, useTheme, - TextFieldProps, } from "@mui/material"; import { addQuestionVariant, deleteQuestionVariant, setQuestionVariantField, } from "@root/questions/actions"; +import { enqueueSnackbar } from "notistack"; import type { ChangeEvent, FC, KeyboardEvent, ReactNode } from "react"; -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"; const TextField = MuiTextField as unknown as FC; @@ -48,21 +43,6 @@ export const AnswerItem = ({ }: AnswerItemProps) => { const theme = useTheme(); const isTablet = useMediaQuery(theme.breakpoints.down(790)); - const [isOpen, setIsOpen] = useState(false); - const [anchorEl, setAnchorEl] = useState(null); - const [inputValue, setInputValue] = useState(variant.answer); - const setQuestionVariantAnswer = useDebouncedCallback((value) => { - setQuestionVariantField(questionId, variant.id, "answer", value); - }, 200); - - const handleClick = (event: React.MouseEvent) => { - setAnchorEl(event.currentTarget); - setIsOpen(true); - }; - - const handleClose = () => { - setIsOpen(false); - }; return ( @@ -80,16 +60,20 @@ export const AnswerItem = ({ }} > ) => { if (target.value.length <= 1000) { - setInputValue(target.value); + setQuestionVariantField( + questionId, + variant.id, + "answer", + target.value || " ", + ); } - setQuestionVariantAnswer(target.value || " "); }} onKeyDown={(event: KeyboardEvent) => { if (disableKeyDown) { diff --git a/src/pages/Questions/DataOptions/settingData.tsx b/src/pages/Questions/DataOptions/settingData.tsx index 2ac9b07a..dfb7382f 100644 --- a/src/pages/Questions/DataOptions/settingData.tsx +++ b/src/pages/Questions/DataOptions/settingData.tsx @@ -1,15 +1,6 @@ -import { - Box, - Tooltip, - Typography, - useMediaQuery, - useTheme, -} from "@mui/material"; -import { setQuestionInnerName, updateQuestion } from "@root/questions/actions"; +import { Box, Typography, useMediaQuery, useTheme } from "@mui/material"; +import { updateQuestion } from "@root/questions/actions"; import CustomCheckbox from "@ui_kit/CustomCheckbox"; -import CustomTextField from "@ui_kit/CustomTextField"; -import { useDebouncedCallback } from "use-debounce"; -import InfoIcon from "../../../assets/icons/InfoIcon"; import type { QuizQuestionDate } from "../../../model/questionTypes/date"; type SettingsDataProps = { @@ -22,10 +13,6 @@ export default function SettingsData({ question }: SettingsDataProps) { const isMobile = useMediaQuery(theme.breakpoints.down(790)); const isFigmaTablte = useMediaQuery(theme.breakpoints.down(990)); - const setInnerName = useDebouncedCallback((value) => { - setQuestionInnerName(question.id, value); - }, 200); - return ( { - setQuestionInnerName(question.id, value); - }, 200); - - const debounceAnswer = useDebouncedCallback((value) => { + const setContentDefault = (value: string) => { updateQuestion(question.id, (question) => { if (question.type !== "select") return; question.content.default = value; }); - }, 200); + }; return ( <> @@ -98,9 +86,9 @@ export default function SettingDropDown({ question }: SettingDropDownProps) { debounceAnswer(target.value)} + onChange={({ target }) => setContentDefault(target.value)} /> @@ -178,7 +166,7 @@ export default function SettingDropDown({ question }: SettingDropDownProps) { debounceAnswer(target.value)} + onChange={({ target }) => setContentDefault(target.value)} /> {/*{question.content.innerNameCheck && (*/} diff --git a/src/pages/Questions/Emoji/settingEmoji.tsx b/src/pages/Questions/Emoji/settingEmoji.tsx index 24de22c9..0fa8f138 100644 --- a/src/pages/Questions/Emoji/settingEmoji.tsx +++ b/src/pages/Questions/Emoji/settingEmoji.tsx @@ -1,15 +1,6 @@ -import { - Box, - Tooltip, - Typography, - useMediaQuery, - useTheme, -} from "@mui/material"; -import { setQuestionInnerName, updateQuestion } from "@root/questions/actions"; +import { Box, Typography, useMediaQuery, useTheme } from "@mui/material"; +import { updateQuestion } from "@root/questions/actions"; import CustomCheckbox from "@ui_kit/CustomCheckbox"; -import CustomTextField from "@ui_kit/CustomTextField"; -import { useDebouncedCallback } from "use-debounce"; -import InfoIcon from "../../../assets/icons/InfoIcon"; import type { QuizQuestionEmoji } from "../../../model/questionTypes/emoji"; type SettingEmojiProps = { @@ -19,14 +10,9 @@ type SettingEmojiProps = { export default function SettingEmoji({ question }: SettingEmojiProps) { const theme = useTheme(); const isWrappColumn = useMediaQuery(theme.breakpoints.down(980)); - const isTablet = useMediaQuery(theme.breakpoints.down(985)); const isFigmaTablte = useMediaQuery(theme.breakpoints.down(990)); const isMobile = useMediaQuery(theme.breakpoints.down(790)); - const setInnerName = useDebouncedCallback((value) => { - setQuestionInnerName(question.id, value); - }, 200); - return ( { + const setTitle = (title: string) => { const updateQuestionFn = question.type === null ? updateUntypedQuestion : updateQuestion; updateQuestionFn(question.id, (question) => { question.title = title; }); - }, 200); + }; const handleInputFocus = () => { setIsTextFieldtActive(true); @@ -137,7 +137,7 @@ export default function QuestionsPageCard({ > { if (target.value.length <= 225) setTitle(target.value); }} diff --git a/src/pages/Questions/OptionsAndPicture/SettingOptionsAndPict.tsx b/src/pages/Questions/OptionsAndPicture/SettingOptionsAndPict.tsx index 31e77cb5..1b3e432d 100644 --- a/src/pages/Questions/OptionsAndPicture/SettingOptionsAndPict.tsx +++ b/src/pages/Questions/OptionsAndPicture/SettingOptionsAndPict.tsx @@ -1,15 +1,7 @@ -import { - Box, - Tooltip, - Typography, - useMediaQuery, - useTheme, -} from "@mui/material"; -import { setQuestionInnerName, updateQuestion } from "@root/questions/actions"; +import { Box, Typography, useMediaQuery, useTheme } from "@mui/material"; +import { updateQuestion } from "@root/questions/actions"; import CustomCheckbox from "@ui_kit/CustomCheckbox"; import CustomTextField from "@ui_kit/CustomTextField"; -import { useDebouncedCallback } from "use-debounce"; -import InfoIcon from "../../../assets/icons/InfoIcon"; import type { QuizQuestionVarImg } from "../../../model/questionTypes/varimg"; type SettingOptionsAndPictProps = { @@ -24,17 +16,13 @@ export default function SettingOptionsAndPict({ const isFigmaTablte = useMediaQuery(theme.breakpoints.down(990)); const isMobile = useMediaQuery(theme.breakpoints.down(680)); - const setReplText = useDebouncedCallback((replText) => { + const setReplText = (replText: string) => { updateQuestion(question.id, (question) => { if (question.type !== "varimg") return; question.content.replText = replText; }); - }, 200); - - const setDescription = useDebouncedCallback((value) => { - setQuestionInnerName(question.id, value); - }, 200); + }; return ( <> diff --git a/src/pages/Questions/OptionsPicture/settingOpytionsPict.tsx b/src/pages/Questions/OptionsPicture/settingOpytionsPict.tsx index 4a7c04b6..fb21f1c1 100644 --- a/src/pages/Questions/OptionsPicture/settingOpytionsPict.tsx +++ b/src/pages/Questions/OptionsPicture/settingOpytionsPict.tsx @@ -1,18 +1,12 @@ import { Box, Button, - Tooltip, Typography, useMediaQuery, useTheme, } from "@mui/material"; -import { setQuestionInnerName, updateQuestion } from "@root/questions/actions"; +import { updateQuestion } from "@root/questions/actions"; import CustomCheckbox from "@ui_kit/CustomCheckbox"; -import CustomTextField from "@ui_kit/CustomTextField"; -import { useDebouncedCallback } from "use-debounce"; -import InfoIcon from "../../../assets/icons/InfoIcon"; -import FormatIcon1 from "../../../assets/icons/questionsPage/FormatIcon1"; -import FormatIcon2 from "../../../assets/icons/questionsPage/FormatIcon2"; import ProportionsIcon11 from "../../../assets/icons/questionsPage/ProportionsIcon11"; import ProportionsIcon12 from "../../../assets/icons/questionsPage/ProportionsIcon12"; import ProportionsIcon21 from "../../../assets/icons/questionsPage/ProportionsIcon21"; @@ -43,18 +37,6 @@ export default function SettingOpytionsPict({ const isMobile = useMediaQuery(theme.breakpoints.down(790)); const isFigmaTablte = useMediaQuery(theme.breakpoints.down(990)); - const debounced = useDebouncedCallback((value) => { - setQuestionInnerName(question.id, value); - }, 200); - - const updateProportions = (proportions: Proportion) => { - updateQuestion(question.id, (question) => { - if (question.type !== "images") return; - - question.content.xy = proportions; - }); - }; - return ( <> { + const setPlaceholder = (value: string) => { updateQuestion(question.id, (question) => { if (question.type !== "text") return; question.content.placeholder = value; }); - }, 200); + }; const SSHC = (data: string) => { setSwitchState(data); @@ -60,7 +58,7 @@ export default function OwnTextField({ > setPlaceholder(target.value)} sx={{ maxWidth: isFigmaTablte ? "549px" : "640px", diff --git a/src/pages/Questions/OwnTextField/settingTextField.tsx b/src/pages/Questions/OwnTextField/settingTextField.tsx index 9f4b5960..42686c6c 100644 --- a/src/pages/Questions/OwnTextField/settingTextField.tsx +++ b/src/pages/Questions/OwnTextField/settingTextField.tsx @@ -1,21 +1,6 @@ -import { - Box, - FormControl, - FormControlLabel, - Radio, - RadioGroup, - Tooltip, - Typography, - useMediaQuery, - useTheme, -} from "@mui/material"; -import { setQuestionInnerName, updateQuestion } from "@root/questions/actions"; +import { Box, Typography, useMediaQuery, useTheme } from "@mui/material"; +import { updateQuestion } from "@root/questions/actions"; import CustomCheckbox from "@ui_kit/CustomCheckbox"; -import CustomTextField from "@ui_kit/CustomTextField"; -import CheckedIcon from "@ui_kit/RadioCheck"; -import CheckIcon from "@ui_kit/RadioIcon"; -import { useDebouncedCallback } from "use-debounce"; -import InfoIcon from "../../../assets/icons/InfoIcon"; import type { QuizQuestionText } from "../../../model/questionTypes/text"; type SettingTextFieldProps = { @@ -38,10 +23,6 @@ export default function SettingTextField({ question }: SettingTextFieldProps) { const isMobile = useMediaQuery(theme.breakpoints.down(790)); const isFigmaTablte = useMediaQuery(theme.breakpoints.down(990)); - const debounced = useDebouncedCallback((value) => { - setQuestionInnerName(question.id, value); - }, 200); - return ( (false); - const setText = useDebouncedCallback((value) => { + const setText = (value: string) => { updateQuestion(question.id, (question) => { if (question.type !== "page") return; question.content.text = value; }); - }, 200); - - const [openDelete, setOpenDelete] = useState(false); - - const openedModal = () => { - updateDesireToOpenABranchingModal(question.content.id); - }; - - const SSHC = (data: string) => { - setSwitchState(data); }; return ( @@ -76,7 +64,7 @@ export default function PageOptions({ disableInput, question }: Props) { setText(target.value)} maxLength={50} /> diff --git a/src/pages/Questions/PageOptions/SettingPageOptions.tsx b/src/pages/Questions/PageOptions/SettingPageOptions.tsx index 3b374395..a5cb8233 100644 --- a/src/pages/Questions/PageOptions/SettingPageOptions.tsx +++ b/src/pages/Questions/PageOptions/SettingPageOptions.tsx @@ -1,16 +1,5 @@ -import { - Box, - Tooltip, - Typography, - useMediaQuery, - useTheme, -} from "@mui/material"; -import CustomCheckbox from "@ui_kit/CustomCheckbox"; -import CustomTextField from "@ui_kit/CustomTextField"; -import { useDebouncedCallback } from "use-debounce"; -import InfoIcon from "../../../assets/icons/InfoIcon"; +import { Box, Typography, useMediaQuery, useTheme } from "@mui/material"; import type { QuizQuestionPage } from "../../../model/questionTypes/page"; -import { setQuestionInnerName, updateQuestion } from "@root/questions/actions"; type SettingPageOptionsProps = { question: QuizQuestionPage; @@ -22,10 +11,6 @@ export default function SettingPageOptions({ const theme = useTheme(); const isMobile = useMediaQuery(theme.breakpoints.down(790)); - const setInnerName = useDebouncedCallback((value) => { - setQuestionInnerName(question.id, value); - }, 200); - return ( ; interface Props { question: QuizQuestionRating; @@ -37,43 +38,36 @@ export default function RatingOptions({ setOpenBranchingPage, }: Props) { const [switchState, setSwitchState] = useState("setting"); - const [negativeText, setNegativeText] = useState(""); - const [positiveText, setPositiveText] = useState(""); const [negativeTextWidth, setNegativeTextWidth] = useState(0); const [positiveTextWidth, setPositiveTextWidth] = useState(0); - const quizId = Number(useParams().quizId); const theme = useTheme(); const isMobile = useMediaQuery(theme.breakpoints.down(790)); const negativeRef = useRef(null); const positiveRef = useRef(null); - const debounceNegativeDescription = useDebouncedCallback((value) => { + const setNegativeDescription = (value: string) => { updateQuestion(question.id, (question) => { if (question.type !== "rating") return; question.content.ratingNegativeDescription = value.substring(0, 15); }); - }, 200); - const debouncePositiveDescription = useDebouncedCallback((value) => { + }; + + const setPositiveDescription = (value: string) => { updateQuestion(question.id, (question) => { if (question.type !== "rating") return; question.content.ratingPositiveDescription = value.substring(0, 15); }); - }, 200); + }; - useEffect(() => { - setNegativeText(question.content.ratingNegativeDescription); - setPositiveText(question.content.ratingPositiveDescription); - }, []); - - useEffect(() => { + useLayoutEffect(() => { setNegativeTextWidth(negativeRef.current?.offsetWidth || 0); - }, [negativeText]); + }, [question.content.ratingNegativeDescription]); - useEffect(() => { + useLayoutEffect(() => { setPositiveTextWidth(positiveRef.current?.offsetWidth || 0); - }, [positiveText]); + }, [question.content.ratingPositiveDescription]); const buttonRatingForm: ButtonRatingFrom[] = [ { @@ -221,21 +215,17 @@ export default function RatingOptions({ fontSize: "16px", }} > - {negativeText} + {question.content.ratingNegativeDescription} { + onChange={({ target }) => { if (target.value.length <= 15) { - setNegativeText(target.value); - debounceNegativeDescription(target.value); + setNegativeDescription(target.value); } }} - onBlur={({ target }: { target: HTMLInputElement }) => - debounceNegativeDescription(target.value) - } + onBlur={({ target }) => setNegativeDescription(target.value)} sx={{ width: negativeTextWidth + 10 + "px", maxWidth: isMobile ? "140px" : "230px", @@ -279,20 +269,17 @@ export default function RatingOptions({ fontSize: "16px", }} > - {positiveText} + {question.content.ratingPositiveDescription} { + onChange={({ target }) => { if (target.value.length <= 15) { - setPositiveText(target.value); - debouncePositiveDescription(target.value); + setPositiveDescription(target.value); } }} - onBlur={({ target }: { target: HTMLInputElement }) => - debouncePositiveDescription(target.value) - } + onBlur={({ target }) => setPositiveDescription(target.value)} sx={{ width: positiveTextWidth + 10 + "px", maxWidth: isMobile ? "140px" : "230px", diff --git a/src/pages/Questions/RatingOptions/settingRating.tsx b/src/pages/Questions/RatingOptions/settingRating.tsx index cb37e5b8..966b9d21 100644 --- a/src/pages/Questions/RatingOptions/settingRating.tsx +++ b/src/pages/Questions/RatingOptions/settingRating.tsx @@ -3,16 +3,12 @@ import { Box, ButtonBase, Slider, - Tooltip, Typography, useMediaQuery, useTheme, } from "@mui/material"; -import { setQuestionInnerName, updateQuestion } from "@root/questions/actions"; +import { updateQuestion } from "@root/questions/actions"; import CustomCheckbox from "@ui_kit/CustomCheckbox"; -import CustomTextField from "@ui_kit/CustomTextField"; -import { useDebouncedCallback } from "use-debounce"; -import InfoIcon from "../../../assets/icons/InfoIcon"; import FlagIcon from "../../../assets/icons/questionsPage/FlagIcon"; import StarIconMini from "../../../assets/icons/questionsPage/StarIconMini"; import HashtagIcon from "../../../assets/icons/questionsPage/hashtagIcon"; @@ -32,10 +28,6 @@ export default function SettingSlider({ question }: SettingSliderProps) { const isMobile = useMediaQuery(theme.breakpoints.down(790)); const isWrappColumn = useMediaQuery(theme.breakpoints.down(980)); - const setInnerName = useDebouncedCallback((value) => { - setQuestionInnerName(question.id, value); - }, 200); - const buttonRatingForm: ButtonRatingFrom[] = [ { name: "star", icon: }, { name: "trophie", icon: }, diff --git a/src/pages/Questions/SliderOptions/settingSlider.tsx b/src/pages/Questions/SliderOptions/settingSlider.tsx index 4ad01637..045bc1a0 100644 --- a/src/pages/Questions/SliderOptions/settingSlider.tsx +++ b/src/pages/Questions/SliderOptions/settingSlider.tsx @@ -1,15 +1,6 @@ -import { - Box, - Tooltip, - Typography, - useMediaQuery, - useTheme, -} from "@mui/material"; -import { setQuestionInnerName, updateQuestion } from "@root/questions/actions"; +import { Box, Typography, useMediaQuery, useTheme } from "@mui/material"; +import { updateQuestion } from "@root/questions/actions"; import CustomCheckbox from "@ui_kit/CustomCheckbox"; -import CustomTextField from "@ui_kit/CustomTextField"; -import { useDebouncedCallback } from "use-debounce"; -import InfoIcon from "../../../assets/icons/InfoIcon"; import type { QuizQuestionNumber } from "../../../model/questionTypes/number"; type SettingSliderProps = { @@ -22,10 +13,6 @@ export default function SettingSlider({ question }: SettingSliderProps) { const isFigmaTablte = useMediaQuery(theme.breakpoints.down(990)); const isMobile = useMediaQuery(theme.breakpoints.down(790)); - const setInnerName = useDebouncedCallback((value) => { - setQuestionInnerName(question.id, value); - }, 200); - return ( ); + case "result": + return null; + default: notReachable(question); } diff --git a/src/pages/Questions/UploadFile/settingUpload.tsx b/src/pages/Questions/UploadFile/settingUpload.tsx index 21a468fa..4bfdece1 100644 --- a/src/pages/Questions/UploadFile/settingUpload.tsx +++ b/src/pages/Questions/UploadFile/settingUpload.tsx @@ -1,15 +1,6 @@ -import { - Box, - Tooltip, - Typography, - useMediaQuery, - useTheme, -} from "@mui/material"; -import { setQuestionInnerName, updateQuestion } from "@root/questions/actions"; +import { Box, Typography, useMediaQuery, useTheme } from "@mui/material"; +import { updateQuestion } from "@root/questions/actions"; import CustomCheckbox from "@ui_kit/CustomCheckbox"; -import CustomTextField from "@ui_kit/CustomTextField"; -import { useDebouncedCallback } from "use-debounce"; -import InfoIcon from "../../../assets/icons/InfoIcon"; import type { QuizQuestionFile } from "../../../model/questionTypes/file"; type SettingsUploadProps = { @@ -20,10 +11,6 @@ export default function SettingsUpload({ question }: SettingsUploadProps) { const theme = useTheme(); const isMobile = useMediaQuery(theme.breakpoints.down(790)); - const setInnerName = useDebouncedCallback((value) => { - setQuestionInnerName(question.id, value); - }, 200); - return ( { - setQuestionInnerName(question.id, value); - }, 200); - return ( ("text"); - const updateQuestionHint = useDebouncedCallback((value) => { + const updateQuestionHint = (value: string) => { updateQuestion(question.id, (question) => { question.content.hint.text = value; }); - }, 200); + }; return ( updateQuestionHint(target.value || " ")} maxLength={100} /> diff --git a/src/pages/startPage/EditPage.tsx b/src/pages/startPage/EditPage.tsx index f810181d..83dd3801 100755 --- a/src/pages/startPage/EditPage.tsx +++ b/src/pages/startPage/EditPage.tsx @@ -59,7 +59,7 @@ export default function EditPage({ const quiz = useCurrentQuiz(); const { editQuizId } = useQuizStore(); const { questions } = useQuestionsStore(); - const { whyCantCreatePublic, showConfirmLeaveModal, nextStep } = useUiTools(); + const { showConfirmLeaveModal, nextStep } = useUiTools(); const theme = useTheme(); const navigate = useNavigate(); const currentStep = useQuizStore((state) => state.currentStep);