2021-09-21 10:18:27 +00:00
/ * *
* External dependencies
* /
2021-10-15 09:48:57 +00:00
import {
useLayoutEffect ,
useRef ,
useCallback ,
useMemo ,
2022-10-07 13:15:53 +00:00
useState ,
2021-10-15 09:48:57 +00:00
} from '@wordpress/element' ;
2021-09-21 10:18:27 +00:00
import { useSelect , useDispatch } from '@wordpress/data' ;
import {
createBlock ,
getBlockType ,
2021-11-22 12:45:48 +00:00
createBlocksFromInnerBlocksTemplate ,
2021-09-21 10:18:27 +00:00
} from '@wordpress/blocks' ;
2022-04-08 13:47:19 +00:00
import type { Block , AttributeSource , TemplateArray } from '@wordpress/blocks' ;
2021-10-15 09:48:57 +00:00
import { isEqual } from 'lodash' ;
2021-09-21 10:18:27 +00:00
const isBlockLocked = ( {
attributes ,
} : {
attributes : Record < string , AttributeSource . Attribute > ;
} ) = > Boolean ( attributes . lock ? . remove || attributes . lock ? . default ? . remove ) ;
2021-10-15 09:48:57 +00:00
/ * *
* useForcedLayout hook
*
* Responsible for ensuring FORCED blocks exist in the inner block layout . Forced blocks cannot be removed .
* /
2021-09-21 10:18:27 +00:00
export const useForcedLayout = ( {
clientId ,
2021-10-15 09:48:57 +00:00
registeredBlocks ,
defaultTemplate = [ ] ,
2021-09-21 10:18:27 +00:00
} : {
2021-10-15 09:48:57 +00:00
// Client ID of the parent block.
2021-09-21 10:18:27 +00:00
clientId : string ;
2021-10-15 09:48:57 +00:00
// An array of registered blocks that may be forced in this particular layout.
registeredBlocks : Array < string > ;
// The default template for the inner blocks in this layout.
defaultTemplate : TemplateArray ;
2021-09-21 10:18:27 +00:00
} ) : void = > {
2021-10-15 09:48:57 +00:00
const currentRegisteredBlocks = useRef ( registeredBlocks ) ;
const currentDefaultTemplate = useRef ( defaultTemplate ) ;
2022-10-07 13:15:53 +00:00
const [ forcedBlocksInserted , setForcedBlocksInserted ] =
useState < number > ( 0 ) ;
2021-10-15 09:48:57 +00:00
2022-06-15 09:56:52 +00:00
const { insertBlock , replaceInnerBlocks } =
useDispatch ( 'core/block-editor' ) ;
2021-10-15 09:48:57 +00:00
const { innerBlocks , registeredBlockTypes } = useSelect (
2021-09-21 10:18:27 +00:00
( select ) = > {
return {
2022-06-15 09:56:52 +00:00
innerBlocks :
select ( 'core/block-editor' ) . getBlocks ( clientId ) ,
2021-10-15 09:48:57 +00:00
registeredBlockTypes : currentRegisteredBlocks.current.map (
( blockName ) = > getBlockType ( blockName )
2021-09-21 10:18:27 +00:00
) ,
} ;
} ,
2022-10-07 13:15:53 +00:00
[ clientId , currentRegisteredBlocks . current , forcedBlocksInserted ]
2021-10-15 09:48:57 +00:00
) ;
const appendBlock = useCallback (
( block , position ) = > {
const newBlock = createBlock ( block . name ) ;
insertBlock ( newBlock , position , clientId , false ) ;
2022-10-07 13:15:53 +00:00
setForcedBlocksInserted ( forcedBlocksInserted + 1 ) ;
2021-10-15 09:48:57 +00:00
} ,
2022-10-11 17:04:55 +00:00
// We need to skip insertBlock here due to a cache issue in wordpress.com that causes an inifinite loop, see https://github.com/Automattic/wp-calypso/issues/66092 for an expanded doc.
// eslint-disable-next-line react-hooks/exhaustive-deps
[ clientId , forcedBlocksInserted ]
2021-09-21 10:18:27 +00:00
) ;
2021-10-15 09:48:57 +00:00
const lockedBlockTypes = useMemo (
( ) = >
registeredBlockTypes . filter (
( block : Block | undefined ) = > block && isBlockLocked ( block )
) ,
[ registeredBlockTypes ]
) as Block [ ] ;
2021-09-21 10:18:27 +00:00
/ * *
* If the current inner blocks differ from the registered blocks , push the differences .
* /
useLayoutEffect ( ( ) = > {
if ( ! clientId ) {
return ;
}
2021-10-15 09:48:57 +00:00
// If there are NO inner blocks, sync with the given template.
if (
innerBlocks . length === 0 &&
currentDefaultTemplate . current . length > 0
) {
2021-11-22 12:45:48 +00:00
const nextBlocks = createBlocksFromInnerBlocksTemplate (
2021-10-15 09:48:57 +00:00
currentDefaultTemplate . current
) ;
if ( ! isEqual ( nextBlocks , innerBlocks ) ) {
replaceInnerBlocks ( clientId , nextBlocks ) ;
return ;
}
}
// Find registered locked blocks missing from Inner Blocks and append them.
lockedBlockTypes . forEach ( ( block ) = > {
// If the locked block type is already in the layout, we can skip this one.
2021-09-21 10:18:27 +00:00
if (
2021-10-15 09:48:57 +00:00
innerBlocks . find (
2021-09-21 10:18:27 +00:00
( { name } : { name : string } ) = > name === block . name
)
) {
2021-10-15 09:48:57 +00:00
return ;
}
// Is the forced block part of the default template, find it's original position.
2022-06-15 09:56:52 +00:00
const defaultTemplatePosition =
currentDefaultTemplate . current . findIndex (
( [ blockName ] ) = > blockName === block . name
) ;
2021-10-15 09:48:57 +00:00
switch ( defaultTemplatePosition ) {
case - 1 :
// The block is not part of the default template so we append it to the current layout.
appendBlock ( block , innerBlocks . length ) ;
break ;
case 0 :
// The block was the first block in the default layout, so prepend it to the current layout.
appendBlock ( block , 0 ) ;
break ;
default :
// The new layout may have extra blocks compared to the default template, so rather than insert
// at the default position, we should append it after another default block.
const adjacentBlock =
currentDefaultTemplate . current [
defaultTemplatePosition - 1
] ;
const position = innerBlocks . findIndex (
( { name : blockName } ) = >
blockName === adjacentBlock [ 0 ]
) ;
appendBlock (
block ,
position === - 1 ? defaultTemplatePosition : position + 1
) ;
break ;
2021-09-21 10:18:27 +00:00
}
} ) ;
2022-10-11 17:04:55 +00:00
/ *
We need to skip replaceInnerBlocks here due to a cache issue in wordpress . com that causes an inifinite loop , see https : //github.com/Automattic/wp-calypso/issues/66092 for an expanded doc.
@todo Add replaceInnerBlocks and insertBlock after fixing https : //github.com/Automattic/wp-calypso/issues/66092
* /
// eslint-disable-next-line react-hooks/exhaustive-deps
} , [ clientId , innerBlocks , lockedBlockTypes , appendBlock ] ) ;
2021-09-21 10:18:27 +00:00
} ;