diff --git a/package-lock.json b/package-lock.json index eb77dcb..2e7a802 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7711,8 +7711,7 @@ "js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "dev": true + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" }, "js-yaml": { "version": "3.13.1", @@ -8070,7 +8069,6 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", - "dev": true, "requires": { "js-tokens": "^3.0.0 || ^4.0.0" } @@ -8997,8 +8995,7 @@ "object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", - "dev": true + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" }, "object-copy": { "version": "0.1.0", @@ -9757,7 +9754,6 @@ "version": "15.7.2", "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.7.2.tgz", "integrity": "sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==", - "dev": true, "requires": { "loose-envify": "^1.4.0", "object-assign": "^4.1.1", @@ -10035,8 +10031,16 @@ "react-is": { "version": "16.13.0", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.0.tgz", - "integrity": "sha512-GFMtL0vHkiBv9HluwNZTggSn/sCyEt9n02aM0dSAjGGyqyNlAyftYm4phPxdvCigG15JreC5biwxCgTAJZ7yAA==", - "dev": true + "integrity": "sha512-GFMtL0vHkiBv9HluwNZTggSn/sCyEt9n02aM0dSAjGGyqyNlAyftYm4phPxdvCigG15JreC5biwxCgTAJZ7yAA==" + }, + "react-photo-gallery": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/react-photo-gallery/-/react-photo-gallery-8.0.0.tgz", + "integrity": "sha512-Y9458yygEB9cIZAWlBWuenlR+ghin1RopmmU3Vice8BeJl0Se7hzfxGDq8W1armB/ic/kphGg+G1jq5fOEd0sw==", + "requires": { + "prop-types": "~15.7.2", + "resize-observer-polyfill": "^1.5.0" + } }, "react-test-renderer": { "version": "16.13.0", @@ -10506,6 +10510,11 @@ "integrity": "sha512-L9jEkOi3ASd9PYit2cwRfyppc9NoABujTP8/5gFcbERmo5jUoAKovIC3fsF17pkTnGsrByysqX+Kxd2OTNI1ww==", "dev": true }, + "resize-observer-polyfill": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz", + "integrity": "sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg==" + }, "resolve": { "version": "1.15.1", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.15.1.tgz", diff --git a/package.json b/package.json index 8212526..d2b8ba7 100644 --- a/package.json +++ b/package.json @@ -15,5 +15,8 @@ }, "devDependencies": { "@wordpress/scripts": "^7.1.2" + }, + "dependencies": { + "react-photo-gallery": "^8.0.0" } } diff --git a/src/components/SearchGiphy.js b/src/components/SearchGiphy.js new file mode 100644 index 0000000..cb1ce0e --- /dev/null +++ b/src/components/SearchGiphy.js @@ -0,0 +1,59 @@ +import { Component, Fragment } from '@wordpress/element'; +import { TextControl, Spinner } from '@wordpress/components'; +import Gallery from "react-photo-gallery"; + +export default class SearchGiphy extends Component { + constructor(props) { + super(props); + } + + render() { + const { + search, + onSearchChangeHandler, + isLoading, + gifs, + onGiphyClick, + pagination + } = this.props; + + let result_gifs; + if ( gifs[ pagination ] ) { + result_gifs = []; + result_gifs = gifs[ pagination ].map( ( gif_data ) => { + return { + src: gif_data.images.downsized_medium.url, + width: gif_data.images.downsized_medium.width, + height: gif_data.images.downsized_medium.height, + }; + } ) + } + + return ( + + { + onSearchChangeHandler( search ); + } } + /> + + { isLoading && ( + + ) } + + { result_gifs && result_gifs.length && ( + + + + ) } + + { result_gifs && result_gifs.length === 0 && ( +

{ `Nothing found for '${search}'.` }

+ ) } + +
+ ); + } +} diff --git a/src/edit.js b/src/edit.js index ce26040..170f8b3 100644 --- a/src/edit.js +++ b/src/edit.js @@ -1,21 +1,146 @@ import { Component } from '@wordpress/element'; -import { TextControl } from '@wordpress/components'; +import { AlignmentToolbar, BlockControls, BlockAlignmentToolbar } from '@wordpress/editor'; +import { addQueryArgs } from '@wordpress/url'; +import { debounce } from 'lodash'; + +import SearchGiphy from "./components/SearchGiphy"; export default class Edit extends Component { + constructor( props ) { + super( props ); + + this.state = { + isLoading: false, // If currently fetching from Giphy. + isSearching: !props.attributes.gif, // If we need to show the search field. + gifs: [], // Cache results from Giphy. + pagination: 0, // Current pagination. + }; + + // Get the API Key from https://developers.giphy.com/. + this.API_KEY = '[INSERT YOUR API KEY HERE]'; + this.GIPHY_ENDPOINT = 'https://api.giphy.com/v1/gifs/search'; + this.GIPHY_RESULTS_LIMIT = 5; + + this.onSearchChangeHandler = this.onSearchChangeHandler.bind( this ); + this.fetchGiphy = this.fetchGiphy.bind( this ); + // Use debounce to prevent multiple concurrent request to Giphy. + this.onSearchChange = debounce( this.onSearchChange.bind( this ), 500 ); + this.onGiphyClick = this.onGiphyClick.bind( this ); + } + + componentWillUnmount() { + this.onSearchChange.cancel(); + } + + /** + * Debounced function + * + * @returns {Promise} + */ + async onSearchChange() { + const { + attributes: { search } + } = this.props; + + const pagination = this.state.pagination / this.GIPHY_RESULTS_LIMIT; + + const results = await this.fetchGiphy( search, pagination ); + + if ( 200 !== results.meta.status ) { + // TODO handle error. + return; + } + + // The idea is to 'cache' the past results in the state. + // Something like + // gifs[0] => Will contain results of pagination 0. + // gifs[1] => Results of pagination 1. + // and so on.. + let gifs = this.state.gifs; + gifs[ pagination ] = results.data; + + this.setState( { + isLoading: false, + pagination: pagination, + gifs: gifs + } ); + } + + /** + * Fetch data from Giphy. + * + * @param search string + * @param pagination int + * + * @returns {Promise} + */ + fetchGiphy( search, pagination ) { + // Build the request url. + const requestUrl = addQueryArgs( this.GIPHY_ENDPOINT, { + q: search, + limit: this.GIPHY_RESULTS_LIMIT, + api_key: this.API_KEY, + offset: pagination, + } ); + + return fetch( requestUrl ) + .then( data => data.json() ) + .catch( error => error ); + }; + + onGiphyClick( event, { photo } ) { + // Save the selected photo data as `gif` in the DB. + this.props.setAttributes( { gif: photo } ); + this.setState( { isSearching: false } ); + } + + onSearchChangeHandler( search ) { + this.setState( { isLoading: true } ); + // Save the search keyword as `search` in the DB. + this.props.setAttributes( { search } ); + this.onSearchChange(); + } + render() { const { attributes: { - search + search, + gif, + blockAlignment, + textAlignment }, - setAttributes, + className, + setAttributes } = this.props; + const { isLoading, isSearching, gifs, pagination } = this.state; + return ( - setAttributes( { search } )} - /> +
+ + setAttributes( { blockAlignment } ) } + /> + setAttributes( { textAlignment } ) } + /> + + + { isSearching ? ( + + ) : ( + + ) } +
); } } diff --git a/src/index.js b/src/index.js index 3c3c30d..b82aa60 100644 --- a/src/index.js +++ b/src/index.js @@ -48,8 +48,23 @@ registerBlockType( 'create-block/giphy-block', { icon: 'smiley', attributes: { + textAlignment: { + type: 'string', + }, + blockAlignment: { + type: 'string', + default: 'wide', + }, search: { type: 'string' + }, + gif: { + type: 'object' + } + }, + getEditWrapperProps( { blockAlignment } ) { + if ( 'left' === blockAlignment || 'right' === blockAlignment || 'full' === blockAlignment ) { + return { 'data-align': blockAlignment }; } }, @@ -81,14 +96,9 @@ registerBlockType( 'create-block/giphy-block', { * * @return {WPElement} Element to render. */ - save() { + save: props => { return ( -

- { __( - 'Giphy Block – hello from the saved content!', - 'create-block' - ) } -

+ ); }, } );