Skip to content

Commit

Permalink
Use Duotone presets in block duotone attributes (#48318)
Browse files Browse the repository at this point in the history
* Naive implementation

* Refactor to named functions

* Fix order of hooks

* Utilise existing duotone presets on front of site

* Improve self documenting code

* Refactor away redundant color prop of filter_preset

* Satisfy master yoda…

* More commenting

* Remove count() check for duotone color array

Duotone color arrays can be more than 2 colors, and the code doesn't seem to handle 2 colors differently than 3 or more, so the count() === 2 was unnecessary. Since we've already defined the $is_duotone_colors_array, we can use it later instead of redoing another is_array() check.

* Improve comments and linting

* Allow Duotone to be cleared using UI

* Add initial tests for getColorsFromDuotonePreset

* Be explicit about handling presets vs colors

Previously the decision about how to handle values were deferred to the utility. Instead the consuming code should decide whether to look for a preset.

* Complete tests for getColorsFromDuotonePreset

* Add tests and fix implementation of getDuotonePresetFromColors

* Fix bug with selecting custom colors introduced during test refactor

* Fix custom colors Duotone on front end rendering

---------

Co-authored-by: Jerry Jones <jones.jeremydavid@gmail.com>
  • Loading branch information
getdave and jeryj authored Feb 23, 2023
1 parent 76a2cf2 commit c74672c
Show file tree
Hide file tree
Showing 3 changed files with 194 additions and 18 deletions.
54 changes: 42 additions & 12 deletions lib/block-supports/duotone.php
Original file line number Diff line number Diff line change
Expand Up @@ -311,9 +311,6 @@ function gutenberg_get_duotone_filter_id( $preset ) {
* @return string Duotone CSS filter property url value.
*/
function gutenberg_get_duotone_filter_property( $preset ) {
if ( isset( $preset['colors'] ) && 'unset' === $preset['colors'] ) {
return 'none';
}
$filter_id = gutenberg_get_duotone_filter_id( $preset );
return "url('#" . $filter_id . "')";
}
Expand Down Expand Up @@ -445,15 +442,45 @@ function gutenberg_render_duotone_support( $block_content, $block ) {
return $block_content;
}

$colors = $block['attrs']['style']['color']['duotone'];
$filter_key = is_array( $colors ) ? implode( '-', $colors ) : $colors;
$filter_preset = array(
'slug' => wp_unique_id( sanitize_key( $filter_key . '-' ) ),
'colors' => $colors,
);
$filter_property = gutenberg_get_duotone_filter_property( $filter_preset );
$filter_id = gutenberg_get_duotone_filter_id( $filter_preset );
// Possible values for duotone attribute:
// 1. Array of colors - e.g. array('#000000', '#ffffff').
// 2. Slug of an existing Duotone preset - e.g. 'green-blue'.
// 3. The string 'unset' - indicates explicitly "no Duotone"..
$duotone_attr = $block['attrs']['style']['color']['duotone'];

$is_duotone_colors_array = is_array( $duotone_attr );
$is_duotone_unset = 'unset' === $duotone_attr;
$is_duotone_preset = ! $is_duotone_colors_array && ! $is_duotone_unset;

if ( $is_duotone_preset ) {
$filter_preset = array(
'slug' => $duotone_attr,
);

// Utilise existing CSS custom property.
$filter_property = "var(--wp--preset--duotone--$duotone_attr)";
} else {
// Handle when Duotone is either:
// - "unset"
// - an array of colors.

// Build a unique slug for the filter based on the array of colors.
$filter_key = $is_duotone_colors_array ? implode( '-', $duotone_attr ) : $duotone_attr;
$filter_preset = array(
'slug' => wp_unique_id( sanitize_key( $filter_key . '-' ) ),
'colors' => $duotone_attr, // required for building the SVG with gutenberg_get_duotone_filter_svg.
);

// Build a customised CSS filter property for unique slug.
$filter_property = $is_duotone_unset ? 'none' : gutenberg_get_duotone_filter_property( $filter_preset );
}

// - Applied as a class attribute to the block wrapper.
// - Used as a selector to apply the filter to the block.
$filter_id = gutenberg_get_duotone_filter_id( $filter_preset );

// Build the CSS selectors to which the filter will be applied.
// Todo - encapsulate this in a function.
$scope = '.' . $filter_id;
$selectors = explode( ',', $duotone_support );
$scoped = array();
Expand Down Expand Up @@ -483,8 +510,11 @@ function gutenberg_render_duotone_support( $block_content, $block ) {
)
);

if ( 'unset' !== $colors ) {
// For *non*-presets then generate an SVG for the filter.
// Note: duotone presets are already pre-generated so no need to do this again.
if ( $is_duotone_colors_array ) {
$filter_svg = gutenberg_get_duotone_filter_svg( $filter_preset );

add_action(
'wp_footer',
static function () use ( $filter_svg, $selector ) {
Expand Down
59 changes: 53 additions & 6 deletions packages/block-editor/src/hooks/duotone.js
Original file line number Diff line number Diff line change
Expand Up @@ -75,9 +75,34 @@ function useMultiOriginPresets( { presetSetting, defaultSetting } ) {
);
}

export function getColorsFromDuotonePreset( duotone, duotonePalette ) {
if ( ! duotone ) {
return;
}
const preset = duotonePalette?.find( ( { slug } ) => {
return slug === duotone;
} );

return preset ? preset.colors : undefined;
}

export function getDuotonePresetFromColors( colors, duotonePalette ) {
if ( ! colors || ! Array.isArray( colors ) ) {
return;
}

const preset = duotonePalette?.find( ( duotonePreset ) => {
return duotonePreset?.colors?.every(
( val, index ) => val === colors[ index ]
);
} );

return preset ? preset.slug : undefined;
}

function DuotonePanel( { attributes, setAttributes } ) {
const style = attributes?.style;
const duotone = style?.color?.duotone;
const duotoneStyle = style?.color?.duotone;

const duotonePalette = useMultiOriginPresets( {
presetSetting: 'color.duotone',
Expand All @@ -96,20 +121,29 @@ function DuotonePanel( { attributes, setAttributes } ) {
return null;
}

const duotonePresetOrColors = ! Array.isArray( duotoneStyle )
? getColorsFromDuotonePreset( duotoneStyle, duotonePalette )
: duotoneStyle;

return (
<BlockControls group="block" __experimentalShareWithChildBlocks>
<DuotoneControl
duotonePalette={ duotonePalette }
colorPalette={ colorPalette }
disableCustomDuotone={ disableCustomDuotone }
disableCustomColors={ disableCustomColors }
value={ duotone }
value={ duotonePresetOrColors }
onChange={ ( newDuotone ) => {
const maybePreset = getDuotonePresetFromColors(
newDuotone,
duotonePalette
);

const newStyle = {
...style,
color: {
...style?.color,
duotone: newDuotone,
duotone: maybePreset ?? newDuotone, // use preset or fallback to custom colors.
},
};
setAttributes( { style: newStyle } );
Expand Down Expand Up @@ -224,14 +258,27 @@ const withDuotoneStyles = createHigherOrderComponent(
props.name,
'color.__experimentalDuotone'
);
const colors = props?.attributes?.style?.color?.duotone;
const duotonePalette = useMultiOriginPresets( {
presetSetting: 'color.duotone',
defaultSetting: 'color.defaultDuotone',
} );

const id = `wp-duotone-${ useInstanceId( BlockListBlock ) }`;

let colors = props?.attributes?.style?.color?.duotone;

if ( ! Array.isArray( colors ) ) {
const duotone = duotonePalette.find( ( dt ) => dt.slug === colors );

if ( duotone ) {
colors = duotone.colors;
}
}

if ( ! duotoneSupport || ! colors ) {
return <BlockListBlock { ...props } />;
}

const id = `wp-duotone-${ useInstanceId( BlockListBlock ) }`;

// Extra .editor-styles-wrapper specificity is needed in the editor
// since we're not using inline styles to apply the filter. We need to
// override duotone applied by global styles and theme.json.
Expand Down
99 changes: 99 additions & 0 deletions packages/block-editor/src/hooks/test/duotone.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
/**
* Internal dependencies
*/
import {
getColorsFromDuotonePreset,
getDuotonePresetFromColors,
} from '../duotone';

describe( 'Duotone utilities', () => {
const duotonePalette = [
{
name: 'Dark grayscale',
colors: [ '#000000', '#7f7f7f' ],
slug: 'dark-grayscale',
},
{
name: 'Grayscale',
colors: [ '#000000', '#ffffff' ],
slug: 'grayscale',
},
{
name: 'Purple and yellow',
colors: [ '#8c00b7', '#fcff41' ],
slug: 'purple-yellow',
},
];
describe( 'getColorsFromDuotonePreset', () => {
it( 'should return undefined if no arguments are provided', () => {
expect( getColorsFromDuotonePreset() ).toBeUndefined();
} );

it( 'should return undefined if no duotone preset is provided', () => {
expect(
getColorsFromDuotonePreset( undefined, duotonePalette )
).toBeUndefined();
} );

it( 'should return undefined if a non-existent preset is provided', () => {
expect(
getColorsFromDuotonePreset( 'does-not-exist', duotonePalette )
).toBeUndefined();
} );

it( 'should return the colors from the preset if found', () => {
expect(
getColorsFromDuotonePreset(
duotonePalette[ 2 ].slug,
duotonePalette
)
).toEqual( duotonePalette[ 2 ].colors );
} );
} );

describe( 'getDuotonePresetFromColors', () => {
it( 'should return undefined if no arguments are provided', () => {
expect( getDuotonePresetFromColors() ).toBeUndefined();
} );

it( 'should return undefined if no colors are provided', () => {
expect(
getDuotonePresetFromColors( undefined, duotonePalette )
).toBeUndefined();
} );

it( 'should return undefined if provided colors is not of valid type', () => {
const notAnArrayOfColorStrings = 'purple-yellow';
expect(
getDuotonePresetFromColors(
notAnArrayOfColorStrings,
duotonePalette
)
).toBeUndefined();
} );

it( 'should return undefined if no duotone palette is provided', () => {
expect(
getDuotonePresetFromColors( [ '#8c00b7', '#fcff41' ] )
).toBeUndefined();
} );

it( 'should return undefined if the provided colors do not match any preset', () => {
expect(
getDuotonePresetFromColors(
[ '#000000', '#000000' ],
duotonePalette
)
).toBeUndefined();
} );

it( 'should return the slug of the preset if found', () => {
expect(
getDuotonePresetFromColors(
duotonePalette[ 2 ].colors,
duotonePalette
)
).toEqual( duotonePalette[ 2 ].slug );
} );
} );
} );

1 comment on commit c74672c

@github-actions
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Flaky tests detected in c74672c.
Some tests passed with failed attempts. The failures may not be related to this commit but are still reported for visibility. See the documentation for more information.

🔍 Workflow run URL: https://github.com/WordPress/gutenberg/actions/runs/4252077524
📝 Reported issues:

Please sign in to comment.