Skip to content
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

Cover: Set custom color when applying initial background image #22564

Closed
wants to merge 34 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
2dab13e
Add useColorExtract hook. Set up color extract with background image …
May 22, 2020
e07f370
Fix example for useColorExtract
May 22, 2020
78732b2
Fix background color extraction to change on replace media
May 23, 2020
fa4d59e
Extract core functionality from color-thief for color extraction
May 23, 2020
5bdefaf
Fix quantize dependency source
May 23, 2020
bad740d
Fix color extract on initial image setting
Jun 1, 2020
df515ae
Merge branch 'master' into try/cover-background-image-auto-color
Jun 2, 2020
60c3578
Add "Pick dominant" button in Overlay InspectorControl
Jun 2, 2020
a705c48
Merge branch 'master' into try/cover-background-image-auto-color
Jun 3, 2020
a6a63db
Add custom dominant color to Solid color palette
Jun 3, 2020
5cb1c44
Merge branch 'master' into try/cover-background-image-auto-color
Jun 4, 2020
c49e4b2
Update Image dominant color tooltip label
Jun 4, 2020
54799c5
Merge branch 'master' into try/cover-background-image-auto-color
Jun 8, 2020
59faa2a
Merge branch 'master' into try/cover-background-image-auto-color
Jun 10, 2020
0794f03
Merge branch 'master' into try/cover-background-image-auto-color
Jun 11, 2020
e5a7e3a
Merge branch 'master' into try/cover-background-image-auto-color
Jun 16, 2020
cc9e0c0
Merge branch 'master' into try/cover-background-image-auto-color
Jun 17, 2020
2b04cd8
Merge branch 'master' into try/cover-background-image-auto-color
Jun 19, 2020
a74392d
Merge branch 'master' into try/cover-background-image-auto-color
Jun 22, 2020
431c6ef
Merge branch 'master' into try/cover-background-image-auto-color
Jun 23, 2020
8a0bbfa
Merge branch 'master' into try/cover-background-image-auto-color
Jun 23, 2020
cf51693
Merge branch 'master' into try/cover-background-image-auto-color
Jun 26, 2020
c407669
Merge branch 'master' into try/cover-background-image-auto-color
Jun 26, 2020
d62e498
Merge branch 'master' into try/cover-background-image-auto-color
Jun 29, 2020
291012b
Fix test from merge issue
Jun 29, 2020
ad809ac
Merge branch 'master' into try/cover-background-image-auto-color
Jun 30, 2020
756656e
Fix E2E tests. Adjust try/catch + only loading if src in extractColor…
Jun 30, 2020
1f6ffc7
Wrap getImageNode() in try/catch to handle CORS error
Jun 30, 2020
91a7aa6
Merge branch 'master' into try/cover-background-image-auto-color
Jul 7, 2020
cec9121
Fix extract function to run only for Editor (not preview)
Jul 7, 2020
ea75467
Remove export of useControlledState
Jul 7, 2020
cdbdab3
Merge branch 'master' into try/cover-background-image-auto-color
Sep 14, 2020
53a15a5
useExtractColor: Replace ColorThief with FastAverageColor module (alr…
Sep 14, 2020
73d87f8
Adjust fast-average-color package to match the one used in block-library
Sep 14, 2020
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ export const PanelColorGradientSettingsInner = ( {
className,
colors,
gradients,
customColors = [],
disableCustomColors,
disableCustomGradients,
children,
Expand All @@ -111,11 +112,13 @@ export const PanelColorGradientSettingsInner = ( {
return null;
}

const mergedColors = [ ...customColors, ...colors ];
Copy link
Contributor

Choose a reason for hiding this comment

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

Depending on how often PanelColorGradientSettingsInner re-renders as a user interacts with the block, might be good to:

Suggested change
const mergedColors = [ ...customColors, ...colors ];
const mergedColors = useMemo( () => [ ...customColors, ...colors ], [
customColors,
colors,
] );
;

Naturally, this supposes that references for customColors and colors are preserved when possible.


const titleElement = (
<span className="block-editor-panel-color-gradient-settings__panel-title">
{ title }
<Indicators
colors={ colors }
colors={ mergedColors }
gradients={ gradients }
settings={ settings }
/>
Expand All @@ -134,7 +137,7 @@ export const PanelColorGradientSettingsInner = ( {
<ColorGradientControl
key={ index }
{ ...{
colors,
colors: mergedColors,
gradients,
disableCustomColors,
disableCustomGradients,
Expand Down
65 changes: 65 additions & 0 deletions packages/block-library/src/cover/edit.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
/**
* External dependencies
*/
import { noop } from 'lodash';
import classnames from 'classnames';
import FastAverageColor from 'fast-average-color';
Copy link
Member

Choose a reason for hiding this comment

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

Hi @ItsJonQ, the cover block was already computing the color of an image using the FastAverageColor module. I think we should avoid using two different modules for the same task so I guess we should opt for one of the modules and update the code to use the chosen module.

Copy link
Author

Choose a reason for hiding this comment

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

@jorgefilipecosta Oh! I wasn't aware of that. Thank you for pointing that out. I'll make an update

import tinycolor from 'tinycolor2';
Expand All @@ -19,6 +20,7 @@ import {
ResizableBox,
ToggleControl,
withNotices,
useColorExtract,
__experimentalBoxControl as BoxControl,
} from '@wordpress/components';
import { compose, withInstanceId, useInstanceId } from '@wordpress/compose';
Expand Down Expand Up @@ -304,10 +306,25 @@ function CoverEdit( {
}
}

const backgroundColorValue = overlayColor.color || gradientValue;
const hasBackground = !! ( url || overlayColor.color || gradientValue );

const showFocalPointPicker =
isVideoBackground || ( isImageBackground && ! hasParallax );

/**
* Custom hook used for setting the initial background color.
*
* If a background image is set, this hook extracts the primary color from
* that image and sets it as a background color.
*/
const { customColors } = useCoverColorExtract( {
color: backgroundColorValue,
isSelected,
onChange: setOverlayColor,
src: url,
} );

const controls = (
<>
<BlockControls>
Expand Down Expand Up @@ -395,6 +412,7 @@ function CoverEdit( {
<PanelColorGradientSettings
title={ __( 'Overlay' ) }
initialOpen={ true }
customColors={ customColors }
settings={ [
{
colorValue: overlayColor.color,
Expand Down Expand Up @@ -548,6 +566,53 @@ function CoverEdit( {
);
}

function useCoverColorExtract( {
backgroundColor,
onChange = noop,
isSelected = false,
src,
} ) {
const [ customExtractedColor, setCustomExtractedColor ] = useState( null );
const [ didSelect, setDidSelect ] = useState( false );

const updateCustomOverlayColor = ( value ) => {
onChange( value );
setCustomExtractedColor( value );
};

const { extractColor } = useColorExtract( {
color: backgroundColor,
onChange: updateCustomOverlayColor,
src,
} );

useEffect( () => {
// Extracts color to add to color palette.
// Run when the block is first selected.
if ( ! didSelect && isSelected ) {
extractColor().then( ( [ value ] ) => {
setCustomExtractedColor( value );
} );
setDidSelect( true );
}
}, [ isSelected, didSelect ] );

let customColors = [];
if ( customExtractedColor ) {
customColors = [
{
name: __( 'Image dominant color' ),
slug: __( 'image-dominant-color' ),
Comment on lines +604 to +605
Copy link
Contributor

Choose a reason for hiding this comment

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

Some context or translators: notes could be good for these.

color: customExtractedColor,
},
];
}

return {
customColors,
};
}

export default compose( [
withDispatch( ( dispatch ) => {
const { toggleSelection } = dispatch( 'core/block-editor' );
Expand Down
1 change: 1 addition & 0 deletions packages/components/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@
"classnames": "^2.2.5",
"dom-scroll-into-view": "^1.2.1",
"downshift": "^5.4.0",
"fast-average-color": "4.3.0",
"gradient-parser": "^0.1.5",
"lodash": "^4.17.19",
"memize": "^1.1.0",
Expand Down
3 changes: 3 additions & 0 deletions packages/components/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -135,3 +135,6 @@ export {
} from './higher-order/with-focus-return';
export { default as withNotices } from './higher-order/with-notices';
export { default as withSpokenMessages } from './higher-order/with-spoken-messages';

// Utilities
export { useColorExtract } from './utils/hooks';
1 change: 1 addition & 0 deletions packages/components/src/utils/hooks/index.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export { useColorExtract } from './use-color-extract';
export { default as useControlledState } from './use-controlled-state';
export { default as useJumpStep } from './use-jump-step';
export { default as useUpdateEffect } from './use-update-effect';
131 changes: 131 additions & 0 deletions packages/components/src/utils/hooks/use-color-extract.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
/**
* External dependencies
*/
import { noop } from 'lodash';
import FastAverageColor from 'fast-average-color';

/**
* WordPress dependencies
*/
import { useState, useEffect, useRef } from '@wordpress/element';

/**
* Internal dependencies
*/

/**
*
* @typedef UseColorExtractProps
*
* @property {string} color The initial color, used to track updates.
* @property {string} onChange Callback when colors are extracted.
* @property {Function} src The source of the image to extract colors from.
*/

/**
*
* @typedef UseColorExtractHookProps
*
* @property {string} colors The color value extracted from an image source.
* @property {Function} extractColor An async method (Promise) that extracts color values from the image source.
*/

/**
* Custom hook that extracts the primary color of an image.
*
* This component's extraction technique may not work depending on
* CORS policy.
*
* @example
*
* ```js
* useColorExtract({
* src: '/my-image.png',
* onChange: imageColor => setState(imageColor)
* })
* ```
* @param {UseColorExtractProps} props Props for the custom hook.
* @return {UseColorExtractHookProps} Values and methods.
*/
export function useColorExtract( {
color: initialColor,
onChange = noop,
src,
} ) {
const [ color, setColor ] = useState();
const srcRef = useRef( src );
const initialColorRef = useRef( initialColor );
const imageNodeRef = useRef();

const getImageNode = () => {
if ( ! imageNodeRef.current ) {
imageNodeRef.current = document.createElement( 'img' );
imageNodeRef.current.crossOrigin = 'Anonymous';
}
return imageNodeRef.current;
};

const extractColor = () => {
return new Promise( ( resolve, reject ) => {
try {
const imageNode = getImageNode();

imageNode.onload = async () => {
const extractor = new FastAverageColor();

extractor.getColorAsync( imageNode, ( data ) => {
setColor( data.hex );
resolve( [ data.hex, data ] );
} );

srcRef.current = src;
};
// Load the image
if ( src ) {
imageNode.src = src;
}
} catch ( err ) {
reject( err );
}
} );
};

useEffect( () => {
// Guard to quick return if the src does not change
if ( srcRef.current === src ) {
return;
}
/**
* Checks to see if the src changes.
* For Cover blocks, the initial src (on load) is undefined.
* This tracks to see if an existing src changes to another.
*/
const didSrcChange =
// Initial src exists
srcRef.current &&
// Next src exists
src &&
// Next src does not match initial src (changed)
srcRef.current !== src;

// Guard to handle the initial load of an image (with potential pre-existing color)
if ( initialColorRef.current && ! didSrcChange ) {
return;
}

extractColor()
.then( ( [ value, data ] ) => {
const imageNode = getImageNode();
onChange( value, { data, node: imageNode } );
} )
.catch( ( err ) => {
// eslint-disable-next-line no-console
console.err( err );
} );
}, [ src ] );

return {
color,
extractColor,
};
}
41 changes: 41 additions & 0 deletions packages/components/src/utils/stories/use-color-extract.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/**
* Internal dependencies
*/
/**
* WordPress dependencies
*/
import { useEffect } from '@wordpress/element';
import { useColorExtract } from '../hooks';

export default { title: 'Utils/Hooks/useColorExtract' };

const Example = () => {
const src =
'https://i0.wp.com/themes.svn.wordpress.org/twentytwenty/1.5/screenshot.png?w=1144&strip=all';
const { color, extractColor } = useColorExtract( { src } );

useEffect( () => {
extractColor();
}, [] );

return (
<div>
<h3>Extracted Color</h3>
<p>
<div
style={ { width: 40, height: 40, backgroundColor: color } }
/>
</p>
<h3>Image Source</h3>
<img
src={ src }
alt="Twenty Twenty Theme Preview"
style={ { width: 300, height: 'auto' } }
/>
</div>
);
};

export const _default = () => {
return <Example />;
};