import { useContext , KeyboardEvent , useState , DragEvent , MouseEvent , memo } from 'react' ; import { ThemeContext } from '../context/theme' ; import { MAX_TOPIC_WIDTH , TOPIC_RADIUS , EDITOR_MODE , KEY_MAPS , TOPIC_CLASS } from '../constant' ; import styles from '../index.module.css' ; import { TopicData } from 'xmind-model/types/models/topic' ; import { HierachyNode } from '@antv/hierarchy' ; import { getTopicFontsize } from '../layout/mindmap' ; import { topicWalker } from '../utils/tree' ; import { selectText } from '../utils/dom' ; import EditorStore from '../store/editor' ; import { RootStore , useRootSelector } from '../store/root' ; import classNames from 'classnames' ; const topicClass = classNames ( TOPIC_CLASS , styles . topic ) ; const Topic = ( props ) => { const { data : { title , id } , x , y , depth , hgap , vgap } = props ; const $theme = useContext ( ThemeContext ) ; const editorStore = EditorStore . useContainer ( ) ; const root = useRootSelector ( ( s ) => s ) ; const rootStore = RootStore . useContainer ( ) ; const { mode , selectedNodeId } = editorStore ; const isSelected = id === selectedNodeId ; const isEditing = isSelected && mode === EDITOR_MODE . edit ; const [ isDragEntering , setIsDragEntering ] = useState ( false ) ; const isMainTopic = depth <= 1 ; function selectNode ( ) { editorStore . SELECT_NODE ( id ) ; } function exitEditMode ( e ) { if ( [ KEY_MAPS . Enter , KEY_MAPS . Escape ] . includes ( e . key ) && mode === EDITOR_MODE . edit ) { editorStore . SET_MODE ( EDITOR_MODE . regular ) ; rootStore . UPDATE_NODE ( id , { title : e . currentTarget . innerText } ) ; getSelection ( ) ?. removeAllRanges ( ) ; } } function handleDragStart ( e ) { if ( id === root . id ) { e . preventDefault ( ) ; return ; } e . dataTransfer . setData ( 'text/plain' , '' ) ; editorStore . DRAG_NODE ( props . data ) ; } function handleDragEnter ( ) { setIsDragEntering ( true ) ; } function handleDragLeave ( ) { setIsDragEntering ( false ) ; } function handleDrop ( ) { if ( editorStore . dragingNode == null ) return ; if ( editorStore . dragingNode . id === id ) return ; const descendants = topicWalker . getDescendants ( editorStore . dragingNode ) ; if ( descendants . some ( ( node ) => node . id === id ) ) { return ; } rootStore . APPEND_CHILD ( id , editorStore . dragingNode ) ; handleDragLeave ( ) ; } function handleDragOver ( e ) { e . preventDefault ( ) ; } const outline = isSelected || isDragEntering ? `2px solid ${ $theme . mainColor }` : 'none' ; const background = isMainTopic || isEditing ? '#fff' : 'transparent' ; function handleKeyDown ( e ) { if ( [ KEY_MAPS . Enter ] . includes ( e . key ) && mode === EDITOR_MODE . edit ) { e . preventDefault ( ) ; } } function editTopic ( e ) { const el = e . target as HTMLDivElement ; el ?. focus ( ) ; selectText ( el ) ; editorStore . SET_MODE ( EDITOR_MODE . edit ) ; } const padding = `${ vgap }px ${ hgap }px` ; return ( < div id = { `topic-${ id }` } className = { topicClass } contentEditable = { isEditing } onClick = { selectNode } onDoubleClick = { editTopic } onKeyUp = { exitEditMode } onKeyDown = { handleKeyDown } draggable onDragStart = { handleDragStart } onDragEnter = { handleDragEnter } onDragLeave = { handleDragLeave } onDrop = { handleDrop } onDragOver = { handleDragOver } onMouseDown = { ( e ) => e . stopPropagation ( ) } onTouchStart = { ( e ) => e . stopPropagation ( ) } style = { { borderRadius : `${ TOPIC_RADIUS }px` , transform : `translate(${ x }px, ${ y }px)` , background : `${ background }` , maxWidth : `${ MAX_TOPIC_WIDTH }px` , padding : `${ padding }` , fontSize : `${ getTopicFontsize ( props . data ) }px` , outline : `${ outline }` , translate : `0 ${ isEditing ? '2px' : 0 }` } } suppressContentEditableWarning > { title } < /div>); } ; export default memo ( Topic ) ;