-
Notifications
You must be signed in to change notification settings - Fork 4.2k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
a3ec098
commit c7e5b7c
Showing
11 changed files
with
662 additions
and
10 deletions.
There are no files selected for viewing
135 changes: 135 additions & 0 deletions
135
packages/block-editor/src/components/inserter/media-tab/hooks.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,135 @@ | ||
/** | ||
* WordPress dependencies | ||
*/ | ||
import { __ } from '@wordpress/i18n'; | ||
import { useEffect, useState } from '@wordpress/element'; | ||
import { useSelect } from '@wordpress/data'; | ||
|
||
/** | ||
* Internal dependencies | ||
*/ | ||
import { store as blockEditorStore } from '../../../store'; | ||
|
||
// 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 = 10 } ) { | ||
const controller = new AbortController(); | ||
const url = new URL( 'https://api.openverse.engineering/v1/images/' ); | ||
// TODO: add licence filters etc.. | ||
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 useMediaResults( category, options = {} ) { | ||
const [ results, setResults ] = useState( [] ); | ||
const settings = useSelect( | ||
( select ) => select( blockEditorStore ).getSettings(), | ||
[] | ||
); | ||
const isOpenverse = category.name === 'openverse'; | ||
useEffect( () => { | ||
( async () => { | ||
// TODO: add loader probably and not set results.. | ||
setResults( [] ); | ||
try { | ||
if ( isOpenverse ) { | ||
const response = await fetchFromOpenverse( options ); | ||
setResults( response.results ); | ||
} else { | ||
const _media = await settings?.__unstableFetchMedia( { | ||
per_page: 7, | ||
media_type: category.mediaType, | ||
} ); | ||
if ( _media ) setResults( _media ); | ||
} | ||
} catch ( error ) { | ||
// TODO: handle this | ||
throw error; | ||
} | ||
} )(); | ||
}, [ category?.name, ...Object.values( options ) ] ); | ||
|
||
return results; | ||
} | ||
|
||
// TODO: Need to think of the props.. :) | ||
const MEDIA_CATEGORIES = [ | ||
{ label: __( 'Images' ), name: 'images', mediaType: 'image' }, | ||
{ label: __( 'Videos' ), name: 'videos', mediaType: 'video' }, | ||
{ label: __( 'Audio' ), name: 'audio', mediaType: 'audio' }, | ||
{ label: 'Openverse', name: 'openverse', mediaType: 'image' }, | ||
]; | ||
// TODO: definitely revisit the implementation and probably add a loader(return loading state).. | ||
export function useMediaCategories( rootClientId ) { | ||
const [ categories, setCategories ] = useState( [] ); | ||
const { canInsertImage, canInsertVideo, canInsertAudio, fetchMedia } = | ||
useSelect( | ||
( select ) => { | ||
const { canInsertBlockType, getSettings } = | ||
select( blockEditorStore ); | ||
return { | ||
fetchMedia: getSettings().__unstableFetchMedia, | ||
canInsertImage: canInsertBlockType( | ||
'core/image', | ||
rootClientId | ||
), | ||
canInsertVideo: canInsertBlockType( | ||
'core/video', | ||
rootClientId | ||
), | ||
canInsertAudio: canInsertBlockType( | ||
'core/audio', | ||
rootClientId | ||
), | ||
}; | ||
}, | ||
[ rootClientId ] | ||
); | ||
useEffect( () => { | ||
( async () => { | ||
const query = { | ||
context: 'view', | ||
per_page: 1, | ||
_fields: [ 'id' ], | ||
}; | ||
const [ showImage, showVideo, showAudio ] = await Promise.all( [ | ||
canInsertImage && | ||
!! ( | ||
await fetchMedia( { | ||
...query, | ||
media_type: 'image', | ||
} ) | ||
).length, | ||
canInsertVideo && | ||
!! ( | ||
await fetchMedia( { | ||
...query, | ||
media_type: 'video', | ||
} ) | ||
).length, | ||
canInsertAudio && | ||
!! ( | ||
await fetchMedia( { | ||
...query, | ||
media_type: 'audio', | ||
} ) | ||
).length, | ||
] ); | ||
setCategories( | ||
MEDIA_CATEGORIES.filter( | ||
( { mediaType } ) => | ||
( mediaType === 'image' && showImage ) || | ||
( mediaType === 'video' && showVideo ) || | ||
( mediaType === 'audio' && showAudio ) | ||
) | ||
); | ||
} )(); | ||
}, [ canInsertImage, canInsertVideo, canInsertAudio, fetchMedia ] ); | ||
return categories; | ||
} |
2 changes: 2 additions & 0 deletions
2
packages/block-editor/src/components/inserter/media-tab/index.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
export { default as MediaTab } from './media-tab'; | ||
export { MediaCategoryDialog } from './media-panel'; |
130 changes: 130 additions & 0 deletions
130
packages/block-editor/src/components/inserter/media-tab/media-list.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,130 @@ | ||
/** | ||
* WordPress dependencies | ||
*/ | ||
// import { useMemo, useCallback } from '@wordpress/element'; | ||
// import { useInstanceId } from '@wordpress/compose'; | ||
import { | ||
__unstableComposite as Composite, | ||
__unstableUseCompositeState as useCompositeState, | ||
__unstableCompositeItem as CompositeItem, | ||
} from '@wordpress/components'; | ||
import { createBlock } from '@wordpress/blocks'; | ||
import { __ } from '@wordpress/i18n'; | ||
|
||
/** | ||
* Internal dependencies | ||
*/ | ||
/** | ||
* Internal dependencies | ||
*/ | ||
import InserterDraggableBlocks from '../../inserter-draggable-blocks'; | ||
import BlockPreview from '../../block-preview'; | ||
|
||
function getBlocksPreview( media, mediaType, isOpenverse ) { | ||
let attributes; | ||
// TODO: check all the needed attributes(alt, caption, etc..) | ||
if ( mediaType === 'image' ) { | ||
attributes = isOpenverse | ||
? { url: media.thumbnail || media.url } | ||
: { | ||
id: media.id, | ||
url: media.source_url, | ||
}; | ||
} else if ( mediaType === 'video' || mediaType === 'audio' ) { | ||
attributes = { | ||
id: media.id, | ||
src: media.source_url, | ||
}; | ||
} | ||
|
||
const blocks = createBlock( `core/${ mediaType }`, attributes ); | ||
return blocks; | ||
} | ||
|
||
function MediaPreview( { media, onClick, composite, mediaType, isOpenverse } ) { | ||
// TODO: Check caption or attribution, etc.. | ||
// TODO: create different blocks per media type.. | ||
const blocks = getBlocksPreview( media, mediaType, isOpenverse ); | ||
// TODO: we have to set a max height for previews as the image can be very tall. | ||
// Probably a fixed-max height for all(?). | ||
const title = media.title?.rendered || media.title; | ||
const baseCssClass = 'block-editor-inserter__media-list'; | ||
// const descriptionId = useInstanceId( | ||
// MediaPreview, | ||
// `${ baseCssClass }__item-description` | ||
// ); | ||
return ( | ||
<InserterDraggableBlocks isEnabled={ true } blocks={ [ blocks ] }> | ||
{ ( { draggable, onDragStart, onDragEnd } ) => ( | ||
<div | ||
className={ `${ baseCssClass }__list-item` } | ||
draggable={ draggable } | ||
onDragStart={ onDragStart } | ||
onDragEnd={ onDragEnd } | ||
> | ||
<CompositeItem | ||
role="option" | ||
as="div" | ||
{ ...composite } | ||
className={ `${ baseCssClass }__item` } | ||
onClick={ () => { | ||
// TODO: We need to handle the case with focus to image's caption | ||
// during insertion. This makes the inserter to close. | ||
onClick( blocks ); | ||
} } | ||
aria-label={ title } | ||
// aria-describedby={} | ||
> | ||
<BlockPreview blocks={ blocks } viewportWidth={ 400 } /> | ||
<div className={ `${ baseCssClass }__item-title` }> | ||
{ title } | ||
</div> | ||
{ /* { !! description && ( | ||
<VisuallyHidden id={ descriptionId }> | ||
{ description } | ||
</VisuallyHidden> | ||
) } */ } | ||
</CompositeItem> | ||
</div> | ||
) } | ||
</InserterDraggableBlocks> | ||
); | ||
} | ||
|
||
function MediaList( { | ||
isOpenverse, | ||
results, | ||
mediaType, | ||
onClick, | ||
label = __( 'Media List' ), | ||
} ) { | ||
const composite = useCompositeState(); | ||
// const blocks = useMemo( () => { | ||
// // TODO: Check caption or attribution, etc.. | ||
// // TODO: create different blocks per media type.. | ||
// // const blockss = getBlocksPreview( media, mediaType, isOpenverse ); | ||
// // return blockss; | ||
// }, [] ); | ||
// const onClickItem = useCallback( () => {}, [] ); | ||
return ( | ||
<Composite | ||
{ ...composite } | ||
role="listbox" | ||
className="block-editor-inserter__media-list" | ||
aria-label={ label } | ||
> | ||
{ results.map( ( media ) => ( | ||
<MediaPreview | ||
key={ media.id } | ||
media={ media } | ||
mediaType={ mediaType } | ||
onClick={ onClick } | ||
composite={ composite } | ||
isOpenverse={ isOpenverse } | ||
/> | ||
) ) } | ||
</Composite> | ||
); | ||
} | ||
|
||
export default MediaList; |
63 changes: 63 additions & 0 deletions
63
packages/block-editor/src/components/inserter/media-tab/media-panel.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
/** | ||
* WordPress dependencies | ||
*/ | ||
import { useRef, useEffect } from '@wordpress/element'; | ||
import { __experimentalHeading as Heading } from '@wordpress/components'; | ||
import { focus } from '@wordpress/dom'; | ||
|
||
/** | ||
* Internal dependencies | ||
*/ | ||
import MediaList from './media-list'; | ||
import { useMediaResults } from './hooks'; | ||
|
||
export function MediaCategoryDialog( { rootClientId, onInsert, category } ) { | ||
const container = useRef(); | ||
useEffect( () => { | ||
const timeout = setTimeout( () => { | ||
const [ firstTabbable ] = focus.tabbable.find( container.current ); | ||
firstTabbable?.focus(); | ||
} ); | ||
return () => clearTimeout( timeout ); | ||
}, [ category ] ); | ||
|
||
const results = useMediaResults( category ); | ||
|
||
// const showOpenverse = category.name === 'openverse'; | ||
// TODO: should probably get the media here..(?) | ||
return ( | ||
<div ref={ container } className="block-editor-inserter__media-panel"> | ||
<MediaCategoryPanel | ||
rootClientId={ rootClientId } | ||
onInsert={ onInsert } | ||
category={ category } | ||
results={ results } | ||
/> | ||
</div> | ||
); | ||
} | ||
|
||
export function MediaCategoryPanel( { | ||
rootClientId, | ||
onInsert, | ||
category, | ||
results, | ||
} ) { | ||
// TODO: add loader probably.. | ||
return ( | ||
<div> | ||
<Heading level="4" weight="400"> | ||
{ category.label } | ||
</Heading> | ||
{ !! results.length && ( | ||
<MediaList | ||
rootClientId={ rootClientId } | ||
onClick={ onInsert } | ||
results={ results } | ||
mediaType={ category.mediaType } | ||
isOpenverse={ category.name === 'openverse' } | ||
/> | ||
) } | ||
</div> | ||
); | ||
} |
Oops, something went wrong.