Onboarding: Add theme uploader component (https://github.com/woocommerce/woocommerce-admin/pull/2580)
* Add theme uploader component * Add theme to theme browser on upload complete * Add a minimum height to dropzone area * Use Muriel style Spinner during upload * Add file field uploader to dropzone for accessibility * Delete transient on theme install or theme switch
This commit is contained in:
parent
91a1f4a889
commit
19870c18c0
|
@ -22,6 +22,7 @@ import { Card, H } from '@woocommerce/components';
|
|||
import withSelect from 'wc-api/with-select';
|
||||
import './style.scss';
|
||||
import { recordEvent } from 'lib/tracks';
|
||||
import ThemeUploader from './uploader';
|
||||
|
||||
class Theme extends Component {
|
||||
constructor() {
|
||||
|
@ -29,8 +30,10 @@ class Theme extends Component {
|
|||
|
||||
this.state = {
|
||||
activeTab: 'all',
|
||||
uploadedThemes: [],
|
||||
};
|
||||
|
||||
this.handleUploadComplete = this.handleUploadComplete.bind( this );
|
||||
this.onChoose = this.onChoose.bind( this );
|
||||
this.onSelectTab = this.onSelectTab.bind( this );
|
||||
this.openDemo = this.openDemo.bind( this );
|
||||
|
@ -128,17 +131,27 @@ class Theme extends Component {
|
|||
}
|
||||
|
||||
getThemes() {
|
||||
const { activeTab, uploadedThemes } = this.state;
|
||||
const { themes } = wcSettings.onboarding;
|
||||
const { activeTab } = this.state;
|
||||
themes.concat( uploadedThemes );
|
||||
const allThemes = [ ...themes, ...uploadedThemes ];
|
||||
|
||||
switch ( activeTab ) {
|
||||
case 'paid':
|
||||
return themes.filter( theme => this.getPriceValue( theme.price ) > 0 );
|
||||
return allThemes.filter( theme => this.getPriceValue( theme.price ) > 0 );
|
||||
case 'free':
|
||||
return themes.filter( theme => this.getPriceValue( theme.price ) <= 0 );
|
||||
return allThemes.filter( theme => this.getPriceValue( theme.price ) <= 0 );
|
||||
case 'all':
|
||||
default:
|
||||
return themes;
|
||||
return allThemes;
|
||||
}
|
||||
}
|
||||
|
||||
handleUploadComplete( upload ) {
|
||||
if ( 'success' === upload.status && upload.theme_data ) {
|
||||
this.setState( {
|
||||
uploadedThemes: [ ...this.state.uploadedThemes, upload.theme_data ],
|
||||
} );
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -175,6 +188,7 @@ class Theme extends Component {
|
|||
{ () => (
|
||||
<div className="woocommerce-profile-wizard__themes">
|
||||
{ themes && themes.map( theme => this.renderTheme( theme ) ) }
|
||||
<ThemeUploader onUploadComplete={ this.handleUploadComplete } />
|
||||
</div>
|
||||
) }
|
||||
</TabPanel>
|
||||
|
|
|
@ -116,3 +116,64 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
.woocommerce-profile-wizard__body .woocommerce-theme-uploader.woocommerce-card {
|
||||
margin: 0;
|
||||
position: relative;
|
||||
|
||||
.woocommerce-card__body {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
&.is-uploading .components-drop-zone__provider {
|
||||
min-height: 382px;
|
||||
}
|
||||
|
||||
.components-drop-zone__provider {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 2px;
|
||||
background: #f6f6f6;
|
||||
border: 1px dashed #b0b5b8;
|
||||
}
|
||||
|
||||
.components-form-file-upload {
|
||||
flex: 1;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.components-form-file-upload > .components-button {
|
||||
flex: 1;
|
||||
flex-direction: column;
|
||||
margin: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
min-height: 380px;
|
||||
|
||||
> .gridicon {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
|
||||
path {
|
||||
fill: #50575d;
|
||||
}
|
||||
}
|
||||
|
||||
.dashicons-upload {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.woocommerce-theme-uploader__title {
|
||||
margin: $gap-smaller 0;
|
||||
@include font-size(24);
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
p {
|
||||
@include font-size(14);
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,124 @@
|
|||
/** @format */
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import apiFetch from '@wordpress/api-fetch';
|
||||
import classnames from 'classnames';
|
||||
import { Component, Fragment } from '@wordpress/element';
|
||||
import { compose } from '@wordpress/compose';
|
||||
import { DropZoneProvider, DropZone, FormFileUpload } from '@wordpress/components';
|
||||
import Gridicon from 'gridicons';
|
||||
import { noop } from 'lodash';
|
||||
import PropTypes from 'prop-types';
|
||||
import { withDispatch } from '@wordpress/data';
|
||||
|
||||
/**
|
||||
* WooCommerce dependencies
|
||||
*/
|
||||
import { Card, H, Spinner } from '@woocommerce/components';
|
||||
|
||||
class ThemeUploader extends Component {
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.state = {
|
||||
isUploading: false,
|
||||
};
|
||||
|
||||
this.handleFilesUpload = this.handleFilesUpload.bind( this );
|
||||
this.handleFilesDrop = this.handleFilesDrop.bind( this );
|
||||
}
|
||||
|
||||
handleFilesDrop( files ) {
|
||||
const file = files[ 0 ];
|
||||
this.uploadTheme( file );
|
||||
}
|
||||
|
||||
handleFilesUpload( e ) {
|
||||
const file = e.target.files[ 0 ];
|
||||
this.uploadTheme( file );
|
||||
}
|
||||
|
||||
uploadTheme( file ) {
|
||||
const { addNotice, onUploadComplete } = this.props;
|
||||
this.setState( { isUploading: true } );
|
||||
|
||||
const body = new FormData();
|
||||
body.append( 'pluginzip', file );
|
||||
|
||||
return apiFetch( { path: '/wc-admin/v1/themes', method: 'POST', body } )
|
||||
.then( response => {
|
||||
onUploadComplete( response );
|
||||
this.setState( { isUploading: false } );
|
||||
addNotice( { status: response.status, message: response.message } );
|
||||
} )
|
||||
.catch( error => {
|
||||
this.setState( { isUploading: false } );
|
||||
if ( error && error.message ) {
|
||||
addNotice( { status: 'error', message: error.message } );
|
||||
}
|
||||
} );
|
||||
}
|
||||
|
||||
render() {
|
||||
const { className } = this.props;
|
||||
const { isUploading } = this.state;
|
||||
|
||||
const classes = classnames( 'woocommerce-theme-uploader', className, {
|
||||
'is-uploading': isUploading,
|
||||
} );
|
||||
|
||||
return (
|
||||
<Card className={ classes }>
|
||||
<DropZoneProvider>
|
||||
{ ! isUploading ? (
|
||||
<Fragment>
|
||||
<FormFileUpload accept=".zip" onChange={ this.handleFilesUpload }>
|
||||
<Gridicon icon="cloud-upload" />
|
||||
<H className="woocommerce-theme-uploader__title">
|
||||
{ __( 'Upload a theme', 'woocommerce-admin' ) }
|
||||
</H>
|
||||
<p>{ __( 'Drop a theme zip file here to upload', 'woocommerce-admin' ) }</p>
|
||||
</FormFileUpload>
|
||||
<DropZone
|
||||
label={ __( 'Drop your theme zip file here', 'woocommerce-admin' ) }
|
||||
onFilesDrop={ this.handleFilesDrop }
|
||||
/>
|
||||
</Fragment>
|
||||
) : (
|
||||
<Fragment>
|
||||
<Spinner />
|
||||
<H className="woocommerce-theme-uploader__title">
|
||||
{ __( 'Uploading theme', 'woocommerce-admin' ) }
|
||||
</H>
|
||||
<p>{ __( 'Your theme is being uploaded', 'woocommerce-admin' ) }</p>
|
||||
</Fragment>
|
||||
) }
|
||||
</DropZoneProvider>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
ThemeUploader.propTypes = {
|
||||
/**
|
||||
* Additional class name to style the component.
|
||||
*/
|
||||
className: PropTypes.string,
|
||||
/**
|
||||
* Function called when an upload has finished.
|
||||
*/
|
||||
onUploadComplete: PropTypes.func,
|
||||
};
|
||||
|
||||
ThemeUploader.defaultProps = {
|
||||
onUploadComplete: noop,
|
||||
};
|
||||
|
||||
export default compose(
|
||||
withDispatch( dispatch => {
|
||||
const { addNotice } = dispatch( 'wc-admin' );
|
||||
return { addNotice };
|
||||
} )
|
||||
)( ThemeUploader );
|
|
@ -89,11 +89,19 @@ class WC_Admin_REST_Themes_Controller extends WC_REST_Data_Controller {
|
|||
}
|
||||
|
||||
if ( ! is_wp_error( $install ) && isset( $install['destination_name'] ) ) {
|
||||
$theme = $install['destination_name'];
|
||||
$result = array(
|
||||
'status' => 'success',
|
||||
'message' => $upgrader->strings['process_success'],
|
||||
'theme' => $install['destination_name'],
|
||||
'theme' => $theme,
|
||||
);
|
||||
|
||||
/**
|
||||
* Fires when a theme is successfully installed.
|
||||
*
|
||||
* @param string $theme The theme name.
|
||||
*/
|
||||
do_action( 'woocommerce_theme_installed', $theme );
|
||||
} else {
|
||||
if ( is_wp_error( $install ) && $install->get_error_code() ) {
|
||||
$error_message = isset( $upgrader->strings[ $install->get_error_code() ] ) ? $upgrader->strings[ $install->get_error_code() ] : $install->get_error_data();
|
||||
|
|
|
@ -17,6 +17,20 @@ class WC_Admin_Onboarding {
|
|||
*/
|
||||
protected static $instance = null;
|
||||
|
||||
/**
|
||||
* Name of themes transient.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const THEMES_TRANSIENT = 'wc_onboarding_themes';
|
||||
|
||||
/**
|
||||
* Name of product data transient.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const PRODUCT_DATA_TRANSIENT = 'wc_onboarding_product_data';
|
||||
|
||||
/**
|
||||
* Get class instance.
|
||||
*/
|
||||
|
@ -32,6 +46,8 @@ class WC_Admin_Onboarding {
|
|||
*/
|
||||
public function __construct() {
|
||||
add_action( 'woocommerce_components_settings', array( $this, 'component_settings' ), 20 ); // Run after WC_Admin_Loader.
|
||||
add_action( 'woocommerce_theme_installed', array( $this, 'delete_themes_transient' ) );
|
||||
add_action( 'after_switch_theme', array( $this, 'delete_themes_transient' ) );
|
||||
add_filter( 'woocommerce_admin_is_loading', array( $this, 'is_loading' ) );
|
||||
add_filter( 'woocommerce_rest_prepare_themes', array( $this, 'add_uploaded_theme_data' ) );
|
||||
}
|
||||
|
@ -116,8 +132,7 @@ class WC_Admin_Onboarding {
|
|||
* @return array
|
||||
*/
|
||||
public static function get_themes() {
|
||||
$themes_transient_name = 'wc_onboarding_themes';
|
||||
$themes = get_transient( $themes_transient_name );
|
||||
$themes = get_transient( self::THEMES_TRANSIENT );
|
||||
if ( false === $themes ) {
|
||||
$theme_data = wp_remote_get( 'https://woocommerce.com/wp-json/wccom-extensions/1.0/search?category=themes' );
|
||||
$themes = array();
|
||||
|
@ -152,7 +167,7 @@ class WC_Admin_Onboarding {
|
|||
|
||||
$themes = array( $active_theme => $themes[ $active_theme ] ) + $themes;
|
||||
|
||||
set_transient( $themes_transient_name, $themes, DAY_IN_SECONDS );
|
||||
set_transient( self::THEMES_TRANSIENT, $themes, DAY_IN_SECONDS );
|
||||
}
|
||||
|
||||
$themes = apply_filters( 'woocommerce_admin_onboarding_themes', $themes );
|
||||
|
@ -221,15 +236,14 @@ class WC_Admin_Onboarding {
|
|||
* @return array
|
||||
*/
|
||||
public static function append_product_data( $product_types ) {
|
||||
$product_data_transient_name = 'wc_onboarding_product_data';
|
||||
$woocommerce_products = get_transient( $product_data_transient_name );
|
||||
$woocommerce_products = get_transient( self::PRODUCT_DATA_TRANSIENT );
|
||||
if ( false === $woocommerce_products ) {
|
||||
$woocommerce_products = wp_remote_get( 'https://woocommerce.com/wp-json/wccom-extensions/1.0/search?category=product-type' );
|
||||
if ( is_wp_error( $woocommerce_products ) ) {
|
||||
return $product_types;
|
||||
}
|
||||
|
||||
set_transient( $product_data_transient_name, $woocommerce_products, DAY_IN_SECONDS );
|
||||
set_transient( self::PRODUCT_DATA_TRANSIENT, $woocommerce_products, DAY_IN_SECONDS );
|
||||
}
|
||||
|
||||
$product_data = json_decode( $woocommerce_products['body'] );
|
||||
|
@ -253,6 +267,13 @@ class WC_Admin_Onboarding {
|
|||
return $product_types;
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete the stored themes transient.
|
||||
*/
|
||||
public static function delete_themes_transient() {
|
||||
delete_transient( self::THEMES_TRANSIENT );
|
||||
}
|
||||
|
||||
/**
|
||||
* Add profiler items to component settings.
|
||||
*
|
||||
|
|
Loading…
Reference in New Issue