Skip to content

Commit

Permalink
Jetpack AI Image: add button entrypoint on the image block (#38123)
Browse files Browse the repository at this point in the history
* Add button to trigger general purpose image generation

* Fix button positioning

* Let media button follow the same margin/padding rule of other buttons

* Fix issue when the selection should be multiple

* Move the AI button to it's own component file

* Do not add the button when it's a featured image generation

* Only support AI button on specific blocks

* Add new placement const for the new button

* Add media source specific to render general purpose image for blocks

* Trigger the general image for block when the button is clicked

* Rename original component to follow pattern and refer to media source placement

* changelog

* Only show the button when the beta extensions are enabled :|

---------

Co-authored-by: Douglas <douglas.henri@automattic.com>
  • Loading branch information
lhkowalski and dhasilva authored Jul 1, 2024
1 parent 73d2664 commit b556b8f
Show file tree
Hide file tree
Showing 10 changed files with 105 additions and 16 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Significance: minor
Type: other

Jetpack AI Image: include new entrypoint as a button on the image/gallery/slideshow block.
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
import FeaturedImage from './featured-image';
import GeneralPurposeImage from './general-purpose-image';
import { PLACEMENT_MEDIA_SOURCE_DROPDOWN } from './types';
import { PLACEMENT_MEDIA_SOURCE_DROPDOWN, PLACEMENT_BLOCK_PLACEHOLDER_BUTTON } from './types';

export { FeaturedImage, PLACEMENT_MEDIA_SOURCE_DROPDOWN, GeneralPurposeImage };
export {
FeaturedImage,
PLACEMENT_MEDIA_SOURCE_DROPDOWN,
PLACEMENT_BLOCK_PLACEHOLDER_BUTTON,
GeneralPurposeImage,
};
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@ export const GENERAL_IMAGE_FEATURE_NAME = 'general-image' as const;
export const IMAGE_GENERATION_MODEL_STABLE_DIFFUSION = 'stable-diffusion' as const;
export const IMAGE_GENERATION_MODEL_DALL_E_3 = 'dall-e-3' as const;
export const PLACEMENT_MEDIA_SOURCE_DROPDOWN = 'media-source-dropdown' as const;
export const PLACEMENT_BLOCK_PLACEHOLDER_BUTTON = 'block-placeholder-button' as const;
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,10 @@ export const SOURCE_OPENVERSE = 'openverse';
export const SOURCE_PEXELS = 'pexels';
export const SOURCE_JETPACK_APP_MEDIA = 'jetpack_app_media';
export const SOURCE_JETPACK_AI_FEATURED_IMAGE = 'jetpack_ai_featured_image';
export const SOURCE_JETPACK_AI_GENERAL_PURPOSE_IMAGE = 'jetpack_ai_general_purpose_image';
export const SOURCE_JETPACK_AI_GENERAL_PURPOSE_IMAGE_FOR_MEDIA_SOURCE =
'jetpack_ai_general_purpose_image_for_media_source';
export const SOURCE_JETPACK_AI_GENERAL_PURPOSE_IMAGE_FOR_BLOCK =
'jetpack_ai_general_purpose_image_for_block';

export const PATH_RECENT = 'recent';
export const PATH_ROOT = '/';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -558,15 +558,17 @@ $grid-size: 8px;
}
}

// Set the wrapper as a flex container to allow displaying multiple buttons side by side.
.jetpack-external-media-button-wrapper {
display: flex;
}

// Reset placeholder button margin.
.components-placeholder__fieldset,
.editor-post-featured-image {
.components-dropdown .jetpack-external-media-button-menu {
width: auto;
margin-right: 8px;
margin-bottom: 1em;
padding-left: 6px;
padding-right: 6px;

> svg {
display: none;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,33 @@
import { useBlockEditContext } from '@wordpress/block-editor';
import { useState } from '@wordpress/element';
import { getExternalLibrary } from '../sources';
import MediaAiButton from './media-ai-button';
import MediaButtonMenu from './media-menu';

const isFeaturedImage = props =>
props.unstableFeaturedImageFlow ||
( props.modalClass && props.modalClass.indexOf( 'featured-image' ) !== -1 );
const isReplaceMenu = props => props.multiple === undefined && ! isFeaturedImage( props );

const blocksWithAiButtonSupport = [ 'core/image', 'core/gallery', 'jetpack/slideshow' ];

/**
* Temporary feature flag to control generalPurposeImageExclusiveMediaSources
* visibility.
*/
const GENERAL_PURPOSE_IMAGE_GENERATOR_BETA_FLAG = 'ai-general-purpose-image-generator';
const isGeneralPurposeImageGeneratorBetaEnabled =
window?.Jetpack_Editor_Initial_State?.available_blocks?.[
GENERAL_PURPOSE_IMAGE_GENERATOR_BETA_FLAG
]?.available === true;

function MediaButton( props ) {
const { name } = useBlockEditContext();
const { mediaProps } = props;
const [ selectedSource, setSelectedSource ] = useState( null );
const ExternalLibrary = getExternalLibrary( selectedSource );
const isFeatured = isFeaturedImage( mediaProps );
const hasAiButtonSupport = blocksWithAiButtonSupport.includes( name );

const closeLibrary = event => {
if ( event ) {
Expand All @@ -29,14 +46,20 @@ function MediaButton( props ) {
return (
// No added functionality, just capping event propagation.
// eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-static-element-interactions
<div onClick={ event => event.stopPropagation() }>
<div
onClick={ event => event.stopPropagation() }
className="jetpack-external-media-button-wrapper"
>
<MediaButtonMenu
{ ...props }
setSelectedSource={ setSelectedSource }
isReplace={ isReplaceMenu( mediaProps ) }
isFeatured={ isFeaturedImage( mediaProps ) }
isFeatured={ isFeatured }
hasImage={ mediaProps.value > 0 }
/>
{ isGeneralPurposeImageGeneratorBetaEnabled && ! isFeatured && hasAiButtonSupport && (
<MediaAiButton setSelectedSource={ setSelectedSource } />
) }

{ ExternalLibrary && <ExternalLibrary { ...mediaProps } onClose={ closeLibrary } /> }
</div>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { Button } from '@wordpress/components';
import { __ } from '@wordpress/i18n';
import { SOURCE_JETPACK_AI_GENERAL_PURPOSE_IMAGE_FOR_BLOCK } from '../constants';

function MediaAiButton( props ) {
const { setSelectedSource } = props;
return (
<Button
variant="tertiary"
className="jetpack-external-media-button-menu"
aria-haspopup="false"
onClick={ () => {
setSelectedSource( SOURCE_JETPACK_AI_GENERAL_PURPOSE_IMAGE_FOR_BLOCK );
} }
>
<div className="jetpack-external-media-button-menu__label">
{ __( 'Generate with AI', 'jetpack' ) }
</div>
</Button>
);
}

export default MediaAiButton;
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,13 @@ import {
SOURCE_PEXELS,
SOURCE_JETPACK_APP_MEDIA,
SOURCE_JETPACK_AI_FEATURED_IMAGE,
SOURCE_JETPACK_AI_GENERAL_PURPOSE_IMAGE,
SOURCE_JETPACK_AI_GENERAL_PURPOSE_IMAGE_FOR_MEDIA_SOURCE,
SOURCE_JETPACK_AI_GENERAL_PURPOSE_IMAGE_FOR_BLOCK,
} from '../constants';
import GooglePhotosMedia from './google-photos';
import JetpackAIFeaturedImage from './jetpack-ai-featured-image';
import JetpackAIGeneralPurposeImage from './jetpack-ai-general-purpose-image';
import JetpackAIGeneralPurposeImageForBlock from './jetpack-ai-general-purpose-image-for-block';
import JetpackAIGeneralPurposeImageForMediaSource from './jetpack-ai-general-purpose-image-for-media-source';
import JetpackAppMedia from './jetpack-app-media';
import OpenverseMedia from './openverse';
import PexelsMedia from './pexels';
Expand Down Expand Up @@ -43,7 +45,7 @@ export const featuredImageExclusiveMediaSources = [
*/
export const generalPurposeImageExclusiveMediaSources = [
{
id: SOURCE_JETPACK_AI_GENERAL_PURPOSE_IMAGE,
id: SOURCE_JETPACK_AI_GENERAL_PURPOSE_IMAGE_FOR_MEDIA_SOURCE,
label: __( 'Generate with AI', 'jetpack' ),
icon: aiAssistantIcon,
keyword: 'jetpack ai',
Expand Down Expand Up @@ -109,8 +111,10 @@ export function getExternalLibrary( type ) {
return JetpackAppMedia;
} else if ( type === SOURCE_JETPACK_AI_FEATURED_IMAGE ) {
return JetpackAIFeaturedImage;
} else if ( type === SOURCE_JETPACK_AI_GENERAL_PURPOSE_IMAGE ) {
return JetpackAIGeneralPurposeImage;
} else if ( type === SOURCE_JETPACK_AI_GENERAL_PURPOSE_IMAGE_FOR_MEDIA_SOURCE ) {
return JetpackAIGeneralPurposeImageForMediaSource;
} else if ( type === SOURCE_JETPACK_AI_GENERAL_PURPOSE_IMAGE_FOR_BLOCK ) {
return JetpackAIGeneralPurposeImageForBlock;
}
return null;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import {
GeneralPurposeImage,
PLACEMENT_BLOCK_PLACEHOLDER_BUTTON,
} from '../../../plugins/ai-assistant-plugin/components/ai-image';

function JetpackAIGeneralPurposeImageForBlock( {
onClose = () => {},
onSelect,
multiple = false,
} ) {
return (
<GeneralPurposeImage
placement={ PLACEMENT_BLOCK_PLACEHOLDER_BUTTON }
onClose={ onClose }
onSetImage={ image => onSelect( multiple ? [ image ] : image ) }
/>
);
}

export default JetpackAIGeneralPurposeImageForBlock;
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,18 @@ import {
PLACEMENT_MEDIA_SOURCE_DROPDOWN,
} from '../../../plugins/ai-assistant-plugin/components/ai-image';

function JetpackAIGeneralPurposeImage( { onClose = () => {}, onSelect } ) {
function JetpackAIGeneralPurposeImageForMediaSource( {
onClose = () => {},
onSelect,
multiple = false,
} ) {
return (
<GeneralPurposeImage
placement={ PLACEMENT_MEDIA_SOURCE_DROPDOWN }
onClose={ onClose }
onSetImage={ onSelect }
onSetImage={ image => onSelect( multiple ? [ image ] : image ) }
/>
);
}

export default JetpackAIGeneralPurposeImage;
export default JetpackAIGeneralPurposeImageForMediaSource;

0 comments on commit b556b8f

Please sign in to comment.