* 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:
Joshua T Flowers 2019-07-08 10:54:26 +08:00 committed by GitHub
parent 91a1f4a889
commit 19870c18c0
5 changed files with 239 additions and 11 deletions

View File

@ -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>

View File

@ -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;
}
}

View File

@ -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 );

View File

@ -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();

View File

@ -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.
*