/** * External dependencies */ import { BlockAttributes, BlockInstance } from '@wordpress/blocks'; import { select, dispatch } from '@wordpress/data'; /** * Internal dependencies */ import { ThumbnailsPosition } from './inner-blocks/product-gallery-thumbnails/constants'; import { getNextPreviousImagesWithClassName } from './inner-blocks/product-gallery-large-image-next-previous/utils'; import { NextPreviousButtonSettingValues } from './inner-blocks/product-gallery-large-image-next-previous/types'; /** * Generates layout attributes based on the position of thumbnails. * * @param {string} thumbnailsPosition - The position of thumbnails ('bottom' or other values). * @return {{type: string, orientation?: string, flexWrap?: string}} - An object representing layout attributes. */ export const getGroupLayoutAttributes = ( thumbnailsPosition: string ): { type: string; orientation?: string; flexWrap?: string } => { switch ( thumbnailsPosition ) { case 'bottom': // Stack return { type: 'flex', orientation: 'vertical' }; case 'off': // Stack return { type: 'flex', orientation: 'vertical' }; default: // Row return { type: 'flex', flexWrap: 'nowrap' }; } }; /** * Returns inner block lock attributes based on provided action. * * @param {string} action - The action to take on the inner blocks ('lock' or 'unlock'). * @return {{lock: {move?: boolean, remove?: boolean}}} - An object representing lock attributes for inner blocks. */ export const getInnerBlocksLockAttributes = ( action: string ): { lock: { move?: boolean; remove?: boolean } } => { switch ( action ) { case 'lock': return { lock: { move: true, remove: true } }; case 'unlock': return { lock: {} }; default: return { lock: {} }; } }; /** * Updates block attributes based on provided attributes. * * @param {BlockAttributes} attributesToUpdate - The new attributes to set on the block. * @param {BlockAttributes | undefined} block - The block object to update. */ export const updateBlockAttributes = ( attributesToUpdate: BlockAttributes, block: BlockAttributes | undefined ): void => { if ( block !== undefined ) { const updatedBlock = { ...block, attributes: { ...block.attributes, ...attributesToUpdate, }, }; dispatch( 'core/block-editor' ).updateBlock( block.clientId, updatedBlock ); } }; const controlBlocksLockAttribute = ( { blocks, lockBlocks, }: { blocks: BlockInstance[]; lockBlocks: boolean; } ) => { for ( const block of blocks ) { if ( lockBlocks ) { updateBlockAttributes( getInnerBlocksLockAttributes( 'lock' ), block ); } else { updateBlockAttributes( getInnerBlocksLockAttributes( 'unlock' ), block ); } } }; /** * Recursively searches through an array of `BlockInstance` objects and their nested `innerBlocks` arrays to find a block that matches a given condition. * * @param { { blocks: BlockInstance[], findCondition: Function } } parameters Parameters containing an array of `BlockInstance` objects to search through and a function that takes a `BlockInstance` object as its argument and returns a boolean indicating whether the block matches the desired condition. * @return If a matching block is found, the function returns the `BlockInstance` object. If no matching block is found, the function returns `undefined`. */ const findBlock = ( { blocks, findCondition, }: { blocks: BlockInstance[]; findCondition: ( block: BlockInstance ) => boolean; } ): BlockInstance | undefined => { for ( const block of blocks ) { if ( findCondition( block ) ) { return block; } if ( block.innerBlocks ) { const largeImageParentBlock = findBlock( { blocks: block.innerBlocks, findCondition, } ); if ( largeImageParentBlock ) { return largeImageParentBlock; } } } return undefined; }; /** * Sets the layout of group block based on the thumbnails' position. * * @param {ThumbnailsPosition} thumbnailsPosition - The position of thumbnails. * @param {string} clientId - The client ID of the block to update. */ const setGroupBlockLayoutByThumbnailsPosition = ( thumbnailsPosition: ThumbnailsPosition, clientId: string ): void => { const block = select( 'core/block-editor' ).getBlock( clientId ); block?.innerBlocks.forEach( ( innerBlock ) => { if ( innerBlock.name === 'core/group' ) { updateBlockAttributes( { layout: getGroupLayoutAttributes( thumbnailsPosition ), }, innerBlock ); } } ); }; /** * Moves inner blocks to a position based on provided attributes. * * @param {BlockAttributes} attributes - The attributes of the parent block. * @param {string} clientId - The clientId of the parent block. */ export const moveInnerBlocksToPosition = ( attributes: BlockAttributes, clientId: string ): void => { const { getBlock, getBlockRootClientId, getBlockIndex } = select( 'core/block-editor' ); const { moveBlockToPosition } = dispatch( 'core/block-editor' ); const productGalleryBlock = getBlock( clientId ); if ( productGalleryBlock ) { const thumbnailsBlock = findBlock( { blocks: [ productGalleryBlock ], findCondition( block ) { return block.name === 'woocommerce/product-gallery-thumbnails'; }, } ); const largeImageParentBlock = findBlock( { blocks: [ productGalleryBlock ], findCondition( block ) { return Boolean( block.innerBlocks?.find( ( innerBlock ) => innerBlock.name === 'woocommerce/product-gallery-large-image' ) ); }, } ); const largeImageParentBlockIndex = getBlockIndex( largeImageParentBlock?.clientId || '' ); const thumbnailsBlockIndex = getBlockIndex( thumbnailsBlock?.clientId || '' ); if ( largeImageParentBlock && thumbnailsBlock && largeImageParentBlockIndex !== -1 && thumbnailsBlockIndex !== -1 ) { controlBlocksLockAttribute( { blocks: [ thumbnailsBlock, largeImageParentBlock ], lockBlocks: false, } ); const { thumbnailsPosition } = attributes; setGroupBlockLayoutByThumbnailsPosition( thumbnailsPosition, clientId ); if ( ( ( thumbnailsPosition === 'bottom' || thumbnailsPosition === 'right' ) && thumbnailsBlockIndex < largeImageParentBlockIndex ) || ( thumbnailsPosition === 'left' && thumbnailsBlockIndex > largeImageParentBlockIndex ) ) { moveBlockToPosition( thumbnailsBlock.clientId, getBlockRootClientId( thumbnailsBlock.clientId ) || undefined, getBlockRootClientId( largeImageParentBlock.clientId ) || undefined, largeImageParentBlockIndex ); } controlBlocksLockAttribute( { blocks: [ thumbnailsBlock, largeImageParentBlock ], lockBlocks: true, } ); } } }; export const getClassNameByNextPreviousButtonsPosition = ( nextPreviousButtonsPosition: NextPreviousButtonSettingValues ) => { return `wc-block-product-gallery--has-next-previous-buttons-${ getNextPreviousImagesWithClassName( nextPreviousButtonsPosition ) ?.classname }`; };