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

[Experiment]: Try Openverse integration #44424

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
/**
* External dependencies
*/
import classnames from 'classnames';

/**
* WordPress dependencies
*/
import { useState } from '@wordpress/element';
import { __, _x } from '@wordpress/i18n';
import { Flex, FlexItem, Button } from '@wordpress/components';

/**
* Internal dependencies
*/
import ImageExplorerModal from './explorer-modal';

export default function ImageExplorerButton() {
const [ isModalOpen, setIsModalOpen ] = useState( false );
const baseCssClass = 'block-editor-inserter__panel-header-images';
const className = classnames(
'block-editor-inserter__panel-header',
baseCssClass
);
return (
<>
<Flex
justify="space-between"
align="center"
gap="4"
className={ className }
>
<FlexItem isBlock className={ `${ baseCssClass }__text` }>
{ __( 'Search external images' ) }
</FlexItem>
<FlexItem>
<Button
variant="secondary"
onClick={ () => setIsModalOpen( true ) }
label={ __( 'Explore all external images' ) }
>
{ _x(
'Explore',
'Label for showing external images list.'
) }
</Button>
</FlexItem>
</Flex>
{ isModalOpen && (
<ImageExplorerModal
onModalClose={ () => setIsModalOpen( false ) }
/>
) }
</>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
/**
* WordPress dependencies
*/
import {
Modal,
SearchControl,
Flex,
FlexItem,
Button,
} from '@wordpress/components';
import { useState } from '@wordpress/element';
import { __ } from '@wordpress/i18n';
import { createBlock } from '@wordpress/blocks';

/**
* Internal dependencies
*/
import InserterListbox from '../../inserter-listbox';
import ImageResults from './image-results';
import useInsertionPoint from '../hooks/use-insertion-point';

const baseClassName = 'block-editor-image-explorer';

function ExplorerSidebar( { filterValue, setFilterValue } ) {
return (
<div className={ `${ baseClassName }__sidebar` }>
<div className={ `${ baseClassName }__search` }>
<SearchControl
onChange={ setFilterValue }
value={ filterValue }
label={ __( 'Search for images' ) }
placeholder={ __( 'Search' ) }
/>
</div>
{ /* // TODO: add filters from Openverse */ }
</div>
);
}

function ExplorerContent( { filterValue, onModalClose } ) {
const [ selectedImages, setSelectedImages ] = useState( [] );
const selectImage = ( image ) => {
setSelectedImages( [ ...selectedImages, image ] );
};
return (
<div className={ `${ baseClassName }__content` }>
<ExplorerSelectedImages
selectedImages={ selectedImages }
setSelectedImages={ setSelectedImages }
onInsert={ onModalClose }
/>
<InserterListbox>
<ImageResults
search={ filterValue }
pageSize={ 9 }
onClick={ selectImage }
/>
</InserterListbox>
</div>
);
}

function ExplorerSelectedImages( {
selectedImages,
setSelectedImages,
onInsert,
} ) {
const [ , onInsertBlocks ] = useInsertionPoint( {
shouldFocusBlock: true,
} );
const onInsertImages = () => {
onInsertBlocks(
selectedImages.map( ( image ) => {
return createBlock( 'core/image', {
url: image.url,
alt: image.title,
} );
} )
);
onInsert();
};
const onInsertGallery = () => {
onInsertBlocks(
createBlock(
'core/gallery',
{},
selectedImages.map( ( image ) =>
createBlock( 'core/image', {
url: image.url,
alt: image.title,
} )
)
)
);
onInsert();
};
if ( ! selectedImages.length ) {
return (
<div className={ `${ baseClassName }__selected-images` }>
{ __( 'No images are selected' ) }
</div>
);
}
return (
<div className={ `${ baseClassName }__selected-images` }>
<h3>{ `${ selectedImages.length } selected` }</h3>
<Flex justify="flex-start" align="center" gap="4">
{ selectedImages.map( ( image ) => (
<FlexItem key={ image.id }>
<img src={ image.url } alt={ image.title } />
</FlexItem>
) ) }
</Flex>
<Flex
justify="flex-end"
align="center"
gap="4"
className={ `${ baseClassName }__selected-images__actions` }
>
<FlexItem>
<Button
variant="primary"
onClick={ () => onInsertImages() }
label={ __( 'Insert Images' ) }
>
{ __( 'Insert Images' ) }
</Button>
</FlexItem>
<FlexItem>
<Button
variant="secondary"
onClick={ () => onInsertGallery() }
label={ __( 'Create Gallery' ) }
>
{ __( 'Create Gallery' ) }
</Button>
</FlexItem>
<FlexItem>
<Button
onClick={ () => setSelectedImages( [] ) }
label={ __( 'Remove Selection' ) }
>
{ __( 'Remove Selection' ) }
</Button>
</FlexItem>
</Flex>
</div>
);
}

function ImageExplorer( { initialValue, onModalClose } ) {
const [ filterValue, setFilterValue ] = useState( initialValue );
return (
<div className="block-editor-image-explorer">
<ExplorerSidebar
filterValue={ filterValue }
setFilterValue={ setFilterValue }
/>
<ExplorerContent
filterValue={ filterValue }
onModalClose={ onModalClose }
/>
</div>
);
}

function ImageExplorerModal( { onModalClose, ...restProps } ) {
return (
<Modal
title={ __( 'External Images' ) }
closeLabel={ __( 'Close' ) }
onRequestClose={ onModalClose }
isFullScreen
>
<ImageExplorer { ...restProps } onModalClose={ onModalClose } />
</Modal>
);
}

export default ImageExplorerModal;
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/**
* WordPress dependencies
*/
import { useEffect, useState } from '@wordpress/element';

// TODO: Even though the `mature` param is `false` by default, we might need to determine where
// and if there is other 'weird' content like in the title. It happened for me to test with `skate`
// and the first result contains a word in the title that might not be suitable for all users.
async function fetchFromOpenverse( { search, pageSize } ) {
const controller = new AbortController();
const url = new URL( 'https://api.openverse.engineering/v1/images/' );
url.searchParams.set( 'q', search );
url.searchParams.set( 'page_size', pageSize );
const response = await window.fetch( url, {
headers: [ [ 'Content-Type', 'application/json' ] ],
signal: controller.signal,
} );
return response.json();
}

export function useImageResults( options ) {
const [ results, setResults ] = useState( [] );

useEffect( () => {
( async () => {
try {
const response = await fetchFromOpenverse( options );
setResults( response.results );
} catch ( error ) {
// TODO: handle this
throw error;
}
} )();
}, [ ...Object.values( options ) ] );

return results;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
/**
* WordPress dependencies
*/
import {
__unstableComposite as Composite,
__unstableUseCompositeState as useCompositeState,
__unstableCompositeItem as CompositeItem,
} from '@wordpress/components';
import { createBlock } from '@wordpress/blocks';
import { __ } from '@wordpress/i18n';

/**
* Internal dependencies
*/
import InserterDraggableBlocks from '../../inserter-draggable-blocks';
import BlockPreview from '../../block-preview';

function ImagePreview( { image, onClick, composite } ) {
// TODO: Check caption or attribution, etc..
const blocks = createBlock( 'core/image', {
url: image.thumbnail || image.url,
} );
// TODO: we have to set a max height for previews as the image can be very tall.
// Probably a fixed-max height for all(?).
return (
<InserterDraggableBlocks isEnabled={ true } blocks={ [ blocks ] }>
{ ( { draggable, onDragStart, onDragEnd } ) => (
<div
className="block-editor-inserter-external-images-list__list-item"
aria-label={ image.title }
// aria-describedby={}
draggable={ draggable }
onDragStart={ onDragStart }
onDragEnd={ onDragEnd }
>
<CompositeItem
role="option"
as="div"
{ ...composite }
className="block-editor-inserter-external-images-list__item"
onClick={ () => {
// TODO: We need to handle the case with focus to image's caption
// during insertion. This makes the inserter to close.
onClick( image );
} }
>
<BlockPreview blocks={ blocks } viewportWidth={ 400 } />
</CompositeItem>
</div>
) }
</InserterDraggableBlocks>
);
}

function ExternalImagesList( {
results,
onClick,
orientation,
label = __( 'External Images List' ),
} ) {
const composite = useCompositeState( { orientation } );
return (
<Composite
{ ...composite }
role="listbox"
className="block-editor-image-explorer__list"
aria-label={ label }
>
{ results.map( ( image ) => (
<ImagePreview
key={ image.id }
image={ image }
onClick={ onClick }
composite={ composite }
/>
) ) }
</Composite>
);
}

export default ExternalImagesList;
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/**
* Internal dependencies
*/
import ExternalImagesList from './image-list';
import { useImageResults } from './hooks';

export default function ImageResults( {
search,
pageSize = 10,
rootClientId,
onClick,
} ) {
const results = useImageResults( { search, pageSize } );
if ( ! results?.length ) {
return null;
}
return (
<ExternalImagesList
results={ results }
rootClientId={ rootClientId }
onClick={ onClick }
/>
);
}
Loading