при удалении корня все использованные вопросы сбрасывают rule

This commit is contained in:
Nastya 2023-12-04 19:33:50 +03:00
commit c3d1353a9b
21 changed files with 1039 additions and 789 deletions

93
package-lock.json generated

@ -40,6 +40,7 @@
"react": "^18.2.0", "react": "^18.2.0",
"react-beautiful-dnd": "^13.1.1", "react-beautiful-dnd": "^13.1.1",
"react-cytoscapejs": "^2.0.0", "react-cytoscapejs": "^2.0.0",
"react-datepicker": "^4.24.0",
"react-dnd": "^16.0.1", "react-dnd": "^16.0.1",
"react-dnd-html5-backend": "^16.0.1", "react-dnd-html5-backend": "^16.0.1",
"react-dom": "^18.2.0", "react-dom": "^18.2.0",
@ -61,7 +62,8 @@
"@emoji-mart/react": "^1.1.1", "@emoji-mart/react": "^1.1.1",
"@types/cytoscape-popper": "^2.0.4", "@types/cytoscape-popper": "^2.0.4",
"@types/react-beautiful-dnd": "^13.1.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", "craco-alias": "^3.0.1",
"cypress": "^13.4.0" "cypress": "^13.4.0"
} }
@ -4524,6 +4526,18 @@
"@types/react": "*" "@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": { "node_modules/@types/react-dnd": {
"version": "3.0.2", "version": "3.0.2",
"resolved": "https://registry.npmjs.org/@types/react-dnd/-/react-dnd-3.0.2.tgz", "resolved": "https://registry.npmjs.org/@types/react-dnd/-/react-dnd-3.0.2.tgz",
@ -6374,6 +6388,11 @@
"integrity": "sha512-cOU9usZw8/dXIXKtwa8pM0OTJQuJkxMN6w30csNRUerHfeQ5R6U3kkU/FtJeIf3M202OHfY2U8ccInBG7/xogA==", "integrity": "sha512-cOU9usZw8/dXIXKtwa8pM0OTJQuJkxMN6w30csNRUerHfeQ5R6U3kkU/FtJeIf3M202OHfY2U8ccInBG7/xogA==",
"license": "MIT" "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": { "node_modules/clean-css": {
"version": "5.3.1", "version": "5.3.1",
"resolved": "https://registry.npmjs.org/clean-css/-/clean-css-5.3.1.tgz", "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-5.3.1.tgz",
@ -7438,6 +7457,21 @@
"node": ">=10" "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": { "node_modules/dayjs": {
"version": "1.11.10", "version": "1.11.10",
"resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.10.tgz", "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.10.tgz",
@ -15126,6 +15160,23 @@
"react": ">=15.0.0" "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": { "node_modules/react-dev-utils": {
"version": "12.0.1", "version": "12.0.1",
"resolved": "https://registry.npmjs.org/react-dev-utils/-/react-dev-utils-12.0.1.tgz", "resolved": "https://registry.npmjs.org/react-dev-utils/-/react-dev-utils-12.0.1.tgz",
@ -15308,6 +15359,38 @@
"integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==",
"license": "MIT" "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": { "node_modules/react-redux": {
"version": "7.2.9", "version": "7.2.9",
"resolved": "https://registry.npmjs.org/react-redux/-/react-redux-7.2.9.tgz", "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-7.2.9.tgz",
@ -18037,6 +18120,14 @@
"makeerror": "1.0.12" "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": { "node_modules/watchpack": {
"version": "2.4.0", "version": "2.4.0",
"resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.0.tgz", "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.0.tgz",

@ -30,7 +30,6 @@ async function getQuestionList(body?: Partial<GetQuestionListRequest>) {
} }
function editQuestion(body: EditQuestionRequest, signal?: AbortSignal) { function editQuestion(body: EditQuestionRequest, signal?: AbortSignal) {
console.log("`${baseUrl}/question/edit` start")
return makeRequest<EditQuestionRequest, EditQuestionResponse>({ return makeRequest<EditQuestionRequest, EditQuestionResponse>({
url: `${baseUrl}/question/edit`, url: `${baseUrl}/question/edit`,
body, body,

@ -1,7 +1,7 @@
import { MessageIcon } from "@icons/messagIcon"; import { MessageIcon } from "@icons/messagIcon";
import { PointsIcon } from "@icons/questionsPage/PointsIcon"; import { PointsIcon } from "@icons/questionsPage/PointsIcon";
import { DeleteIcon } from "@icons/questionsPage/deleteIcon"; import { DeleteIcon } from "@icons/questionsPage/deleteIcon";
import TextareaAutosize from "@mui/base/TextareaAutosize"; import {TextareaAutosize} from "@mui/base/TextareaAutosize";
import { import {
Box, Box,
FormControl, FormControl,

@ -1,4 +1,4 @@
import { useEffect, useRef, useState } from "react"; import { useEffect, useLayoutEffect, useRef, useState } from "react";
import Cytoscape from "cytoscape"; import Cytoscape from "cytoscape";
import CytoscapeComponent from "react-cytoscapejs"; import CytoscapeComponent from "react-cytoscapejs";
import popper from "cytoscape-popper"; import popper from "cytoscape-popper";
@ -122,16 +122,21 @@ export const CsComponent = ({
const crossesContainer = useRef<HTMLDivElement | null>(null); const crossesContainer = useRef<HTMLDivElement | null>(null);
const gearsContainer = useRef<HTMLDivElement | null>(null); const gearsContainer = useRef<HTMLDivElement | null>(null);
useLayoutEffect(() => {
updateOpenedModalSettingsId()
}, [])
useEffect(() => { useEffect(() => {
console.log("modalQuestionTargetContentId " + modalQuestionTargetContentId)
if (modalQuestionTargetContentId.length !== 0 && modalQuestionParentContentId.length !== 0) { if (modalQuestionTargetContentId.length !== 0 && modalQuestionParentContentId.length !== 0) {
console.log("был выбран вопрос " + modalQuestionTargetContentId) addNode({ parentNodeContentId: modalQuestionParentContentId, targetNodeContentId: modalQuestionTargetContentId })
addNode({ parentNodeContentId:modalQuestionParentContentId, targetNodeContentId:modalQuestionTargetContentId })
} }
setModalQuestionParentContentId("")
setModalQuestionTargetContentId("")
console.log("modalQuestionTargetContentId " + modalQuestionTargetContentId)
}, [modalQuestionTargetContentId]) }, [modalQuestionTargetContentId])
const addNode = ({ parentNodeContentId, targetNodeContentId }: { parentNodeContentId: string, targetNodeContentId?: string }) => { const addNode = ({ parentNodeContentId, targetNodeContentId }: { parentNodeContentId: string, targetNodeContentId?: string }) => {
console.log("dragQuestionContentId " + dragQuestionContentId)
const cy = cyRef?.current const cy = cyRef?.current
const parentNodeChildren = cy?.$('edge[source = "' + parentNodeContentId + '"]')?.length const parentNodeChildren = cy?.$('edge[source = "' + parentNodeContentId + '"]')?.length
//если есть инфо о выбранном вопросе из модалки - берём родителя из инфо модалки. Иначе из значения дропа //если есть инфо о выбранном вопросе из модалки - берём родителя из инфо модалки. Иначе из значения дропа
@ -139,7 +144,7 @@ export const CsComponent = ({
if (Object.keys(targetQuestion).length !== 0 && Object.keys(targetQuestion).length !== 0 && parentNodeContentId && parentNodeChildren !== undefined) { if (Object.keys(targetQuestion).length !== 0 && Object.keys(targetQuestion).length !== 0 && parentNodeContentId && parentNodeChildren !== undefined) {
clearDataAfterAddNode({ parentNodeContentId, targetQuestion, parentNodeChildren }) clearDataAfterAddNode({ parentNodeContentId, targetQuestion, parentNodeChildren })
cy?.data('changed',true) cy?.data('changed', true)
cy?.add([ cy?.add([
{ {
data: { data: {
@ -161,8 +166,6 @@ export const CsComponent = ({
} }
const clearDataAfterAddNode = ({ parentNodeContentId, targetQuestion, parentNodeChildren }: { parentNodeContentId: string, targetQuestion: AnyQuizQuestion, parentNodeChildren: number }) => { const clearDataAfterAddNode = ({ parentNodeContentId, targetQuestion, parentNodeChildren }: { parentNodeContentId: string, targetQuestion: AnyQuizQuestion, parentNodeChildren: number }) => {
console.log("записываю на бек ид родителя")
console.log({ parentNodeContentId, targetQuestion, parentNodeChildren })
//предупреждаем добавленный вопрос о том, кто его родитель //предупреждаем добавленный вопрос о том, кто его родитель
updateQuestion(targetQuestion.content.id, question => { updateQuestion(targetQuestion.content.id, question => {
question.content.rule.parentId = parentNodeContentId question.content.rule.parentId = parentNodeContentId
@ -170,8 +173,8 @@ export const CsComponent = ({
}) })
//Если детей больше 1 - предупреждаем стор вопросов об открытии модалки ветвления //Если детей больше 1 - предупреждаем стор вопросов об открытии модалки ветвления
if (parentNodeChildren > 1) { if (parentNodeChildren >= 1) {
updateOpenedModalSettingsId(parentNodeContentId) updateOpenedModalSettingsId(targetQuestion.content.id)
} else { } else {
//Если ребёнок первый - добавляем его родителю как дефолтный //Если ребёнок первый - добавляем его родителю как дефолтный
updateQuestion(parentNodeContentId, question => question.content.rule.default = targetQuestion.content.id) updateQuestion(parentNodeContentId, question => question.content.rule.default = targetQuestion.content.id)
@ -179,9 +182,8 @@ export const CsComponent = ({
} }
const removeNode = ({ targetNodeContentId }: { targetNodeContentId: string }) => { const removeNode = ({ targetNodeContentId }: { targetNodeContentId: string }) => {
const deleteNodes = [] as string[] const deleteNodes = [] as string[]
const deleteEdges:any = [] const deleteEdges: any = []
console.log("remove")
const cy = cyRef?.current const cy = cyRef?.current
const findChildrenToDelete = (node) => { const findChildrenToDelete = (node) => {
@ -205,60 +207,54 @@ export const CsComponent = ({
const targetQuestion = getQuestionByContentId(targetNodeContentId) const targetQuestion = getQuestionByContentId(targetNodeContentId)
if (targetQuestion.content.rule.parentId === "root" && quiz) { if (targetQuestion.content.rule.parentId === "root" && quiz) {
console.log("click ROOT") console.log("ROOT")
console.log(quiz)
updateRootContentId(quiz?.id, "")
updateQuestion(targetNodeContentId, question => { updateQuestion(targetNodeContentId, question => {
question.content.rule.parentId = "" question.content.rule.parentId = ""
question.content.rule.main = [] question.content.rule.main = []
question.content.rule.default = "" question.content.rule.default = ""
}) })
updateRootContentId(quiz?.id, false) console.log(quiz, getQuestionByContentId(targetNodeContentId))
} else { } else {
console.log("click not ROOT")
const parentQuestionContentId = cy?.$('edge[target = "' + targetNodeContentId + '"]')?.toArray()?.[0]?.data()?.source const parentQuestionContentId = cy?.$('edge[target = "' + targetNodeContentId + '"]')?.toArray()?.[0]?.data()?.source
if (targetNodeContentId && parentQuestionContentId) { if (targetNodeContentId && parentQuestionContentId) {
clearDataAfterRemoveNode({ targetQuestionContentId: targetNodeContentId, parentQuestionContentId }) clearDataAfterRemoveNode({ targetQuestionContentId: targetNodeContentId, parentQuestionContentId })
cy?.remove(cy?.$('#' + targetNodeContentId)).layout(lyopts).run() cy?.remove(cy?.$('#' + targetNodeContentId)).layout(lyopts).run()
} }
console.log(deleteNodes, deleteEdges)
//После всех манипуляций удаляем грани из CS и ноды из бекенда
deleteNodes.forEach((nodeId) => {//Ноды
cy?.remove(cy?.$("#" + nodeId))
removeButtons(nodeId)
updateQuestion(nodeId, question => {
question.content.rule.parentId = ""
question.content.rule.main = []
question.content.rule.default = ""
})
})
deleteEdges.forEach((edge:any) => {//Грани
cy?.remove(edge)
})
removeButtons(targetNodeContentId)
cy?.data('changed',true)
cy?.layout(lyopts).run()
} }
//После всех манипуляций удаляем грани из CS и ноды из бекенда
deleteNodes.forEach((nodeId) => {//Ноды
cy?.remove(cy?.$("#" + nodeId))
removeButtons(nodeId)
updateQuestion(nodeId, question => {
question.content.rule.parentId = ""
question.content.rule.main = []
question.content.rule.default = ""
})
})
deleteEdges.forEach((edge: any) => {//Грани
cy?.remove(edge)
})
removeButtons(targetNodeContentId)
cy?.data('changed', true)
cy?.layout(lyopts).run()
} }
const clearDataAfterRemoveNode = ({ targetQuestionContentId, parentQuestionContentId }: { targetQuestionContentId: string, parentQuestionContentId: string }) => { const clearDataAfterRemoveNode = ({ targetQuestionContentId, parentQuestionContentId }: { targetQuestionContentId: string, parentQuestionContentId: string }) => {
console.log({ targetQuestionContentId, parentQuestionContentId })
updateQuestion(targetQuestionContentId, question => { updateQuestion(targetQuestionContentId, question => {
question.content.rule.parentId = "" question.content.rule.parentId = ""
question.content.rule.main = [] question.content.rule.main = []
question.content.rule.default = "" question.content.rule.default = ""
}) })
updateQuestion(parentQuestionContentId, question => { updateQuestion(parentQuestionContentId, question => {
console.log("parent default " + question.content.rule.default) //Заменяем id удаляемого вопроса либо на id одного из оставшихся, либо на пустую строку
console.log("target ID " + targetQuestionContentId)
console.log(question.content.rule.default === targetQuestionContentId)
if (question.content.rule.default === targetQuestionContentId) question.content.rule.default = "" if (question.content.rule.default === targetQuestionContentId) question.content.rule.default = ""
}) })
@ -282,22 +278,20 @@ export const CsComponent = ({
const readyLO = (e) => { const readyLO = (e) => {
console.log(e.cy.data('firstNode'),"SKEEER",e.cy.data('changed'))
if (e.cy.data('firstNode') === 'nonroot') { if (e.cy.data('firstNode') === 'nonroot') {
e.cy.data('firstNode','root') e.cy.data('firstNode', 'root')
e.cy.nodes().sort((a,b) => (a.data('root')?1:-1)).layout(lyopts).run() 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.data('changed', false)
e.cy.removeData('firstNode')
}
//удаляем иконки //удаляем иконки
e.cy.nodes().forEach((ele: any) => { e.cy.nodes().forEach((ele: any) => {
const data = ele.data() const data = ele.data()
console.log(data)
data.id && removeButtons(data.id); data.id && removeButtons(data.id);
}) })
initialPopperIcons(e) initialPopperIcons(e)
@ -307,11 +301,9 @@ console.log(e.cy.data('firstNode'),"SKEEER",e.cy.data('changed'))
name: 'preset', name: 'preset',
positions: (e) => { positions: (e) => {
console.log('BBBBBBBBBBBBBBB', e.cy().data('changed'))
if (!e.cy().data('changed')) { if (!e.cy().data('changed')) {
return e.data('oldPos') return e.data('oldPos')
} else {e.removeData('oldPos')} } else { e.removeData('oldPos') }
console.log('POSITIIIIIIIONS')
const id = e.id() const id = e.id()
const incomming = e.cy().edges(`[target="${id}"]`) const incomming = e.cy().edges(`[target="${id}"]`)
const layer = 0 const layer = 0
@ -319,7 +311,7 @@ console.log(e.cy.data('firstNode'),"SKEEER",e.cy.data('changed'))
if (incomming.length === 0) { if (incomming.length === 0) {
if (e.cy().data('firstNode') === undefined) if (e.cy().data('firstNode') === undefined)
e.cy().data('firstNode','root') e.cy().data('firstNode', 'root')
e.data('root', true) e.data('root', true)
const children = e.cy().edges(`[source="${id}"]`).targets() const children = e.cy().edges(`[source="${id}"]`).targets()
e.data('layer', layer) e.data('layer', layer)
@ -341,7 +333,6 @@ console.log(e.cy.data('firstNode'),"SKEEER",e.cy.data('changed'))
queue.push({ parent: e, children: children }) queue.push({ parent: e, children: children })
while (queue.length) { while (queue.length) {
const task = queue.pop() const task = queue.pop()
console.log('WIIIING',task.parent.data(),task.children.length)
if (task.children.length === 0) { if (task.children.length === 0) {
task.parent.data('subtreeWidth', task.parent.height()) task.parent.data('subtreeWidth', task.parent.height())
continue continue
@ -360,22 +351,20 @@ console.log('WIIIING',task.parent.data(),task.children.length)
task?.parent.data('subtreeWidth', task.children.reduce((p, n) => p + n.data('subtreeWidth'), 0)) task?.parent.data('subtreeWidth', task.children.reduce((p, n) => p + n.data('subtreeWidth'), 0))
} }
const pos = { x: 0, y: 0 } const pos = { x: 0, y: 0 }
console.log(e.data())
e.data('oldPos', pos) e.data('oldPos', pos)
return pos return pos
} else { } else {
if (e.cy().data('firstNode') !== 'root') { if (e.cy().data('firstNode') !== 'root') {
e.cy().data('firstNode','nonroot') e.cy().data('firstNode', 'nonroot')
return {x:0,y:0} return { x: 0, y: 0 }
} }
if (e.cy().data('firstNode') === undefined) if (e.cy().data('firstNode') === undefined)
e.cy().data('firstNode','nonroot') e.cy().data('firstNode', 'nonroot')
const parent = e.cy().edges(`[target="${e.id()}"]`)[0].source() const parent = e.cy().edges(`[target="${e.id()}"]`)[0].source()
const wing = (parent.data('children') === 1) ? 0 : parent.data('subtreeWidth') / 2 + 50 const wing = (parent.data('children') === 1) ? 0 : parent.data('subtreeWidth') / 2 + 50
const lastOffset = parent.data('lastChild') const lastOffset = parent.data('lastChild')
const step = wing * 2 / (parent.data('children') - 1) const step = wing * 2 / (parent.data('children') - 1)
//e.removeData('subtreeWidth') //e.removeData('subtreeWidth')
console.log('AAAAAAAAAAa', e.data(), lastOffset, step, parent.position().y, wing)
if (lastOffset !== undefined) { if (lastOffset !== undefined) {
parent.data('lastChild', lastOffset + step) parent.data('lastChild', lastOffset + step)
const pos = { x: 250 * e.data('layer'), y: (lastOffset + step) } const pos = { x: 250 * e.data('layer'), y: (lastOffset + step) }
@ -386,7 +375,7 @@ if (e.cy().data('firstNode') !== 'root') {
const pos = { x: 250 * e.data('layer'), y: (parent.position().y - wing) } const pos = { x: 250 * e.data('layer'), y: (parent.position().y - wing) }
e.data('oldPos', pos) e.data('oldPos', pos)
return pos return pos
} }
} }
}, // map of (node id) => (position obj); or function(node){ return somPos; } }, // map of (node id) => (position obj); or function(node){ return somPos; }
zoom: undefined, // the zoom level to set (prob want fit = false if set) zoom: undefined, // the zoom level to set (prob want fit = false if set)
@ -502,7 +491,7 @@ if (e.cy().data('firstNode') !== 'root') {
layoutElement.classList.add("popper-layout"); layoutElement.classList.add("popper-layout");
layoutElement.setAttribute("data-id", item.id()); layoutElement.setAttribute("data-id", item.id());
layoutElement.addEventListener("mouseup", () => { layoutElement.addEventListener("mouseup", () => {
//Узнаём грани, идущие от этой ноды //Узнаём грани, идущие от этой ноды
setModalQuestionParentContentId(item.id()) setModalQuestionParentContentId(item.id())
setOpenedModalQuestions(true) setOpenedModalQuestions(true)
}); });
@ -533,7 +522,7 @@ if (e.cy().data('firstNode') !== 'root') {
plusElement.addEventListener("mouseup", () => { plusElement.addEventListener("mouseup", () => {
setStartCreate(node.id()); setStartCreate(node.id());
}); });
plusesContainer.current?.appendChild(plusElement); plusesContainer.current?.appendChild(plusElement);
return plusElement; return plusElement;
@ -594,7 +583,6 @@ if (e.cy().data('firstNode') !== 'root') {
gearElement.style.zIndex = "1" gearElement.style.zIndex = "1"
gearsContainer.current?.appendChild(gearElement); gearsContainer.current?.appendChild(gearElement);
gearElement.addEventListener("mouseup", (e) => { gearElement.addEventListener("mouseup", (e) => {
console.log(item.id())
updateOpenedModalSettingsId(item.id()) updateOpenedModalSettingsId(item.id())
}); });
@ -672,23 +660,27 @@ if (e.cy().data('firstNode') !== 'root') {
return ( return (
<> <>
<CytoscapeComponent <CytoscapeComponent
wheelSensitivity={0.1} wheelSensitivity={0.1}
elements={[]} elements={[]}
// elements={createGraphElements(tree, quiz)} // elements={createGraphElements(tree, quiz)}
style={{ height: "480px", background: "#F2F3F7" }} style={{ height: "480px", background: "#F2F3F7" }}
stylesheet={stylesheet} stylesheet={stylesheet}
layout={(lyopts)} layout={(lyopts)}
cy={(cy) => { cy={(cy) => {
cyRef.current = cy; cyRef.current = cy;
}} }}
/> />
<button onClick={() => { <button onClick={() => {
console.log("ELEMENTS____________________________") console.log("NODES____________________________")
cyRef.current?.elements().forEach((ele:any) => { cyRef.current?.elements().forEach((ele: any) => {
console.log(ele.data()) console.log(ele.data())
}) })
}}>elements</button> }}>nodes</button>
<button onClick={() => {
console.log("ELEMENTS____________________________")
console.log(questions)
}}>elements</button>
</> </>
); );
}; };

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

@ -15,11 +15,9 @@ interface Edges {
} }
export const storeToNodes = (questions: AnyQuizQuestion[]) => { export const storeToNodes = (questions: AnyQuizQuestion[]) => {
console.log(questions)
const nodes: Nodes[] = [] const nodes: Nodes[] = []
const edges: Edges[] = [] const edges: Edges[] = []
questions.forEach((question) => { questions.forEach((question) => {
console.log(question)
if (question.content.rule.parentId) { if (question.content.rule.parentId) {
nodes.push({data: { nodes.push({data: {
id: question.content.id, id: question.content.id,
@ -38,6 +36,5 @@ export const storeToNodes = (questions: AnyQuizQuestion[]) => {
}}) }})
} }
}) })
console.log([...nodes, ...edges])
return [...nodes, ...edges]; return [...nodes, ...edges];
} }

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

@ -32,35 +32,38 @@ export default function BranchingQuestions() {
const { openedModalSettingsId } = useQuestionsStore(); const { openedModalSettingsId } = useQuestionsStore();
const [targetQuestion, setTargetQuestion] = useState<AnyQuizQuestion | null>(getQuestionById(openedModalSettingsId) || getQuestionByContentId(openedModalSettingsId)) const [targetQuestion, setTargetQuestion] = useState<AnyQuizQuestion | null>(getQuestionById(openedModalSettingsId) || getQuestionByContentId(openedModalSettingsId))
console.log(targetQuestion)
const [parentQuestion, setParentQuestion] = useState<AnyQuizQuestion | null>(getQuestionByContentId(targetQuestion?.content.rule.parentId)) const [parentQuestion, setParentQuestion] = useState<AnyQuizQuestion | null>(getQuestionByContentId(targetQuestion?.content.rule.parentId))
console.log(parentQuestion) console.log(parentQuestion.content.rule.default)
console.log(targetQuestion.content.id)
console.log(parentQuestion.content.rule.default === targetQuestion.content.id)
useLayoutEffect(() => { useLayoutEffect(() => {
if (parentQuestion === null) return if (parentQuestion === null) return
if (parentQuestion.content.rule.main.length === 0) updateQuestion(parentQuestion.id, question => question.content.rule.main.push({ if (parentQuestion.content.rule.main.length === 0) {
next: targetQuestion.content.id, let mutate = JSON.parse(JSON.stringify(parentQuestion))
or: true, mutate.content.rule.main = [{
rules: [{ next: targetQuestion.content.id,
question: parentQuestion.content.id, or: true,
answers: [] rules: [{
question: parentQuestion.content.id,
answers: []
}]
}] }]
})) setParentQuestion(mutate)
}
}) })
console.log({targetQuestion, parentQuestion})
if (targetQuestion === null || parentQuestion === null) { if (targetQuestion === null || parentQuestion === null) {
console.log(openedModalSettingsId)
enqueueSnackbar("Невозможно найти данные ветвления для этого вопроса") enqueueSnackbar("Невозможно найти данные ветвления для этого вопроса")
return <></> return <></>
} }
const saveData = () => { const saveData = () => {
console.log(parentQuestion)
if (parentQuestion !== null) { if (parentQuestion !== null) {
updateQuestion(parentQuestion.content.id, question => question.content = parentQuestion.content) updateQuestion(parentQuestion.content.id, question => question.content = parentQuestion.content)
} }
handleClose() handleClose()
@ -149,11 +152,11 @@ export default function BranchingQuestions() {
marginBottom: "10px", marginBottom: "10px",
cursor: "pointer" cursor: "pointer"
}} }}
onClick={() => { onClick={() => {
const mutate = JSON.parse(JSON.stringify(parentQuestion)) const mutate = JSON.parse(JSON.stringify(parentQuestion))
mutate.content.rule.main.push(createBranchingRuleMain(targetQuestion.content.id, parentQuestion.content.id)) mutate.content.rule.main.push(createBranchingRuleMain(targetQuestion.content.id, parentQuestion.content.id))
setParentQuestion(mutate) setParentQuestion(mutate)
}} }}
> >
Добавить условие Добавить условие
</Link> </Link>
@ -164,7 +167,7 @@ export default function BranchingQuestions() {
sx={{ sx={{
margin: 0 margin: 0
}} }}
checked={parentQuestion.content.rule.default === targetQuestion.id} checked={parentQuestion.content.rule.default === targetQuestion.content.id}
onClick={() => { onClick={() => {
let mutate = JSON.parse(JSON.stringify(parentQuestion)) let mutate = JSON.parse(JSON.stringify(parentQuestion))

@ -264,21 +264,43 @@ const NumberInputsType = ({ parentQuestion, targetQuestion, ruleIndex, setParent
(Укажите один или несколько вариантов) (Укажите один или несколько вариантов)
</Typography> </Typography>
</Box> </Box>
{/* <TextField <TextField
sx={{ sx={{
marginTop: "20px", marginTop: "20px",
width: "100%" width: "100%"
}} }}
placeholder="от"
value={parentQuestion.content.rule.main[ruleIndex].rules[0].answers[0]} value={parentQuestion.content.rule.main[ruleIndex].rules[0].answers[0]}
onChange={(event: React.FormEvent<HTMLInputElement>) => { onChange={(event: React.FormEvent<HTMLInputElement>) => {
let newParentQuestion = JSON.parse(JSON.stringify(parentQuestion)) let newParentQuestion = JSON.parse(JSON.stringify(parentQuestion))
newParentQuestion.content.rule.main[ruleIndex].rules[0].answers = [(event.target as HTMLInputElement).value.replace(/[^0-9,\s]/g, "")] newParentQuestion.content.rule.main[ruleIndex].rules[0].answers[0] = Number((event.target as HTMLInputElement).value.replace(/[^0-9,\s]/g, ""))
if (newParentQuestion.content.rule.main[ruleIndex].rules[0].answers[1] === undefined) newParentQuestion.content.rule.main[ruleIndex].rules[0].answers[1] = 0
setParentQuestion(newParentQuestion) setParentQuestion(newParentQuestion)
}} }}
/> */} />
{parentQuestion.content.chooseRange &&
<TextField
placeholder="до"
sx={{
marginTop: "20px",
width: "100%"
}}
value={parentQuestion.content.rule.main[ruleIndex].rules[0].answers[1]}
onChange={(event: React.FormEvent<HTMLInputElement>) => {
let newParentQuestion = JSON.parse(JSON.stringify(parentQuestion))
newParentQuestion.content.rule.main[ruleIndex].rules[0].answers[1] = Number((event.target as HTMLInputElement).value.replace(/[^0-9,\s]/g, ""))
if (newParentQuestion.content.rule.main[ruleIndex].rules[0].answers[0] === undefined) newParentQuestion.content.rule.main[ruleIndex].rules[0].answers[0] = 0
setParentQuestion(newParentQuestion)
}}
/>
}
@ -338,7 +360,7 @@ const TextInputsType = ({ parentQuestion, targetQuestion, ruleIndex, setParentQu
(Укажите текст, при совпадении с которым пользователь попадёт на этот вопрос) (Укажите текст, при совпадении с которым пользователь попадёт на этот вопрос)
</Typography> </Typography>
</Box> </Box>
{/* <TextField <TextField
sx={{ sx={{
marginTop: "20px", marginTop: "20px",
width: "100%" width: "100%"
@ -351,7 +373,7 @@ const TextInputsType = ({ parentQuestion, targetQuestion, ruleIndex, setParentQu
setParentQuestion(newParentQuestion) setParentQuestion(newParentQuestion)
}} }}
/> */} />

@ -23,7 +23,6 @@ type AnyQuestion = UntypedQuizQuestion | AnyTypedQuizQuestion
export const QuestionsList = () => { export const QuestionsList = () => {
const { questions } = useQuestionsStore() const { questions } = useQuestionsStore()
console.log(questions)
return ( return (
<Box sx={{ padding: "15px" }}> <Box sx={{ padding: "15px" }}>

@ -1,24 +1,30 @@
import { useParams } from "react-router-dom";
import { Box, Modal, Button, Typography } from "@mui/material"; import { Box, Modal, Button, Typography } from "@mui/material";
import { useQuestionsStore } from "@root/questions/store"; import { useQuestionsStore } from "@root/questions/store";
import { AnyTypedQuizQuestion, UntypedQuizQuestion } from "@model/questionTypes/shared"; import { AnyTypedQuizQuestion } from "@model/questionTypes/shared";
type AnyQuestion = UntypedQuizQuestion | AnyTypedQuizQuestion
interface Props { interface Props {
openedModalQuestions: boolean; openedModalQuestions: boolean;
setModalQuestionTargetContentId: (contentId:string) => void; setModalQuestionTargetContentId: (contentId: string) => void;
setOpenedModalQuestions: (open:boolean) => void; setOpenedModalQuestions: (open: boolean) => void;
setModalQuestionParentContentId: (open: string) => void;
} }
export const BranchingQuestionsModal = ({ openedModalQuestions, setOpenedModalQuestions, setModalQuestionTargetContentId}:Props) => { export const BranchingQuestionsModal = ({
const quizId = Number(useParams().quizId); openedModalQuestions,
setOpenedModalQuestions,
setModalQuestionTargetContentId,
setModalQuestionParentContentId
}: Props) => {
const { questions } = useQuestionsStore(); const { questions } = useQuestionsStore();
const handleClose = () => { const handleClose = () => {
setOpenedModalQuestions(false); setOpenedModalQuestions(false);
}; };
const typedQuestions: AnyTypedQuizQuestion[] = questions.filter(
(question) => question.type && !question.content.rule.parentId
) as AnyTypedQuizQuestion[];
return ( return (
<Modal open={openedModalQuestions} onClose={handleClose}> <Modal open={openedModalQuestions} onClose={handleClose}>
<Box <Box
@ -37,14 +43,11 @@ export const BranchingQuestionsModal = ({ openedModalQuestions, setOpenedModalQu
}} }}
> >
<Box sx={{ margin: "0 auto", maxWidth: "350px" }}> <Box sx={{ margin: "0 auto", maxWidth: "350px" }}>
{questions.filter((q:AnyQuestion) => { {typedQuestions.map((question) => (
console.log(q.content)
if (q.content === null) return true
return (q.type && !q.content.rule.parentId)}).map((question: AnyTypedQuizQuestion, index:number) => (
<Button <Button
key={question.content.id} key={question.content.id}
onClick={() => { onClick={() => {
setModalQuestionTargetContentId(question.content.id) setModalQuestionTargetContentId(question.content.id);
handleClose(); handleClose();
}} }}
sx={{ sx={{

@ -20,6 +20,7 @@ import { HideIcon } from "../../assets/icons/questionsPage/hideIcon";
import SettingIcon from "../../assets/icons/questionsPage/settingIcon"; import SettingIcon from "../../assets/icons/questionsPage/settingIcon";
import type { AnyTypedQuizQuestion } from "../../model/questionTypes/shared"; import type { AnyTypedQuizQuestion } from "../../model/questionTypes/shared";
import { updateOpenedModalSettingsId } from "@root/questions/actions"; import { updateOpenedModalSettingsId } from "@root/questions/actions";
import { useCurrentQuiz } from "@root/quizes/hooks";
import {enqueueSnackbar} from "notistack"; import {enqueueSnackbar} from "notistack";
@ -38,6 +39,7 @@ export default function ButtonsOptions({
const theme = useTheme(); const theme = useTheme();
const isMobile = useMediaQuery(theme.breakpoints.down(790)); const isMobile = useMediaQuery(theme.breakpoints.down(790));
const isWrappMiniButtonSetting = useMediaQuery(theme.breakpoints.down(920)); const isWrappMiniButtonSetting = useMediaQuery(theme.breakpoints.down(920));
const quiz = useCurrentQuiz();
const openedModal = () => { const openedModal = () => {
updateOpenedModalSettingsId(question.id) updateOpenedModalSettingsId(question.id)
@ -45,6 +47,7 @@ export default function ButtonsOptions({
const handleClickBranching = (_, value) => { const handleClickBranching = (_, value) => {
const parentId = question.content.rule.parentId const parentId = question.content.rule.parentId
console.log(parentId)
console.log(parentId.length) console.log(parentId.length)
if (parentId.length === 0 ){ if (parentId.length === 0 ){
return enqueueSnackbar("Вопрос не учавствует в ветвлении") return enqueueSnackbar("Вопрос не учавствует в ветвлении")
@ -293,7 +296,7 @@ export default function ButtonsOptions({
// deleteTimeoutId: newTimeoutId, // deleteTimeoutId: newTimeoutId,
// }); // });
deleteQuestion(question.id); deleteQuestion(question.id, quiz.id);
}} }}
data-cy="delete-question" data-cy="delete-question"
> >

@ -26,6 +26,7 @@ import { updateOpenedModalSettingsId } from "@root/questions/actions";
import { updateOpenBranchingPanel } from "@root/questions/actions"; import { updateOpenBranchingPanel } from "@root/questions/actions";
import {useQuestionsStore} from "@root/questions/store"; import {useQuestionsStore} from "@root/questions/store";
import { enqueueSnackbar } from "notistack"; import { enqueueSnackbar } from "notistack";
import { useCurrentQuiz } from "@root/quizes/hooks";
interface Props { interface Props {
@ -46,6 +47,7 @@ export default function ButtonsOptionsAndPict({
const isMobile = useMediaQuery(theme.breakpoints.down(790)); const isMobile = useMediaQuery(theme.breakpoints.down(790));
const isIconMobile = useMediaQuery(theme.breakpoints.down(1050)); const isIconMobile = useMediaQuery(theme.breakpoints.down(1050));
const {openBranchingPanel} = useQuestionsStore.getState() const {openBranchingPanel} = useQuestionsStore.getState()
const quiz = useCurrentQuiz();
useEffect(() => { useEffect(() => {
if (question.deleteTimeoutId) { if (question.deleteTimeoutId) {
@ -55,6 +57,7 @@ export default function ButtonsOptionsAndPict({
const handleClickBranching = (_, value) => { const handleClickBranching = (_, value) => {
const parentId = question.content.rule.parentId const parentId = question.content.rule.parentId
console.log(parentId)
console.log(parentId.length) console.log(parentId.length)
if (parentId.length === 0 ) { if (parentId.length === 0 ) {
return enqueueSnackbar("Вопрос не учавствует в ветвлении") return enqueueSnackbar("Вопрос не учавствует в ветвлении")
@ -336,7 +339,7 @@ export default function ButtonsOptionsAndPict({
// deleteTimeoutId: newTimeoutId, // deleteTimeoutId: newTimeoutId,
// }); // });
deleteQuestion(question.id); deleteQuestion(question.id, quiz?.id);
}} }}
data-cy="delete-question" data-cy="delete-question"
> >

@ -39,7 +39,7 @@ import SwitchQuestionsPage from "../SwitchQuestionsPage";
import { ChooseAnswerModal } from "./ChooseAnswerModal"; import { ChooseAnswerModal } from "./ChooseAnswerModal";
import TypeQuestions from "../TypeQuestions"; import TypeQuestions from "../TypeQuestions";
import { QuestionType } from "@model/question/question"; import { QuestionType } from "@model/question/question";
import { useCurrentQuiz } from "@root/quizes/hooks"
interface Props { interface Props {
question: AnyTypedQuizQuestion | UntypedQuizQuestion; question: AnyTypedQuizQuestion | UntypedQuizQuestion;
@ -55,6 +55,7 @@ export default function QuestionsPageCard({ question, draggableProps, isDragging
const isTablet = useMediaQuery(theme.breakpoints.down(1000)); const isTablet = useMediaQuery(theme.breakpoints.down(1000));
const isMobile = useMediaQuery(theme.breakpoints.down(790)); const isMobile = useMediaQuery(theme.breakpoints.down(790));
const anchorRef = useRef(null); const anchorRef = useRef(null);
const quiz = useCurrentQuiz();
const setTitle = useDebouncedCallback((title) => { const setTitle = useDebouncedCallback((title) => {
const updateQuestionFn = question.type === null ? updateUntypedQuestion : updateQuestion; const updateQuestionFn = question.type === null ? updateUntypedQuestion : updateQuestion;
@ -253,7 +254,7 @@ export default function QuestionsPageCard({ question, draggableProps, isDragging
// deleteTimeoutId: newTimeoutId, // deleteTimeoutId: newTimeoutId,
// }); // });
deleteQuestion(question.id); deleteQuestion(question.id, quiz.id);
}} }}
data-cy="delete-question" data-cy="delete-question"
> >

@ -27,6 +27,7 @@ export default function QuestionsPage() {
const openBranchingPanel = useQuestionsStore.getState() const openBranchingPanel = useQuestionsStore.getState()
if (!quiz) return null; if (!quiz) return null;
return ( return (
<> <>
<Box <Box

@ -1,9 +1,12 @@
import { useState, useEffect } from "react"; import { useState, useEffect } from "react";
import { Box, Typography, Button, useTheme } from "@mui/material"; import { Box, Typography, Button, useTheme } from "@mui/material";
import { useQuizViewStore, getAnswersByQuestionId } from "@root/quizView"; import { useQuizViewStore } from "@root/quizView";
import type { AnyTypedQuizQuestion, QuizQuestionBase } from "../../model/questionTypes/shared"; import type {
AnyTypedQuizQuestion,
QuizQuestionBase,
} from "../../model/questionTypes/shared";
import { getQuestionByContentId } from "@root/questions/actions"; import { getQuestionByContentId } from "@root/questions/actions";
import { enqueueSnackbar } from "notistack"; import { enqueueSnackbar } from "notistack";
@ -12,10 +15,7 @@ type FooterProps = {
question: QuizQuestionBase; question: QuizQuestionBase;
}; };
export const Footer = ({ export const Footer = ({ setCurrentQuestion, question }: FooterProps) => {
setCurrentQuestion,
question,
}: FooterProps) => {
const [disabledQuestionsId, setDisabledQuestionsId] = useState<Set<string>>( const [disabledQuestionsId, setDisabledQuestionsId] = useState<Set<string>>(
new Set() new Set()
); );
@ -24,60 +24,67 @@ export const Footer = ({
const followPreviousStep = () => { const followPreviousStep = () => {
if (question?.content.rule.parentId !== "root") { if (question?.content.rule.parentId !== "root") {
const parent = getQuestionByContentId(question?.content.rule.parentId) const parent = getQuestionByContentId(question?.content.rule.parentId);
if (parent) { if (parent) {
setCurrentQuestion(parent) setCurrentQuestion(parent);
} else { } else {
enqueueSnackbar("не могу получить предыдущий вопрос") enqueueSnackbar("не могу получить предыдущий вопрос");
} }
} else { } else {
enqueueSnackbar("вы находитесь на первом вопросе") enqueueSnackbar("вы находитесь на первом вопросе");
} }
}; };
const followNextStep = () => { const followNextStep = () => {
const answers = getAnswersByQuestionId(question.content.id) || [] if (answers.length) {
console.log(answers) let readyBeNextQuestion = "";
if (answers) {
let readyBeNextQuestion = ""
question.content.rule.main.forEach(({ next, rules }) => { question.content.rule.main.forEach(({ next, rules }) => {
console.log({ next, rules }) console.log({ next, rules });
console.log("[storeAnswers] ", rules[0].answers) console.log("[storeAnswers] ", rules[0].answers);
console.log("[answers.answer] ", [answers.answer]) console.log("[answers.answer] ", [answers.at(-1)?.answer]);
let longerArray = Math.max(rules[0].answers.length, [answers.answer].length) let longerArray = Math.max(
rules[0].answers.length,
[answers.at(-1)?.answer].length
);
for (var i = 0; i < longerArray; i++) // Цикл по всем эле­мен­там бОльшего массива for (
if (rules[0].answers[i] !== [answers.answer][i]) readyBeNextQuestion = next; // Ес­ли хоть один эле­мент от­ли­ча­ет­ся, мас­си­вы не рав­ны var i = 0;
i < longerArray;
i++ // Цикл по всем эле­мен­там бОльшего массива
}) ) {
if (readyBeNextQuestion) { if (rules[0].answers[i] === answers.at(-1)?.answer) {
console.log("мы нашли совпадение в " + readyBeNextQuestion) readyBeNextQuestion = next; // Ес­ли хоть один эле­мент от­ли­ча­ет­ся, мас­си­вы не рав­ны
const nextQuestion = getQuestionByContentId(readyBeNextQuestion)
console.log("next question ", nextQuestion)
if (nextQuestion) {
console.log("я устанавливаю следующий вопрос " + question.title)
setCurrentQuestion(nextQuestion)
return
} else {
enqueueSnackbar("не могу получить последующий вопрос")
}
} else {
const nextQuestion = getQuestionByContentId(question.content.rule.default)
console.log("я устанавливаю дефолтный вопрос")
if (nextQuestion) {
setCurrentQuestion(nextQuestion)
} else {
enqueueSnackbar("не могу получить последующий вопрос (дефолтный)")
} }
} }
});
if (readyBeNextQuestion) {
console.log("мы нашли совпадение в " + readyBeNextQuestion);
const nextQuestion = getQuestionByContentId(readyBeNextQuestion);
console.log("next question ", nextQuestion);
if (nextQuestion) {
console.log("я устанавливаю следующий вопрос " + question.title);
setCurrentQuestion(nextQuestion);
return;
} else {
enqueueSnackbar("не могу получить последующий вопрос");
}
} else {
const nextQuestion = getQuestionByContentId(
question.content.rule.default
);
console.log("я устанавливаю дефолтный вопрос");
if (nextQuestion) {
setCurrentQuestion(nextQuestion);
} else {
enqueueSnackbar("не могу получить последующий вопрос (дефолтный)");
}
}
} }
}; };
return ( return (

@ -10,6 +10,8 @@ import { nanoid } from "nanoid";
import { enqueueSnackbar } from "notistack"; import { enqueueSnackbar } from "notistack";
import { isAxiosCanceledError } from "../../utils/isAxiosCanceledError"; import { isAxiosCanceledError } from "../../utils/isAxiosCanceledError";
import { RequestQueue } from "../../utils/requestQueue"; import { RequestQueue } from "../../utils/requestQueue";
import { updateRootContentId } from "@root/quizes/actions"
import { useCurrentQuiz } from "@root/quizes/hooks"
import { QuestionsStore, useQuestionsStore } from "./store"; import { QuestionsStore, useQuestionsStore } from "./store";
@ -136,22 +138,20 @@ export const updateQuestion = (
// clearTimeout(requestTimeoutId); // clearTimeout(requestTimeoutId);
// requestTimeoutId = setTimeout(() => { // requestTimeoutId = setTimeout(() => {
requestQueue.enqueue(async () => { requestQueue.enqueue(async () => {
const q = useQuestionsStore.getState().questions.find(q => q.id === questionId) || useQuestionsStore.getState().questions.find(q => q.content.id === questionId); 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) return; if (q.type === null) throw new Error("Cannot send update request for untyped question");
if (q.type === null) throw new Error("Cannot send update request for untyped question");
console.log(q.title)
const response = await questionApi.edit(questionToEditQuestionRequest(q)); const response = await questionApi.edit(questionToEditQuestionRequest(q));
setQuestionBackendId(questionId, response.updated); setQuestionBackendId(questionId, response.updated);
}).catch(error => { }).catch(error => {
if (isAxiosCanceledError(error)) return; if (isAxiosCanceledError(error)) return;
devlog("Error editing question", { error, questionId }); devlog("Error editing question", { error, questionId });
enqueueSnackbar("Не удалось сохранить вопрос"); enqueueSnackbar("Не удалось сохранить вопрос");
}); });
// }, REQUEST_DEBOUNCE); // }, REQUEST_DEBOUNCE);
}; };
@ -261,8 +261,10 @@ export const changeQuestionType = (
type: QuestionType, type: QuestionType,
) => { ) => {
updateQuestion(questionId, question => { updateQuestion(questionId, question => {
const oldId = question.content.id
question.type = type; question.type = type;
question.content = defaultQuestionByType[type].content; 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); const question = useQuestionsStore.getState().questions.find(q => q.id === questionId);
if (!question) return; if (!question) return;
if (question.type !== null) throw new Error("Cannot upgrade already typed question"); if (question.type !== null) throw new Error("Cannot upgrade already typed question");
try { try {
const createdQuestion = await questionApi.create({ const createdQuestion = await questionApi.create({
quiz_id: question.quizId, quiz_id: question.quizId,
@ -302,7 +304,8 @@ export const createTypedQuestion = async (
} }
}); });
export const deleteQuestion = async (questionId: string) => requestQueue.enqueue(async () => { export const deleteQuestion = async (questionId: string, quizId: string) => requestQueue.enqueue(async () => {
const question = useQuestionsStore.getState().questions.find(q => q.id === questionId); const question = useQuestionsStore.getState().questions.find(q => q.id === questionId);
if (!question) return; if (!question) return;
@ -312,7 +315,12 @@ export const deleteQuestion = async (questionId: string) => requestQueue.enqueue
} }
try { try {
console.log(question.content.rule.parentId)
await questionApi.delete(question.backendId); await questionApi.delete(question.backendId);
if (question.content.rule.parentId === "root") { //удалить из стора root и очистить rule всем вопросам
updateRootContentId(quizId, "")
clearRoleForAll()
}
removeQuestion(questionId); removeQuestion(questionId);
} catch (error) { } catch (error) {
@ -325,9 +333,11 @@ export const copyQuestion = async (questionId: string, quizId: number) => reques
const question = useQuestionsStore.getState().questions.find(q => q.id === questionId); const question = useQuestionsStore.getState().questions.find(q => q.id === questionId);
if (!question) return; if (!question) return;
const frontId = nanoid();
if (question.type === null) { if (question.type === null) {
const copiedQuestion = structuredClone(question); const copiedQuestion = structuredClone(question);
copiedQuestion.id = nanoid(); copiedQuestion.id = frontId
copiedQuestion.content.id = frontId
setProducedState(state => { setProducedState(state => {
state.questions.push(copiedQuestion); state.questions.push(copiedQuestion);
@ -369,7 +379,7 @@ function setProducedState<A extends string | { type: unknown; }>(
export const cleardragQuestionContentId = () => { export const cleardragQuestionContentId = () => {
useQuestionsStore.setState({dragQuestionContentId: null}); useQuestionsStore.setState({ dragQuestionContentId: null });
}; };
export const getQuestionById = (questionId: string | null) => { export const getQuestionById = (questionId: string | null) => {
@ -377,17 +387,28 @@ export const getQuestionById = (questionId: string | null) => {
return useQuestionsStore.getState().questions.find(q => q.id === questionId) || null; return useQuestionsStore.getState().questions.find(q => q.id === questionId) || null;
}; };
export const getQuestionByContentId = (questionContentId: string | null) => { export const getQuestionByContentId = (questionContentId: string | null) => {
console.log("questionContentId " + questionContentId)
if (questionContentId === null) return null; if (questionContentId === null) return null;
return useQuestionsStore.getState().questions.find(q => { return useQuestionsStore.getState().questions.find(q => {
console.log(q.content.id) return (q.content.id === questionContentId)
console.log(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) => { 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 = ""
})
}
});
}
export const updateOpenBranchingPanel = (value: boolean) => useQuestionsStore.setState({openBranchingPanel: !value}); export const updateOpenBranchingPanel = (value: boolean) => useQuestionsStore.setState({openBranchingPanel: !value});

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

@ -33,9 +33,3 @@ export const updateAnswer = (questionId: string, answer: string) => {
useQuizViewStore.setState({ answers }); useQuizViewStore.setState({ answers });
}; };
export const getAnswersByQuestionId = (questionId: string) => {
if (questionId === null) return null;
const answers = [...useQuizViewStore.getState().answers];
return answers.find(a => a.questionId === questionId) || null;
}

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

1249
yarn.lock

File diff suppressed because it is too large Load Diff