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" ;
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-03 02:16:53 +00:00
import { cleardragQuestionContentId , updateQuestion , updateOpenedModalSettingsId , getQuestionByContentId } from "@root/questions/actions" ;
2023-11-29 15:45:15 +00:00
import { storeToNodes } from "./helper" ;
import "./styles.css" ;
import type {
Stylesheet ,
Core ,
NodeSingular ,
AbstractEventObject ,
ElementDefinition ,
} from "cytoscape" ;
import { QuestionsList } from "../BranchingPanel/QuestionsList" ;
2023-12-02 09:59:31 +00:00
import { enqueueSnackbar } from "notistack" ;
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" ,
} ,
} ,
{
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
}
export const CsComponent = ( {
modalQuestionParentContentId ,
modalQuestionTargetContentId ,
setOpenedModalQuestions ,
setModalQuestionParentContentId ,
setModalQuestionTargetContentId
2023-12-02 13:11:57 +00:00
} : Props ) = > {
2023-11-29 15:45:15 +00:00
const quiz = useCurrentQuiz ( ) ;
2023-12-01 19:56:13 +00:00
const { dragQuestionContentId , questions } = useQuestionsStore ( )
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-04 15:40:15 +00:00
useLayoutEffect ( ( ) = > {
updateOpenedModalSettingsId ( )
} , [ ] )
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-01 08:12:59 +00:00
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-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-01 08:12:59 +00:00
2023-12-02 09:59:31 +00:00
if ( Object . keys ( targetQuestion ) . length !== 0 && Object . keys ( targetQuestion ) . length !== 0 && parentNodeContentId && parentNodeChildren !== undefined ) {
clearDataAfterAddNode ( { parentNodeContentId , targetQuestion , parentNodeChildren } )
2023-12-04 09:30:14 +00:00
cy ? . data ( 'changed' , true )
2023-12-01 08:12:59 +00:00
cy ? . add ( [
{
data : {
2023-12-01 19:56:13 +00:00
id : targetQuestion.content.id ,
2023-12-01 08:12:59 +00:00
label : targetQuestion.title || "noname"
}
} ,
{
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-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-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-01 19:56:13 +00:00
2023-12-02 13:11:57 +00:00
//Если детей больше 1 - предупреждаем стор вопросов о б открытии модалки ветвления
2023-12-04 13:33:43 +00:00
if ( parentNodeChildren >= 1 ) {
updateOpenedModalSettingsId ( targetQuestion . content . id )
2023-12-02 13:11:57 +00:00
} else {
//Если ребёнок первый - добавляем е г о родителю как дефолтный
updateQuestion ( parentNodeContentId , question = > question . content . rule . default = targetQuestion . content . id )
}
2023-12-01 19:56:13 +00:00
}
2023-12-02 09:59:31 +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 = [ ]
question . content . rule . default = ""
} )
} else {
const parentQuestionContentId = cy ? . $ ( 'edge[target = "' + targetNodeContentId + '"]' ) ? . toArray ( ) ? . [ 0 ] ? . data ( ) ? . source
2023-12-02 13:51:37 +00:00
if ( targetNodeContentId && parentQuestionContentId ) {
2023-12-02 13:11:57 +00:00
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
}
//После всех манипуляций удаляем грани из CS и ноды из бекенда
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-02 20:47:28 +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-11-29 15:45:15 +00:00
}
2023-12-02 13:11:57 +00:00
const clearDataAfterRemoveNode = ( { targetQuestionContentId , parentQuestionContentId } : { targetQuestionContentId : string , parentQuestionContentId : string } ) = > {
updateQuestion ( targetQuestionContentId , question = > {
question . content . rule . parentId = ""
question . content . rule . main = [ ]
question . content . rule . default = ""
} )
2023-12-04 13:33:43 +00:00
2023-12-02 13:11:57 +00:00
updateQuestion ( parentQuestionContentId , question = > {
2023-12-04 13:33:43 +00:00
//Заменяем id удаляемого вопроса либо на id одного из оставшихся, либо на пустую строку
2023-12-02 20:47:28 +00:00
if ( question . content . rule . default === targetQuestionContentId ) question . content . rule . default = ""
2023-12-02 13:11:57 +00:00
} )
}
2023-12-02 09:59:31 +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-04 09:30:14 +00:00
} else { e . removeData ( 'oldPos' ) }
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-02 16:44:52 +00:00
task . parent . data ( 'subtreeWidth' , task . parent . height ( ) )
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-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-02 16:44:52 +00:00
return pos
2023-12-02 09:59:31 +00:00
} else {
2023-12-04 09:30:14 +00:00
if ( e . cy ( ) . data ( 'firstNode' ) !== 'root' ) {
e . cy ( ) . data ( 'firstNode' , 'nonroot' )
return { x : 0 , y : 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' , 'nonroot' )
2023-12-02 09:59:31 +00:00
const parent = e . cy ( ) . edges ( ` [target=" ${ e . id ( ) } "] ` ) [ 0 ] . source ( )
2023-12-03 10:47:52 +00:00
const wing = ( parent . data ( 'children' ) === 1 ) ? 0 : parent.data ( 'subtreeWidth' ) / 2 + 50
2023-12-02 09:59:31 +00:00
const lastOffset = parent . data ( 'lastChild' )
const step = wing * 2 / ( parent . data ( 'children' ) - 1 )
//e.removeData('subtreeWidth')
if ( lastOffset !== undefined ) {
parent . data ( 'lastChild' , lastOffset + step )
2023-12-02 16:44:52 +00:00
const pos = { x : 250 * e . data ( 'layer' ) , y : ( lastOffset + step ) }
2023-12-02 20:47:28 +00:00
e . data ( 'oldPos' , pos )
2023-12-02 16:44:52 +00:00
return pos
2023-12-02 09:59:31 +00:00
} else {
parent . data ( 'lastChild' , parent . position ( ) . y - wing )
2023-12-02 16:44:52 +00:00
const pos = { x : 250 * e . data ( 'layer' ) , y : ( parent . position ( ) . y - wing ) }
2023-12-02 20:47:28 +00:00
e . data ( 'oldPos' , pos )
2023-12-02 16:44:52 +00:00
return pos
2023-12-04 09:30:14 +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
animateFilter : function ( node , i ) { return true ; } , // a function that determines whether the node should be animated. All nodes animated by default on animate enabled. Non-animated nodes are positioned immediately when the layout starts
ready : readyLO , // callback on layoutready
transform : function ( node , position ) { return position ; } // transform a given node position. Useful for changing flow direction in discrete layouts
}
useEffect ( ( ) = > {
document . querySelector ( "#root" ) ? . addEventListener ( "mouseup" , cleardragQuestionContentId ) ;
const cy = cyRef . current ;
2023-12-02 11:52:33 +00:00
const eles = cy ? . add ( storeToNodes ( questions ) )
2023-12-02 20:47:28 +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 ;
} ,
} ) ;
const gearsPopper = node . popper ( {
popper : {
placement : "left" ,
modifiers : [ { name : "flip" , options : { boundary : node } } ] ,
} ,
content : ( [ item ] ) = > {
const itemId = item . id ( ) ;
2023-12-02 16:44:52 +00:00
if ( item . cy ( ) . edges ( ` [target=" ${ itemId } "] ` ) . sources ( ) . length === 0 ) {
2023-12-01 08:12:59 +00:00
return ;
}
2023-11-29 15:45:15 +00:00
const itemElement = gearsContainer . current ? . querySelector (
` .popper-gear[data-id=' ${ itemId } '] `
) ;
if ( itemElement ) {
return itemElement ;
}
2023-12-01 08:12:59 +00:00
const gearElement = document . createElement ( "div" ) ;
gearElement . classList . add ( "popper-gear" ) ;
gearElement . setAttribute ( "data-id" , item . id ( ) ) ;
2023-12-02 13:11:57 +00:00
gearElement . style . zIndex = "1"
2023-12-01 08:12:59 +00:00
gearsContainer . current ? . appendChild ( gearElement ) ;
2023-12-02 13:11:57 +00:00
gearElement . addEventListener ( "mouseup" , ( e ) = > {
2023-12-03 02:16:53 +00:00
updateOpenedModalSettingsId ( item . id ( ) )
2023-12-02 13:11:57 +00:00
} ) ;
2023-12-01 08:12:59 +00:00
return gearElement ;
2023-11-29 15:45:15 +00:00
} ,
} ) ;
const update = async ( ) = > {
await plusesPopper . update ( ) ;
await crossesPopper . update ( ) ;
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 ( ) ;
update ( ) ;
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 ] } } ,
] ,
} ) ;
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 ` ;
} ) ;
gearsContainer . current
? . 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
} ) ;
} ;
node ? . on ( "position" , update ) ;
cy ? . on ( "pan zoom resize render" , onZoom ) ;
} ) ;
} ;
return (
2023-12-02 20:47:28 +00:00
< >
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-04 20:41:44 +00:00
{ / * < b u t t o n o n C l i c k = { ( ) = > {
2023-12-04 09:30:14 +00:00
console . log ( "NODES____________________________" )
cyRef . current ? . elements ( ) . forEach ( ( ele : any ) = > {
console . log ( ele . data ( ) )
} )
} } > nodes < / button >
< button onClick = { ( ) = > {
console . log ( "ELEMENTS____________________________" )
2023-12-04 07:50:55 +00:00
console . log ( questions )
2023-12-04 20:41:44 +00:00
} } > elements < / button > * / }
2023-12-02 20:47:28 +00:00
< / >
2023-11-29 15:45:15 +00:00
) ;
} ;