import { deprecationWarning , ERROR_INVALID_NODEID , ROOT_NODE , DEPRECATED_ROOT_NODE , QueryCallbacksFor , ERROR_NOPARENT , ERROR_DELETE_TOP_LEVEL_NODE , CallbacksFor , Delete , ERROR_NOT_IN_RESOLVER , } from '@craftjs/utils' ; import invariant from 'tiny-invariant' ; import { QueryMethods } from './query' ; import { EditorState , Indicator , NodeId , Node , Nodes , Options , NodeEventTypes , NodeTree , SerializedNodes , NodeSelector , NodeSelectorType , } from '../interfaces' ; import { fromEntries } from '../utils/fromEntries' ; import { getNodesFromSelector } from '../utils/getNodesFromSelector' ; import { removeNodeFromEvents } from '../utils/removeNodeFromEvents' ; const Methods = ( state , query ) => { /** Helper functions */ const addNodeTreeToParent = ( tree , parentId ? , addNodeType ? ) => { const iterateChildren = ( id , parentId ? ) => { const node = tree . nodes [ id ] ; if ( typeof node . data . type !== 'string' ) { invariant ( state . options . resolver [ node . data . name ] , ERROR_NOT_IN_RESOLVER . replace ( '%node_type%' , `${ ( node . data . type as any ) . name }` ) ) ; } state . nodes [ id ] = { ... node , data : { ... node . data , parent : parentId , } , } ; if ( node . data . nodes . length > 0 ) { delete state . nodes [ id ] . data . props . children ; node . data . nodes . forEach ( ( childNodeId ) => iterateChildren ( childNodeId , node . id ) ) ; } Object . values ( node . data . linkedNodes ) . forEach ( ( linkedNodeId ) => iterateChildren ( linkedNodeId , node . id ) ) ; } ; iterateChildren ( tree . rootNodeId , parentId ) ; if ( ! parentId ) { invariant ( tree . rootNodeId === ROOT_NODE , 'Cannot add non-root Node without a parent' ) ; return ; } const parent = getParentAndValidate ( parentId ) ; if ( addNodeType . type === 'child' ) { const index = addNodeType . index ; if ( index != null ) { parent . data . nodes . splice ( index , 0 , tree . rootNodeId ) ; } else { parent . data . nodes . push ( tree . rootNodeId ) ; } return ; } parent . data . linkedNodes [ addNodeType . id ] = tree . rootNodeId ; } ; const getParentAndValidate = ( parentId ) : Node => { invariant ( parentId , ERROR_NOPARENT ) ; const parent = state . nodes [ parentId ] ; invariant ( parent , ERROR_INVALID_NODEID ) ; return parent ; } ; const deleteNode = ( id ) => { const targetNode = state . nodes [ id ] , parentNode = state . nodes [ targetNode . data . parent ] ; if ( targetNode . data . nodes ) { [ ... targetNode . data . nodes ] . forEach ( ( childId ) => deleteNode ( childId ) ) ; } if ( targetNode . data . linkedNodes ) { Object . values ( targetNode . data . linkedNodes ) . map ( ( linkedNodeId ) => deleteNode ( linkedNodeId ) ) ; } const isChildNode = parentNode . data . nodes . includes ( id ) ; if ( isChildNode ) { const parentChildren = parentNode . data . nodes ; parentChildren . splice ( parentChildren . indexOf ( id ) , 1 ) ; } else { const linkedId = Object . keys ( parentNode . data . linkedNodes ) . find ( ( id ) => parentNode . data . linkedNodes [ id ] === id ) ; if ( linkedId ) { delete parentNode . data . linkedNodes [ linkedId ] ; } } removeNodeFromEvents ( state , id ) ; delete state . nodes [ id ] ; } ; return { private param tree param parentId param id addLinkedNodeFromTree ( tree , parentId , id ) { const parent = getParentAndValidate ( parentId ) ; const existingLinkedNode = parent . data . linkedNodes [ id ] ; if ( existingLinkedNode ) { deleteNode ( existingLinkedNode ) ; } addNodeTreeToParent ( tree , parentId , { type : 'linked' , id } ) ; } , param nodeToAdd param parentId param index add ( nodeToAdd , parentId , index ? ) { let nodes = [ nodeToAdd ] ; if ( Array . isArray ( nodeToAdd ) ) { deprecationWarning ( 'actions.add(node: Node[])' , { suggest : 'actions.add(node: Node)' , } ) ; nodes = nodeToAdd ; } nodes . forEach ( ( node ) => { addNodeTreeToParent ( { nodes : { [ node . id ] : node , } , rootNodeId : node . id , } , parentId , { type : 'child' , index } ) ; } ) ; } , param tree param parentId param index addNodeTree ( tree , parentId ? , index ? ) { addNodeTreeToParent ( tree , parentId , { type : 'child' , index } ) ; } , param id delete ( selector ) { const targets = getNodesFromSelector ( state . nodes , selector , { existOnly : true , idOnly : true , } ) ; targets . forEach ( ( { node } ) => { invariant ( ! query . node ( node . id ) . isTopLevelNode ( ) , ERROR_DELETE_TOP_LEVEL_NODE ) ; deleteNode ( node . id ) ; } ) ; } , deserialize ( input ) { const dehydratedNodes = typeof input == 'string' ? JSON . parse ( input ) : input ; const nodePairs = Object . keys ( dehydratedNodes ) . map ( ( id ) => { let nodeId = id ; if ( id === DEPRECATED_ROOT_NODE ) { nodeId = ROOT_NODE ; } return [ nodeId , query . parseSerializedNode ( dehydratedNodes [ id ] ) . toNode ( ( node ) => ( node . id = nodeId ) ) , ] ; } ) ; this . replaceNodes ( fromEntries ( nodePairs ) ) ; } , param targetId param newParentId param index move ( selector , newParentId , index ) { const targets = getNodesFromSelector ( state . nodes , selector , { existOnly : true , } ) ; const newParent = state . nodes [ newParentId ] ; targets . forEach ( ( { node : targetNode } , i ) => { const targetId = targetNode . id ; const currentParentId = targetNode . data . parent ; query . node ( newParentId ) . isDroppable ( [ targetId ] , ( err ) => { throw new Error ( err ) ; } ) ; state . options . onBeforeMoveEnd ( targetNode , newParent , state . nodes [ currentParentId ] ) ; const currentParent = state . nodes [ currentParentId ] ; const currentParentNodes = currentParent . data . nodes ; currentParentNodes [ currentParentNodes . indexOf ( targetId ) ] = 'marked' ; newParent . data . nodes . splice ( index + i , 0 , targetId ) ; state . nodes [ targetId ] . data . parent = newParentId ; currentParentNodes . splice ( currentParentNodes . indexOf ( 'marked' ) , 1 ) ; } ) ; } , replaceNodes ( nodes ) { this . clearEvents ( ) ; state . nodes = nodes ; } , clearEvents ( ) { this . setNodeEvent ( 'selected' , null ) ; this . setNodeEvent ( 'hovered' , null ) ; this . setNodeEvent ( 'dragged' , null ) ; this . setIndicator ( null ) ; } , /**
         * Resets all the editor state.
         */ reset ( ) { this . clearEvents ( ) ; this . replaceNodes ( { } ) ; } , param cb setOptions ( cb ) { cb ( state . options ) ; } , setNodeEvent ( eventType , nodeIdSelector ) { state . events [ eventType ] . forEach ( ( id ) => { if ( state . nodes [ id ] ) { state . nodes [ id ] . events [ eventType ] = false ; } } ) ; state . events [ eventType ] = new Set ( ) ; if ( ! nodeIdSelector ) { return ; } const targets = getNodesFromSelector ( state . nodes , nodeIdSelector , { idOnly : true , existOnly : true , } ) ; const nodeIds = new Set ( targets . map ( ( { node } ) => node . id ) ) ; nodeIds . forEach ( ( id ) => { state . nodes [ id ] . events [ eventType ] = true ; } ) ; state . events [ eventType ] = nodeIds ; } , param id param cb setCustom < T extends NodeId > ( selector , cb ) { const targets = getNodesFromSelector ( state . nodes , selector , { idOnly : true , existOnly : true , } ) ; targets . forEach ( ( { node } ) => cb ( state . nodes [ node . id ] . data . custom ) ) ; } , param id param dom setDOM ( id , dom ) { if ( ! state . nodes [ id ] ) { return ; } state . nodes [ id ] . dom = dom ; } , setIndicator ( indicator ) { if ( indicator && ( ! indicator . placement . parent . dom || ( indicator . placement . currentNode && ! indicator . placement . currentNode . dom ) ) ) return ; state . indicator = indicator ; } , param id param bool setHidden ( id , bool ) { state . nodes [ id ] . data . hidden = bool ; } , param id param cb setProp ( selector , cb ) { const targets = getNodesFromSelector ( state . nodes , selector , { idOnly : true , existOnly : true , } ) ; targets . forEach ( ( { node } ) => cb ( state . nodes [ node . id ] . data . props ) ) ; } , selectNode ( nodeIdSelector ? ) { if ( nodeIdSelector ) { const targets = getNodesFromSelector ( state . nodes , nodeIdSelector , { idOnly : true , existOnly : true , } ) ; this . setNodeEvent ( 'selected' , targets . map ( ( { node } ) => node . id ) ) ; } else { this . setNodeEvent ( 'selected' , null ) ; } this . setNodeEvent ( 'hovered' , null ) ; } , } ; } ; export const ActionMethods = ( state , query ) => { return { ... Methods ( state , query ) , setState ( cb ) { const { history , ... actions } = this ; cb ( state , actions ) ; } , } ; } ;