280 lines
7.8 KiB
TypeScript
280 lines
7.8 KiB
TypeScript
import jetpackAnalytics from '@automattic/jetpack-analytics';
|
|
import apiFetch from '@wordpress/api-fetch';
|
|
import { dispatch, select } from '@wordpress/data';
|
|
import { __ } from '@wordpress/i18n';
|
|
import { addQueryArgs } from '@wordpress/url';
|
|
import { waitFor } from '../../wait-for';
|
|
import { store as mediaStore } from '../store';
|
|
import { MediaSource } from './types';
|
|
|
|
// Pexels constants
|
|
const PEXELS_ID = 'pexels';
|
|
const PEXELS_NAME = __( 'Pexels Free Photos', 'jetpack' );
|
|
const PEXELS_SEARCH_PLACEHOLDER = __( 'Search Pexels Free Photos', 'jetpack' );
|
|
const DEFAULT_PEXELS_SEARCH: MediaSearch = {
|
|
per_page: 10,
|
|
search: 'mountain',
|
|
};
|
|
|
|
// Google Photos constants
|
|
const GOOGLE_PHOTOS_ID = 'google_photos';
|
|
const GOOGLE_PHOTOS_NAME = __( 'Google Photos', 'jetpack' );
|
|
const GOOGLE_PHOTOS_SEARCH_PLACEHOLDER = __( 'Search Google Photos', 'jetpack' );
|
|
const DEFAULT_GOOGLE_PHOTOS_SEARCH: MediaSearch = {
|
|
per_page: 25,
|
|
search: '',
|
|
};
|
|
|
|
/**
|
|
* External media endpoints.
|
|
*/
|
|
// eslint-disable-next-line no-shadow
|
|
enum WpcomMediaEndpoints {
|
|
List = '/wpcom/v2/external-media/list/',
|
|
}
|
|
|
|
/**
|
|
* WPCOM media type of the WPCOM Media Api.
|
|
*/
|
|
// eslint-disable-next-line no-shadow
|
|
enum WpcomMediaItemType {
|
|
Image = 'image',
|
|
Video = 'video',
|
|
}
|
|
|
|
/**
|
|
* Gutenberg media category search DTO.
|
|
*/
|
|
type MediaSearch = {
|
|
per_page: number;
|
|
search: string;
|
|
};
|
|
|
|
/**
|
|
* Gutenberg media item DTO.
|
|
*/
|
|
type MediaItem = {
|
|
caption: string;
|
|
previewUrl: string;
|
|
title: string;
|
|
url: string;
|
|
};
|
|
|
|
/**
|
|
* WPCOM media list item DTO.
|
|
*/
|
|
type WpcomMediaItem = {
|
|
URL: string;
|
|
caption: string;
|
|
// Sometimes the title is null, so we need to handle that case.
|
|
title: string | null;
|
|
name: string | null;
|
|
file: string;
|
|
thumbnails: {
|
|
thumbnail: string;
|
|
};
|
|
type: WpcomMediaItemType;
|
|
};
|
|
|
|
/**
|
|
* WPCOM media list response DTO.
|
|
*/
|
|
type WpcomMediaResponse = {
|
|
found: number;
|
|
media: WpcomMediaItem[];
|
|
};
|
|
|
|
/**
|
|
* Get media URL for a given MediaSource.
|
|
*
|
|
* @param {MediaSource} source - MediaSource to get URL for.
|
|
* @param {MediaSearch} mediaSearch - MediaCategorySearch to filter for.
|
|
* @returns {string} Media URL.
|
|
*/
|
|
const getMediaApiUrl = ( source: MediaSource, mediaSearch: MediaSearch ) =>
|
|
addQueryArgs( `${ WpcomMediaEndpoints.List }${ source }`, {
|
|
...( mediaSearch.search && { search: mediaSearch.search } ),
|
|
number: mediaSearch.per_page || 25,
|
|
path: 'recent',
|
|
} );
|
|
|
|
/**
|
|
* Maps a WPCOM media item to a Gutenberg media item.
|
|
*
|
|
* @param {WpcomMediaItem} item - WPCOM media list item to map.
|
|
* @returns {MediaItem} Mapped media category item.
|
|
*/
|
|
const mapWpcomMediaToMedia = ( item: WpcomMediaItem ): MediaItem => ( {
|
|
caption: item?.caption ?? '',
|
|
previewUrl: item.thumbnails.thumbnail,
|
|
title: item?.title ?? item?.name ?? item.file,
|
|
url: item.URL,
|
|
} );
|
|
|
|
/**
|
|
* Builds a Gutenberg media category object.
|
|
*
|
|
* @param {string} name - Name of the media category.
|
|
* @param {string} label - Label of the media category.
|
|
* @param {string} searchPlaceholder - Search placeholder of the media category.
|
|
* @param {MediaSource} source - MediaSource of the media category.
|
|
* @param {MediaSearch} defaultSearch - Default search of the media category.
|
|
* @returns {object} Media category object.
|
|
*/
|
|
const buildMediaCategory = (
|
|
name: string,
|
|
label: string,
|
|
searchPlaceholder: string,
|
|
source: MediaSource,
|
|
defaultSearch: MediaSearch
|
|
) => ( {
|
|
name: name,
|
|
labels: {
|
|
name: label,
|
|
search_items: searchPlaceholder,
|
|
},
|
|
mediaType: 'image',
|
|
fetch: async ( mediaCategorySearch: MediaSearch ) =>
|
|
await apiFetch( {
|
|
path: getMediaApiUrl( source, {
|
|
per_page: defaultSearch.per_page,
|
|
search:
|
|
mediaCategorySearch?.search === '' ? defaultSearch.search : mediaCategorySearch.search,
|
|
} ),
|
|
method: 'GET',
|
|
} )
|
|
.then( ( response: WpcomMediaResponse ) => {
|
|
const mediaItems = response.media
|
|
.filter( wpcomMediaItem => wpcomMediaItem.type === WpcomMediaItemType.Image )
|
|
.map( mapWpcomMediaToMedia );
|
|
jetpackAnalytics.tracks.recordEvent( 'jetpack_editor_media_inserter_external_source', {
|
|
mediaSource: source.toString(),
|
|
results: mediaItems.length,
|
|
search:
|
|
mediaCategorySearch?.search === '' ? defaultSearch.search : mediaCategorySearch.search,
|
|
} );
|
|
|
|
return mediaItems;
|
|
} )
|
|
// Null object pattern, we don't want to break if the API fails.
|
|
.catch( () => [] ),
|
|
getReportUrl: null,
|
|
isExternalResource: true,
|
|
} );
|
|
|
|
/**
|
|
* Get Google Photos media category.
|
|
*
|
|
* @returns {object} Google Photos media category.
|
|
*/
|
|
const googlePhotosProvider = () =>
|
|
buildMediaCategory(
|
|
GOOGLE_PHOTOS_ID,
|
|
GOOGLE_PHOTOS_NAME,
|
|
GOOGLE_PHOTOS_SEARCH_PLACEHOLDER,
|
|
MediaSource.GooglePhotos,
|
|
DEFAULT_GOOGLE_PHOTOS_SEARCH
|
|
);
|
|
|
|
/**
|
|
* Checks if a given MediaSource is connected and calls the callback with the response.
|
|
*
|
|
* @param {MediaSource} source - MediaSource to check.
|
|
* @returns {void}
|
|
*/
|
|
const isMediaSourceConnected = async ( source: MediaSource ) =>
|
|
apiFetch< boolean | WpcomMediaResponse >( {
|
|
path: getMediaApiUrl( source, DEFAULT_PEXELS_SEARCH ),
|
|
method: 'GET',
|
|
} )
|
|
.then( response => response )
|
|
.catch( () => false );
|
|
|
|
/**
|
|
* Checks if the inserter is opened.
|
|
*
|
|
* @returns {boolean} True if the inserter is opened false otherwise.
|
|
*/
|
|
const isInserterOpened = (): boolean => {
|
|
// Prior to WP 6.5, the isInserterOpened selector was available in core/edit-post.
|
|
// In WP 6.5, it was moved to core/editor. This check is to support both versions of WordPress.
|
|
// @to-do: remove exception when Jetpack requires WordPress 6.5.
|
|
const selectIsInserterOpened =
|
|
/* eslint-disable-next-line @typescript-eslint/no-explicit-any */
|
|
( select( 'core/editor' ) as any )?.isInserterOpened ??
|
|
select( 'core/edit-post' )?.isInserterOpened;
|
|
|
|
const editorIsInserterOpened = selectIsInserterOpened?.();
|
|
|
|
return (
|
|
editorIsInserterOpened ||
|
|
select( 'core/edit-site' )?.isInserterOpened() ||
|
|
select( 'core/edit-widgets' )?.isInserterOpened()
|
|
);
|
|
};
|
|
|
|
const registerInInserter = ( mediaCategoryProvider: () => object ) =>
|
|
// Remove as soon @types/wordpress__block-editor is up to date
|
|
// eslint-disable-next-line
|
|
// @ts-ignore
|
|
dispatch( 'core/block-editor' )?.registerInserterMediaCategory?.( mediaCategoryProvider() );
|
|
|
|
/**
|
|
* Get Pexels media category.
|
|
*
|
|
* @returns {object} Pexels media category.
|
|
*/
|
|
const pexelsProvider = () =>
|
|
buildMediaCategory(
|
|
PEXELS_ID,
|
|
PEXELS_NAME,
|
|
PEXELS_SEARCH_PLACEHOLDER,
|
|
MediaSource.Pexels,
|
|
DEFAULT_PEXELS_SEARCH
|
|
);
|
|
|
|
/**
|
|
* Checks if a given MediaSource is authenticated in the store.
|
|
*
|
|
* @param {MediaSource} source - MediaSource to check.
|
|
* @returns {boolean} True if the MediaSource is authenticated false otherwise.
|
|
*/
|
|
const isAuthenticatedByWithMediaComponent = ( source: MediaSource ) =>
|
|
!! select( mediaStore ).isAuthenticated( source );
|
|
|
|
/**
|
|
* Adds Google Photos to the media inserter if/when it's connected.
|
|
* We will not remove Google Photos from the inserter if the user disconnects Google Photos during runtime.
|
|
*/
|
|
export const addGooglePhotosToMediaInserter = async () => {
|
|
waitFor( isInserterOpened ).then( async () => {
|
|
const isConnected = await isMediaSourceConnected( MediaSource.GooglePhotos );
|
|
|
|
if ( isConnected ) {
|
|
registerInInserter( googlePhotosProvider );
|
|
return;
|
|
}
|
|
|
|
waitFor( () => isAuthenticatedByWithMediaComponent( MediaSource.GooglePhotos ) ).then( () =>
|
|
registerInInserter( googlePhotosProvider )
|
|
);
|
|
} );
|
|
};
|
|
|
|
/**
|
|
* Adds Pexels to the media inserter. There is no need to check if it's connected because it's always connected.
|
|
*/
|
|
export const addPexelsToMediaInserter = () => {
|
|
waitFor( isInserterOpened ).then( () => registerInInserter( pexelsProvider ) );
|
|
};
|
|
|
|
/**
|
|
* Authenticates a given MediaSource.
|
|
*
|
|
* @param {MediaSource} source - MediaSource to authenticate.
|
|
* @param {boolean} isAuthenticated - True if the MediaSource is authenticated false otherwise.
|
|
*/
|
|
export const authenticateMediaSource = ( source: MediaSource, isAuthenticated: boolean ) => {
|
|
dispatch( mediaStore ).setAuthenticated( source, isAuthenticated );
|
|
};
|