Repository navigation
Global Styles: filter and indicate blocks that have custom styles #81373
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 1 commit
c247330
6b3bc56
fd1c32d
3cee8ef
fad9022
b60cf21
186331b
1da774e
692af90
e93ecc9
b2519a2
b582168
ea85b2f
818fff5
06a1b96
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
Replace the indicator dot with a line naming the styles the user changed, e.g. "Changed: Colors, Typography, Plain variation". The dot said a block had been touched but not what was touched, so it raised the question it was meant to answer. Reuse getGlobalStylesChangelist for the categories, calling it per block so the reported paths start at the block's own entry rather than at the whole config, where the block name consumes both levels the changelist keeps. Name a changed style variation rather than describing it, since the categories inside it are rarely what the reader is looking for. The changelist only reports background, color, typography and spacing, so a block changed only through its border counts as customized but has no summary to show. Left as is, to be addressed separately.
- Loading branch information
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -8,7 +8,6 @@ import { | |
| __experimentalHStack as HStack, | ||
| __experimentalText as WCText, | ||
| } from '@wordpress/components'; | ||
| import { VisuallyHidden } from '@wordpress/ui'; | ||
| import { useSelect } from '@wordpress/data'; | ||
| import { | ||
| useState, | ||
|
|
@@ -19,7 +18,10 @@ import { | |
| useDeferredValue, | ||
| memo, | ||
| } from '@wordpress/element'; | ||
| import type { GlobalStylesConfig } from '@wordpress/global-styles-engine'; | ||
| import { | ||
| getGlobalStylesChangelist, | ||
| type GlobalStylesConfig, | ||
| } from '@wordpress/global-styles-engine'; | ||
| import { | ||
| BlockIcon, | ||
| privateApis as blockEditorPrivateApis, | ||
|
|
@@ -95,6 +97,79 @@ export function hasUserStylesForBlock( | |
| ); | ||
| } | ||
|
|
||
| interface BlockStyle { | ||
| name: string; | ||
| label?: string; | ||
| } | ||
|
|
||
| /** | ||
| * A short summary of what the user changed on a block, e.g. | ||
| * "Changed: Typography, Colors, Plain variation". | ||
| * | ||
| * A changed style variation is named rather than described: whatever was | ||
| * changed inside it, it contributes one "{Label} variation" entry. | ||
| * | ||
| * Returns an empty string when nothing can be named. `getGlobalStylesChangelist` | ||
| * only reports background, color, typography and spacing, so a block changed | ||
| * only through, say, its border counts as customized but has nothing to show. | ||
| * | ||
| * @param user The user's global styles config. | ||
| * @param blockName The block to summarize, e.g. `core/quote`. | ||
| * @param registeredStyles The block's registered style variations, used to turn | ||
| * a variation's slug into its label. | ||
| * @return The summary, or an empty string. | ||
| */ | ||
| export function getUserStylesSummary( | ||
| user: GlobalStylesConfig | undefined, | ||
| blockName: string, | ||
| registeredStyles: BlockStyle[] = [] | ||
| ): string { | ||
| const blockStyles = user?.styles?.blocks?.[ blockName ]; | ||
| // Compare this block's own entry against nothing, so the changelist reports | ||
| // the block's categories rather than the whole config's. `variations` is not | ||
| // one of the keys it inspects, so it never reports them here. | ||
| const changes = getGlobalStylesChangelist( | ||
| { | ||
| styles: blockStyles, | ||
| settings: user?.settings?.blocks?.[ blockName ], | ||
| }, | ||
| {} | ||
| ); | ||
| const entries = [ ...new Set( changes.map( ( [ , label ] ) => label ) ) ]; | ||
|
|
||
| // Name each changed variation instead of describing what changed in it. | ||
| Object.entries( blockStyles?.variations ?? {} ).forEach( | ||
| ( [ slug, variationStyles ] ) => { | ||
| if ( ! hasAnyValue( variationStyles ) ) { | ||
| return; | ||
| } | ||
| const label = | ||
| registeredStyles.find( ( style ) => style.name === slug ) | ||
| ?.label || slug; | ||
| entries.push( | ||
| sprintf( | ||
| /* translators: %s: the name of a block style variation, e.g. "Plain". */ | ||
| __( '%s variation' ), | ||
| label | ||
| ) | ||
| ); | ||
| } | ||
| ); | ||
|
|
||
| if ( ! entries.length ) { | ||
| return ''; | ||
| } | ||
|
|
||
| return sprintf( | ||
| /* translators: %s: a list of the styles a user changed, e.g. "Typography, Colors". */ | ||
| __( 'Changed: %s' ), | ||
| entries.join( | ||
| /* translators: Used between list items, there is a space after the comma. */ | ||
| __( ', ' ) // eslint-disable-line @wordpress/i18n-no-flanking-whitespace | ||
| ) | ||
| ); | ||
| } | ||
|
|
||
| type StyleFilter = 'all' | 'customized'; | ||
|
|
||
| function useSortedBlockTypes() { | ||
|
|
@@ -141,10 +216,10 @@ export function useBlockHasGlobalStyles( blockName: string ) { | |
|
|
||
| interface BlockMenuItemProps { | ||
| block: any; | ||
| isCustomized: boolean; | ||
| summary: string; | ||
| } | ||
|
|
||
| function BlockMenuItem( { block, isCustomized }: BlockMenuItemProps ) { | ||
| function BlockMenuItem( { block, summary }: BlockMenuItemProps ) { | ||
| const hasBlockMenuItem = useBlockHasGlobalStyles( block.name ); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not for now, but potentially a refactor idea: this currently places the check of whether or not to render within So, at some point, we might want the check to occur at the same place where we do the filtering. Just a thought for later, nothing blocking for now!
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good point. Hoisting might change a few files, so I'll do it a follow up. 👍🏻 |
||
| if ( ! hasBlockMenuItem ) { | ||
| return null; | ||
|
|
@@ -154,20 +229,23 @@ function BlockMenuItem( { block, isCustomized }: BlockMenuItemProps ) { | |
| <NavigationButtonAsItem | ||
| path={ '/blocks/' + encodeURIComponent( block.name ) } | ||
| > | ||
| <HStack justify="flex-start"> | ||
| <BlockIcon icon={ block.icon } /> | ||
| <FlexItem>{ block.title }</FlexItem> | ||
| { isCustomized && ( | ||
| <> | ||
| <VisuallyHidden> | ||
| { __( 'Has custom styles' ) } | ||
| </VisuallyHidden> | ||
| <span | ||
| aria-hidden="true" | ||
| className="global-styles-ui-block-types-item__indicator" | ||
| /> | ||
| </> | ||
| ) } | ||
| <HStack | ||
| justify="flex-start" | ||
| alignment={ !! summary ? 'flex-start' : 'center' } | ||
| spacing={ 2 } | ||
| > | ||
| <BlockIcon | ||
| className="global-styles-ui-block-types-item__icon" | ||
| icon={ block.icon } | ||
| /> | ||
| <FlexItem> | ||
| { block.title } | ||
| { !! summary && ( | ||
| <span className="global-styles-ui-block-types-item__summary"> | ||
| { summary } | ||
| </span> | ||
| ) } | ||
| </FlexItem> | ||
| </HStack> | ||
| </NavigationButtonAsItem> | ||
| ); | ||
|
|
@@ -197,24 +275,32 @@ interface BlockListProps { | |
| function BlockList( { filterValue, styleFilter }: BlockListProps ) { | ||
| const sortedBlockTypes = useSortedBlockTypes(); | ||
| const debouncedSpeak = useDebounce( speak, 500 ); | ||
| const { isMatchingSearchTerm } = useSelect( blocksStore ); | ||
| const { isMatchingSearchTerm, getBlockStyles } = useSelect( blocksStore ); | ||
| const { user } = useContext( GlobalStylesContext ); | ||
|
|
||
| // Computed once for the whole list rather than per row, so the list does | ||
| // not open a context subscription for every registered block. | ||
| const customizedBlockNames = useMemo( () => { | ||
| const names = new Set< string >(); | ||
| const blockNames = [ | ||
| // Computed once for the whole list rather than per row, so the list does not | ||
| // open a context subscription for every registered block. Maps a customized | ||
| // block's name to its summary, which can be an empty string. | ||
| const customizedBlocks = useMemo( () => { | ||
| const summaries = new Map< string, string >(); | ||
| const blockNames = new Set( [ | ||
| ...Object.keys( user?.styles?.blocks ?? {} ), | ||
| ...Object.keys( user?.settings?.blocks ?? {} ), | ||
| ]; | ||
| ] ); | ||
| blockNames.forEach( ( blockName ) => { | ||
| if ( hasUserStylesForBlock( user, blockName ) ) { | ||
| names.add( blockName ); | ||
| summaries.set( | ||
| blockName, | ||
| getUserStylesSummary( | ||
| user, | ||
| blockName, | ||
| getBlockStyles( blockName ) | ||
| ) | ||
| ); | ||
| } | ||
| } ); | ||
| return names; | ||
| }, [ user ] ); | ||
| return summaries; | ||
| }, [ user, getBlockStyles ] ); | ||
|
|
||
| const searchedBlockTypes = ! filterValue | ||
| ? sortedBlockTypes | ||
|
|
@@ -225,7 +311,7 @@ function BlockList( { filterValue, styleFilter }: BlockListProps ) { | |
| const filteredBlockTypes = | ||
| styleFilter === 'customized' | ||
| ? searchedBlockTypes.filter( ( blockType ) => | ||
| customizedBlockNames.has( blockType.name ) | ||
| customizedBlocks.has( blockType.name ) | ||
| ) | ||
| : searchedBlockTypes; | ||
|
|
||
|
|
@@ -266,7 +352,7 @@ function BlockList( { filterValue, styleFilter }: BlockListProps ) { | |
| filteredBlockTypes.map( ( block ) => ( | ||
| <BlockMenuItem | ||
| block={ block } | ||
| isCustomized={ customizedBlockNames.has( block.name ) } | ||
| summary={ customizedBlocks.get( block.name ) ?? '' } | ||
| key={ 'menu-itemblock-' + block.name } | ||
| /> | ||
| ) ) | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No, not like this. Add random text here is not a good idea.