This commit is contained in:
Nastya 2023-12-04 16:33:43 +03:00
parent cceafeb615
commit cc20575a78
10 changed files with 843 additions and 604 deletions

93
package-lock.json generated

@ -40,6 +40,7 @@
"react": "^18.2.0",
"react-beautiful-dnd": "^13.1.1",
"react-cytoscapejs": "^2.0.0",
"react-datepicker": "^4.24.0",
"react-dnd": "^16.0.1",
"react-dnd-html5-backend": "^16.0.1",
"react-dom": "^18.2.0",
@ -61,7 +62,8 @@
"@emoji-mart/react": "^1.1.1",
"@types/cytoscape-popper": "^2.0.4",
"@types/react-beautiful-dnd": "^13.1.4",
"@types/react-cytoscapejs": "^1.2.5",
"@types/react-cytoscapejs": "^1.2.4",
"@types/react-datepicker": "^4.19.3",
"craco-alias": "^3.0.1",
"cypress": "^13.4.0"
}
@ -4524,6 +4526,18 @@
"@types/react": "*"
}
},
"node_modules/@types/react-datepicker": {
"version": "4.19.3",
"resolved": "https://registry.npmjs.org/@types/react-datepicker/-/react-datepicker-4.19.3.tgz",
"integrity": "sha512-85F9eKWu9fGiD9r4KVVMPYAdkJJswR3Wci9PvqplmB6T+D+VbUqPeKtifg96NZ4nEhufjehW+SX4JLrEWVplWw==",
"dev": true,
"dependencies": {
"@popperjs/core": "^2.9.2",
"@types/react": "*",
"date-fns": "^2.0.1",
"react-popper": "^2.2.5"
}
},
"node_modules/@types/react-dnd": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/@types/react-dnd/-/react-dnd-3.0.2.tgz",
@ -6374,6 +6388,11 @@
"integrity": "sha512-cOU9usZw8/dXIXKtwa8pM0OTJQuJkxMN6w30csNRUerHfeQ5R6U3kkU/FtJeIf3M202OHfY2U8ccInBG7/xogA==",
"license": "MIT"
},
"node_modules/classnames": {
"version": "2.3.2",
"resolved": "https://registry.npmjs.org/classnames/-/classnames-2.3.2.tgz",
"integrity": "sha512-CSbhY4cFEJRe6/GQzIk5qXZ4Jeg5pcsP7b5peFSDpffpe1cqjASH/n9UTjBwOp6XpMSTwQ8Za2K5V02ueA7Tmw=="
},
"node_modules/clean-css": {
"version": "5.3.1",
"resolved": "https://registry.npmjs.org/clean-css/-/clean-css-5.3.1.tgz",
@ -7438,6 +7457,21 @@
"node": ">=10"
}
},
"node_modules/date-fns": {
"version": "2.30.0",
"resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.30.0.tgz",
"integrity": "sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==",
"dependencies": {
"@babel/runtime": "^7.21.0"
},
"engines": {
"node": ">=0.11"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/date-fns"
}
},
"node_modules/dayjs": {
"version": "1.11.10",
"resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.10.tgz",
@ -15126,6 +15160,23 @@
"react": ">=15.0.0"
}
},
"node_modules/react-datepicker": {
"version": "4.24.0",
"resolved": "https://registry.npmjs.org/react-datepicker/-/react-datepicker-4.24.0.tgz",
"integrity": "sha512-2QUC2pP+x4v3Jp06gnFllxKsJR0yoT/K6y86ItxEsveTXUpsx+NBkChWXjU0JsGx/PL8EQnsxN0wHl4zdA1m/g==",
"dependencies": {
"@popperjs/core": "^2.11.8",
"classnames": "^2.2.6",
"date-fns": "^2.30.0",
"prop-types": "^15.7.2",
"react-onclickoutside": "^6.13.0",
"react-popper": "^2.3.0"
},
"peerDependencies": {
"react": "^16.9.0 || ^17 || ^18",
"react-dom": "^16.9.0 || ^17 || ^18"
}
},
"node_modules/react-dev-utils": {
"version": "12.0.1",
"resolved": "https://registry.npmjs.org/react-dev-utils/-/react-dev-utils-12.0.1.tgz",
@ -15308,6 +15359,38 @@
"integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==",
"license": "MIT"
},
"node_modules/react-onclickoutside": {
"version": "6.13.0",
"resolved": "https://registry.npmjs.org/react-onclickoutside/-/react-onclickoutside-6.13.0.tgz",
"integrity": "sha512-ty8So6tcUpIb+ZE+1HAhbLROvAIJYyJe/1vRrrcmW+jLsaM+/powDRqxzo6hSh9CuRZGSL1Q8mvcF5WRD93a0A==",
"funding": {
"type": "individual",
"url": "https://github.com/Pomax/react-onclickoutside/blob/master/FUNDING.md"
},
"peerDependencies": {
"react": "^15.5.x || ^16.x || ^17.x || ^18.x",
"react-dom": "^15.5.x || ^16.x || ^17.x || ^18.x"
}
},
"node_modules/react-popper": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/react-popper/-/react-popper-2.3.0.tgz",
"integrity": "sha512-e1hj8lL3uM+sgSR4Lxzn5h1GxBlpa4CQz0XLF8kx4MDrDRWY0Ena4c97PUeSX9i5W3UAfDP0z0FXCTQkoXUl3Q==",
"dependencies": {
"react-fast-compare": "^3.0.1",
"warning": "^4.0.2"
},
"peerDependencies": {
"@popperjs/core": "^2.0.0",
"react": "^16.8.0 || ^17 || ^18",
"react-dom": "^16.8.0 || ^17 || ^18"
}
},
"node_modules/react-popper/node_modules/react-fast-compare": {
"version": "3.2.2",
"resolved": "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-3.2.2.tgz",
"integrity": "sha512-nsO+KSNgo1SbJqJEYRE9ERzo7YtYbou/OqjSQKxV7jcKox7+usiUVZOAC+XnDOABXggQTno0Y1CpVnuWEc1boQ=="
},
"node_modules/react-redux": {
"version": "7.2.9",
"resolved": "https://registry.npmjs.org/react-redux/-/react-redux-7.2.9.tgz",
@ -18037,6 +18120,14 @@
"makeerror": "1.0.12"
}
},
"node_modules/warning": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/warning/-/warning-4.0.3.tgz",
"integrity": "sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==",
"dependencies": {
"loose-envify": "^1.0.0"
}
},
"node_modules/watchpack": {
"version": "2.4.0",
"resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.0.tgz",

@ -1,7 +1,7 @@
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,

@ -1,4 +1,4 @@
import { useEffect, useRef, useState } from "react";
import { useEffect, useLayoutEffect, useRef, useState } from "react";
import Cytoscape from "cytoscape";
import CytoscapeComponent from "react-cytoscapejs";
import popper from "cytoscape-popper";
@ -122,6 +122,9 @@ export const CsComponent = ({
const crossesContainer = useRef<HTMLDivElement | null>(null);
const gearsContainer = useRef<HTMLDivElement | null>(null);
// useLayoutEffect(() => {
// updateOpenedModalSettingsId()
// }, [])
useEffect(() => {
if (modalQuestionTargetContentId.length !== 0 && modalQuestionParentContentId.length !== 0) {
addNode({ parentNodeContentId: modalQuestionParentContentId, targetNodeContentId: modalQuestionTargetContentId })
@ -166,8 +169,8 @@ export const CsComponent = ({
})
//Если детей больше 1 - предупреждаем стор вопросов об открытии модалки ветвления
if (parentNodeChildren > 1) {
updateOpenedModalSettingsId(parentNodeContentId)
if (parentNodeChildren >= 1) {
updateOpenedModalSettingsId(targetQuestion.content.id)
} else {
//Если ребёнок первый - добавляем его родителю как дефолтный
updateQuestion(parentNodeContentId, question => question.content.rule.default = targetQuestion.content.id)
@ -200,12 +203,15 @@ export const CsComponent = ({
const targetQuestion = getQuestionByContentId(targetNodeContentId)
if (targetQuestion.content.rule.parentId === "root" && quiz) {
console.log("ROOT")
console.log(quiz)
updateRootContentId(quiz?.id, "")
updateQuestion(targetNodeContentId, question => {
question.content.rule.parentId = ""
question.content.rule.main = []
question.content.rule.default = ""
})
console.log(quiz, getQuestionByContentId(targetNodeContentId))
} else {
const parentQuestionContentId = cy?.$('edge[target = "' + targetNodeContentId + '"]')?.toArray()?.[0]?.data()?.source
if (targetNodeContentId && parentQuestionContentId) {
@ -242,7 +248,9 @@ export const CsComponent = ({
question.content.rule.main = []
question.content.rule.default = ""
})
updateQuestion(parentQuestionContentId, question => {
//Заменяем id удаляемого вопроса либо на id одного из оставшихся, либо на пустую строку
if (question.content.rule.default === targetQuestionContentId) question.content.rule.default = ""
})

@ -2,8 +2,8 @@ import { Box } from "@mui/material"
import { useEffect, useRef, useState } from "react";
import { updateDragQuestionContentId, updateQuestion } from "@root/questions/actions"
import { updateRootContentId } from "@root/quizes/actions"
import { useCurrentQuiz } from "@root/quizes/hooks"
import { useQuestionsStore } from "@root/questions/store"
import { useCurrentQuiz } from "@root/quizes/hooks";
import { enqueueSnackbar } from "notistack";
interface Props {
@ -11,9 +11,10 @@ interface Props {
modalQuestionTargetContentId: string;
}
export const FirstNodeField = ({ setOpenedModalQuestions, modalQuestionTargetContentId }: Props) => {
const { dragQuestionContentId } = useQuestionsStore()
const Container = useRef<HTMLDivElement | null>(null);
const quiz = useCurrentQuiz();
const { dragQuestionContentId, questions } = useQuestionsStore()
console.log(questions)
const Container = useRef<HTMLDivElement | null>(null);
const modalOpen = () => setOpenedModalQuestions(true)
@ -26,6 +27,7 @@ export const FirstNodeField = ({ setOpenedModalQuestions, modalQuestionTargetCon
} else {
enqueueSnackbar("Нет информации о взятом опроснике")
}
}
useEffect(() => {
@ -50,6 +52,7 @@ export const FirstNodeField = ({ setOpenedModalQuestions, modalQuestionTargetCon
}, [modalQuestionTargetContentId])
return (
<Box
ref={Container}

@ -14,6 +14,8 @@ export const BranchingMap = () => {
const [modalQuestionTargetContentId, setModalQuestionTargetContentId] = useState<string>("")
const [openedModalQuestions, setOpenedModalQuestions] = useState<boolean>(false)
return (
<Box
id="cytoscape-container"

@ -33,6 +33,9 @@ export default function BranchingQuestions() {
const { openedModalSettingsId } = useQuestionsStore();
const [targetQuestion, setTargetQuestion] = useState<AnyQuizQuestion | null>(getQuestionById(openedModalSettingsId) || getQuestionByContentId(openedModalSettingsId))
const [parentQuestion, setParentQuestion] = useState<AnyQuizQuestion | null>(getQuestionByContentId(targetQuestion?.content.rule.parentId))
console.log(parentQuestion.content.rule.default)
console.log(targetQuestion.content.id)
console.log(parentQuestion.content.rule.default === targetQuestion.content.id)
useLayoutEffect(() => {
if (parentQuestion === null) return
@ -51,6 +54,7 @@ export default function BranchingQuestions() {
})
console.log({targetQuestion, parentQuestion})
if (targetQuestion === null || parentQuestion === null) {
enqueueSnackbar("Невозможно найти данные ветвления для этого вопроса")
@ -163,7 +167,7 @@ export default function BranchingQuestions() {
sx={{
margin: 0
}}
checked={parentQuestion.content.rule.default === targetQuestion.id}
checked={parentQuestion.content.rule.default === targetQuestion.content.id}
onClick={() => {
let mutate = JSON.parse(JSON.stringify(parentQuestion))

@ -10,6 +10,8 @@ import { nanoid } from "nanoid";
import { enqueueSnackbar } from "notistack";
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";
@ -136,22 +138,20 @@ export const updateQuestion = (
// clearTimeout(requestTimeoutId);
// requestTimeoutId = setTimeout(() => {
requestQueue.enqueue(async () => {
const q = useQuestionsStore.getState().questions.find(q => q.id === questionId) || useQuestionsStore.getState().questions.find(q => q.content.id === questionId);
console.log("мы пытаемся найти вопрос ")
if (!q) return;
if (q.type === null) throw new Error("Cannot send update request for untyped question");
console.log(q.title)
requestQueue.enqueue(async () => {
const q = useQuestionsStore.getState().questions.find(q => q.id === questionId) || useQuestionsStore.getState().questions.find(q => q.content.id === questionId);
if (!q) return;
if (q.type === null) throw new Error("Cannot send update request for untyped question");
const response = await questionApi.edit(questionToEditQuestionRequest(q));
const response = await questionApi.edit(questionToEditQuestionRequest(q));
setQuestionBackendId(questionId, response.updated);
}).catch(error => {
if (isAxiosCanceledError(error)) return;
setQuestionBackendId(questionId, response.updated);
}).catch(error => {
if (isAxiosCanceledError(error)) return;
devlog("Error editing question", { error, questionId });
enqueueSnackbar("Не удалось сохранить вопрос");
});
devlog("Error editing question", { error, questionId });
enqueueSnackbar("Не удалось сохранить вопрос");
});
// }, REQUEST_DEBOUNCE);
};
@ -261,8 +261,10 @@ export const changeQuestionType = (
type: QuestionType,
) => {
updateQuestion(questionId, question => {
const oldId = question.content.id
question.type = type;
question.content = defaultQuestionByType[type].content;
question.content.id = oldId
});
};
@ -273,7 +275,7 @@ export const createTypedQuestion = async (
const question = useQuestionsStore.getState().questions.find(q => q.id === questionId);
if (!question) return;
if (question.type !== null) throw new Error("Cannot upgrade already typed question");
try {
const createdQuestion = await questionApi.create({
quiz_id: question.quizId,
@ -303,11 +305,16 @@ export const createTypedQuestion = async (
});
export const deleteQuestion = async (questionId: string) => requestQueue.enqueue(async () => {
const question = useQuestionsStore.getState().questions.find(q => q.id === questionId);
if (!question) return;
if (question.type === null) {
removeQuestion(questionId);
if (question.content.rule.parentId === "root") { //удалить из стора root и очистить rule всем вопросам
clearRoleForAll()
updateRootContentId(quiz.id, "")
}
return;
}
@ -325,9 +332,11 @@ export const copyQuestion = async (questionId: string, quizId: number) => reques
const question = useQuestionsStore.getState().questions.find(q => q.id === questionId);
if (!question) return;
const frontId = nanoid();
if (question.type === null) {
const copiedQuestion = structuredClone(question);
copiedQuestion.id = nanoid();
copiedQuestion.id = frontId
copiedQuestion.content.id = frontId
setProducedState(state => {
state.questions.push(copiedQuestion);
@ -369,7 +378,7 @@ function setProducedState<A extends string | { type: unknown; }>(
export const cleardragQuestionContentId = () => {
useQuestionsStore.setState({dragQuestionContentId: null});
useQuestionsStore.setState({ dragQuestionContentId: null });
};
export const getQuestionById = (questionId: string | null) => {
@ -377,16 +386,26 @@ export const getQuestionById = (questionId: string | null) => {
return useQuestionsStore.getState().questions.find(q => q.id === questionId) || null;
};
export const getQuestionByContentId = (questionContentId: string | null) => {
console.log("questionContentId " + questionContentId)
if (questionContentId === null) return null;
return useQuestionsStore.getState().questions.find(q => {
console.log(q.content.id)
console.log(q.content.id === questionContentId)
return ( q.content.id === questionContentId)}) || null;
return (q.content.id === questionContentId)
}) || null;
};
export const updateOpenedModalSettingsId = (id?: string) => useQuestionsStore.setState({openedModalSettingsId: id ? id : null});
export const updateOpenedModalSettingsId = (id?: string) => useQuestionsStore.setState({ openedModalSettingsId: id ? id : null });
export const updateDragQuestionContentId = (contentId?: string) => {
console.log("contentId " + contentId)
useQuestionsStore.setState({dragQuestionContentId: contentId ? contentId : null});
useQuestionsStore.setState({ dragQuestionContentId: contentId ? contentId : null });
}
export const clearRoleForAll = () => {
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)) {
updateQuestion(question.content.id, question => {
question.content.rule.parentId = ""
question.content.rule.main = []
question.content.rule.default = ""
})
}
});
}

@ -4,7 +4,7 @@ import { devtools } from "zustand/middleware";
export type QuestionsStore = {
questions: (AnyTypedQuizQuestion | UntypedQuizQuestion);
questions: (AnyTypedQuizQuestion | UntypedQuizQuestion)[];
openedModalSettingsId: string | null;
dragQuestionContentId: string | null;
};

@ -10,6 +10,7 @@ import { isAxiosCanceledError } from "../../utils/isAxiosCanceledError";
import { RequestQueue } from "../../utils/requestQueue";
import { QuizStore, useQuizStore } from "./store";
import { createUntypedQuestion } from "@root/questions/actions";
import { useCurrentQuiz } from "./hooks"
export const setEditQuizId = (quizId: number | null) => setProducedState(state => {
@ -127,6 +128,7 @@ export const updateQuiz = async (
requestTimeoutId = setTimeout(async () => {
requestQueue.enqueue(async () => {
const quiz = useQuizStore.getState().quizes.find(q => q.id === quizId);
console.log("измененный квиз", quiz)
if (!quiz) return;
const response = await quizApi.edit(quizToEditQuizRequest(quiz));
@ -178,10 +180,11 @@ export const deleteQuiz = async (quizId: string) => requestQueue.enqueue(async (
export const updateRootContentId = (quizId: string, id:string) => updateQuiz(
quizId,
quiz => {
quiz.config.haveRoot = id;
quiz.config.haveRoot = id
},
);
// TODO copy quiz
export const uploadQuizImage = async (

1249
yarn.lock

File diff suppressed because it is too large Load Diff