Skip to content
This repository has been archived by the owner on Feb 23, 2024. It is now read-only.

Commit

Permalink
Product Gallery block: Restrict block to be available only on the Sin…
Browse files Browse the repository at this point in the history
…gle Product template or the Product Gallery template part (#11664)

* WIP: experimenting with strategy pattern for block registration

* Add TemplateChangeDetector to BlocksRegistrationManager

* Handle blocks registration

* Fix issue causing blocks to be registered multiple times

* Allow register/unregister blocks when on pages or posts

* Add BlockRegistrationStrategy logic

* Fix import error

* Add doc comments for BlockRegistrationManager class

* Add doc comments to TemplateChangeDetector class

* Fix eslint errors

* Import domReady from @wordpress/dom-ready

* Prevent error when using blockName for registerBlockType function

* Add e2e tests to check for block availability in different contexts

* Add e2e tests to cover block availability on different contexts
  • Loading branch information
thealexandrelara authored Nov 16, 2023
1 parent 879b1d2 commit 83e5b64
Show file tree
Hide file tree
Showing 11 changed files with 462 additions and 9 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
/**
* External dependencies
*/
import {
BlockConfiguration,
registerBlockType,
unregisterBlockType,
registerBlockVariation,
unregisterBlockVariation,
BlockVariation,
BlockAttributes,
} from '@wordpress/blocks';

export interface BlockRegistrationStrategy {
register(
blockNameOrMetadata: string | Partial< BlockConfiguration >,
blockSettings: Partial< BlockConfiguration >
): boolean;
unregister( blockName: string, variationName?: string ): boolean;
}

export class BlockTypeStrategy implements BlockRegistrationStrategy {
register(
blockNameOrMetadata: string | Partial< BlockConfiguration >,
blockSettings: Partial< BlockConfiguration >
): boolean {
return Boolean(
// @ts-expect-error: `registerBlockType` is typed in WordPress core
registerBlockType( blockNameOrMetadata, blockSettings )
);
}

unregister( blockName: string ): boolean {
return Boolean( unregisterBlockType( blockName ) );
}
}

// Strategy for BlockVariation
export class BlockVariationStrategy implements BlockRegistrationStrategy {
register(
blockName: string,
blockSettings: Partial< BlockConfiguration >
): boolean {
return Boolean(
registerBlockVariation(
blockName,
blockSettings as BlockVariation< BlockAttributes >
)
);
}

unregister( blockName: string, variationName: string ): boolean {
return Boolean( unregisterBlockVariation( blockName, variationName ) );
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
/**
* Internal dependencies
*/
import {
TemplateChangeDetector,
TemplateChangeDetectorObserver,
} from './template-change-detector';
import {
BlockRegistrationStrategy,
BlockTypeStrategy,
BlockVariationStrategy,
} from './block-registration-strategy';
import { BLOCKS_WITH_RESTRICTION } from './blocks-with-restriction';

/**
* Manages the registration and unregistration of blocks based on template or page restrictions.
*
* This class implements the TemplateChangeDetectorObserver interface and is responsible for managing the registration and unregistration of blocks based on the restrictions defined in the BLOCKS_WITH_RESTRICTION constant.
*
* The class maintains a list of unregistered blocks and uses a block registration strategy to register and unregister blocks as needed. The strategy used depends on whether the block is a variation block or a regular block.
*
* The `run` method is the main entry point for the class. It is called with a TemplateChangeDetector object and registers and unregisters blocks based on the current template and whether the editor is in post or page mode.
*/
export class BlockRegistrationManager
implements TemplateChangeDetectorObserver
{
private unregisteredBlocks: string[] = [];
private blockRegistrationStrategy: BlockRegistrationStrategy;

constructor() {
this.blockRegistrationStrategy = new BlockTypeStrategy();
}

/**
* Determines whether a block should be registered based on the current template or page.
*
* This method checks whether a block with restrictions should be registered based on the current template ID and
* whether the editor is in post or page mode. It checks whether the current template ID starts with any of the
* allowed templates or template parts for the block, and whether the block is available in the post or page editor.
*
* @param {Object} params - The parameters for the method.
* @param {string} params.blockWithRestrictionName - The name of the block with restrictions.
* @param {string} params.currentTemplateId - The ID of the current template.
* @param {boolean} params.isPostOrPage - Whether the editor is in a post or page.
* @return {boolean} True if the block should be registered, false otherwise.
*/
private shouldBlockBeRegistered( {
blockWithRestrictionName,
currentTemplateId,
isPostOrPage,
}: {
blockWithRestrictionName: string;
currentTemplateId: string;
isPostOrPage: boolean;
} ) {
const {
allowedTemplates,
allowedTemplateParts,
availableInPostOrPageEditor,
} = BLOCKS_WITH_RESTRICTION[ blockWithRestrictionName ];
const shouldBeAvailableOnTemplate = Object.keys(
allowedTemplates
).some( ( allowedTemplate ) =>
currentTemplateId.startsWith( allowedTemplate )
);
const shouldBeAvailableOnTemplatePart = Object.keys(
allowedTemplateParts
).some( ( allowedTemplate ) =>
currentTemplateId.startsWith( allowedTemplate )
);
const shouldBeAvailableOnPostOrPageEditor =
isPostOrPage && availableInPostOrPageEditor;

return (
shouldBeAvailableOnTemplate ||
shouldBeAvailableOnTemplatePart ||
shouldBeAvailableOnPostOrPageEditor
);
}

/**
* Unregisters blocks before entering a restricted area based on the current template or page/post.
*
* This method iterates over all blocks with restrictions and unregisters them if they should not be registered
* based on the current template ID and whether the editor is in a post or page. It uses a block registration
* strategy to unregister the blocks, which depends on whether the block is a variation block or a regular block.
*
* @param {Object} params - The parameters for the method.
* @param {string} params.currentTemplateId - The ID of the current template.
* @param {boolean} params.isPostOrPage - Whether the editor is in post or page mode.
*/
unregisterBlocksBeforeEnteringRestrictedArea( {
currentTemplateId,
isPostOrPage,
}: {
currentTemplateId: string;
isPostOrPage: boolean;
} ) {
for ( const blockWithRestrictionName of Object.keys(
BLOCKS_WITH_RESTRICTION
) ) {
if (
this.shouldBlockBeRegistered( {
blockWithRestrictionName,
currentTemplateId,
isPostOrPage,
} )
) {
continue;
}
this.blockRegistrationStrategy = BLOCKS_WITH_RESTRICTION[
blockWithRestrictionName
].isVariationBlock
? new BlockVariationStrategy()
: new BlockTypeStrategy();

this.blockRegistrationStrategy.unregister(
blockWithRestrictionName
);
this.unregisteredBlocks.push( blockWithRestrictionName );
}
}

/**
* Registers blocks after leaving a restricted area.
*
* This method iterates over all unregistered blocks and registers them if they are not restricted in the current context.
* It uses a block registration strategy to register the blocks, which depends on whether the block is a variation block or a regular block.
* If the block is successfully registered, it is removed from the list of unregistered blocks.
*/
registerBlocksAfterLeavingRestrictedArea() {
for ( const unregisteredBlockName of this.unregisteredBlocks ) {
const restrictedBlockData =
BLOCKS_WITH_RESTRICTION[ unregisteredBlockName ];
this.blockRegistrationStrategy = BLOCKS_WITH_RESTRICTION[
unregisteredBlockName
].isVariationBlock
? new BlockVariationStrategy()
: new BlockTypeStrategy();
const isBlockRegistered = this.blockRegistrationStrategy.register(
restrictedBlockData.blockMetadata,
restrictedBlockData.blockSettings
);
this.unregisteredBlocks = isBlockRegistered
? this.unregisteredBlocks.filter(
( blockName ) => blockName !== unregisteredBlockName
)
: this.unregisteredBlocks;
}
}

/**
* Runs the block registration manager.
*
* This method is the main entry point for the block registration manager. It is called with a TemplateChangeDetector object,
* and registers and unregisters blocks based on the current template and whether the editor is in a post or page.
*
* @param {TemplateChangeDetector} templateChangeDetector - The template change detector object.
*/
run( templateChangeDetector: TemplateChangeDetector ) {
this.registerBlocksAfterLeavingRestrictedArea();
this.unregisterBlocksBeforeEnteringRestrictedArea( {
currentTemplateId:
templateChangeDetector.getCurrentTemplateId() || '',
isPostOrPage: templateChangeDetector.getIsPostOrPage(),
} );
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/**
* External dependencies
*/
import { BlockConfiguration } from '@wordpress/blocks';
import { ProductGalleryBlockSettings } from '@woocommerce/blocks/product-gallery/settings';

/**
* Internal dependencies
*/
import productGalleryBlockMetadata from '../../../blocks/product-gallery/block.json';

export interface BlocksWithRestriction {
[ key: string ]: {
blockMetadata: Partial< BlockConfiguration >;
blockSettings: Partial< BlockConfiguration >;
allowedTemplates: {
[ key: string ]: boolean;
};
allowedTemplateParts: {
[ key: string ]: boolean;
};
availableInPostOrPageEditor: boolean;
isVariationBlock: boolean;
};
}

// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-expect-error: `metadata` currently does not have a type definition in WordPress core
export const BLOCKS_WITH_RESTRICTION: BlocksWithRestriction = {
[ productGalleryBlockMetadata.name ]: {
blockMetadata: productGalleryBlockMetadata,
blockSettings: ProductGalleryBlockSettings,
allowedTemplates: {
'single-product': true,
},
allowedTemplateParts: {
'product-gallery': true,
},
availableInPostOrPageEditor: false,
isVariationBlock: false,
},
};
16 changes: 16 additions & 0 deletions assets/js/atomic/utils/blocks-registration-manager/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
/**
* External dependencies
*/
import domReady from '@wordpress/dom-ready';

/**
* Internal dependencies
*/
import { BlockRegistrationManager } from './blocks-registration-manager';
import { TemplateChangeDetector } from './template-change-detector';

domReady( () => {
const templateChangeDetector = new TemplateChangeDetector();
const blockRegistrationManager = new BlockRegistrationManager();
templateChangeDetector.add( blockRegistrationManager );
} );
Loading

0 comments on commit 83e5b64

Please sign in to comment.