2023-12-04 13:33:43 +00:00
import { useEffect , useLayoutEffect , useRef , useState } from "react" ;
2023-11-29 15:45:15 +00:00
import Cytoscape from "cytoscape" ;
2023-12-19 15:00:51 +00:00
import { Button , Box } from "@mui/material" ;
2023-11-29 15:45:15 +00:00
import CytoscapeComponent from "react-cytoscapejs" ;
import popper from "cytoscape-popper" ;
import { useCurrentQuiz } from "@root/quizes/hooks" ;
2023-12-03 10:48:00 +00:00
import { updateRootContentId } from "@root/quizes/actions"
2023-12-03 13:09:57 +00:00
import { AnyTypedQuizQuestion } from "@model/questionTypes/shared"
2023-11-29 15:45:15 +00:00
import { useQuestionsStore } from "@root/questions/store" ;
2023-12-16 03:05:15 +00:00
import { deleteQuestion , updateQuestion , getQuestionByContentId , clearRuleForAll , createFrontResult } from "@root/questions/actions" ;
2023-12-19 15:00:51 +00:00
import { updateCanCreatePublic , updateModalInfoWhyCantCreate , updateOpenedModalSettingsId , } from "@root/uiTools/actions" ;
2023-12-14 09:40:53 +00:00
import { cleardragQuestionContentId } from "@root/uiTools/actions" ;
2023-12-07 00:03:43 +00:00
import { withErrorBoundary } from "react-error-boundary" ;
2023-12-19 15:00:51 +00:00
import { ProblemIcon } from "@ui_kit/ProblemIcon" ;
2023-11-29 15:45:15 +00:00
import { storeToNodes } from "./helper" ;
import "./styles.css" ;
import type {
Stylesheet ,
Core ,
NodeSingular ,
AbstractEventObject ,
ElementDefinition ,
} from "cytoscape" ;
2023-12-02 09:59:31 +00:00
import { enqueueSnackbar } from "notistack" ;
2023-12-14 09:40:53 +00:00
import { useUiTools } from "@root/uiTools/store" ;
2023-11-29 15:45:15 +00:00
type PopperItem = {
id : ( ) = > string ;
} ;
type Modifier = {
name : string ;
options : unknown ;
} ;
type PopperConfig = {
popper : {
placement : string ;
modifiers? : Modifier [ ] ;
} ;
content : ( items : PopperItem [ ] ) = > void ;
} ;
type Popper = {
update : ( ) = > Promise < void > ;
setOptions : ( modifiers : { modifiers? : Modifier [ ] } ) = > void ;
} ;
type NodeSingularWithPopper = NodeSingular & {
popper : ( config : PopperConfig ) = > Popper ;
} ;
const stylesheet : Stylesheet [ ] = [
{
selector : "node" ,
style : {
shape : "round-rectangle" ,
width : 130 ,
height : 130 ,
backgroundColor : "#FFFFFF" ,
label : "data(label)" ,
"font-size" : "16" ,
color : "#4D4D4D" ,
"text-halign" : "center" ,
"text-valign" : "center" ,
"text-wrap" : "wrap" ,
"text-max-width" : "80" ,
} ,
} ,
2023-12-05 23:34:40 +00:00
{
selector : "[?eroticeyeblink]" ,
style : {
"border-width" : "4px" ,
"border-style" : "solid" ,
"border-color" : "#7e2aea" ,
} ,
} ,
2023-11-29 15:45:15 +00:00
{
selector : ".multiline-auto" ,
style : {
"text-wrap" : "wrap" ,
"text-max-width" : "80" ,
} ,
} ,
{
selector : "edge" ,
style : {
width : 30 ,
"line-color" : "#DEDFE7" ,
"curve-style" : "taxi" ,
"taxi-direction" : "horizontal" ,
"taxi-turn" : 60 ,
} ,
} ,
{
selector : ":selected" ,
style : {
"border-style" : "solid" ,
"border-width" : 1.5 ,
"border-color" : "#9A9AAF" ,
} ,
} ,
] ;
Cytoscape . use ( popper ) ;
2023-12-01 19:56:13 +00:00
interface Props {
modalQuestionParentContentId : string ;
modalQuestionTargetContentId : string ;
2023-12-02 13:11:57 +00:00
setOpenedModalQuestions : ( open : boolean ) = > void ;
setModalQuestionParentContentId : ( id : string ) = > void ;
setModalQuestionTargetContentId : ( id : string ) = > void ;
2023-12-01 19:56:13 +00:00
}
2023-12-19 15:00:51 +00:00
2023-12-11 10:08:54 +00:00
function CsComponent ( {
2023-12-01 19:56:13 +00:00
modalQuestionParentContentId ,
modalQuestionTargetContentId ,
setOpenedModalQuestions ,
setModalQuestionParentContentId ,
setModalQuestionTargetContentId
2023-12-11 10:08:54 +00:00
} : Props ) {
2023-11-29 15:45:15 +00:00
const quiz = useCurrentQuiz ( ) ;
2023-12-19 15:00:51 +00:00
const { dragQuestionContentId , desireToOpenABranchingModal , canCreatePublic } = useUiTools ( )
2023-12-07 22:56:31 +00:00
const trashQuestions = useQuestionsStore ( ) . questions
2023-12-09 14:52:03 +00:00
const questions = trashQuestions . filter ( ( question ) = > question . type !== "result" && question . type !== null )
2023-11-29 15:45:15 +00:00
const [ startCreate , setStartCreate ] = useState ( "" ) ;
const [ startRemove , setStartRemove ] = useState ( "" ) ;
const cyRef = useRef < Core | null > ( null ) ;
2023-12-01 08:12:59 +00:00
const layoutsContainer = useRef < HTMLDivElement | null > ( null ) ;
2023-11-29 15:45:15 +00:00
const plusesContainer = useRef < HTMLDivElement | null > ( null ) ;
const crossesContainer = useRef < HTMLDivElement | null > ( null ) ;
const gearsContainer = useRef < HTMLDivElement | null > ( null ) ;
2023-12-19 19:55:59 +00:00
useEffect ( ( ) = > {
return ( ) = > {
// if (!canCreatePublic) updateModalInfoWhyCantCreate(true)
}
} , [ ] ) ;
2023-12-05 23:34:40 +00:00
useLayoutEffect ( ( ) = > {
2023-12-07 00:03:43 +00:00
const cy = cyRef ? . current
2023-12-05 23:34:40 +00:00
if ( desireToOpenABranchingModal ) {
2023-12-07 00:03:43 +00:00
setTimeout ( ( ) = > {
cy ? . getElementById ( desireToOpenABranchingModal ) ? . data ( "eroticeyeblink" , true )
} , 250 )
2023-12-05 23:34:40 +00:00
} else {
cy ? . elements ( ) . data ( "eroticeyeblink" , false )
}
} , [ desireToOpenABranchingModal ] )
useLayoutEffect ( ( ) = > {
updateOpenedModalSettingsId ( )
2023-12-19 15:00:51 +00:00
// updateRootContentId(quiz.id, "")
// clearRuleForAll()
2023-12-05 23:34:40 +00:00
} , [ ] )
2023-12-02 13:11:57 +00:00
useEffect ( ( ) = > {
if ( modalQuestionTargetContentId . length !== 0 && modalQuestionParentContentId . length !== 0 ) {
2023-12-04 09:30:14 +00:00
addNode ( { parentNodeContentId : modalQuestionParentContentId , targetNodeContentId : modalQuestionTargetContentId } )
2023-12-02 13:11:57 +00:00
}
2023-12-04 15:40:15 +00:00
setModalQuestionParentContentId ( "" )
setModalQuestionTargetContentId ( "" )
2023-12-02 13:11:57 +00:00
} , [ modalQuestionTargetContentId ] )
2023-12-01 08:12:59 +00:00
2023-12-02 21:20:41 +00:00
const addNode = ( { parentNodeContentId , targetNodeContentId } : { parentNodeContentId : string , targetNodeContentId? : string } ) = > {
2023-12-19 15:00:51 +00:00
2023-12-13 10:53:59 +00:00
//запрещаем работу родителя-ребенка если это один и тот же вопрос
if ( parentNodeContentId === targetNodeContentId ) return
2023-11-29 15:45:15 +00:00
const cy = cyRef ? . current
2023-12-01 19:56:13 +00:00
const parentNodeChildren = cy ? . $ ( 'edge[source = "' + parentNodeContentId + '"]' ) ? . length
2023-12-02 21:20:41 +00:00
//если есть инфо о выбранном вопросе из модалки - берём родителя из инфо модалки. Иначе из значения дропа
2023-12-03 13:09:57 +00:00
const targetQuestion = { . . . getQuestionByContentId ( targetNodeContentId || dragQuestionContentId ) } as AnyTypedQuizQuestion
2023-12-19 15:00:51 +00:00
if ( Object . keys ( targetQuestion ) . length !== 0 && parentNodeContentId && parentNodeChildren !== undefined ) {
2023-12-02 09:59:31 +00:00
clearDataAfterAddNode ( { parentNodeContentId , targetQuestion , parentNodeChildren } )
2023-12-04 09:30:14 +00:00
cy ? . data ( 'changed' , true )
2023-12-16 03:05:15 +00:00
createFrontResult ( quiz . backendId , targetQuestion . content . id )
2023-12-09 20:49:08 +00:00
const es = cy ? . add ( [
2023-12-01 08:12:59 +00:00
{
data : {
2023-12-01 19:56:13 +00:00
id : targetQuestion.content.id ,
2023-12-14 17:58:56 +00:00
label : targetQuestion.title === "" || targetQuestion . title === " " ? "noname" : targetQuestion . title
2023-12-01 08:12:59 +00:00
}
} ,
{
data : {
2023-12-01 19:56:13 +00:00
source : parentNodeContentId ,
target : targetQuestion.content.id
2023-12-01 08:12:59 +00:00
}
}
2023-12-03 00:46:58 +00:00
] )
cy ? . layout ( lyopts ) . run ( )
2023-12-16 18:38:17 +00:00
cy ? . center ( es )
2023-12-02 09:59:31 +00:00
} else {
2023-12-02 13:11:57 +00:00
enqueueSnackbar ( "Добавляемый вопрос не найден" )
}
2023-12-01 19:56:13 +00:00
}
2023-12-03 13:09:57 +00:00
const clearDataAfterAddNode = ( { parentNodeContentId , targetQuestion , parentNodeChildren } : { parentNodeContentId : string , targetQuestion : AnyTypedQuizQuestion , parentNodeChildren : number } ) = > {
2023-12-11 15:40:42 +00:00
const parentQuestion = { . . . getQuestionByContentId ( parentNodeContentId ) } as AnyTypedQuizQuestion
//смотрим не добавлен ли родителю result. Если да - убираем е г о . Веточкам result не нужен
trashQuestions . forEach ( ( targetQuestion ) = > {
if ( targetQuestion . type === "result" && targetQuestion . content . rule . parentId === parentQuestion . content . id ) {
deleteQuestion ( targetQuestion . id ) ;
}
} )
2023-12-02 09:59:31 +00:00
//предупреждаем добавленный вопрос о том, кто е г о родитель
2023-12-02 13:11:57 +00:00
updateQuestion ( targetQuestion . content . id , question = > {
question . content . rule . parentId = parentNodeContentId
question . content . rule . main = [ ]
} )
2023-12-11 15:40:42 +00:00
//предупреждаем родителя о новом потомке (если он ещё не знает о нём)
if ( ! parentQuestion . content . rule . children . includes ( targetQuestion . content . id ) ) updateQuestion ( parentNodeContentId , question = > {
2023-12-11 10:08:54 +00:00
question . content . rule . children = [ . . . question . content . rule . children , targetQuestion . content . id ]
} )
2023-12-01 19:56:13 +00:00
2023-12-02 13:11:57 +00:00
//Если детей больше 1 - предупреждаем стор вопросов о б открытии модалки ветвления
2023-12-11 15:40:42 +00:00
if ( parentQuestion . content . rule . children >= 1 ) {
2023-12-04 13:33:43 +00:00
updateOpenedModalSettingsId ( targetQuestion . content . id )
2023-12-02 13:11:57 +00:00
}
2023-12-01 19:56:13 +00:00
}
2023-12-02 09:59:31 +00:00
2023-12-12 15:44:18 +00:00
2023-12-01 19:56:13 +00:00
const removeNode = ( { targetNodeContentId } : { targetNodeContentId : string } ) = > {
2023-12-04 09:30:14 +00:00
const deleteNodes = [ ] as string [ ]
const deleteEdges : any = [ ]
2023-12-01 19:56:13 +00:00
const cy = cyRef ? . current
2023-12-01 08:12:59 +00:00
2023-12-02 20:47:28 +00:00
const findChildrenToDelete = ( node ) = > {
2023-12-02 13:11:57 +00:00
2023-12-02 20:47:28 +00:00
//Узнаём грани, идущие от этой ноды
cy ? . $ ( 'edge[source = "' + node . id ( ) + '"]' ) ? . toArray ( ) . forEach ( ( edge ) = > {
const edgeData = edge . data ( )
//записываем id грани для дальнейшего удаления
deleteEdges . push ( edge )
//ищем ноду на конце грани, записываем её ID для дальнейшего удаления
const targetNode = cy ? . $ ( "#" + edgeData . target )
deleteNodes . push ( targetNode . data ( ) . id )
//вызываем функцию для анализа потомков уже у этой ноды
findChildrenToDelete ( targetNode )
} )
}
findChildrenToDelete ( cy ? . getElementById ( targetNodeContentId ) )
const targetQuestion = getQuestionByContentId ( targetNodeContentId )
if ( targetQuestion . content . rule . parentId === "root" && quiz ) {
2023-12-04 09:30:14 +00:00
updateRootContentId ( quiz ? . id , "" )
2023-12-02 13:51:37 +00:00
updateQuestion ( targetNodeContentId , question = > {
2023-12-02 13:11:57 +00:00
question . content . rule . parentId = ""
question . content . rule . main = [ ]
2023-12-11 10:08:54 +00:00
question . content . rule . children = [ ]
2023-12-02 13:11:57 +00:00
question . content . rule . default = ""
} )
2023-12-11 15:40:42 +00:00
trashQuestions . forEach ( q = > {
2023-12-11 10:08:54 +00:00
if ( q . type === "result" ) {
deleteQuestion ( q . id ) ;
}
} ) ;
2023-12-07 00:03:43 +00:00
clearRuleForAll ( )
2023-12-11 10:08:54 +00:00
2023-12-02 13:11:57 +00:00
} else {
2023-12-11 10:08:54 +00:00
2023-12-02 13:11:57 +00:00
const parentQuestionContentId = cy ? . $ ( 'edge[target = "' + targetNodeContentId + '"]' ) ? . toArray ( ) ? . [ 0 ] ? . data ( ) ? . source
2023-12-02 13:51:37 +00:00
if ( targetNodeContentId && parentQuestionContentId ) {
2023-12-19 15:00:51 +00:00
if ( cy ? . edges ( ` [source=" ${ parentQuestionContentId } "] ` ) . length === 0 )
createFrontResult ( quiz . backendId , parentQuestionContentId )
2023-12-04 09:30:14 +00:00
clearDataAfterRemoveNode ( { targetQuestionContentId : targetNodeContentId , parentQuestionContentId } )
cy ? . remove ( cy ? . $ ( '#' + targetNodeContentId ) ) . layout ( lyopts ) . run ( )
2023-12-02 13:11:57 +00:00
}
2023-12-04 09:30:14 +00:00
}
2023-12-11 10:08:54 +00:00
//После всех манипуляций удаляем грани и ноды из CS Чистим rule потомков на беке
2023-12-02 20:47:28 +00:00
2023-12-04 09:30:14 +00:00
deleteNodes . forEach ( ( nodeId ) = > { //Ноды
cy ? . remove ( cy ? . $ ( "#" + nodeId ) )
2023-12-03 10:47:52 +00:00
removeButtons ( nodeId )
2023-12-04 09:30:14 +00:00
updateQuestion ( nodeId , question = > {
question . content . rule . parentId = ""
question . content . rule . main = [ ]
question . content . rule . default = ""
2023-12-11 10:08:54 +00:00
question . content . rule . children = [ ]
2023-12-02 20:47:28 +00:00
} )
2023-12-11 15:40:42 +00:00
2023-12-04 09:30:14 +00:00
} )
2023-12-02 20:47:28 +00:00
2023-12-04 09:30:14 +00:00
deleteEdges . forEach ( ( edge : any ) = > { //Грани
cy ? . remove ( edge )
} )
2023-12-01 08:12:59 +00:00
2023-12-04 09:30:14 +00:00
removeButtons ( targetNodeContentId )
cy ? . data ( 'changed' , true )
cy ? . layout ( lyopts ) . run ( )
2023-12-11 10:08:54 +00:00
//удаляем result всех потомков
2023-12-11 15:40:42 +00:00
trashQuestions . forEach ( ( qr ) = > {
if ( qr . type === "result" ) {
if ( deleteNodes . includes ( qr . content . rule . parentId ) || qr . content . rule . parentId === targetQuestion . content . id ) {
deleteQuestion ( qr . id ) ;
2023-12-11 10:08:54 +00:00
}
}
} )
2023-11-29 15:45:15 +00:00
}
2023-12-11 10:08:54 +00:00
2023-12-02 13:11:57 +00:00
const clearDataAfterRemoveNode = ( { targetQuestionContentId , parentQuestionContentId } : { targetQuestionContentId : string , parentQuestionContentId : string } ) = > {
2023-12-11 10:08:54 +00:00
2023-12-02 13:11:57 +00:00
updateQuestion ( targetQuestionContentId , question = > {
question . content . rule . parentId = ""
2023-12-11 10:08:54 +00:00
question . content . rule . children = [ ]
2023-12-02 13:11:57 +00:00
question . content . rule . main = [ ]
question . content . rule . default = ""
} )
2023-12-04 13:33:43 +00:00
2023-12-11 15:40:42 +00:00
2023-12-07 00:03:43 +00:00
//чистим rule родителя
const parentQuestion = getQuestionByContentId ( parentQuestionContentId )
const newRule = { }
2023-12-11 15:40:42 +00:00
const newChildren = [ . . . parentQuestion . content . rule . children ]
newChildren . splice ( parentQuestion . content . rule . children . indexOf ( targetQuestionContentId ) , 1 ) ;
2023-12-07 00:03:43 +00:00
newRule . main = parentQuestion . content . rule . main . filter ( ( data ) = > data . next !== targetQuestionContentId ) //удаляем условия перехода от родителя к этому вопросу
newRule . parentId = parentQuestion . content . rule . parentId
2023-12-11 15:40:42 +00:00
newRule . default = parentQuestion . content . rule . default === targetQuestionContentId ? "" : parentQuestion . content . rule . default
newRule . children = newChildren
2023-12-07 00:03:43 +00:00
updateQuestion ( parentQuestionContentId , ( PQ ) = > {
PQ . content . rule = newRule
2023-12-02 13:11:57 +00:00
} )
}
2023-12-02 09:59:31 +00:00
2023-12-12 15:44:18 +00:00
2023-11-29 15:45:15 +00:00
useEffect ( ( ) = > {
if ( startCreate ) {
2023-12-01 19:56:13 +00:00
addNode ( { parentNodeContentId : startCreate } ) ;
cleardragQuestionContentId ( )
2023-11-29 15:45:15 +00:00
setStartCreate ( "" ) ;
}
} , [ startCreate ] ) ;
useEffect ( ( ) = > {
if ( startRemove ) {
2023-12-02 09:59:31 +00:00
removeNode ( { targetNodeContentId : startRemove } ) ;
2023-11-29 15:45:15 +00:00
setStartRemove ( "" ) ;
}
} , [ startRemove ] ) ;
2023-12-02 09:59:31 +00:00
const readyLO = ( e ) = > {
2023-12-03 00:46:58 +00:00
if ( e . cy . data ( 'firstNode' ) === 'nonroot' ) {
2023-12-04 09:30:14 +00:00
e . cy . data ( 'firstNode' , 'root' )
e . cy . nodes ( ) . sort ( ( a , b ) = > ( a . data ( 'root' ) ? 1 : - 1 ) ) . layout ( lyopts ) . run ( )
} else {
2023-12-03 00:46:58 +00:00
2023-12-04 09:30:14 +00:00
e . cy . data ( 'changed' , false )
e . cy . removeData ( 'firstNode' )
}
2023-12-03 00:46:58 +00:00
2023-12-02 09:59:31 +00:00
//удаляем иконки
e . cy . nodes ( ) . forEach ( ( ele : any ) = > {
const data = ele . data ( )
data . id && removeButtons ( data . id ) ;
} )
initialPopperIcons ( e )
}
2023-12-02 13:11:57 +00:00
const lyopts = {
2023-12-02 09:59:31 +00:00
name : 'preset' ,
positions : ( e ) = > {
2023-12-02 16:44:52 +00:00
if ( ! e . cy ( ) . data ( 'changed' ) ) {
return e . data ( 'oldPos' )
2023-12-11 10:08:54 +00:00
}
2023-12-02 09:59:31 +00:00
const id = e . id ( )
const incomming = e . cy ( ) . edges ( ` [target=" ${ id } "] ` )
const layer = 0
e . removeData ( 'lastChild' )
if ( incomming . length === 0 ) {
2023-12-03 00:46:58 +00:00
if ( e . cy ( ) . data ( 'firstNode' ) === undefined )
2023-12-04 09:30:14 +00:00
e . cy ( ) . data ( 'firstNode' , 'root' )
2023-12-03 00:46:58 +00:00
e . data ( 'root' , true )
const children = e . cy ( ) . edges ( ` [source=" ${ id } "] ` ) . targets ( )
2023-12-02 09:59:31 +00:00
e . data ( 'layer' , layer )
2023-12-03 00:46:58 +00:00
e . data ( 'children' , children . length )
2023-12-02 09:59:31 +00:00
const queue = [ ]
children . forEach ( n = > {
2023-12-03 00:46:58 +00:00
queue . push ( { task : n , layer : layer + 1 } )
2023-12-02 09:59:31 +00:00
} )
while ( queue . length ) {
const task = queue . pop ( )
task . task . data ( 'layer' , task . layer )
2023-12-03 21:54:44 +00:00
task . task . removeData ( 'subtreeWidth' )
2023-12-03 00:46:58 +00:00
const children = e . cy ( ) . edges ( ` [source=" ${ task . task . id ( ) } "] ` ) . targets ( )
task . task . data ( 'children' , children . length )
2023-12-02 09:59:31 +00:00
if ( children . length !== 0 ) {
2023-12-03 00:46:58 +00:00
children . forEach ( n = > queue . push ( { task : n , layer : task.layer + 1 } ) )
2023-12-01 19:56:13 +00:00
}
2023-12-02 09:59:31 +00:00
}
2023-12-03 21:54:44 +00:00
queue . push ( { parent : e , children : children } )
2023-12-02 09:59:31 +00:00
while ( queue . length ) {
const task = queue . pop ( )
if ( task . children . length === 0 ) {
2023-12-11 10:08:54 +00:00
task . parent . data ( 'subtreeWidth' , task . parent . height ( ) + 50 )
2023-12-02 09:59:31 +00:00
continue
2023-12-01 19:56:13 +00:00
}
2023-12-02 09:59:31 +00:00
const unprocessed = task ? . children . filter ( e = > {
return ( e . data ( 'subtreeWidth' ) === undefined )
} )
if ( unprocessed . length !== 0 ) {
queue . push ( task )
unprocessed . forEach ( t = > {
queue . push ( { parent : t , children : t.cy ( ) . edges ( ` [source=" ${ t . id ( ) } "] ` ) . targets ( ) } )
} )
continue
2023-12-01 19:56:13 +00:00
}
2023-12-02 09:59:31 +00:00
task ? . parent . data ( 'subtreeWidth' , task . children . reduce ( ( p , n ) = > p + n . data ( 'subtreeWidth' ) , 0 ) )
}
2023-12-09 19:31:06 +00:00
2023-12-03 00:46:58 +00:00
const pos = { x : 0 , y : 0 }
2023-12-02 20:47:28 +00:00
e . data ( 'oldPos' , pos )
2023-12-11 10:08:54 +00:00
queue . push ( { task : children , parent : e } )
2023-12-09 19:31:06 +00:00
while ( queue . length ) {
const task = queue . pop ( )
const oldPos = task . parent . data ( 'oldPos' )
2023-12-11 10:08:54 +00:00
let yoffset = oldPos . y - task . parent . data ( 'subtreeWidth' ) / 2
2023-12-09 19:31:06 +00:00
task . task . forEach ( n = > {
const width = n . data ( 'subtreeWidth' )
2023-12-11 10:08:54 +00:00
n . data ( 'oldPos' , { x : 250 * n . data ( 'layer' ) , y : yoffset + width / 2 } )
yoffset += width
queue . push ( { task : n.cy ( ) . edges ( ` [source=" ${ n . id ( ) } "] ` ) . targets ( ) , parent : n } )
2023-12-09 19:31:06 +00:00
} )
}
2023-12-09 20:49:08 +00:00
e . cy ( ) . data ( 'changed' , false )
2023-12-02 16:44:52 +00:00
return pos
2023-12-02 09:59:31 +00:00
} else {
2023-12-09 19:31:06 +00:00
const opos = e . data ( 'oldPos' )
if ( opos ) {
return opos
2023-12-11 10:08:54 +00:00
}
2023-12-02 09:59:31 +00:00
}
} , // map of (node id) => (position obj); or function(node){ return somPos; }
zoom : undefined , // the zoom level to set (prob want fit = false if set)
pan : true , // the pan level to set (prob want fit = false if set)
fit : false , // whether to fit to viewport
padding : 30 , // padding on fit
animate : false , // whether to transition the node positions
animationDuration : 500 , // duration of animation in ms if enabled
animationEasing : undefined , // easing of animation if enabled
2023-12-10 01:01:56 +00:00
animateFilter : function ( node , i ) { return false ; } , // a function that determines whether the node should be animated. All nodes animated by default on animate enabled. Non-animated nodes are positioned immediately when the layout starts
2023-12-02 09:59:31 +00:00
ready : readyLO , // callback on layoutready
transform : function ( node , position ) { return position ; } // transform a given node position. Useful for changing flow direction in discrete layouts
}
useEffect ( ( ) = > {
document . querySelector ( "#root" ) ? . addEventListener ( "mouseup" , cleardragQuestionContentId ) ;
const cy = cyRef . current ;
2023-12-11 10:08:54 +00:00
const eles = cy ? . add ( storeToNodes ( questions . filter ( ( question : AnyTypedQuizQuestion ) = > ( question . type !== "result" && question . type !== null ) ) ) )
2023-12-02 20:47:28 +00:00
cy . data ( 'changed' , true )
2023-12-05 23:34:40 +00:00
// cy.data('changed', true)
2023-12-02 11:52:33 +00:00
const elecs = eles . layout ( lyopts ) . run ( )
2023-12-02 20:47:28 +00:00
cy ? . on ( 'add' , ( ) = > cy . data ( 'changed' , true ) )
2023-12-02 11:52:33 +00:00
cy ? . fit ( )
2023-12-02 09:59:31 +00:00
//cy?.layout().run()
2023-11-29 15:45:15 +00:00
return ( ) = > {
2023-12-01 19:56:13 +00:00
document . querySelector ( "#root" ) ? . removeEventListener ( "mouseup" , cleardragQuestionContentId ) ;
2023-12-01 08:12:59 +00:00
layoutsContainer . current ? . remove ( ) ;
2023-11-29 15:45:15 +00:00
plusesContainer . current ? . remove ( ) ;
crossesContainer . current ? . remove ( ) ;
gearsContainer . current ? . remove ( ) ;
} ;
} , [ ] ) ;
const removeButtons = ( id : string ) = > {
2023-12-01 08:12:59 +00:00
layoutsContainer . current
? . querySelector ( ` .popper-layout[data-id=' ${ id } '] ` )
? . remove ( ) ;
2023-11-29 15:45:15 +00:00
plusesContainer . current
? . querySelector ( ` .popper-plus[data-id=' ${ id } '] ` )
? . remove ( ) ;
crossesContainer . current
? . querySelector ( ` .popper-cross[data-id=' ${ id } '] ` )
? . remove ( ) ;
gearsContainer . current
? . querySelector ( ` .popper-gear[data-id=' ${ id } '] ` )
? . remove ( ) ;
} ;
2023-12-01 08:12:59 +00:00
const initialPopperIcons = ( e ) = > {
const cy = e . cy
2023-11-29 15:45:15 +00:00
const container =
( document . body . querySelector (
".__________cytoscape_container"
) as HTMLDivElement ) || null ;
if ( ! container ) {
return ;
}
container . style . overflow = "hidden" ;
if ( ! plusesContainer . current ) {
plusesContainer . current = document . createElement ( "div" ) ;
plusesContainer . current . setAttribute ( "id" , "popper-pluses" ) ;
container . append ( plusesContainer . current ) ;
}
if ( ! crossesContainer . current ) {
crossesContainer . current = document . createElement ( "div" ) ;
crossesContainer . current . setAttribute ( "id" , "popper-crosses" ) ;
container . append ( crossesContainer . current ) ;
}
if ( ! gearsContainer . current ) {
gearsContainer . current = document . createElement ( "div" ) ;
gearsContainer . current . setAttribute ( "id" , "popper-gears" ) ;
container . append ( gearsContainer . current ) ;
}
2023-12-02 09:59:31 +00:00
if ( ! layoutsContainer . current ) {
layoutsContainer . current = document . createElement ( "div" ) ;
layoutsContainer . current . setAttribute ( "id" , "popper-layouts" ) ;
container . append ( layoutsContainer . current ) ;
}
const ext = cy . extent ( )
2023-12-02 13:11:57 +00:00
const nodesInView = cy . nodes ( ) . filter ( n = > {
const bb = n . boundingBox ( )
2023-12-02 16:44:52 +00:00
return bb . x2 > ext . x1 && bb . x1 < ext . x2 && bb . y2 > ext . y1 && bb . y1 < ext . y2
2023-12-02 13:11:57 +00:00
} )
2023-11-29 15:45:15 +00:00
2023-12-02 13:11:57 +00:00
nodesInView
2023-11-29 15:45:15 +00:00
. toArray ( )
? . forEach ( ( item ) = > {
const node = item as NodeSingularWithPopper ;
2023-12-01 08:12:59 +00:00
2023-12-02 13:11:57 +00:00
const layoutsPopper = node . popper ( {
popper : {
placement : "left" ,
modifiers : [ { name : "flip" , options : { boundary : node } } ] ,
} ,
content : ( [ item ] ) = > {
const itemId = item . id ( ) ;
const itemElement = layoutsContainer . current ? . querySelector (
` .popper-layout[data-id=' ${ itemId } '] `
) ;
if ( itemElement ) {
return itemElement ;
}
const layoutElement = document . createElement ( "div" ) ;
layoutElement . style . zIndex = "0"
layoutElement . classList . add ( "popper-layout" ) ;
layoutElement . setAttribute ( "data-id" , item . id ( ) ) ;
layoutElement . addEventListener ( "mouseup" , ( ) = > {
2023-12-04 09:30:14 +00:00
//Узнаём грани, идущие от этой ноды
2023-12-02 21:20:41 +00:00
setModalQuestionParentContentId ( item . id ( ) )
setOpenedModalQuestions ( true )
} ) ;
2023-12-02 13:11:57 +00:00
layoutsContainer . current ? . appendChild ( layoutElement ) ;
return layoutElement ;
} ,
} ) ;
2023-11-29 15:45:15 +00:00
const plusesPopper = node . popper ( {
popper : {
placement : "right" ,
modifiers : [ { name : "flip" , options : { boundary : node } } ] ,
} ,
content : ( [ item ] ) = > {
const itemId = item . id ( ) ;
const itemElement = plusesContainer . current ? . querySelector (
` .popper-plus[data-id=' ${ itemId } '] `
) ;
if ( itemElement ) {
return itemElement ;
}
const plusElement = document . createElement ( "div" ) ;
plusElement . classList . add ( "popper-plus" ) ;
plusElement . setAttribute ( "data-id" , item . id ( ) ) ;
2023-12-02 13:11:57 +00:00
plusElement . style . zIndex = "1"
2023-12-01 08:12:59 +00:00
plusElement . addEventListener ( "mouseup" , ( ) = > {
setStartCreate ( node . id ( ) ) ;
} ) ;
2023-12-04 09:30:14 +00:00
2023-11-29 15:45:15 +00:00
plusesContainer . current ? . appendChild ( plusElement ) ;
return plusElement ;
} ,
} ) ;
const crossesPopper = node . popper ( {
popper : {
placement : "top-end" ,
2023-12-01 08:12:59 +00:00
modifiers : [ { name : "flip" , options : { boundary : node } } ] ,
2023-11-29 15:45:15 +00:00
} ,
content : ( [ item ] ) = > {
const itemId = item . id ( ) ;
const itemElement = crossesContainer . current ? . querySelector (
` .popper-cross[data-id=' ${ itemId } '] `
) ;
if ( itemElement ) {
return itemElement ;
}
const crossElement = document . createElement ( "div" ) ;
crossElement . classList . add ( "popper-cross" ) ;
crossElement . setAttribute ( "data-id" , item . id ( ) ) ;
2023-12-02 13:11:57 +00:00
crossElement . style . zIndex = "2"
2023-11-29 15:45:15 +00:00
crossesContainer . current ? . appendChild ( crossElement ) ;
2023-12-02 13:11:57 +00:00
crossElement . addEventListener ( "mouseup" , ( ) = > {
setStartRemove ( node . id ( ) )
2023-12-02 09:59:31 +00:00
2023-12-02 13:11:57 +00:00
}
) ;
2023-11-29 15:45:15 +00:00
return crossElement ;
} ,
} ) ;
2023-12-19 15:00:51 +00:00
let gearsPopper = null
2023-12-12 16:12:03 +00:00
if ( node . data ( ) . root !== true ) {
2023-12-19 15:00:51 +00:00
gearsPopper = node . popper ( {
popper : {
placement : "left" ,
modifiers : [ { name : "flip" , options : { boundary : node } } ] ,
} ,
content : ( [ item ] ) = > {
const itemId = item . id ( ) ;
const itemElement = gearsContainer . current ? . querySelector (
` .popper-gear[data-id=' ${ itemId } '] `
) ;
if ( itemElement ) {
return itemElement ;
}
const gearElement = document . createElement ( "div" ) ;
gearElement . classList . add ( "popper-gear" ) ;
gearElement . setAttribute ( "data-id" , item . id ( ) ) ;
gearElement . style . zIndex = "1"
gearsContainer . current ? . appendChild ( gearElement ) ;
gearElement . addEventListener ( "mouseup" , ( e ) = > {
updateOpenedModalSettingsId ( item . id ( ) )
} ) ;
return gearElement ;
} ,
} ) ;
2023-12-12 16:12:03 +00:00
}
2023-11-29 15:45:15 +00:00
const update = async ( ) = > {
await plusesPopper . update ( ) ;
await crossesPopper . update ( ) ;
2023-12-12 16:12:03 +00:00
await gearsPopper ? . update ( ) ;
2023-12-01 08:12:59 +00:00
await layoutsPopper . update ( ) ;
2023-11-29 15:45:15 +00:00
} ;
const onZoom = ( event : AbstractEventObject ) = > {
const zoom = event . cy . zoom ( ) ;
2023-12-10 01:01:56 +00:00
//update();
2023-11-29 15:45:15 +00:00
crossesPopper . setOptions ( {
modifiers : [
{ name : "flip" , options : { boundary : node } } ,
{ name : "offset" , options : { offset : [ - 5 * zoom , - 30 * zoom ] } } ,
] ,
} ) ;
2023-12-01 08:12:59 +00:00
layoutsPopper . setOptions ( {
modifiers : [
{ name : "flip" , options : { boundary : node } } ,
{ name : "offset" , options : { offset : [ 0 , - 130 * zoom ] } } ,
] ,
} ) ;
2023-12-12 00:07:01 +00:00
plusesPopper . setOptions ( {
modifiers : [
{ name : "flip" , options : { boundary : node } } ,
{ name : "offset" , options : { offset : [ 0 , 0 * zoom ] } } ,
] ,
} ) ;
2023-12-12 16:12:03 +00:00
gearsPopper ? . setOptions ( {
2023-12-12 00:07:01 +00:00
modifiers : [
{ name : "flip" , options : { boundary : node } } ,
{ name : "offset" , options : { offset : [ 0 , 0 ] } } ,
] ,
} ) ;
2023-12-01 08:12:59 +00:00
layoutsContainer . current
? . querySelectorAll ( "#popper-layouts > .popper-layout" )
. forEach ( ( item ) = > {
const element = item as HTMLDivElement ;
element . style . width = ` ${ 130 * zoom } px ` ;
element . style . height = ` ${ 130 * zoom } px ` ;
} ) ;
2023-11-29 15:45:15 +00:00
plusesContainer . current
? . querySelectorAll ( "#popper-pluses > .popper-plus" )
. forEach ( ( item ) = > {
const element = item as HTMLDivElement ;
element . style . width = ` ${ 40 * zoom } px ` ;
element . style . height = ` ${ 40 * zoom } px ` ;
element . style . fontSize = ` ${ 40 * zoom } px ` ;
element . style . borderRadius = ` ${ 6 * zoom } px ` ;
} ) ;
crossesContainer . current
? . querySelectorAll ( "#popper-crosses > .popper-cross" )
. forEach ( ( item ) = > {
const element = item as HTMLDivElement ;
element . style . width = ` ${ 24 * zoom } px ` ;
element . style . height = ` ${ 24 * zoom } px ` ;
element . style . fontSize = ` ${ 24 * zoom } px ` ;
element . style . borderRadius = ` ${ 6 * zoom } px ` ;
} ) ;
2023-12-12 16:12:03 +00:00
gearsContainer ? . current
2023-11-29 15:45:15 +00:00
? . querySelectorAll ( "#popper-gears > .popper-gear" )
. forEach ( ( item ) = > {
const element = item as HTMLDivElement ;
2023-12-01 08:12:59 +00:00
element . style . width = ` ${ 60 * zoom } px ` ;
element . style . height = ` ${ 40 * zoom } px ` ;
2023-11-29 15:45:15 +00:00
} ) ;
} ;
2023-12-10 01:01:56 +00:00
//node?.on("position", update);
2023-12-12 15:44:18 +00:00
let pressed = false
2023-12-10 01:01:56 +00:00
let hide = false
2023-12-12 15:44:18 +00:00
cy ? . on ( 'mousedown' , ( ) = > { pressed = true } )
cy ? . on ( 'mouseup' , ( ) = > {
2023-12-10 01:01:56 +00:00
pressed = false
hide = false
2023-12-12 15:44:18 +00:00
2023-12-10 01:01:56 +00:00
const gc = gearsContainer . current
if ( gc ) gc . style . display = 'block'
const pc = plusesContainer . current
const xc = crossesContainer . current
const lc = layoutsContainer . current
if ( pc ) pc . style . display = 'block'
if ( xc ) xc . style . display = 'block'
if ( lc ) lc . style . display = 'block'
2023-12-12 15:44:18 +00:00
update ( )
2023-12-10 01:01:56 +00:00
} )
2023-12-12 15:44:18 +00:00
cy ? . on ( 'mousemove' , ( ) = > {
2023-12-10 01:01:56 +00:00
if ( pressed && ! hide ) {
hide = true
const gc = gearsContainer . current
if ( gc ) gc . style . display = 'none'
2023-12-12 15:44:18 +00:00
const pc = plusesContainer . current
const xc = crossesContainer . current
const lc = layoutsContainer . current
if ( pc ) pc . style . display = 'none'
if ( xc ) xc . style . display = 'none'
if ( lc ) lc . style . display = 'block'
2023-12-10 01:01:56 +00:00
}
2023-12-12 15:44:18 +00:00
} ) ;
2023-12-10 01:01:56 +00:00
cy ? . on ( "zoom render" , onZoom ) ;
2023-11-29 15:45:15 +00:00
} ) ;
} ;
return (
2023-12-02 20:47:28 +00:00
< >
2023-12-19 15:00:51 +00:00
< Box
mb = "20px" >
< Button
sx = { {
height : "27px" ,
color : "#7E2AEA" ,
textDecoration : "underline" ,
fontSize : "16px" ,
} }
variant = "text"
onClick = { ( ) = > {
cyRef . current ? . fit ( )
} }
>
В ы р о в н я т ь
< / Button >
< ProblemIcon blink = { ! canCreatePublic } onClick = { ( ) = > updateModalInfoWhyCantCreate ( true ) } / >
< / Box >
2023-12-04 09:30:14 +00:00
< CytoscapeComponent
wheelSensitivity = { 0.1 }
elements = { [ ] }
// elements={createGraphElements(tree, quiz)}
style = { { height : "480px" , background : "#F2F3F7" } }
stylesheet = { stylesheet }
layout = { ( lyopts ) }
cy = { ( cy ) = > {
cyRef . current = cy ;
} }
2023-12-11 10:08:54 +00:00
autoungrabify = { true }
2023-12-04 09:30:14 +00:00
/ >
2023-12-02 20:47:28 +00:00
< / >
2023-11-29 15:45:15 +00:00
) ;
} ;
2023-12-07 00:03:43 +00:00
2023-12-11 10:08:54 +00:00
function Clear() {
const quiz = useCurrentQuiz ( ) ;
updateRootContentId ( quiz . id , "" )
clearRuleForAll ( )
return < > < / >
2023-12-07 00:03:43 +00:00
}
export default withErrorBoundary ( CsComponent , {
2023-12-11 10:08:54 +00:00
fallback : < Clear / > ,
2023-12-07 00:03:43 +00:00
onError : ( error , info ) = > {
enqueueSnackbar ( "Дерево порвалось" )
console . log ( info )
console . log ( error )
} ,
2023-12-09 19:31:06 +00:00
} ) ;