Skip to content

Commit

Permalink
Make BlockManager component reusable
Browse files Browse the repository at this point in the history
  • Loading branch information
t-hamano committed Nov 17, 2024
1 parent 865a058 commit 28723d5
Show file tree
Hide file tree
Showing 5 changed files with 168 additions and 86 deletions.
82 changes: 44 additions & 38 deletions packages/editor/src/components/block-manager/category.js
Original file line number Diff line number Diff line change
@@ -1,73 +1,79 @@
/**
* WordPress dependencies
*/
import { useMemo, useCallback } from '@wordpress/element';
import { useDispatch, useSelect } from '@wordpress/data';
import { useCallback } from '@wordpress/element';
import { useInstanceId } from '@wordpress/compose';
import { CheckboxControl } from '@wordpress/components';
import { store as preferencesStore } from '@wordpress/preferences';

/**
* Internal dependencies
*/
import BlockTypesChecklist from './checklist';
import { store as editorStore } from '../../store';
import { unlock } from '../../lock-unlock';

function BlockManagerCategory( { title, blockTypes } ) {
function BlockManagerCategory( {
title,
blockTypes,
selectedBlockTypes,
onChange,
} ) {
const instanceId = useInstanceId( BlockManagerCategory );
const { allowedBlockTypes, hiddenBlockTypes } = useSelect( ( select ) => {
const { getEditorSettings } = select( editorStore );
const { get } = select( preferencesStore );
return {
allowedBlockTypes: getEditorSettings().allowedBlockTypes,
hiddenBlockTypes: get( 'core', 'hiddenBlockTypes' ),
};
}, [] );
const filteredBlockTypes = useMemo( () => {
if ( allowedBlockTypes === true ) {
return blockTypes;
}
return blockTypes.filter( ( { name } ) => {
return allowedBlockTypes?.includes( name );
} );
}, [ allowedBlockTypes, blockTypes ] );
const { showBlockTypes, hideBlockTypes } = unlock(
useDispatch( editorStore )
);

const toggleVisible = useCallback(
( blockName, nextIsChecked ) => {
( blockType, nextIsChecked ) => {
if ( nextIsChecked ) {
showBlockTypes( blockName );
onChange( [ ...selectedBlockTypes, blockType ] );
} else {
hideBlockTypes( blockName );
onChange(
selectedBlockTypes.filter(
( { name } ) => name !== blockType.name
)
);
}
},
[ showBlockTypes, hideBlockTypes ]
[ selectedBlockTypes, onChange ]
);

const toggleAllVisible = useCallback(
( nextIsChecked ) => {
const blockNames = blockTypes.map( ( { name } ) => name );
if ( nextIsChecked ) {
showBlockTypes( blockNames );
onChange( [
...selectedBlockTypes,
...blockTypes.filter(
( blockType ) =>
! selectedBlockTypes.find(
( { name } ) => name === blockType.name
)
),
] );
} else {
hideBlockTypes( blockNames );
onChange(
selectedBlockTypes.filter(
( selectedBlockType ) =>
! blockTypes.find(
( { name } ) => name === selectedBlockType.name
)
)
);
}
},
[ blockTypes, showBlockTypes, hideBlockTypes ]
[ blockTypes, selectedBlockTypes, onChange ]
);

if ( ! filteredBlockTypes.length ) {
if ( ! blockTypes.length ) {
return null;
}

const checkedBlockNames = filteredBlockTypes
const checkedBlockNames = blockTypes
.map( ( { name } ) => name )
.filter( ( type ) => ! ( hiddenBlockTypes ?? [] ).includes( type ) );
.filter( ( type ) =>
( selectedBlockTypes ?? [] ).some(
( selectedBlockType ) => selectedBlockType.name === type
)
);

const titleId = 'editor-block-manager__category-title-' + instanceId;

const isAllChecked = checkedBlockNames.length === filteredBlockTypes.length;
const isAllChecked = checkedBlockNames.length === blockTypes.length;
const isIndeterminate = ! isAllChecked && checkedBlockNames.length > 0;

return (
Expand All @@ -85,7 +91,7 @@ function BlockManagerCategory( { title, blockTypes } ) {
label={ <span id={ titleId }>{ title }</span> }
/>
<BlockTypesChecklist
blockTypes={ filteredBlockTypes }
blockTypes={ blockTypes }
value={ checkedBlockNames }
onItemChange={ toggleVisible }
/>
Expand Down
2 changes: 1 addition & 1 deletion packages/editor/src/components/block-manager/checklist.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ function BlockTypesChecklist( { blockTypes, value, onItemChange } ) {
label={ blockType.title }
checked={ value.includes( blockType.name ) }
onChange={ ( ...args ) =>
onItemChange( blockType.name, ...args )
onItemChange( blockType, ...args )
}
/>
<BlockIcon icon={ blockType.icon } />
Expand Down
72 changes: 27 additions & 45 deletions packages/editor/src/components/block-manager/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,69 +2,49 @@
* WordPress dependencies
*/
import { store as blocksStore } from '@wordpress/blocks';
import { useDispatch, useSelect } from '@wordpress/data';
import { useSelect } from '@wordpress/data';
import { SearchControl, Button } from '@wordpress/components';
import { __, _n, sprintf } from '@wordpress/i18n';
import { useEffect, useState } from '@wordpress/element';
import { useDebounce } from '@wordpress/compose';
import { speak } from '@wordpress/a11y';
import { store as preferencesStore } from '@wordpress/preferences';

/**
* Internal dependencies
*/
import { unlock } from '../../lock-unlock';
import { store as editorStore } from '../../store';
import BlockManagerCategory from './category';

export default function BlockManager() {
/**
* Provides a list of blocks with checkboxes.
*
* @param {Object} props Props.
* @param {Array} props.blockTypes An array of blocks.
* @param {Array} props.selectedBlockTypes An array of selected blocks.
* @param {Function} props.onChange Function to be called when the selected blocks change.
*/
export default function BlockManager( {
blockTypes,
selectedBlockTypes,
onChange,
} ) {
const debouncedSpeak = useDebounce( speak, 500 );
const [ search, setSearch ] = useState( '' );
const { showBlockTypes } = unlock( useDispatch( editorStore ) );

const {
blockTypes,
categories,
hasBlockSupport,
isMatchingSearchTerm,
numberOfHiddenBlocks,
} = useSelect( ( select ) => {
// Some hidden blocks become unregistered
// by removing for instance the plugin that registered them, yet
// they're still remain as hidden by the user's action.
// We consider "hidden", blocks which were hidden and
// are still registered.
const _blockTypes = select( blocksStore ).getBlockTypes();
const hiddenBlockTypes = (
select( preferencesStore ).get( 'core', 'hiddenBlockTypes' ) ?? []
).filter( ( hiddenBlock ) => {
return _blockTypes.some(
( registeredBlock ) => registeredBlock.name === hiddenBlock
);
} );

const { categories, isMatchingSearchTerm } = useSelect( ( select ) => {
return {
blockTypes: _blockTypes,
categories: select( blocksStore ).getCategories(),
hasBlockSupport: select( blocksStore ).hasBlockSupport,
isMatchingSearchTerm: select( blocksStore ).isMatchingSearchTerm,
numberOfHiddenBlocks:
Array.isArray( hiddenBlockTypes ) && hiddenBlockTypes.length,
};
}, [] );

function enableAllBlockTypes( newBlockTypes ) {
const blockNames = newBlockTypes.map( ( { name } ) => name );
showBlockTypes( blockNames );
function enableAllBlockTypes() {
onChange( blockTypes );
}

const filteredBlockTypes = blockTypes.filter(
( blockType ) =>
hasBlockSupport( blockType, 'inserter', true ) &&
( ! search || isMatchingSearchTerm( blockType, search ) ) &&
( ! blockType.parent ||
blockType.parent.includes( 'core/post-content' ) )
);
const filteredBlockTypes = blockTypes.filter( ( blockType ) => {
return ! search || isMatchingSearchTerm( blockType, search );
} );

const numberOfHiddenBlocks = blockTypes.length - selectedBlockTypes.length;

// Announce search results on change
useEffect( () => {
Expand Down Expand Up @@ -96,9 +76,7 @@ export default function BlockManager() {
<Button
__next40pxDefaultSize
variant="link"
onClick={ () =>
enableAllBlockTypes( filteredBlockTypes )
}
onClick={ enableAllBlockTypes }
>
{ __( 'Reset' ) }
</Button>
Expand Down Expand Up @@ -131,13 +109,17 @@ export default function BlockManager() {
( blockType ) =>
blockType.category === category.slug
) }
selectedBlockTypes={ selectedBlockTypes }
onChange={ onChange }
/>
) ) }
<BlockManagerCategory
title={ __( 'Uncategorized' ) }
blockTypes={ filteredBlockTypes.filter(
( { category } ) => ! category
) }
selectedBlockTypes={ selectedBlockTypes }
onChange={ onChange }
/>
</div>
</div>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
/**
* WordPress dependencies
*/
import { useSelect, useDispatch } from '@wordpress/data';
import { store as preferencesStore } from '@wordpress/preferences';
import { hasBlockSupport, store as blocksStore } from '@wordpress/blocks';
import { useMemo } from '@wordpress/element';

/**
* Internal dependencies
*/
import { store as editorStore } from '../../store';
import { unlock } from '../../lock-unlock';
import BlockManager from '../block-manager';

export default function BlockVisibility() {
const { showBlockTypes, hideBlockTypes } = unlock(
useDispatch( editorStore )
);

const {
blockTypes,
allowedBlockTypes: _allowedBlockTypes,
hiddenBlockTypes: _hiddenBlockTypes,
} = useSelect( ( select ) => {
return {
blockTypes: select( blocksStore ).getBlockTypes(),
allowedBlockTypes:
select( editorStore ).getEditorSettings().allowedBlockTypes,
hiddenBlockTypes:
select( preferencesStore ).get( 'core', 'hiddenBlockTypes' ) ??
[],
};
}, [] );

const allowedBlockTypes = useMemo( () => {
if ( _allowedBlockTypes === true ) {
return blockTypes;
}
return blockTypes.filter( ( { name } ) => {
return _allowedBlockTypes?.includes( name );
} );
}, [ _allowedBlockTypes, blockTypes ] );

const filteredBlockTypes = allowedBlockTypes.filter(
( blockType ) =>
hasBlockSupport( blockType, 'inserter', true ) &&
( ! blockType.parent ||
blockType.parent.includes( 'core/post-content' ) )
);

// Some hidden blocks become unregistered
// by removing for instance the plugin that registered them, yet
// they're still remain as hidden by the user's action.
// We consider "hidden", blocks which were hidden and
// are still registered.
const hiddenBlockTypes = _hiddenBlockTypes.filter( ( hiddenBlock ) => {
return filteredBlockTypes.some(
( registeredBlock ) => registeredBlock.name === hiddenBlock
);
} );

const selectedBlockTypes = filteredBlockTypes.filter(
( blockType ) => ! hiddenBlockTypes.includes( blockType.name )
);

const onChangeSelectedBlockTypes = ( newSelectedBlockTypes ) => {
if ( selectedBlockTypes.length > newSelectedBlockTypes.length ) {
const blockTypesToHide = selectedBlockTypes.filter(
( blockType ) =>
! newSelectedBlockTypes.find(
( { name } ) => name === blockType.name
)
);
hideBlockTypes( blockTypesToHide.map( ( { name } ) => name ) );
} else if ( selectedBlockTypes.length < newSelectedBlockTypes.length ) {
const blockTypesToShow = newSelectedBlockTypes.filter(
( blockType ) =>
! selectedBlockTypes.find(
( { name } ) => name === blockType.name
)
);
showBlockTypes( blockTypesToShow.map( ( { name } ) => name ) );
}
};

return (
<BlockManager
blockTypes={ filteredBlockTypes }
selectedBlockTypes={ selectedBlockTypes }
onChange={ onChangeSelectedBlockTypes }
/>
);
}
4 changes: 2 additions & 2 deletions packages/editor/src/components/preferences-modal/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import { store as interfaceStore } from '@wordpress/interface';
import EnablePanelOption from './enable-panel';
import EnablePluginDocumentSettingPanelOption from './enable-plugin-document-setting-panel';
import EnablePublishSidebarOption from './enable-publish-sidebar';
import BlockManager from '../block-manager';
import BlockVisibility from './block-visibility';
import PostTaxonomies from '../post-taxonomies';
import PostFeaturedImageCheck from '../post-featured-image/check';
import PostExcerptCheck from '../post-excerpt/check';
Expand Down Expand Up @@ -297,7 +297,7 @@ function PreferencesModalContents( { extraSections = {} } ) {
"Disable blocks that you don't want to appear in the inserter. They can always be toggled back on later."
) }
>
<BlockManager />
<BlockVisibility />
</PreferencesModalSection>
</>
),
Expand Down

0 comments on commit 28723d5

Please sign in to comment.