first commit
This commit is contained in:
@@ -0,0 +1,186 @@
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import ReactSelect from 'react-select';
|
||||
import { debounce } from 'throttle-debounce';
|
||||
|
||||
/**
|
||||
* WordPress dependencies
|
||||
*/
|
||||
const {
|
||||
Component,
|
||||
} = wp.element;
|
||||
|
||||
const {
|
||||
withSelect,
|
||||
} = wp.data;
|
||||
|
||||
const {
|
||||
BaseControl,
|
||||
} = wp.components;
|
||||
|
||||
const cachedCategories = {};
|
||||
|
||||
function maybeCache( newCategories ) {
|
||||
if ( ! newCategories || ! newCategories.length ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( ! cachedCategories ) {
|
||||
cachedCategories = {};
|
||||
}
|
||||
|
||||
newCategories.forEach( ( postData ) => {
|
||||
if ( ! cachedCategories[ postData.id ] ) {
|
||||
cachedCategories[ postData.id ] = postData;
|
||||
}
|
||||
} );
|
||||
}
|
||||
|
||||
/**
|
||||
* Component
|
||||
*/
|
||||
class ComponentCategoriesSelectorControl extends Component {
|
||||
constructor() {
|
||||
super( ...arguments );
|
||||
|
||||
this.state = {
|
||||
searchTerm: '',
|
||||
};
|
||||
|
||||
this.updateSearchTerm = debounce( 300, this.updateSearchTerm.bind( this ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Update search term with debounce.
|
||||
*
|
||||
* @param {String} search - search term.
|
||||
*/
|
||||
updateSearchTerm( search ) {
|
||||
this.setState( {
|
||||
searchTerm: search,
|
||||
} );
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
value,
|
||||
label,
|
||||
help,
|
||||
onChange,
|
||||
allСategories,
|
||||
findСategories,
|
||||
} = this.props;
|
||||
|
||||
const {
|
||||
searchTerm,
|
||||
} = this.state;
|
||||
|
||||
const foundСategories = findСategories( searchTerm ) || [];
|
||||
|
||||
return (
|
||||
<BaseControl
|
||||
label={ label }
|
||||
help={ help }
|
||||
>
|
||||
<ReactSelect
|
||||
options={
|
||||
foundСategories.map( ( catData ) => {
|
||||
return {
|
||||
value: catData.slug,
|
||||
label: catData.name,
|
||||
};
|
||||
} )
|
||||
}
|
||||
value={ ( () => {
|
||||
let result = value ? value.split(',') : [];
|
||||
if ( result && result.length ) {
|
||||
result = result.map( ( name ) => {
|
||||
let thisData = {
|
||||
value: name,
|
||||
label: name,
|
||||
};
|
||||
|
||||
// get label from categories list
|
||||
for (var id in allСategories ) {
|
||||
if ( name === allСategories[id].slug ) {
|
||||
thisData.label = allСategories[id].name;
|
||||
}
|
||||
}
|
||||
|
||||
return thisData;
|
||||
} );
|
||||
return result;
|
||||
}
|
||||
return [];
|
||||
} )() }
|
||||
isMulti
|
||||
name="filter-by-categories"
|
||||
className="basic-multi-select"
|
||||
classNamePrefix="select"
|
||||
components={ {
|
||||
IndicatorSeparator: () => null,
|
||||
ClearIndicator: () => null,
|
||||
} }
|
||||
onInputChange={ ( val ) => {
|
||||
this.updateSearchTerm( val );
|
||||
} }
|
||||
onChange={ ( val ) => {
|
||||
let result = '';
|
||||
let slug = '';
|
||||
|
||||
if ( val ) {
|
||||
val.forEach( ( catData ) => {
|
||||
result += slug + catData.value;
|
||||
slug = ',';
|
||||
} );
|
||||
}
|
||||
|
||||
onChange( result );
|
||||
} }
|
||||
/>
|
||||
</BaseControl>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default withSelect( ( select, props, a, b, c ) => {
|
||||
const {
|
||||
getEntityRecords,
|
||||
} = select( 'core' );
|
||||
|
||||
const {
|
||||
value,
|
||||
} = props;
|
||||
|
||||
|
||||
let slugs = value ? value.split(',') : [];
|
||||
|
||||
// find non-cached categories and try to retrieve.
|
||||
slugs = slugs.filter( ( slug ) => {
|
||||
return ! cachedCategories || ! cachedCategories[ slug ];
|
||||
} );
|
||||
|
||||
if ( slugs && slugs.length ) {
|
||||
const newСategories = getEntityRecords( 'taxonomy', 'category', {
|
||||
slug: slugs,
|
||||
per_page: 100,
|
||||
} );
|
||||
|
||||
maybeCache( newСategories );
|
||||
}
|
||||
|
||||
return {
|
||||
findСategories( search = '' ) {
|
||||
const searchСategories = getEntityRecords( 'taxonomy', 'category', {
|
||||
search,
|
||||
per_page: 20,
|
||||
} );
|
||||
|
||||
maybeCache( searchСategories );
|
||||
|
||||
return searchСategories;
|
||||
},
|
||||
allСategories: cachedCategories || {},
|
||||
};
|
||||
})( ComponentCategoriesSelectorControl );
|
||||
@@ -0,0 +1,56 @@
|
||||
/**
|
||||
* WordPress dependencies
|
||||
*/
|
||||
const {
|
||||
Component,
|
||||
} = wp.element;
|
||||
|
||||
const {
|
||||
ColorPalette,
|
||||
} = wp.blockEditor;
|
||||
|
||||
/**
|
||||
* Component
|
||||
*/
|
||||
export default class ComponentColors extends Component {
|
||||
constructor() {
|
||||
super(...arguments);
|
||||
|
||||
this.updateColors = this.updateColors.bind(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update colors value.
|
||||
*
|
||||
* @param {String} slug - slug.
|
||||
* @param {String} prefix - type prefix.
|
||||
* @param {String} suffix - responsive suffix.
|
||||
* @param {String} val - new value.
|
||||
*/
|
||||
updateColors(slug, suffix = '', val) {
|
||||
const {
|
||||
onChange,
|
||||
} = this.props;
|
||||
|
||||
const updateAttrs = {
|
||||
[slug + suffix]: val,
|
||||
};
|
||||
|
||||
onChange(updateAttrs);
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
slug = '',
|
||||
val = '',
|
||||
suffix = '',
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
<ColorPalette
|
||||
value={ val || '' }
|
||||
onChange={ (val) => this.updateColors(slug, suffix, val) }
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
/**
|
||||
* WordPress dependencies
|
||||
*/
|
||||
const {
|
||||
__,
|
||||
} = wp.i18n;
|
||||
|
||||
const {
|
||||
Component,
|
||||
Fragment,
|
||||
} = wp.element;
|
||||
|
||||
const {
|
||||
TextControl,
|
||||
Notice,
|
||||
} = wp.components;
|
||||
|
||||
/**
|
||||
* Component
|
||||
*/
|
||||
export default class ComponentDimensionControl extends Component {
|
||||
constructor() {
|
||||
super( ...arguments );
|
||||
|
||||
this.isValidValue = this.isValidValue.bind( this );
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if value valid.
|
||||
*
|
||||
* @param {Mixed} value value to check.
|
||||
* @returns {Boolean}
|
||||
*/
|
||||
isValidValue( value ) {
|
||||
const validUnits = [ 'fr', 'rem', 'em', 'ex', '%', 'px', 'cm', 'mm', 'in', 'pt', 'pc', 'ch', 'vh', 'vw', 'vmin', 'vmax' ];
|
||||
|
||||
// Whitelist values.
|
||||
if ( ! value || '' === value || 0 === value || '0' === value || 'auto' === value || 'inherit' === value || 'initial' === value ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Skip checking if calc().
|
||||
if ( 0 <= value.indexOf( 'calc(' ) && 0 <= value.indexOf( ')' ) ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Get the numeric value.
|
||||
const numericValue = parseFloat( value );
|
||||
|
||||
// Get the unit
|
||||
const unit = value.replace( numericValue, '' );
|
||||
|
||||
// Allow unitless.
|
||||
if ( ! unit ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check the validity of the numeric value and units.
|
||||
return ( ! isNaN( numericValue ) && -1 !== validUnits.indexOf( unit ) );
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
value,
|
||||
label,
|
||||
help,
|
||||
onChange,
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<TextControl
|
||||
label={ label }
|
||||
help={ help }
|
||||
value={ value }
|
||||
onChange={ onChange }
|
||||
/>
|
||||
{ ! this.isValidValue( value ) ? (
|
||||
<Notice status="warning" isDismissible={ false }>
|
||||
{ __( 'Invalid dimension value.' ) }
|
||||
</Notice>
|
||||
) : '' }
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,192 @@
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import isFieldVisible from '../../utils/is-field-visible';
|
||||
|
||||
/**
|
||||
* WordPress dependencies
|
||||
*/
|
||||
const {
|
||||
Component,
|
||||
} = wp.element;
|
||||
|
||||
const {
|
||||
canvasBreakpoints,
|
||||
canvasAllBreakpoints,
|
||||
} = window;
|
||||
|
||||
/**
|
||||
* Component
|
||||
*/
|
||||
export default class ComponentFieldsCSSOutput extends Component {
|
||||
constructor() {
|
||||
super( ...arguments );
|
||||
|
||||
this.prepareStylesFromParams = this.prepareStylesFromParams.bind( this );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current field value. If value doesn't exist, use default value.
|
||||
*
|
||||
* @param {Object} fieldData field data.
|
||||
* @param {String} breakpoint breakpoint name.
|
||||
*
|
||||
* @returns {Mixed} field value.
|
||||
*/
|
||||
getFieldValue( fieldData, breakpoint = '' ) {
|
||||
const {
|
||||
attributes = {},
|
||||
} = this.props;
|
||||
|
||||
let suffix = '';
|
||||
|
||||
if ( breakpoint ) {
|
||||
suffix = `_${ breakpoint }`;
|
||||
}
|
||||
|
||||
if ( typeof attributes[ fieldData.key + suffix ] !== 'undefined' ) {
|
||||
return attributes[ fieldData.key + suffix ];
|
||||
} else if ( typeof fieldData[ 'default' + suffix ] !== 'undefined' ) {
|
||||
return fieldData[ 'default' + suffix ];
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare styles from params
|
||||
* Params example:
|
||||
{
|
||||
'element' : '$',
|
||||
'property' : 'height',
|
||||
'value_pattern' : 'linear-gradient(to bottom, $ 14%,#7db9e8 77%)',
|
||||
'media_query' : '@media ( min-width: 760px )',
|
||||
'units' : 'px',
|
||||
'prefix' : 'calc(1px + ',
|
||||
'suffix' : ') !important',
|
||||
}
|
||||
*
|
||||
* @param {String} selector CSS selector.
|
||||
* @param {Mixed} value Property value.
|
||||
* @param {Object} params Output params.
|
||||
*
|
||||
* @returns {String}
|
||||
*/
|
||||
prepareStylesFromParams( selector, value, params ) {
|
||||
let result = '';
|
||||
|
||||
if ( ! selector || typeof value === 'undefined' || '' === value || null === value || ! params.property ) {
|
||||
return result;
|
||||
}
|
||||
|
||||
// check for context
|
||||
if ( params.context && params.context.indexOf( 'editor' ) === -1 ) {
|
||||
return result;
|
||||
}
|
||||
|
||||
// Reverse smart selector.
|
||||
if ( params.reverse && params.reverse_max ) {
|
||||
let multi_selector = '';
|
||||
for (let iteration = 1; iteration < params.reverse_max; iteration++) {
|
||||
if ( iteration <= value ) {
|
||||
continue;
|
||||
}
|
||||
multi_selector += ( multi_selector ? ', ' : '' ) + params.reverse.replace( /\$numb/g, iteration );
|
||||
}
|
||||
|
||||
selector = multi_selector;
|
||||
}
|
||||
|
||||
// value pattern
|
||||
if ( params.value_pattern ) {
|
||||
value = params.value_pattern.replace( /\$/g, value );
|
||||
}
|
||||
|
||||
// prepare CSS
|
||||
result = `
|
||||
${ params.element ? params.element.replace( /\$/g, selector ) : selector } {
|
||||
${ params.property }: ${ params.prefix || '' }${ value }${ params.units || '' }${ params.suffix || '' };
|
||||
}
|
||||
`;
|
||||
|
||||
// add media query
|
||||
if ( params.media_query ) {
|
||||
result = `
|
||||
${ params.media_query } {
|
||||
${ result }
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
selector = '',
|
||||
fields = [],
|
||||
attributes,
|
||||
} = this.props;
|
||||
|
||||
let result = '';
|
||||
|
||||
fields
|
||||
.filter( ( fieldData ) => {
|
||||
if ( ! fieldData || ! fieldData.type ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// check active_callback
|
||||
return isFieldVisible( fieldData, attributes, fields );
|
||||
})
|
||||
.forEach( ( fieldData, i ) => {
|
||||
if ( selector && fieldData.output ) {
|
||||
fieldData.output.forEach( ( outputData ) => {
|
||||
// general styles.
|
||||
result += this.prepareStylesFromParams( selector, this.getFieldValue( fieldData ), outputData );
|
||||
|
||||
// Dark styles.
|
||||
if ( canvasSchemes && ( 'color' === fieldData.type ) ) {
|
||||
Object.keys( canvasSchemes ).forEach( ( name ) => {
|
||||
if ( name && 'default' !== name ) {
|
||||
|
||||
let rule = `[data-scheme="${name}"] ${selector}`;
|
||||
|
||||
result += this.prepareStylesFromParams( rule, this.getFieldValue( fieldData, name ), {
|
||||
...outputData
|
||||
} );
|
||||
}
|
||||
} );
|
||||
}
|
||||
|
||||
// Responsive styles.
|
||||
if ( fieldData.responsive ) {
|
||||
Object.keys( canvasAllBreakpoints ).forEach( ( name ) => {
|
||||
if ( name && 'desktop' !== name ) {
|
||||
let rule = selector;
|
||||
|
||||
// If exist scheme.
|
||||
if ( canvasAllBreakpoints[ name ].scheme ) {
|
||||
rule = `[data-scheme="${name}"] ${canvasAllBreakpoints[ name ].scheme}`;
|
||||
}
|
||||
|
||||
result += this.prepareStylesFromParams( rule, this.getFieldValue( fieldData, name ), {
|
||||
...outputData,
|
||||
media_query: `@media (max-width: ${ canvasAllBreakpoints[ name ].width }px)`,
|
||||
} );
|
||||
}
|
||||
} );
|
||||
}
|
||||
} );
|
||||
}
|
||||
} );
|
||||
|
||||
return (
|
||||
result ? (
|
||||
<style>
|
||||
{ result }
|
||||
</style>
|
||||
) : ''
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,200 @@
|
||||
<?php
|
||||
/**
|
||||
* Parse fields data and generate styles output.
|
||||
*
|
||||
* @package Canvas
|
||||
*/
|
||||
|
||||
require_once CNVS_PATH . 'gutenberg/utils/is-field-visible/index.php';
|
||||
|
||||
if ( ! class_exists( 'CNVS_Gutenberg_Fields_CSS_Output' ) ) {
|
||||
|
||||
/**
|
||||
* Class Gutenberg Fields CSS Output.
|
||||
*/
|
||||
class CNVS_Gutenberg_Fields_CSS_Output {
|
||||
/**
|
||||
* Prepare CSS for selected fields.
|
||||
*
|
||||
* @param string $selector CSS selector.
|
||||
* @param array $fields Fields data.
|
||||
* @param array $attributes Block attributes.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function get( $selector, $fields = array(), $attributes = array() ) {
|
||||
$schemes = cnvs_gutenberg()->get_schemes_data();
|
||||
$breakpoints = cnvs_gutenberg()->get_breakpoints_data();
|
||||
$all_breakpoints = cnvs_gutenberg()->get_all_breakpoints_data();
|
||||
|
||||
$result = '';
|
||||
|
||||
foreach ( $fields as $field ) {
|
||||
if ( ! isset( $field['type'] ) || ! isset( $field['output'] ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// check if field visible.
|
||||
if ( ! CNVS_Gutenberg_Utils_Is_Field_Visible::check( $field, $attributes, $fields ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach ( $field['output'] as $data ) {
|
||||
$result .= self::prepare_styles_from_params( $selector, self::get_field_value( $field, $attributes ), $data );
|
||||
|
||||
if ( $schemes && ( 'color' === $field['type'] ) ) {
|
||||
|
||||
foreach ( $schemes as $name => $scheme ) {
|
||||
$rule = sprintf( '[data-scheme="%s"] %s', $name, $selector );
|
||||
|
||||
if ( $name && 'default' !== $name ) {
|
||||
$result .= self::prepare_styles_from_params(
|
||||
$rule,
|
||||
self::get_field_value( $field, $attributes, $name ),
|
||||
$data
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ( isset( $field['responsive'] ) && $field['responsive'] ) {
|
||||
foreach ( $all_breakpoints as $name => $breakpoint ) {
|
||||
if ( $name && 'desktop' !== $name ) {
|
||||
|
||||
$rule = $selector;
|
||||
|
||||
// If exist scheme.
|
||||
if ( isset( $breakpoint['scheme'] ) ) {
|
||||
$rule = sprintf( '[data-scheme="%s"] %s', $breakpoint['scheme'], $selector );
|
||||
}
|
||||
|
||||
$result .= self::prepare_styles_from_params(
|
||||
$rule,
|
||||
self::get_field_value( $field, $attributes, $name ),
|
||||
array_merge(
|
||||
$data,
|
||||
array(
|
||||
'media_query' => '@media (max-width: ' . apply_filters( 'canvas_blocks_dynamic_breakpoint_width', $breakpoint['width'], $field ) . 'px)',
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current field value. If value doesn't exist, use default value.
|
||||
*
|
||||
* @param array $field Field data.
|
||||
* @param array $attributes Field attributes.
|
||||
* @param string $breakpoint Breakpoint name.
|
||||
*
|
||||
* @return mixed Field value.
|
||||
*/
|
||||
public static function get_field_value( $field, $attributes, $breakpoint = '' ) {
|
||||
$suffix = '';
|
||||
|
||||
if ( $breakpoint ) {
|
||||
$suffix = '_' . $breakpoint;
|
||||
}
|
||||
|
||||
if ( isset( $attributes[ $field['key'] . $suffix ] ) ) {
|
||||
return $attributes[ $field['key'] . $suffix ];
|
||||
} elseif ( isset( $field[ 'default' . $suffix ] ) ) {
|
||||
return $field[ 'default' . $suffix ];
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare styles from params
|
||||
* Params example:
|
||||
array(
|
||||
'element' => '$',
|
||||
'property' => 'height',
|
||||
'value_pattern' => 'linear-gradient(to bottom, $ 14%,#7db9e8 77%)',
|
||||
'media_query' => '@media ( min-width: 760px )',
|
||||
'units' => 'px',
|
||||
'prefix' => 'calc(1px + ',
|
||||
'suffix' => ') !important',
|
||||
)
|
||||
*
|
||||
* @param string $selector CSS selector.
|
||||
* @param mixed $value Property value.
|
||||
* @param array $params Output params.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function prepare_styles_from_params( $selector, $value, $params ) {
|
||||
$result = '';
|
||||
|
||||
if ( ! $selector || ! isset( $value ) || '' === $value || null === $value || ! isset( $params['property'] ) ) {
|
||||
return $result;
|
||||
}
|
||||
|
||||
// Check for context.
|
||||
if ( isset( $params['context'] ) && ! in_array( 'front', $params['context'], true ) ) {
|
||||
return $result;
|
||||
}
|
||||
|
||||
// Custom selector pattern.
|
||||
if ( isset( $params['element'] ) ) {
|
||||
$selector = str_replace( '$', $selector, $params['element'] );
|
||||
}
|
||||
|
||||
// Reverse smart selector.
|
||||
if ( isset( $params['reverse'] ) && isset( $params['reverse_max'] ) ) {
|
||||
$multi_selector = __return_empty_string();
|
||||
|
||||
for ( $iteration = 1; $iteration <= $params['reverse_max']; $iteration++ ) {
|
||||
if ( $iteration <= $value ) {
|
||||
continue;
|
||||
}
|
||||
$multi_selector .= ( $multi_selector ? ', ' : '' ) . str_replace( '$numb', $iteration, $params['reverse'] );
|
||||
}
|
||||
|
||||
$selector = str_replace( '$', $selector, $multi_selector );
|
||||
}
|
||||
|
||||
// Value pattern.
|
||||
if ( isset( $params['value_pattern'] ) ) {
|
||||
$value = str_replace( '$', $value, $params['value_pattern'] );
|
||||
}
|
||||
|
||||
// Prefix.
|
||||
if ( isset( $params['prefix'] ) ) {
|
||||
$value = $params['prefix'] . $value;
|
||||
}
|
||||
|
||||
// Units.
|
||||
if ( isset( $params['units'] ) ) {
|
||||
$value = $value . $params['units'];
|
||||
}
|
||||
|
||||
// Suffix.
|
||||
if ( isset( $params['suffix'] ) ) {
|
||||
$value = $value . $params['suffix'];
|
||||
}
|
||||
|
||||
$property = $params['property'];
|
||||
|
||||
// Prepare CSS.
|
||||
$result = "${selector} { ${property}: ${value}; }";
|
||||
|
||||
// Add media query.
|
||||
if ( isset( $params['media_query'] ) ) {
|
||||
$media_query = $params['media_query'];
|
||||
|
||||
$result = "${media_query} { ${result} }";
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,857 @@
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import ReactSelect from 'react-select';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import './style.scss';
|
||||
import SchemeWrapper from '../scheme-wrapper';
|
||||
import ResponsiveWrapper from '../responsive-wrapper';
|
||||
import DimensionControl from '../dimension-control';
|
||||
import CategoriesSelectorControl from '../categories-selector-control';
|
||||
import TagsSelectorControl from '../tags-selector-control';
|
||||
import PostsSelectorControl from '../posts-selector-control';
|
||||
import QueryControl from '../query-control';
|
||||
import isFieldVisible from '../../utils/is-field-visible';
|
||||
|
||||
/**
|
||||
* WordPress dependencies
|
||||
*/
|
||||
const {
|
||||
__,
|
||||
sprintf,
|
||||
} = wp.i18n;
|
||||
|
||||
const {
|
||||
Component,
|
||||
Fragment,
|
||||
RawHTML,
|
||||
} = wp.element;
|
||||
|
||||
const {
|
||||
BaseControl,
|
||||
ToggleControl,
|
||||
TextControl,
|
||||
TextareaControl,
|
||||
RangeControl,
|
||||
SelectControl,
|
||||
PanelBody,
|
||||
Notice,
|
||||
DropZone,
|
||||
Button,
|
||||
Toolbar,
|
||||
} = wp.components;
|
||||
|
||||
const {
|
||||
ColorPalette,
|
||||
MediaPlaceholder,
|
||||
MediaUpload,
|
||||
mediaUpload,
|
||||
} = wp.blockEditor;
|
||||
|
||||
const {
|
||||
applyFilters,
|
||||
} = wp.hooks;
|
||||
|
||||
/**
|
||||
* Component
|
||||
*/
|
||||
export default class ComponentFieldsRender extends Component {
|
||||
constructor() {
|
||||
super( ...arguments );
|
||||
|
||||
this.getAllFieldsSections = this.getAllFieldsSections.bind( this );
|
||||
this.getFieldValue = this.getFieldValue.bind( this );
|
||||
this.updateFieldValue = this.updateFieldValue.bind( this );
|
||||
this.renderControl = this.renderControl.bind( this );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all available sections.
|
||||
*
|
||||
* @returns {Object} sections.
|
||||
*/
|
||||
getAllFieldsSections() {
|
||||
const {
|
||||
fields = [],
|
||||
} = this.props;
|
||||
|
||||
const sections = {
|
||||
...{ '': '' },
|
||||
...this.props.sections,
|
||||
};
|
||||
|
||||
// check all fields and add section if not defined.
|
||||
fields.forEach( ( field ) => {
|
||||
if ( field.section && typeof sections[ field.section ] === 'undefined' ) {
|
||||
sections[ field.section ] = field.section;
|
||||
}
|
||||
} );
|
||||
|
||||
return sections;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current field value. If value doesn't exist, use default value.
|
||||
*
|
||||
* @param {Object} fieldData field data.
|
||||
* @param {String} suffix attribute name suffix.
|
||||
*
|
||||
* @returns {Mixed} field value.
|
||||
*/
|
||||
getFieldValue( fieldData, suffix = '' ) {
|
||||
const {
|
||||
attributes = {},
|
||||
} = this.props;
|
||||
|
||||
if ( typeof attributes[ fieldData.key + suffix ] !== 'undefined' ) {
|
||||
return attributes[ fieldData.key + suffix ];
|
||||
} else if ( typeof fieldData[ 'default' + suffix ] !== 'undefined' ) {
|
||||
return fieldData[ 'default' + suffix ];
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update current field value.
|
||||
*
|
||||
* @param {Object} fieldData field data.
|
||||
* @param {Mixed} val field value.
|
||||
* @param {String} suffix attribute name suffix.
|
||||
*/
|
||||
updateFieldValue( fieldData, val, suffix = '' ) {
|
||||
const {
|
||||
onChange,
|
||||
} = this.props;
|
||||
|
||||
onChange( fieldData.key + suffix, val );
|
||||
}
|
||||
|
||||
/**
|
||||
* Render control
|
||||
*
|
||||
* @param {Object} fieldData field data.
|
||||
*
|
||||
* @returns {JSX}
|
||||
*/
|
||||
renderControl( fieldData ) {
|
||||
const renderName = `renderControl${ fieldData.type.replace( /(\b\w)|(-.)/g, ( x ) => ( x[1] || x[0] ).toUpperCase() ) }`;
|
||||
|
||||
// check if render method exist.
|
||||
if (this[renderName]) {
|
||||
|
||||
return (
|
||||
<SchemeWrapper>
|
||||
{({ schemeSuffix, ComponentSchemeDropdown }) => {
|
||||
return (
|
||||
<ResponsiveWrapper>
|
||||
{({ responsiveSuffix, ComponentResponsiveDropdown }) => {
|
||||
let fieldSuffix = '';
|
||||
let newFieldData = { ...fieldData };
|
||||
|
||||
// Scheme dropdown.
|
||||
if (canvasSchemes && ('color' === newFieldData.type)) {
|
||||
fieldSuffix += schemeSuffix;
|
||||
newFieldData = {
|
||||
...newFieldData,
|
||||
label: (
|
||||
<Fragment>
|
||||
{newFieldData.label || ''}
|
||||
<ComponentSchemeDropdown />
|
||||
</Fragment>
|
||||
)
|
||||
};
|
||||
}
|
||||
|
||||
// Responsive dropdown.
|
||||
if (newFieldData.responsive) {
|
||||
fieldSuffix += responsiveSuffix;
|
||||
newFieldData = {
|
||||
...newFieldData,
|
||||
label: (
|
||||
<Fragment>
|
||||
{newFieldData.label || ''}
|
||||
<ComponentResponsiveDropdown />
|
||||
</Fragment>
|
||||
)
|
||||
};
|
||||
}
|
||||
|
||||
return this[renderName](
|
||||
newFieldData,
|
||||
this.getFieldValue(newFieldData, fieldSuffix),
|
||||
(val) => {
|
||||
this.updateFieldValue(newFieldData, val, fieldSuffix);
|
||||
}
|
||||
);
|
||||
}}
|
||||
</ResponsiveWrapper>
|
||||
)
|
||||
}}
|
||||
</SchemeWrapper>
|
||||
);
|
||||
}
|
||||
|
||||
// render method does not exist.
|
||||
return (
|
||||
<Notice status="warning" isDismissible={ false }>
|
||||
{ sprintf( __( 'Unfortunately, `%s` method doesn\'t exist.' ), renderName ) }
|
||||
</Notice>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Render Separator control
|
||||
*
|
||||
* @param {Object} fieldData field data.
|
||||
* @param {String} val field value.
|
||||
* @param {Object} onChange field on change callback.
|
||||
*
|
||||
* @returns {JSX}
|
||||
*/
|
||||
renderControlSeparator( fieldData, val, onChange ) {
|
||||
return (
|
||||
<hr />
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Render Text control
|
||||
*
|
||||
* @param {Object} fieldData field data.
|
||||
* @param {String} val field value.
|
||||
* @param {Object} onChange field on change callback.
|
||||
*
|
||||
* @returns {JSX}
|
||||
*/
|
||||
renderControlText( fieldData, val, onChange ) {
|
||||
return (
|
||||
<TextControl
|
||||
label={ fieldData.label || false }
|
||||
help={ <RawHTML>{ fieldData.help || '' }</RawHTML> }
|
||||
value={ val }
|
||||
onChange={ onChange }
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Render Textarea control
|
||||
*
|
||||
* @param {Object} fieldData field data.
|
||||
* @param {String} val field value.
|
||||
* @param {Object} onChange field on change callback.
|
||||
*
|
||||
* @returns {JSX}
|
||||
*/
|
||||
renderControlTextarea( fieldData, val, onChange ) {
|
||||
return (
|
||||
<TextareaControl
|
||||
label={ fieldData.label || false }
|
||||
help={ fieldData.help || false }
|
||||
value={ val }
|
||||
onChange={ onChange }
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Render Toggle control
|
||||
*
|
||||
* @param {Object} fieldData field data.
|
||||
* @param {Boolean} val field value.
|
||||
* @param {Object} onChange field on change callback.
|
||||
*
|
||||
* @returns {JSX}
|
||||
*/
|
||||
renderControlToggle( fieldData, val, onChange ) {
|
||||
return (
|
||||
<ToggleControl
|
||||
label={ fieldData.label || false }
|
||||
help={ fieldData.help || false }
|
||||
checked={ !! val }
|
||||
onChange={ onChange }
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Render Toggle List control
|
||||
*
|
||||
* @param {Object} fieldData field data.
|
||||
* @param {Object} val field value.
|
||||
* @param {Object} onChange field on change callback.
|
||||
*
|
||||
* @returns {JSX}
|
||||
*/
|
||||
renderControlToggleList( fieldData, val, onChange ) {
|
||||
return (
|
||||
<BaseControl
|
||||
label={ fieldData.label || false }
|
||||
help={ fieldData.help || false }
|
||||
>
|
||||
<p />
|
||||
{ Object.keys( val ).map( ( valName ) => {
|
||||
return (
|
||||
<ToggleControl
|
||||
key={ `toggle-list-control-${ fieldData.key }-${ valName }` }
|
||||
label={ fieldData.choices[ valName ] || false }
|
||||
checked={ !! val[ valName ] }
|
||||
onChange={ () => {
|
||||
const result = Object.assign( {}, val );
|
||||
result[ valName ] = ! result[ valName ];
|
||||
onChange( result );
|
||||
} }
|
||||
/>
|
||||
);
|
||||
} ) }
|
||||
</BaseControl>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Render Dimension control
|
||||
*
|
||||
* @param {Object} fieldData field data.
|
||||
* @param {String} val field value.
|
||||
* @param {Object} onChange field on change callback.
|
||||
*
|
||||
* @returns {JSX}
|
||||
*/
|
||||
renderControlDimension( fieldData, val, onChange ) {
|
||||
return (
|
||||
<DimensionControl
|
||||
label={ fieldData.label || false }
|
||||
help={ fieldData.help || false }
|
||||
value={ val }
|
||||
onChange={ onChange }
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Render Number control
|
||||
*
|
||||
* @param {Object} fieldData field data.
|
||||
* @param {Number} val field value.
|
||||
* @param {Object} onChange field on change callback.
|
||||
*
|
||||
* @returns {JSX}
|
||||
*/
|
||||
renderControlNumber( fieldData, val, onChange ) {
|
||||
return (
|
||||
<RangeControl
|
||||
label={ fieldData.label || false }
|
||||
help={ fieldData.help || false }
|
||||
min={ fieldData.min || false }
|
||||
max={ fieldData.max || false }
|
||||
step={ fieldData.step || 1 }
|
||||
value={ val }
|
||||
onChange={ onChange }
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Render Icon Buttons control
|
||||
*
|
||||
* @param {Object} fieldData field data.
|
||||
* @param {String} val field value.
|
||||
* @param {Object} onChange field on change callback.
|
||||
*
|
||||
* @returns {JSX}
|
||||
*/
|
||||
renderControlIconButtons( fieldData, val, onChange ) {
|
||||
return (
|
||||
<BaseControl
|
||||
label={ fieldData.label || false }
|
||||
help={ fieldData.help || false }
|
||||
>
|
||||
<Toolbar
|
||||
className="cnvs-control-icon-buttons"
|
||||
controls={ Object.keys( fieldData.choices ).map( ( option ) => {
|
||||
return {
|
||||
icon: <RawHTML className="cnvs-control-icon-buttons-svg">{ fieldData.choices[ option ] }</RawHTML>,
|
||||
isActive: val === option,
|
||||
onClick() {
|
||||
onChange( val === option ? '' : option );
|
||||
},
|
||||
};
|
||||
} ) }
|
||||
/>
|
||||
</BaseControl>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Render Select control
|
||||
*
|
||||
* @param {Object} fieldData field data.
|
||||
* @param {String} val field value.
|
||||
* @param {Object} onChange field on change callback.
|
||||
*
|
||||
* @returns {JSX}
|
||||
*/
|
||||
renderControlSelect( fieldData, val, onChange ) {
|
||||
const options = Object.keys( fieldData.choices ).map( ( option ) => {
|
||||
return {
|
||||
label: fieldData.choices[ option ],
|
||||
value: option,
|
||||
};
|
||||
} );
|
||||
|
||||
return (
|
||||
<SelectControl
|
||||
label={ fieldData.label || false }
|
||||
help={ fieldData.help || false }
|
||||
multiple={ fieldData.multiple || false }
|
||||
value={ val }
|
||||
options={ options }
|
||||
onChange={ ( val ) => {
|
||||
onChange( val );
|
||||
} }
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Render React Select control
|
||||
*
|
||||
* @param {Object} fieldData field data.
|
||||
* @param {String} val field value.
|
||||
* @param {Object} onChange field on change callback.
|
||||
*
|
||||
* @returns {JSX}
|
||||
*/
|
||||
renderControlReactSelect( fieldData, val, onChange ) {
|
||||
const options = Object.keys( fieldData.choices ).map( ( option ) => {
|
||||
return {
|
||||
label: fieldData.choices[ option ],
|
||||
value: option,
|
||||
};
|
||||
} );
|
||||
|
||||
return (
|
||||
<BaseControl
|
||||
label={ fieldData.label || false }
|
||||
help={ fieldData.help || false }
|
||||
>
|
||||
<ReactSelect
|
||||
isMulti={ fieldData.multiple || false }
|
||||
name="colors"
|
||||
options={ options }
|
||||
value={ ( () => {
|
||||
if ( fieldData.multiple ) {
|
||||
if ( ! Array.isArray( val ) ) {
|
||||
val = [];
|
||||
}
|
||||
|
||||
// options
|
||||
const result = val.map( ( val ) => {
|
||||
return {
|
||||
value: val,
|
||||
label: fieldData.choices[ val ] || val,
|
||||
};
|
||||
} );
|
||||
|
||||
return result;
|
||||
}
|
||||
return val;
|
||||
} )() }
|
||||
onChange={ ( val ) => {
|
||||
if ( fieldData.multiple ) {
|
||||
if ( val ) {
|
||||
const result = val.map( ( opt ) => {
|
||||
return opt.value;
|
||||
} );
|
||||
|
||||
onChange( result );
|
||||
} else {
|
||||
onChange( [] );
|
||||
}
|
||||
} else {
|
||||
onChange( val );
|
||||
}
|
||||
} }
|
||||
/>
|
||||
</BaseControl>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Render Color Picker control
|
||||
*
|
||||
* @param {Object} fieldData field data.
|
||||
* @param {String} val field value.
|
||||
* @param {Object} onChange field on change callback.
|
||||
*
|
||||
* @returns {JSX}
|
||||
*/
|
||||
renderControlColor( fieldData, val, onChange ) {
|
||||
let result = (
|
||||
<ColorPalette
|
||||
value={ val || '' }
|
||||
onChange={ onChange }
|
||||
/>
|
||||
);
|
||||
|
||||
if ( fieldData.label || fieldsData.help ) {
|
||||
return (
|
||||
<BaseControl
|
||||
label={ fieldData.label || false }
|
||||
help={ fieldData.help || false }
|
||||
>
|
||||
{ result }
|
||||
</BaseControl>
|
||||
);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Render Image control
|
||||
*
|
||||
* @param {Object} fieldData field data.
|
||||
* @param {Number} val field value.
|
||||
* @param {Object} onChange field on change callback.
|
||||
*
|
||||
* @returns {JSX}
|
||||
*/
|
||||
renderControlImage( fieldData, val = {}, onChange ) {
|
||||
const {
|
||||
id = 0,
|
||||
url = '',
|
||||
} = val;
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<BaseControl
|
||||
label={ fieldData.label || false }
|
||||
help={ fieldData.help || false }
|
||||
>
|
||||
{ ! id ? (
|
||||
<MediaPlaceholder
|
||||
icon="format-image"
|
||||
labels={ {
|
||||
title: __( 'Image' ),
|
||||
name: __( 'image' ),
|
||||
} }
|
||||
onSelect={ ( image ) => {
|
||||
onChange( {
|
||||
id: image.id,
|
||||
url: image.url,
|
||||
} );
|
||||
} }
|
||||
accept="image/*"
|
||||
allowedTypes={ [ 'image' ] }
|
||||
disableMaxUploadErrorMessages
|
||||
onError={ ( e ) => {
|
||||
console.log( e );
|
||||
} }
|
||||
/>
|
||||
) : '' }
|
||||
{ url ? (
|
||||
<Fragment>
|
||||
<DropZone
|
||||
onFilesDrop={ ( files ) => {
|
||||
mediaUpload( {
|
||||
allowedTypes: [ 'image' ],
|
||||
filesList: files,
|
||||
onFileChange: ( image ) => {
|
||||
onChange( {
|
||||
id: image.id,
|
||||
url: image.url,
|
||||
} );
|
||||
},
|
||||
onError( e ) {
|
||||
console.log( e );
|
||||
},
|
||||
} );
|
||||
} }
|
||||
/>
|
||||
{ url ? (
|
||||
<img src={ url } />
|
||||
) : '' }
|
||||
<div>
|
||||
<Button
|
||||
isDefault={ true }
|
||||
onClick={ () => {
|
||||
onChange( {
|
||||
id: 0,
|
||||
url: '',
|
||||
} );
|
||||
} }
|
||||
>
|
||||
{ __( 'Remove Image' ) }
|
||||
</Button>
|
||||
</div>
|
||||
</Fragment>
|
||||
) : '' }
|
||||
</BaseControl>
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Render Gallery control
|
||||
*
|
||||
* @param {Object} fieldData field data.
|
||||
* @param {Number} val field value.
|
||||
* @param {Object} onChange field on change callback.
|
||||
*
|
||||
* @returns {JSX}
|
||||
*/
|
||||
renderControlGallery( fieldData, val = [], onChange ) {
|
||||
const ALLOWED_MEDIA_TYPES = [ 'image' ];
|
||||
|
||||
return (
|
||||
<BaseControl
|
||||
label={ fieldData.label || false }
|
||||
help={ fieldData.help || false }
|
||||
>
|
||||
{ ! val || ! val.length ? (
|
||||
<MediaPlaceholder
|
||||
icon="format-gallery"
|
||||
labels={ {
|
||||
title: fieldData.label,
|
||||
name: __( 'images' ),
|
||||
} }
|
||||
onSelect={ ( images ) => {
|
||||
const result = images.map( ( image ) => {
|
||||
return image.id;
|
||||
} );
|
||||
|
||||
onChange( result );
|
||||
} }
|
||||
accept="image/*"
|
||||
allowedTypes={ ALLOWED_MEDIA_TYPES }
|
||||
disableMaxUploadErrorMessages
|
||||
multiple
|
||||
onError={ ( e ) => {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log( e );
|
||||
} }
|
||||
/>
|
||||
) : '' }
|
||||
{ val && val.length ? (
|
||||
<MediaUpload
|
||||
onSelect={ ( images ) => {
|
||||
const result = images.map( ( image ) => {
|
||||
return image.id;
|
||||
} );
|
||||
|
||||
onChange( result );
|
||||
} }
|
||||
allowedTypes={ ALLOWED_MEDIA_TYPES }
|
||||
multiple
|
||||
gallery
|
||||
value={ val }
|
||||
render={ ( { open } ) => (
|
||||
<div
|
||||
className="cnvs-gutenberg-component-gallery"
|
||||
onClick={ open }
|
||||
role="presentation"
|
||||
>
|
||||
<DropZone
|
||||
onFilesDrop={ ( files ) => {
|
||||
const currentImages = val || [];
|
||||
mediaUpload( {
|
||||
allowedTypes: ALLOWED_MEDIA_TYPES,
|
||||
filesList: files,
|
||||
onFileChange: ( images ) => {
|
||||
const result = images.map( ( image ) => {
|
||||
return image.id;
|
||||
} );
|
||||
|
||||
onChange( currentImages.concat( result ) );
|
||||
},
|
||||
onError( e ) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log( e );
|
||||
},
|
||||
} );
|
||||
} }
|
||||
/>
|
||||
|
||||
{ val ? (
|
||||
<div className="cnvs-gutenberg-component-gallery-list">
|
||||
{val.map(imageId => {
|
||||
return (
|
||||
<img src={ canvasLocalize.ajaxURL + '?action=cnvs_render_thumbnail&image_id=' + imageId } />
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
) : '' }
|
||||
|
||||
<div className="cnvs-gutenberg-component-gallery-button">
|
||||
<Button isDefault={ true }>{ __( 'Edit Gallery' ) }</Button>
|
||||
</div>
|
||||
</div>
|
||||
) }
|
||||
/>
|
||||
) : '' }
|
||||
</BaseControl>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Render Categories Selector control
|
||||
*
|
||||
* @param {Object} fieldData field data.
|
||||
* @param {String} val field value.
|
||||
* @param {Object} onChange field on change callback.
|
||||
*
|
||||
* @returns {JSX}
|
||||
*/
|
||||
renderControlCategoriesSelector( fieldData, val, onChange ) {
|
||||
return (
|
||||
<CategoriesSelectorControl
|
||||
label={ fieldData.label || false }
|
||||
help={ fieldData.help || false }
|
||||
value={ val }
|
||||
onChange={ onChange }
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Render Tags Selector control
|
||||
*
|
||||
* @param {Object} fieldData field data.
|
||||
* @param {String} val field value.
|
||||
* @param {Object} onChange field on change callback.
|
||||
*
|
||||
* @returns {JSX}
|
||||
*/
|
||||
renderControlTagsSelector( fieldData, val, onChange ) {
|
||||
return (
|
||||
<TagsSelectorControl
|
||||
label={ fieldData.label || false }
|
||||
help={ fieldData.help || false }
|
||||
value={ val }
|
||||
onChange={ onChange }
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Render Posts Selector control
|
||||
*
|
||||
* @param {Object} fieldData field data.
|
||||
* @param {String} val field value.
|
||||
* @param {Object} onChange field on change callback.
|
||||
*
|
||||
* @returns {JSX}
|
||||
*/
|
||||
renderControlPostsSelector( fieldData, val, onChange ) {
|
||||
return (
|
||||
<PostsSelectorControl
|
||||
label={ fieldData.label || false }
|
||||
help={ fieldData.help || false }
|
||||
value={ val }
|
||||
onChange={ onChange }
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Render Query control
|
||||
*
|
||||
* @param {Object} fieldData field data.
|
||||
* @param {String} val field value.
|
||||
* @param {Object} onChange field on change callback.
|
||||
*
|
||||
* @returns {JSX}
|
||||
*/
|
||||
renderControlQuery( fieldData, val, onChange ) {
|
||||
return (
|
||||
<QueryControl
|
||||
label={ fieldData.label || false }
|
||||
help={ fieldData.help || false }
|
||||
value={ val }
|
||||
onChange={ onChange }
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
fields = [],
|
||||
attributes,
|
||||
} = this.props;
|
||||
|
||||
const sections = this.getAllFieldsSections();
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
{ Object.keys( sections ).map( ( sectionName ) => {
|
||||
|
||||
const sectionTitle = sections[ sectionName ].title;
|
||||
const initialOpen = sections[ sectionName ].open || ( sectionTitle ? false : true );
|
||||
|
||||
if ( ! fields || ! fields.length ) {
|
||||
return '';
|
||||
}
|
||||
|
||||
const sectionFields = fields
|
||||
.filter( ( fieldData ) => {
|
||||
if ( ! fieldData || ! fieldData.type ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// prevent invisible fields, that used only for registering block attributes.
|
||||
if ( 'type-string' === fieldData.type || 'type-number' === fieldData.type || 'type-boolean' === fieldData.type || 'type-array' === fieldData.type ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// limit fields for current section only.
|
||||
if ( sectionName && fieldData.section !== sectionName ) {
|
||||
return false;
|
||||
} else if ( ! sectionName && fieldData.section ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// check active_callback
|
||||
return isFieldVisible( fieldData, attributes, fields );
|
||||
})
|
||||
.map( ( fieldData, i ) => {
|
||||
let fieldKey = `field-${ fieldData.type }-${ i }`;
|
||||
|
||||
return (
|
||||
applyFilters( 'canvas.component.fieldsRender.singleField', (
|
||||
<Fragment key={ fieldKey }>
|
||||
{ this.renderControl( fieldData ) }
|
||||
</Fragment>
|
||||
), {
|
||||
fieldData,
|
||||
props: this.props,
|
||||
} )
|
||||
);
|
||||
} );
|
||||
|
||||
if ( ! sectionFields || ! sectionFields.length ) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return (
|
||||
<PanelBody
|
||||
key={ `section-${ sectionName }` }
|
||||
title={ sectionTitle }
|
||||
initialOpen={ initialOpen }
|
||||
>
|
||||
{ applyFilters( 'canvas.component.fieldsRender', sectionFields, {
|
||||
sectionName,
|
||||
sectionTitle,
|
||||
props: this.props,
|
||||
} ) }
|
||||
</PanelBody>
|
||||
);
|
||||
} ) }
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import classnames from 'classnames';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import './style.scss';
|
||||
|
||||
/**
|
||||
* WordPress dependencies
|
||||
*/
|
||||
const {
|
||||
Component,
|
||||
} = wp.element;
|
||||
|
||||
const {
|
||||
Button,
|
||||
Popover,
|
||||
} = wp.components;
|
||||
|
||||
/**
|
||||
* Component
|
||||
*/
|
||||
export default class ComponentImageSelector extends Component {
|
||||
constructor() {
|
||||
super( ...arguments );
|
||||
|
||||
this.state = {
|
||||
showPopover: false,
|
||||
};
|
||||
}
|
||||
|
||||
render() {
|
||||
let {
|
||||
className,
|
||||
value,
|
||||
} = this.props;
|
||||
|
||||
const {
|
||||
items = [],
|
||||
onChange,
|
||||
} = this.props;
|
||||
|
||||
className = classnames(
|
||||
'cnvs-component-image-selector',
|
||||
className
|
||||
);
|
||||
|
||||
return (
|
||||
<div className={ className }>
|
||||
{ items && items.length ? (
|
||||
items.map( ( itemData, i ) => {
|
||||
const isDisabled = itemData.isDisabled;
|
||||
const disabledNotice = itemData.disabledNotice;
|
||||
|
||||
const itemClassName = classnames(
|
||||
'cnvs-component-image-selector-item',
|
||||
{
|
||||
'cnvs-component-image-selector-item-active': value === itemData.value,
|
||||
'cnvs-component-image-selector-item-disabled': isDisabled,
|
||||
}
|
||||
);
|
||||
|
||||
return (
|
||||
<div
|
||||
key={ `cnvs-component-image-selector-item-${ itemData.value }` }
|
||||
className={ itemClassName }
|
||||
>
|
||||
<Button
|
||||
onClick={ () => {
|
||||
if ( ! isDisabled ) {
|
||||
onChange( itemData.value );
|
||||
} else {
|
||||
this.setState( { showPopover: itemData.value } );
|
||||
}
|
||||
} }
|
||||
>
|
||||
{ itemData.content }
|
||||
{ this.state.showPopover === itemData.value && disabledNotice ? (
|
||||
<Popover
|
||||
className="cnvs-component-image-selector-item-popover"
|
||||
focusOnMount={ false }
|
||||
onClickOutside={ () => {
|
||||
this.setState( { showPopover: false } );
|
||||
} }
|
||||
>
|
||||
{ disabledNotice }
|
||||
</Popover>
|
||||
) : '' }
|
||||
</Button>
|
||||
<span>{ itemData.label }</span>
|
||||
</div>
|
||||
);
|
||||
} )
|
||||
) : '' }
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
+114
@@ -0,0 +1,114 @@
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import ReactSelect from 'react-select';
|
||||
|
||||
/**
|
||||
* WordPress dependencies
|
||||
*/
|
||||
const {
|
||||
Component,
|
||||
} = wp.element;
|
||||
|
||||
const {
|
||||
withSelect,
|
||||
} = wp.data;
|
||||
|
||||
const {
|
||||
BaseControl,
|
||||
} = wp.components;
|
||||
|
||||
/**
|
||||
* Component
|
||||
*/
|
||||
class ComponentPostFormatsSelectorControl extends Component {
|
||||
constructor() {
|
||||
super( ...arguments );
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
value,
|
||||
label,
|
||||
help,
|
||||
onChange,
|
||||
postFormats,
|
||||
} = this.props;
|
||||
|
||||
if ( ! postFormats.length ) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return (
|
||||
<BaseControl
|
||||
label={ label }
|
||||
help={ help }
|
||||
>
|
||||
<ReactSelect
|
||||
options={ postFormats }
|
||||
value={ ( () => {
|
||||
let result = value ? value.split(',') : [];
|
||||
if ( result && result.length ) {
|
||||
result = result.map( ( name ) => {
|
||||
let thisData = {
|
||||
value: name,
|
||||
label: name,
|
||||
};
|
||||
|
||||
// get label from formats list
|
||||
postFormats.map( ( formatData ) => {
|
||||
if ( formatData.name === name ) {
|
||||
thisData.label = formatData.name;
|
||||
}
|
||||
} )
|
||||
|
||||
return thisData;
|
||||
} );
|
||||
return result;
|
||||
}
|
||||
return [];
|
||||
} )() }
|
||||
isMulti
|
||||
name="filter-by-formats"
|
||||
className="basic-multi-select"
|
||||
classNamePrefix="select"
|
||||
components={ {
|
||||
IndicatorSeparator: () => null,
|
||||
ClearIndicator: () => null,
|
||||
} }
|
||||
onChange={ ( val ) => {
|
||||
let result = '';
|
||||
let slug = '';
|
||||
|
||||
if ( val ) {
|
||||
val.forEach( ( formatData ) => {
|
||||
result += slug + formatData.value;
|
||||
slug = ',';
|
||||
} );
|
||||
}
|
||||
|
||||
onChange( result );
|
||||
} }
|
||||
/>
|
||||
</BaseControl>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default withSelect( ( select, props ) => {
|
||||
const {
|
||||
getThemeSupports,
|
||||
} = select( 'core' );
|
||||
|
||||
const themeSupports = getThemeSupports();
|
||||
const postFormats = themeSupports.formats ? themeSupports.formats : [];
|
||||
|
||||
return {
|
||||
postFormats: postFormats.map( ( format ) => {
|
||||
return {
|
||||
label: format,
|
||||
value: format,
|
||||
};
|
||||
} ),
|
||||
};
|
||||
})( ComponentPostFormatsSelectorControl );
|
||||
@@ -0,0 +1,81 @@
|
||||
/**
|
||||
* WordPress dependencies
|
||||
*/
|
||||
const {
|
||||
Component,
|
||||
} = wp.element;
|
||||
|
||||
const {
|
||||
withSelect,
|
||||
} = wp.data;
|
||||
|
||||
const {
|
||||
SelectControl,
|
||||
} = wp.components;
|
||||
|
||||
const {
|
||||
applyFilters,
|
||||
} = wp.hooks;
|
||||
|
||||
/**
|
||||
* Component
|
||||
*/
|
||||
class ComponentPostTypeSelectorControl extends Component {
|
||||
constructor() {
|
||||
super( ...arguments );
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
value,
|
||||
label,
|
||||
help,
|
||||
onChange,
|
||||
postTypes,
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
<SelectControl
|
||||
label={ label }
|
||||
help={ help }
|
||||
value={ value }
|
||||
options={ applyFilters( 'canvas.selector.postTypes', postTypes ) }
|
||||
onChange={ ( val ) => {
|
||||
onChange( val );
|
||||
} }
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default withSelect( ( select, props ) => {
|
||||
const {
|
||||
getPostTypes,
|
||||
} = select( 'core' );
|
||||
|
||||
const {
|
||||
value,
|
||||
} = props;
|
||||
|
||||
const postTypes = getPostTypes();
|
||||
|
||||
return {
|
||||
postTypes: postTypes ? (
|
||||
postTypes
|
||||
.filter( ( postType ) => {
|
||||
return postType.viewable;
|
||||
} )
|
||||
.map( ( postType ) => {
|
||||
return {
|
||||
label: postType.name,
|
||||
value: postType.slug,
|
||||
};
|
||||
} )
|
||||
) : [
|
||||
{
|
||||
label: value,
|
||||
value: value,
|
||||
},
|
||||
],
|
||||
};
|
||||
})( ComponentPostTypeSelectorControl );
|
||||
@@ -0,0 +1,184 @@
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import ReactSelect from 'react-select';
|
||||
import { debounce } from 'throttle-debounce';
|
||||
|
||||
/**
|
||||
* WordPress dependencies
|
||||
*/
|
||||
const {
|
||||
Component,
|
||||
} = wp.element;
|
||||
|
||||
const {
|
||||
withSelect,
|
||||
} = wp.data;
|
||||
|
||||
const {
|
||||
BaseControl,
|
||||
} = wp.components;
|
||||
|
||||
const cachedPosts = {};
|
||||
|
||||
function maybeCache( postType, newPosts ) {
|
||||
if ( ! newPosts || ! newPosts.length ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( ! cachedPosts[ postType ] ) {
|
||||
cachedPosts[ postType ] = {};
|
||||
}
|
||||
|
||||
newPosts.forEach( ( postData ) => {
|
||||
if ( ! cachedPosts[ postType ][ postData.id ] ) {
|
||||
cachedPosts[ postType ][ postData.id ] = postData;
|
||||
}
|
||||
} );
|
||||
}
|
||||
|
||||
/**
|
||||
* Component
|
||||
*/
|
||||
class ComponentPostsSelectorControl extends Component {
|
||||
constructor() {
|
||||
super( ...arguments );
|
||||
|
||||
this.state = {
|
||||
searchTerm: '',
|
||||
};
|
||||
|
||||
this.updateSearchTerm = debounce( 300, this.updateSearchTerm.bind( this ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Update search term with debounce.
|
||||
*
|
||||
* @param {String} search - search term.
|
||||
*/
|
||||
updateSearchTerm( search ) {
|
||||
this.setState( {
|
||||
searchTerm: search,
|
||||
} );
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
value,
|
||||
label,
|
||||
help,
|
||||
onChange,
|
||||
allPosts,
|
||||
findPosts,
|
||||
} = this.props;
|
||||
|
||||
const {
|
||||
searchTerm,
|
||||
} = this.state;
|
||||
|
||||
const foundPosts = findPosts( searchTerm ) || [];
|
||||
|
||||
return (
|
||||
<BaseControl
|
||||
label={ label }
|
||||
help={ help }
|
||||
>
|
||||
<ReactSelect
|
||||
options={
|
||||
foundPosts.map( ( postData ) => {
|
||||
return {
|
||||
value: postData.id,
|
||||
label: postData.title.raw,
|
||||
};
|
||||
} )
|
||||
}
|
||||
value={ ( () => {
|
||||
let result = value ? value.split(',') : [];
|
||||
if ( result && result.length ) {
|
||||
result = result.map( ( id ) => {
|
||||
let thisData = {
|
||||
value: id,
|
||||
label: id,
|
||||
};
|
||||
|
||||
// get label from categories list
|
||||
if ( allPosts[ id ] ) {
|
||||
thisData.label = allPosts[ id ].title.raw;
|
||||
}
|
||||
|
||||
return thisData;
|
||||
} );
|
||||
return result;
|
||||
}
|
||||
return [];
|
||||
} )() }
|
||||
isMulti
|
||||
name="filter-by-categories"
|
||||
className="basic-multi-select"
|
||||
classNamePrefix="select"
|
||||
components={ {
|
||||
IndicatorSeparator: () => null,
|
||||
ClearIndicator: () => null,
|
||||
} }
|
||||
onInputChange={ ( val ) => {
|
||||
this.updateSearchTerm( val );
|
||||
} }
|
||||
onChange={ ( val ) => {
|
||||
let result = '';
|
||||
let slug = '';
|
||||
|
||||
if ( val ) {
|
||||
val.forEach( ( catData ) => {
|
||||
result += slug + catData.value;
|
||||
slug = ',';
|
||||
} );
|
||||
}
|
||||
|
||||
onChange( result );
|
||||
} }
|
||||
/>
|
||||
</BaseControl>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default withSelect( ( select, props, a, b, c ) => {
|
||||
const {
|
||||
getEntityRecords,
|
||||
} = select( 'core' );
|
||||
|
||||
const {
|
||||
value,
|
||||
postType = 'post',
|
||||
} = props;
|
||||
|
||||
let ids = value ? value.split(',') : [];
|
||||
|
||||
// find non-cached posts and try to retrieve.
|
||||
ids = ids.filter( ( id ) => {
|
||||
return ! cachedPosts[ postType ] || ! cachedPosts[ postType ][ id ];
|
||||
} );
|
||||
|
||||
if ( ids && ids.length ) {
|
||||
const newPosts = getEntityRecords( 'postType', postType, {
|
||||
include: ids,
|
||||
per_page: 100,
|
||||
} );
|
||||
|
||||
maybeCache( postType, newPosts );
|
||||
}
|
||||
|
||||
return {
|
||||
findPosts( search = '' ) {
|
||||
const searchPosts = getEntityRecords( 'postType', postType, {
|
||||
search,
|
||||
per_page: 20,
|
||||
} );
|
||||
|
||||
maybeCache( postType, searchPosts );
|
||||
|
||||
return searchPosts;
|
||||
},
|
||||
allPosts: cachedPosts[ postType ] || {},
|
||||
};
|
||||
})( ComponentPostsSelectorControl );
|
||||
@@ -0,0 +1,255 @@
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import PostTypeSelectorControl from '../post-type-selector-control';
|
||||
import PostFormatsSelectorControl from '../post-formats-selector-control';
|
||||
import CategoriesSelectorControl from '../categories-selector-control';
|
||||
import TagsSelectorControl from '../tags-selector-control';
|
||||
import PostsSelectorControl from '../posts-selector-control';
|
||||
|
||||
/**
|
||||
* WordPress dependencies
|
||||
*/
|
||||
const {
|
||||
__,
|
||||
} = wp.i18n;
|
||||
|
||||
const {
|
||||
Component,
|
||||
Fragment,
|
||||
} = wp.element;
|
||||
|
||||
const {
|
||||
TextControl,
|
||||
SelectControl,
|
||||
} = wp.components;
|
||||
|
||||
/**
|
||||
* Component
|
||||
*/
|
||||
export default class ComponentQueryControl extends Component {
|
||||
constructor() {
|
||||
super( ...arguments );
|
||||
|
||||
this.updateValue = this.updateValue.bind( this );
|
||||
}
|
||||
|
||||
updateValue( newData ) {
|
||||
const {
|
||||
value,
|
||||
onChange,
|
||||
} = this.props;
|
||||
|
||||
const result = {
|
||||
...value,
|
||||
...newData,
|
||||
};
|
||||
|
||||
// reset categories, tags and posts filters if post_type != 'post'
|
||||
if ( 'post' === value.post_type && 'post' !== result.post_type ) {
|
||||
result.categories = '';
|
||||
result.tags = '';
|
||||
result.posts = '';
|
||||
}
|
||||
|
||||
onChange( result );
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
value: {
|
||||
posts_type = 'post',
|
||||
categories = '',
|
||||
tags = '',
|
||||
exclude_categories = '',
|
||||
exclude_tags = '',
|
||||
formats = '',
|
||||
posts = '',
|
||||
offset = '',
|
||||
orderby = 'date',
|
||||
order = 'DESC',
|
||||
time_frame = '',
|
||||
taxonomy = '',
|
||||
terms = '',
|
||||
},
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<PostTypeSelectorControl
|
||||
label={ __( 'Post Type' ) }
|
||||
value={ posts_type }
|
||||
onChange={ ( val ) => {
|
||||
this.updateValue( {
|
||||
posts_type: val,
|
||||
} );
|
||||
} }
|
||||
/>
|
||||
{ 'post' === posts_type ? (
|
||||
<Fragment>
|
||||
<CategoriesSelectorControl
|
||||
label={ __( 'Filter by Categories' ) }
|
||||
value={ categories }
|
||||
onChange={ ( val ) => {
|
||||
this.updateValue( {
|
||||
categories: val,
|
||||
} );
|
||||
} }
|
||||
/>
|
||||
<TagsSelectorControl
|
||||
label={ __( 'Filter by Tags' ) }
|
||||
value={ tags }
|
||||
onChange={ ( val ) => {
|
||||
this.updateValue( {
|
||||
tags: val,
|
||||
} );
|
||||
} }
|
||||
/>
|
||||
</Fragment>
|
||||
) : '' }
|
||||
{ 'post' === posts_type ? (
|
||||
<Fragment>
|
||||
<CategoriesSelectorControl
|
||||
label={ __( 'Exclude Categories' ) }
|
||||
value={ exclude_categories }
|
||||
onChange={ ( val ) => {
|
||||
this.updateValue( {
|
||||
exclude_categories: val,
|
||||
} );
|
||||
} }
|
||||
/>
|
||||
<TagsSelectorControl
|
||||
label={ __( 'Exclude Tags' ) }
|
||||
value={ exclude_tags }
|
||||
onChange={ ( val ) => {
|
||||
this.updateValue( {
|
||||
exclude_tags: val,
|
||||
} );
|
||||
} }
|
||||
/>
|
||||
</Fragment>
|
||||
) : '' }
|
||||
<PostFormatsSelectorControl
|
||||
label={ __( 'Filter by Formats' ) }
|
||||
value={ formats }
|
||||
onChange={ ( val ) => {
|
||||
this.updateValue( {
|
||||
formats: val,
|
||||
} );
|
||||
} }
|
||||
/>
|
||||
<PostsSelectorControl
|
||||
label={ __( 'Filter by Posts' ) }
|
||||
value={ posts }
|
||||
postType={ posts_type }
|
||||
onChange={ ( val ) => {
|
||||
this.updateValue( {
|
||||
posts: val,
|
||||
} );
|
||||
} }
|
||||
/>
|
||||
<TextControl
|
||||
label={ __( 'Offset' ) }
|
||||
value={ offset }
|
||||
onChange={ ( val ) => {
|
||||
this.updateValue( {
|
||||
offset: val,
|
||||
} );
|
||||
} }
|
||||
/>
|
||||
<SelectControl
|
||||
label={ __( 'Order by' ) }
|
||||
value={ orderby }
|
||||
options={ [
|
||||
{
|
||||
label: __( 'Published Date' ),
|
||||
value: 'date',
|
||||
},
|
||||
{
|
||||
label: __( 'Modified Date' ),
|
||||
value: 'modified',
|
||||
},
|
||||
{
|
||||
label: __( 'Title' ),
|
||||
value: 'title',
|
||||
},
|
||||
{
|
||||
label: __( 'Random' ),
|
||||
value: 'rand',
|
||||
},
|
||||
{
|
||||
label: __( 'Views' ),
|
||||
value: 'views',
|
||||
},
|
||||
{
|
||||
label: __( 'Comment Count' ),
|
||||
value: 'comment_count',
|
||||
},
|
||||
{
|
||||
label: __( 'ID' ),
|
||||
value: 'ID',
|
||||
},
|
||||
{
|
||||
label: __( 'Custom' ),
|
||||
value: 'post__in',
|
||||
},
|
||||
] }
|
||||
onChange={ ( val ) => {
|
||||
this.updateValue( {
|
||||
orderby: val,
|
||||
} );
|
||||
} }
|
||||
/>
|
||||
{ 'views' === orderby ? (
|
||||
<TextControl
|
||||
label={ __( 'Filter by Time Frame' ) }
|
||||
help={ __( 'Add period of posts in English. For example: «2 months», «14 days» or even «1 year»' ) }
|
||||
value={ time_frame }
|
||||
onChange={ ( val ) => {
|
||||
this.updateValue( {
|
||||
time_frame: val,
|
||||
} );
|
||||
} }
|
||||
/>
|
||||
) : '' }
|
||||
<SelectControl
|
||||
label={ __( 'Order' ) }
|
||||
value={ order }
|
||||
options={ [
|
||||
{
|
||||
label: __( 'Descending' ),
|
||||
value: 'DESC',
|
||||
},
|
||||
{
|
||||
label: __( 'Ascending' ),
|
||||
value: 'ASC',
|
||||
},
|
||||
] }
|
||||
onChange={ ( val ) => {
|
||||
this.updateValue( {
|
||||
order: val,
|
||||
} );
|
||||
} }
|
||||
/>
|
||||
<TextControl
|
||||
label={ __( 'Filter by Taxonomy' ) }
|
||||
value={ taxonomy }
|
||||
onChange={ ( val ) => {
|
||||
this.updateValue( {
|
||||
taxonomy: val,
|
||||
} );
|
||||
} }
|
||||
/>
|
||||
<TextControl
|
||||
label={ __( 'Filter by Terms' ) }
|
||||
value={ terms }
|
||||
onChange={ ( val ) => {
|
||||
this.updateValue( {
|
||||
terms: val,
|
||||
} );
|
||||
} }
|
||||
/>
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,132 @@
|
||||
/**
|
||||
* Styles
|
||||
*/
|
||||
import './style.scss';
|
||||
|
||||
/**
|
||||
* WordPress dependencies
|
||||
*/
|
||||
const {
|
||||
Component,
|
||||
} = wp.element;
|
||||
|
||||
const {
|
||||
TextControl,
|
||||
Button,
|
||||
} = wp.components;
|
||||
|
||||
const allRadius = [ 'topLeft', 'topRight', 'bottomLeft', 'bottomRight'];
|
||||
|
||||
/**
|
||||
* Component
|
||||
*/
|
||||
export default class ComponentRadius extends Component {
|
||||
constructor() {
|
||||
super( ...arguments );
|
||||
|
||||
this.updateRadius = this.updateRadius.bind( this );
|
||||
}
|
||||
|
||||
/**
|
||||
* Update number value.
|
||||
*
|
||||
* @param {String} name - number name.
|
||||
* @param {String} prefix - type prefix.
|
||||
* @param {String} suffix - responsive suffix.
|
||||
* @param {String} val - new value.
|
||||
*/
|
||||
updateRadius( name, prefix = '', suffix = '', val ) {
|
||||
const {
|
||||
onChange,
|
||||
} = this.props;
|
||||
|
||||
// TextControl return string value, we need to convert to int manually.
|
||||
val = parseInt( val, 10 );
|
||||
|
||||
if ( Number.isNaN( val ) ) {
|
||||
val = undefined;
|
||||
}
|
||||
|
||||
const updateAttrs = {
|
||||
[ prefix + name.charAt(0).toUpperCase() + name.slice(1) + suffix ]: val,
|
||||
};
|
||||
|
||||
// Change all linked values.
|
||||
if ( this.props.link ) {
|
||||
allRadius.forEach( ( radius ) => {
|
||||
updateAttrs[ prefix + radius.charAt(0).toUpperCase() + radius.slice(1) + suffix ] = val;
|
||||
} )
|
||||
}
|
||||
|
||||
onChange( updateAttrs );
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
prefix = '',
|
||||
suffix = '',
|
||||
units = [ 'px', 'em' ],
|
||||
onChange,
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
<div className="cnvs-component-radius">
|
||||
<div className="cnvs-component-radius-units">
|
||||
{ units.map( ( unit ) => {
|
||||
return (
|
||||
<Button
|
||||
isPrimary={ unit === 'px' ? ! this.props.unit : this.props.unit === unit }
|
||||
onClick={ () => {
|
||||
onChange( {
|
||||
[ prefix + 'Unit' + suffix ]: unit === 'px' ? '' : unit,
|
||||
} );
|
||||
} }
|
||||
>
|
||||
{ unit }
|
||||
</Button>
|
||||
);
|
||||
} ) }
|
||||
</div>
|
||||
<div className="cnvs-component-radius-wrap">
|
||||
{ allRadius.map( ( radius ) => {
|
||||
let val = this.props[ radius ];
|
||||
|
||||
if ( typeof val == 'undefined' ) {
|
||||
val = '';
|
||||
}
|
||||
|
||||
return (
|
||||
<TextControl
|
||||
key={ radius }
|
||||
type="number"
|
||||
value={ val }
|
||||
onChange={ ( val ) => this.updateRadius( radius, prefix, suffix, val ) }
|
||||
autoComplete="off"
|
||||
/>
|
||||
);
|
||||
} ) }
|
||||
<Button
|
||||
isDefault
|
||||
isPrimary={ this.props.link }
|
||||
onClick={ () => {
|
||||
if ( this.props.link ) {
|
||||
onChange( {
|
||||
[ prefix + 'Link' + suffix ]: false,
|
||||
} );
|
||||
} else {
|
||||
onChange( {
|
||||
[ prefix + 'TopRight' + suffix ]: this.props.topLeft,
|
||||
[ prefix + 'BottomLeft' + suffix ]: this.props.topLeft,
|
||||
[ prefix + 'BottomRight' + suffix ]: this.props.topLeft,
|
||||
[ prefix + 'Link' + suffix ]: true,
|
||||
} );
|
||||
}
|
||||
} }
|
||||
>
|
||||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M18.8379 8.23251L13.5352 13.5352C12.0703 15.0001 9.69727 15.0001 8.23242 13.5352L6.46484 11.7677L8.23242 10.0001L10 11.7677C10.4883 12.2547 11.2805 12.2559 11.7676 11.7677L17.0703 6.46493C17.5574 5.97727 17.5574 5.18442 17.0703 4.69672L15.3027 2.92914C14.8157 2.44207 14.0222 2.44207 13.5352 2.92914L11.6418 4.82247C10.7641 4.3061 9.76684 4.08454 8.7793 4.15106L11.7676 1.16157C13.2324 -0.302655 15.6067 -0.302655 17.0703 1.16157L18.8379 2.92914C20.3027 4.3934 20.3027 6.76829 18.8379 8.23251ZM8.35695 15.1783L6.46484 17.0704C5.97656 17.5587 5.18434 17.5575 4.69727 17.0704L2.92969 15.3028C2.44141 14.8158 2.44141 14.0235 2.92969 13.5352L8.23242 8.23251C8.71949 7.74544 9.51293 7.74544 10 8.23251L11.7676 10.0001L13.5352 8.23251L11.7676 6.46493C10.3027 5.00071 7.92969 5.00071 6.46484 6.46493L1.16211 11.7677C-0.302734 13.2325 -0.302734 15.6068 1.16211 17.0704L2.92969 18.838C4.39332 20.3028 6.76758 20.3028 8.23242 18.838L11.2207 15.8497C10.2332 15.9156 9.23828 15.6935 8.35695 15.1783Z" fill="currentColor"/></svg>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,162 @@
|
||||
/**
|
||||
* Styles
|
||||
*/
|
||||
import './style.scss';
|
||||
|
||||
/**
|
||||
* WordPress dependencies
|
||||
*/
|
||||
import classnames from 'classnames';
|
||||
|
||||
const {
|
||||
canvasBreakpoints,
|
||||
} = window;
|
||||
|
||||
const {
|
||||
Component,
|
||||
createRef,
|
||||
} = wp.element;
|
||||
|
||||
const {
|
||||
compose,
|
||||
} = wp.compose;
|
||||
|
||||
const {
|
||||
withSelect,
|
||||
withDispatch,
|
||||
} = wp.data;
|
||||
|
||||
/**
|
||||
* Component
|
||||
*/
|
||||
class ComponentResponsiveDropdown extends Component {
|
||||
constructor() {
|
||||
super( ...arguments );
|
||||
|
||||
this.state = {
|
||||
isOpened: false,
|
||||
};
|
||||
|
||||
this.componentRef = createRef();
|
||||
|
||||
this.handleClickOutside = this.handleClickOutside.bind(this);
|
||||
this.getButton = this.getButton.bind( this );
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
document.addEventListener( 'mousedown', this.handleClickOutside );
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
document.removeEventListener( 'mousedown', this.handleClickOutside );
|
||||
}
|
||||
|
||||
/**
|
||||
* Hide opened dropdown
|
||||
*/
|
||||
handleClickOutside( e ) {
|
||||
if ( this.componentRef && this.componentRef.current && this.componentRef.current.contains( e.target ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.setState( {
|
||||
isOpened: false,
|
||||
} );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get responsive button
|
||||
*
|
||||
* @param {string} name - breakpoint name.
|
||||
* @param {function} onClick - click callback
|
||||
*
|
||||
* @return {JSX}
|
||||
*/
|
||||
getButton( name, onClick ) {
|
||||
const {
|
||||
breakpoint,
|
||||
} = this.props;
|
||||
|
||||
name = name || 'desktop';
|
||||
|
||||
if ( typeof canvasBreakpoints[ name ] === 'undefined' ) {
|
||||
return '';
|
||||
}
|
||||
|
||||
const info = canvasBreakpoints[ name ];
|
||||
|
||||
return (
|
||||
<button
|
||||
key={ name }
|
||||
className={ classnames(
|
||||
'cnvs-component-breakpoints-dropdown-item',
|
||||
breakpoint === name ? 'cnvs-component-breakpoints-dropdown-item-active' : ''
|
||||
) }
|
||||
onClick={ () => {
|
||||
onClick( name === 'desktop' ? '' : name );
|
||||
} }
|
||||
dangerouslySetInnerHTML={ { __html: info.icon } }
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
breakpoint,
|
||||
updateBreakpoint,
|
||||
} = this.props;
|
||||
|
||||
const {
|
||||
isOpened,
|
||||
} = this.state;
|
||||
|
||||
return (
|
||||
<div
|
||||
className="cnvs-component-breakpoints"
|
||||
ref={ this.componentRef }
|
||||
>
|
||||
{ this.getButton( breakpoint, () => {
|
||||
this.setState( {
|
||||
isOpened: ! isOpened,
|
||||
} );
|
||||
} ) }
|
||||
{ isOpened ? (
|
||||
<div className="cnvs-component-breakpoints-dropdown">
|
||||
{ Object.keys( canvasBreakpoints ).map( ( name ) => {
|
||||
return (
|
||||
this.getButton( name, ( breakpoint ) => {
|
||||
updateBreakpoint( breakpoint );
|
||||
|
||||
this.setState( {
|
||||
isOpened: false,
|
||||
} );
|
||||
} )
|
||||
);
|
||||
} ) }
|
||||
</div>
|
||||
) : '' }
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default compose( [
|
||||
withSelect( ( select ) => {
|
||||
const {
|
||||
getBreakpoint,
|
||||
} = select( 'canvas/breakpoint' );
|
||||
|
||||
return {
|
||||
breakpoint: getBreakpoint(),
|
||||
};
|
||||
} ),
|
||||
withDispatch( ( dispatch ) => {
|
||||
const {
|
||||
updateBreakpoint,
|
||||
} = dispatch( 'canvas/breakpoint' );
|
||||
|
||||
return {
|
||||
updateBreakpoint,
|
||||
};
|
||||
} ),
|
||||
] )( ComponentResponsiveDropdown );
|
||||
@@ -0,0 +1,71 @@
|
||||
/**
|
||||
* WordPress dependencies
|
||||
*/
|
||||
const {
|
||||
Component,
|
||||
Fragment,
|
||||
} = wp.element;
|
||||
|
||||
const {
|
||||
compose,
|
||||
} = wp.compose;
|
||||
|
||||
const {
|
||||
withSelect,
|
||||
withDispatch,
|
||||
} = wp.data;
|
||||
|
||||
import ComponentResponsiveDropdown from '../responsive-dropdown';
|
||||
|
||||
/**
|
||||
* Component
|
||||
*/
|
||||
class ComponentResponsiveWrapper extends Component {
|
||||
constructor() {
|
||||
super( ...arguments );
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
breakpoint,
|
||||
children,
|
||||
} = this.props;
|
||||
|
||||
const data = {
|
||||
responsiveSuffix: '',
|
||||
breakpoint,
|
||||
ComponentResponsiveDropdown,
|
||||
};
|
||||
|
||||
if ( breakpoint && 'desktop' !== breakpoint ) {
|
||||
data.responsiveSuffix = '_' + breakpoint;
|
||||
}
|
||||
|
||||
return (
|
||||
<Fragment key={ `responsive-wrapper-${ breakpoint }` }>
|
||||
{ children( data ) }
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default compose( [
|
||||
withSelect( ( select ) => {
|
||||
const {
|
||||
getBreakpoint,
|
||||
} = select( 'canvas/breakpoint' );
|
||||
|
||||
return {
|
||||
breakpoint: getBreakpoint(),
|
||||
};
|
||||
} ),
|
||||
withDispatch( ( dispatch ) => {
|
||||
const {
|
||||
updateBreakpoint,
|
||||
} = dispatch( 'canvas/breakpoint' );
|
||||
|
||||
return {
|
||||
updateBreakpoint,
|
||||
};
|
||||
} ),
|
||||
] )( ComponentResponsiveWrapper );
|
||||
@@ -0,0 +1,163 @@
|
||||
/**
|
||||
* Styles
|
||||
*/
|
||||
import './style.scss';
|
||||
|
||||
/**
|
||||
* WordPress dependencies
|
||||
*/
|
||||
import classnames from 'classnames';
|
||||
|
||||
const {
|
||||
canvasSchemes,
|
||||
} = window;
|
||||
|
||||
const {
|
||||
Component,
|
||||
createRef,
|
||||
} = wp.element;
|
||||
|
||||
const {
|
||||
compose,
|
||||
} = wp.compose;
|
||||
|
||||
const {
|
||||
withSelect,
|
||||
withDispatch,
|
||||
} = wp.data;
|
||||
|
||||
/**
|
||||
* Component
|
||||
*/
|
||||
class ComponentSchemeDropdown extends Component {
|
||||
constructor() {
|
||||
super( ...arguments );
|
||||
|
||||
this.state = {
|
||||
isOpened: false,
|
||||
};
|
||||
|
||||
this.componentRef = createRef();
|
||||
|
||||
this.handleClickOutside = this.handleClickOutside.bind(this);
|
||||
this.getButton = this.getButton.bind( this );
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
document.addEventListener( 'mousedown', this.handleClickOutside );
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
document.removeEventListener( 'mousedown', this.handleClickOutside );
|
||||
}
|
||||
|
||||
/**
|
||||
* Hide opened dropdown
|
||||
*/
|
||||
handleClickOutside( e ) {
|
||||
if ( this.componentRef && this.componentRef.current && this.componentRef.current.contains( e.target ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.setState( {
|
||||
isOpened: false,
|
||||
} );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get responsive button
|
||||
*
|
||||
* @param {string} name - scheme name.
|
||||
* @param {function} onClick - click callback
|
||||
*
|
||||
* @return {JSX}
|
||||
*/
|
||||
getButton( name, onClick ) {
|
||||
const {
|
||||
scheme,
|
||||
} = this.props;
|
||||
|
||||
name = name || 'default';
|
||||
|
||||
if ( typeof canvasSchemes[ name ] === 'undefined' ) {
|
||||
return '';
|
||||
}
|
||||
|
||||
const info = canvasSchemes[ name ];
|
||||
|
||||
return (
|
||||
<button
|
||||
key={ name }
|
||||
className={ classnames(
|
||||
'cnvs-component-schemes-dropdown-item',
|
||||
scheme === name ? 'cnvs-component-schemes-dropdown-item-active' : ''
|
||||
) }
|
||||
onClick={ () => {
|
||||
onClick( name === 'default' ? '' : name );
|
||||
} }
|
||||
dangerouslySetInnerHTML={ { __html: info.icon } }
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
scheme,
|
||||
updateScheme,
|
||||
} = this.props;
|
||||
|
||||
const {
|
||||
isOpened,
|
||||
} = this.state;
|
||||
|
||||
return (
|
||||
<div
|
||||
className="cnvs-component-schemes"
|
||||
ref={ this.componentRef }
|
||||
>
|
||||
{ this.getButton( scheme, () => {
|
||||
this.setState( {
|
||||
isOpened: ! isOpened,
|
||||
} );
|
||||
} ) }
|
||||
{ isOpened ? (
|
||||
<div className="cnvs-component-schemes-dropdown">
|
||||
{ Object.keys( canvasSchemes ).map( ( name ) => {
|
||||
return (
|
||||
this.getButton( name, ( scheme ) => {
|
||||
|
||||
updateScheme( scheme );
|
||||
|
||||
this.setState( {
|
||||
isOpened: false,
|
||||
} );
|
||||
} )
|
||||
);
|
||||
} ) }
|
||||
</div>
|
||||
) : '' }
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default compose( [
|
||||
withSelect( ( select ) => {
|
||||
const {
|
||||
getScheme,
|
||||
} = select( 'canvas/scheme' );
|
||||
|
||||
return {
|
||||
scheme: getScheme(),
|
||||
};
|
||||
} ),
|
||||
withDispatch( ( dispatch ) => {
|
||||
const {
|
||||
updateScheme,
|
||||
} = dispatch( 'canvas/scheme' );
|
||||
|
||||
return {
|
||||
updateScheme,
|
||||
};
|
||||
} ),
|
||||
] )( ComponentSchemeDropdown );
|
||||
@@ -0,0 +1,71 @@
|
||||
/**
|
||||
* WordPress dependencies
|
||||
*/
|
||||
const {
|
||||
Component,
|
||||
Fragment,
|
||||
} = wp.element;
|
||||
|
||||
const {
|
||||
compose,
|
||||
} = wp.compose;
|
||||
|
||||
const {
|
||||
withSelect,
|
||||
withDispatch,
|
||||
} = wp.data;
|
||||
|
||||
import ComponentSchemeDropdown from '../scheme-dropdown';
|
||||
|
||||
/**
|
||||
* Component
|
||||
*/
|
||||
class ComponentSchemeWrapper extends Component {
|
||||
constructor() {
|
||||
super(...arguments);
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
scheme,
|
||||
children,
|
||||
} = this.props;
|
||||
|
||||
const data = {
|
||||
schemeSuffix: '',
|
||||
scheme,
|
||||
ComponentSchemeDropdown,
|
||||
};
|
||||
|
||||
if (scheme && 'default' !== scheme) {
|
||||
data.schemeSuffix = '_' + scheme;
|
||||
}
|
||||
|
||||
return (
|
||||
<Fragment key={`scheme-wrapper-${scheme}`}>
|
||||
{children(data)}
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default compose([
|
||||
withSelect((select) => {
|
||||
const {
|
||||
getScheme,
|
||||
} = select('canvas/scheme');
|
||||
|
||||
return {
|
||||
scheme: getScheme(),
|
||||
};
|
||||
}),
|
||||
withDispatch((dispatch) => {
|
||||
const {
|
||||
updateScheme,
|
||||
} = dispatch('canvas/scheme');
|
||||
|
||||
return {
|
||||
updateScheme,
|
||||
};
|
||||
}),
|
||||
])(ComponentSchemeWrapper);
|
||||
@@ -0,0 +1,46 @@
|
||||
/**
|
||||
* WordPress dependencies
|
||||
*/
|
||||
const { useMemo } = wp.element;
|
||||
const { withSelect } = wp.data;
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import ServerSideRender from './server-side-render';
|
||||
|
||||
/**
|
||||
* Constants
|
||||
*/
|
||||
const EMPTY_OBJECT = {};
|
||||
|
||||
export default withSelect(
|
||||
( select ) => {
|
||||
const coreEditorSelect = select( 'core/editor' );
|
||||
if ( coreEditorSelect ) {
|
||||
const currentPostId = coreEditorSelect.getCurrentPostId();
|
||||
if ( currentPostId ) {
|
||||
return {
|
||||
currentPostId,
|
||||
};
|
||||
}
|
||||
}
|
||||
return EMPTY_OBJECT;
|
||||
}
|
||||
)(
|
||||
( { urlQueryArgs = EMPTY_OBJECT, currentPostId, ...props } ) => {
|
||||
const newUrlQueryArgs = useMemo( () => {
|
||||
if ( ! currentPostId ) {
|
||||
return urlQueryArgs;
|
||||
}
|
||||
return {
|
||||
post_id: currentPostId,
|
||||
...urlQueryArgs,
|
||||
};
|
||||
}, [ currentPostId, urlQueryArgs ] );
|
||||
|
||||
return (
|
||||
<ServerSideRender urlQueryArgs={ newUrlQueryArgs } { ...props } />
|
||||
);
|
||||
}
|
||||
);
|
||||
+210
@@ -0,0 +1,210 @@
|
||||
/*
|
||||
* This is a copy of Gutenberg component https://github.com/WordPress/gutenberg/blob/master/packages/server-side-render/src/server-side-render.js
|
||||
* With the changes in our component:
|
||||
* - callbacks before and after new content render
|
||||
* - slightly different rendering process - don't remove previously rendered content. So, we will not see page jumps after every attribute change.
|
||||
*/
|
||||
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import classnames from 'classnames';
|
||||
const { isEqual, debounce } = window.lodash;
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import './style.scss';
|
||||
|
||||
/**
|
||||
* WordPress dependencies
|
||||
*/
|
||||
const {
|
||||
Component,
|
||||
RawHTML,
|
||||
} = wp.element;
|
||||
|
||||
const { __, sprintf } = wp.i18n;
|
||||
|
||||
const apiFetch = wp.apiFetch;
|
||||
|
||||
const { addQueryArgs } = wp.url;
|
||||
|
||||
const {
|
||||
Placeholder,
|
||||
Spinner,
|
||||
} = wp.components;
|
||||
|
||||
const {
|
||||
doAction,
|
||||
} = wp.hooks;
|
||||
|
||||
export function rendererPath( block, attributes = null, urlQueryArgs = {} ) {
|
||||
return addQueryArgs( `/wp/v2/canvas-renderer/${ block }`, {
|
||||
context: 'edit',
|
||||
...( null !== attributes ? { attributes } : {} ),
|
||||
...urlQueryArgs,
|
||||
} );
|
||||
}
|
||||
|
||||
export class ServerSideRender extends Component {
|
||||
constructor( props ) {
|
||||
super( props );
|
||||
this.state = {
|
||||
response: null,
|
||||
prevResponse: null,
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.isStillMounted = true;
|
||||
this.fetch( this.props );
|
||||
// Only debounce once the initial fetch occurs to ensure that the first
|
||||
// renders show data as soon as possible.
|
||||
this.fetch = debounce( this.fetch, 500 );
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.isStillMounted = false;
|
||||
}
|
||||
|
||||
componentDidUpdate( prevProps ) {
|
||||
const prevAttributes = prevProps.attributes;
|
||||
const curAttributes = this.props.attributes;
|
||||
|
||||
if ( ! isEqual( prevAttributes, curAttributes ) ) {
|
||||
this.fetch( this.props );
|
||||
}
|
||||
}
|
||||
|
||||
fetch( props ) {
|
||||
if ( ! this.isStillMounted ) {
|
||||
return;
|
||||
}
|
||||
|
||||
const {
|
||||
block,
|
||||
attributes = null,
|
||||
urlQueryArgs = {},
|
||||
onBeforeChange = () => {},
|
||||
onChange = () => {},
|
||||
} = props;
|
||||
|
||||
if ( null !== this.state.response ) {
|
||||
this.setState( {
|
||||
response: null,
|
||||
prevResponse: this.state.response,
|
||||
} );
|
||||
}
|
||||
|
||||
const path = rendererPath( block );
|
||||
|
||||
// Store the latest fetch request so that when we process it, we can
|
||||
// check if it is the current request, to avoid race conditions on slow networks.
|
||||
const fetchRequest = this.currentFetchRequest = apiFetch( { method: 'POST', path, data: { attributes: attributes } } )
|
||||
.then( ( response ) => {
|
||||
if ( this.isStillMounted && fetchRequest === this.currentFetchRequest && response ) {
|
||||
onBeforeChange();
|
||||
doAction( 'canvas.components.serverSideRender.onBeforeChange', this.props );
|
||||
|
||||
this.setState( {
|
||||
response: response.rendered,
|
||||
prevResponse: null,
|
||||
}, () => {
|
||||
onChange();
|
||||
doAction( 'canvas.components.serverSideRender.onChange', this.props );
|
||||
} );
|
||||
}
|
||||
} )
|
||||
.catch( ( error ) => {
|
||||
if ( this.isStillMounted && fetchRequest === this.currentFetchRequest ) {
|
||||
onBeforeChange();
|
||||
doAction( 'canvas.components.serverSideRender.onBeforeChange', this.props );
|
||||
|
||||
this.setState( {
|
||||
response: {
|
||||
error: true,
|
||||
errorMsg: error.message,
|
||||
},
|
||||
prevResponse: null,
|
||||
}, () => {
|
||||
onChange();
|
||||
doAction( 'canvas.components.serverSideRender.onChange', this.props );
|
||||
} );
|
||||
}
|
||||
} );
|
||||
return fetchRequest;
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
response,
|
||||
prevResponse,
|
||||
} = this.state;
|
||||
|
||||
let { className } = this.props;
|
||||
|
||||
className = classnames(
|
||||
className,
|
||||
'cnvs-component-server-side-render'
|
||||
);
|
||||
|
||||
if ( response === '' ) {
|
||||
return (
|
||||
<Placeholder
|
||||
className={ className }
|
||||
>
|
||||
{ __( 'Block rendered as empty.' ) }
|
||||
</Placeholder>
|
||||
);
|
||||
} else if ( ! response && prevResponse ) {
|
||||
className = classnames(
|
||||
className,
|
||||
'cnvs-component-server-side-render-loading'
|
||||
);
|
||||
|
||||
return (
|
||||
<div className={ className }>
|
||||
<Spinner />
|
||||
<RawHTML
|
||||
key="html"
|
||||
className="cnvs-component-server-side-render-content"
|
||||
>
|
||||
{ prevResponse }
|
||||
</RawHTML>
|
||||
</div>
|
||||
);
|
||||
} else if ( ! response ) {
|
||||
return (
|
||||
<Placeholder
|
||||
className={ className }
|
||||
>
|
||||
<Spinner />
|
||||
</Placeholder>
|
||||
);
|
||||
} else if ( response.error ) {
|
||||
// translators: %s: error message describing the problem
|
||||
const errorMessage = sprintf( __( 'Error loading block: %s' ), response.errorMsg );
|
||||
return (
|
||||
<Placeholder
|
||||
className={ className }
|
||||
>
|
||||
{ errorMessage }
|
||||
</Placeholder>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={ className }>
|
||||
<RawHTML
|
||||
key="html"
|
||||
className="cnvs-component-server-side-render-content"
|
||||
>
|
||||
{ response }
|
||||
</RawHTML>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default ServerSideRender;
|
||||
@@ -0,0 +1,133 @@
|
||||
/**
|
||||
* Styles
|
||||
*/
|
||||
import './style.scss';
|
||||
|
||||
/**
|
||||
* WordPress dependencies
|
||||
*/
|
||||
const {
|
||||
Component,
|
||||
} = wp.element;
|
||||
|
||||
const {
|
||||
TextControl,
|
||||
Button,
|
||||
} = wp.components;
|
||||
|
||||
const allSpacings = [ 'top', 'bottom', 'left', 'right' ];
|
||||
|
||||
/**
|
||||
* Component
|
||||
*/
|
||||
export default class ComponentSpacings extends Component {
|
||||
constructor() {
|
||||
super( ...arguments );
|
||||
|
||||
this.updateSpacing = this.updateSpacing.bind( this );
|
||||
}
|
||||
|
||||
/**
|
||||
* Update spacing value.
|
||||
*
|
||||
* @param {String} name - spacing name.
|
||||
* @param {String} prefix - type prefix.
|
||||
* @param {String} suffix - responsive suffix.
|
||||
* @param {String} val - new value.
|
||||
*/
|
||||
updateSpacing( name, prefix = '', suffix = '', val ) {
|
||||
const {
|
||||
onChange,
|
||||
} = this.props;
|
||||
|
||||
// TextControl return string value, we need to convert to int manually.
|
||||
val = parseInt( val, 10 );
|
||||
|
||||
if ( Number.isNaN( val ) ) {
|
||||
val = undefined;
|
||||
}
|
||||
|
||||
const updateAttrs = {
|
||||
[ prefix + name.charAt(0).toUpperCase() + name.slice(1) + suffix ]: val,
|
||||
};
|
||||
|
||||
// Change all linked values.
|
||||
if ( this.props.link ) {
|
||||
allSpacings.forEach( ( spacing ) => {
|
||||
updateAttrs[ prefix + spacing.charAt(0).toUpperCase() + spacing.slice(1) + suffix ] = val;
|
||||
} )
|
||||
}
|
||||
|
||||
onChange( updateAttrs );
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
prefix = '',
|
||||
suffix = '',
|
||||
units = [ 'px', 'em' ],
|
||||
onChange,
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
<div className="cnvs-component-spacings">
|
||||
<div className="cnvs-component-spacings-units">
|
||||
{ units.map( ( unit ) => {
|
||||
return (
|
||||
<Button
|
||||
isPrimary={ unit === 'px' ? ! this.props.unit : this.props.unit === unit }
|
||||
onClick={ () => {
|
||||
onChange( {
|
||||
[ prefix + 'Unit' + suffix ]: unit === 'px' ? '' : unit,
|
||||
} );
|
||||
} }
|
||||
>
|
||||
{ unit }
|
||||
</Button>
|
||||
);
|
||||
} ) }
|
||||
</div>
|
||||
<div className="cnvs-component-spacings-wrap">
|
||||
{ allSpacings.map( ( spacing ) => {
|
||||
let val = this.props[ spacing ];
|
||||
|
||||
if ( typeof val == 'undefined' ) {
|
||||
val = '';
|
||||
}
|
||||
|
||||
return (
|
||||
<TextControl
|
||||
key={ spacing }
|
||||
type="number"
|
||||
label={ spacing }
|
||||
value={ val }
|
||||
onChange={ ( val ) => this.updateSpacing( spacing, prefix, suffix, val ) }
|
||||
autoComplete="off"
|
||||
/>
|
||||
);
|
||||
} ) }
|
||||
<Button
|
||||
isDefault
|
||||
isPrimary={ this.props.link }
|
||||
onClick={ () => {
|
||||
if ( this.props.link ) {
|
||||
onChange( {
|
||||
[ prefix + 'Link' + suffix ]: false,
|
||||
} );
|
||||
} else {
|
||||
onChange( {
|
||||
[ prefix + 'Bottom' + suffix ]: this.props.top,
|
||||
[ prefix + 'Left' + suffix ]: this.props.top,
|
||||
[ prefix + 'Right' + suffix ]: this.props.top,
|
||||
[ prefix + 'Link' + suffix ]: true,
|
||||
} );
|
||||
}
|
||||
} }
|
||||
>
|
||||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M18.8379 8.23251L13.5352 13.5352C12.0703 15.0001 9.69727 15.0001 8.23242 13.5352L6.46484 11.7677L8.23242 10.0001L10 11.7677C10.4883 12.2547 11.2805 12.2559 11.7676 11.7677L17.0703 6.46493C17.5574 5.97727 17.5574 5.18442 17.0703 4.69672L15.3027 2.92914C14.8157 2.44207 14.0222 2.44207 13.5352 2.92914L11.6418 4.82247C10.7641 4.3061 9.76684 4.08454 8.7793 4.15106L11.7676 1.16157C13.2324 -0.302655 15.6067 -0.302655 17.0703 1.16157L18.8379 2.92914C20.3027 4.3934 20.3027 6.76829 18.8379 8.23251ZM8.35695 15.1783L6.46484 17.0704C5.97656 17.5587 5.18434 17.5575 4.69727 17.0704L2.92969 15.3028C2.44141 14.8158 2.44141 14.0235 2.92969 13.5352L8.23242 8.23251C8.71949 7.74544 9.51293 7.74544 10 8.23251L11.7676 10.0001L13.5352 8.23251L11.7676 6.46493C10.3027 5.00071 7.92969 5.00071 6.46484 6.46493L1.16211 11.7677C-0.302734 13.2325 -0.302734 15.6068 1.16211 17.0704L2.92969 18.838C4.39332 20.3028 6.76758 20.3028 8.23242 18.838L11.2207 15.8497C10.2332 15.9156 9.23828 15.6935 8.35695 15.1783Z" fill="currentColor"/></svg>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,185 @@
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import ReactSelect from 'react-select';
|
||||
import { debounce } from 'throttle-debounce';
|
||||
|
||||
/**
|
||||
* WordPress dependencies
|
||||
*/
|
||||
const {
|
||||
Component,
|
||||
} = wp.element;
|
||||
|
||||
const {
|
||||
withSelect,
|
||||
} = wp.data;
|
||||
|
||||
const {
|
||||
BaseControl,
|
||||
} = wp.components;
|
||||
|
||||
const cachedTags = {};
|
||||
|
||||
function maybeCache( newTags ) {
|
||||
if ( ! newTags || ! newTags.length ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( ! cachedTags ) {
|
||||
cachedTags = {};
|
||||
}
|
||||
|
||||
newTags.forEach( ( postData ) => {
|
||||
if ( ! cachedTags[ postData.id ] ) {
|
||||
cachedTags[ postData.id ] = postData;
|
||||
}
|
||||
} );
|
||||
}
|
||||
|
||||
/**
|
||||
* Component
|
||||
*/
|
||||
class ComponentTagsSelectorControl extends Component {
|
||||
constructor() {
|
||||
super( ...arguments );
|
||||
|
||||
this.state = {
|
||||
searchTerm: '',
|
||||
};
|
||||
|
||||
this.updateSearchTerm = debounce( 300, this.updateSearchTerm.bind( this ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Update search term with debounce.
|
||||
*
|
||||
* @param {String} search - search term.
|
||||
*/
|
||||
updateSearchTerm( search ) {
|
||||
this.setState( {
|
||||
searchTerm: search,
|
||||
} );
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
value,
|
||||
label,
|
||||
help,
|
||||
onChange,
|
||||
allTags,
|
||||
findTags,
|
||||
} = this.props;
|
||||
|
||||
const {
|
||||
searchTerm,
|
||||
} = this.state;
|
||||
|
||||
const foundTags = findTags( searchTerm ) || [];
|
||||
|
||||
return (
|
||||
<BaseControl
|
||||
label={ label }
|
||||
help={ help }
|
||||
>
|
||||
<ReactSelect
|
||||
options={
|
||||
foundTags.map( ( catData ) => {
|
||||
return {
|
||||
value: catData.slug,
|
||||
label: catData.name,
|
||||
};
|
||||
} )
|
||||
}
|
||||
value={ ( () => {
|
||||
let result = value ? value.split(',') : [];
|
||||
if ( result && result.length ) {
|
||||
result = result.map( ( name ) => {
|
||||
let thisData = {
|
||||
value: name,
|
||||
label: name,
|
||||
};
|
||||
|
||||
// get label from tags list
|
||||
for (var id in allTags ) {
|
||||
if ( name === allTags[id].slug ) {
|
||||
thisData.label = allTags[id].name;
|
||||
}
|
||||
}
|
||||
|
||||
return thisData;
|
||||
} );
|
||||
return result;
|
||||
}
|
||||
return [];
|
||||
} )() }
|
||||
isMulti
|
||||
name="filter-by-categories"
|
||||
className="basic-multi-select"
|
||||
classNamePrefix="select"
|
||||
components={ {
|
||||
IndicatorSeparator: () => null,
|
||||
ClearIndicator: () => null,
|
||||
} }
|
||||
onInputChange={ ( val ) => {
|
||||
this.updateSearchTerm( val );
|
||||
} }
|
||||
onChange={ ( val ) => {
|
||||
let result = '';
|
||||
let slug = '';
|
||||
|
||||
if ( val ) {
|
||||
val.forEach( ( catData ) => {
|
||||
result += slug + catData.value;
|
||||
slug = ',';
|
||||
} );
|
||||
}
|
||||
|
||||
onChange( result );
|
||||
} }
|
||||
/>
|
||||
</BaseControl>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default withSelect( ( select, props, a, b, c ) => {
|
||||
const {
|
||||
getEntityRecords,
|
||||
} = select( 'core' );
|
||||
|
||||
const {
|
||||
value,
|
||||
} = props;
|
||||
|
||||
let slugs = value ? value.split(',') : [];
|
||||
|
||||
// find non-cached tags and try to retrieve.
|
||||
slugs = slugs.filter( ( slug ) => {
|
||||
return ! cachedTags || ! cachedTags[ slug ];
|
||||
} );
|
||||
|
||||
if ( slugs && slugs.length ) {
|
||||
const newTags = getEntityRecords( 'taxonomy', 'post_tag', {
|
||||
slug: slugs,
|
||||
per_page: 100,
|
||||
} );
|
||||
|
||||
maybeCache( newTags );
|
||||
}
|
||||
|
||||
return {
|
||||
findTags( search = '' ) {
|
||||
const searchTags = getEntityRecords( 'taxonomy', 'post_tag', {
|
||||
search,
|
||||
per_page: 20,
|
||||
} );
|
||||
|
||||
maybeCache( searchTags );
|
||||
|
||||
return searchTags;
|
||||
},
|
||||
allTags: cachedTags || {},
|
||||
};
|
||||
})( ComponentTagsSelectorControl );
|
||||
Reference in New Issue
Block a user