import './style.scss'; import classnames from 'classnames/dedupe'; import { __experimentalUnitControl, BaseControl, Button, ButtonGroup, CheckboxControl, Notice, PanelBody, RadioControl, RangeControl, TextareaControl, TextControl, ToggleControl, Tooltip, UnitControl as __stableUnitControl, } from '@wordpress/components'; import { Component, RawHTML, useEffect, useRef, useState, } from '@wordpress/element'; import { applyFilters } from '@wordpress/hooks'; import { __ } from '@wordpress/i18n'; import controlConditionCheck from '../../utils/control-condition-check'; import controlGetValue from '../../utils/control-get-value'; import { maybeDecode, maybeEncode } from '../../utils/encode-decode'; import AlignControl from '../align-control'; import AspectRatio from '../aspect-ratio'; import ClassesTree from '../classes-tree'; import CodeEditor from '../code-editor'; import CollapseControl from '../collapse-control'; import ColorPicker from '../color-picker'; import DatePicker from '../date-picker'; import ElementsSelector from '../elements-selector'; import GalleryControl from '../gallery-control'; import IconsSelector from '../icons-selector'; import NavigatorControl from '../navigator-control'; import ProNote from '../pro-note'; import SelectControl from '../select-control'; import SortableControl from '../sortable-control'; import TabsControl from '../tabs-control'; import TilesSelector from '../tiles-selector'; import ToggleGroupControl from '../toggle-group-control'; import ToggleModal from '../toggle-modal'; const UnitControl = __stableUnitControl || __experimentalUnitControl; const { controls: registeredControls, controls_categories: registeredControlsCategories, plugin_version: pluginVersion, } = window.VPGutenbergVariables; const openedCategoriesCache = {}; /** * Component Class */ class ControlsRender extends Component { render() { const { category, categoryToggle = true, attributes, setAttributes, controls, clientId, isSetupWizard, showPanel = true, } = this.props; if (!attributes) { return null; } // content source conditions. if ( /^content-source-/g.test(category) && category !== 'content-source-general' && `content-source-${attributes.content_source}` !== category ) { return null; } const usedControls = controls || registeredControls; const result = []; Object.keys(usedControls).forEach((name) => { const control = usedControls[name]; if ( category && (!control.category || category !== control.category) ) { return; } const controlData = applyFilters( 'vpf.editor.controls-render-data', { attributes, setAttributes, onChange: (val) => { const newAttrs = applyFilters( 'vpf.editor.controls-on-change', { [control.name]: val }, control, val, attributes ); setAttributes(newAttrs); }, ...control, } ); // Conditions check. if (!ControlsRender.AllowRender(controlData, isSetupWizard)) { return; } result.push( applyFilters( 'vpf.editor.controls-render', , controlData, this.props ) ); }); let categoryTitle = categoryToggle ? category : false; let categoryIcon = false; let categoryPro = false; let categoryOpened = !categoryToggle; if ( categoryToggle && typeof registeredControlsCategories[category] !== 'undefined' ) { categoryTitle = registeredControlsCategories[category].title; categoryIcon = registeredControlsCategories[category].icon || false; categoryPro = !!registeredControlsCategories[category].is_pro; if (typeof openedCategoriesCache[category] === 'undefined') { openedCategoriesCache[category] = registeredControlsCategories[category].is_opened || false; } categoryOpened = openedCategoriesCache[category]; } if (isSetupWizard) { return result.length ? (
{result}
) : ( '' ); } if (!showPanel) { return result.length ? result : ''; } return result.length ? ( {categoryIcon ? ( {categoryIcon} ) : null} {categoryTitle} {categoryPro ? ( {__('PRO', 'visual-portfolio')} ) : ( '' )} ) : ( false ) } onToggle={() => { openedCategoriesCache[category] = !categoryOpened; }} initialOpen={categoryOpened} scrollAfterOpen > {result} ) : ( '' ); } } /** * Render Single Control. * * @param {Object} props - control props. * * @return {JSX} control. */ ControlsRender.Control = function (props) { const { attributes, onChange, isSetupWizard } = props; const $ref = useRef(); const [positionInGroup, setPositionInGroup] = useState(''); const controlVal = controlGetValue(props.name, attributes); useEffect(() => { if (props.group && $ref.current) { const $element = $ref.current.parentElement.parentElement; let $prevSibling = $element.previousElementSibling; let $nextSibling = $element.nextElementSibling; // Skip separator. while ( $prevSibling && $prevSibling.classList.contains('vpf-control-group-separator') ) { $prevSibling = $prevSibling.previousElementSibling; } while ( $nextSibling && $nextSibling.classList.contains('vpf-control-group-separator') ) { $nextSibling = $nextSibling.nextElementSibling; } const isGroupEnabled = ($prevSibling && $prevSibling.classList.contains( `vpf-control-group-${props.group}` )) || ($nextSibling && $nextSibling.classList.contains( `vpf-control-group-${props.group}` )); const isStart = $prevSibling && !$prevSibling.classList.contains( `vpf-control-group-${props.group}` ) && $prevSibling.classList.contains(`vpf-control-wrap`); const isEnd = $nextSibling && !$nextSibling.classList.contains( `vpf-control-group-${props.group}` ) && $nextSibling.classList.contains(`vpf-control-wrap`); let newPosition = ''; if (!isGroupEnabled) { // skip } else if (isStart) { newPosition = 'start'; } else if (isEnd) { newPosition = 'end'; } if (positionInGroup !== newPosition) { setPositionInGroup(newPosition); } } }, [$ref, props.group, controlVal, positionInGroup]); // Conditions check. if (!ControlsRender.AllowRender(props, isSetupWizard)) { return null; } let renderControl = ''; let renderControlLabel = props.label; let renderControlAfter = ''; let renderControlHelp = props.description ? ( {props.description} ) : null; let renderControlClassName = classnames( 'vpf-control-wrap', `vpf-control-wrap-${props.type}` ); if (props.group) { renderControlClassName = classnames( renderControlClassName, 'vpf-control-with-group', `vpf-control-group-${props.group}`, positionInGroup ? `vpf-control-group-position-${positionInGroup}` : false ); } const categoryControlOptions = []; // Check if category is empty. if ( (props.type === 'category_tabs' || props.type === 'category_toggle_group' || props.type === 'category_collapse' || props.type === 'category_navigator') && props.options && props.options.length ) { props.options.forEach((opt) => { const isEmpty = ControlsRender.isCategoryEmpty({ ...props.renderProps, category: opt.category, categoryToggle: false, }); if (!isEmpty) { categoryControlOptions.push(opt); } }); } // Specific controls. switch (props.type) { case 'category_tabs': if (categoryControlOptions.length) { renderControl = ( {(tab) => { return ( ); }} ); } else { renderControl = null; } break; case 'category_toggle_group': if (categoryControlOptions.length) { renderControl = ( {(group) => { return ( ); }} ); } else { renderControl = null; } break; case 'category_collapse': if (categoryControlOptions.length) { renderControl = ( {(tab) => { return ( ); }} ); } else { renderControl = null; } break; case 'category_navigator': if (categoryControlOptions.length) { renderControl = ( {(tab) => { return ( ); }} ); } else { renderControl = null; } break; case 'html': renderControl = {props.default}; break; case 'select': case 'select2': renderControl = ( onChange(val)} isSearchable={props.searchable} isMultiple={props.multiple} isCreatable={props.creatable || props.tags} /> ); break; case 'buttons': renderControl = ( {Object.keys(props.options || {}).map((val) => ( ))} ); break; case 'icons_selector': renderControl = ( onChange(val)} collapseRows={props.collapse_rows || false} isSetupWizard={isSetupWizard} /> ); break; case 'tiles_selector': renderControl = ( onChange(val)} /> ); break; case 'elements_selector': renderControl = ( onChange(val)} props={props} /> ); break; case 'align': { renderControl = ( onChange(val)} /> ); break; } case 'aspect_ratio': { renderControl = ( onChange(val)} /> ); break; } case 'gallery': renderControl = ( onChange(val)} isSetupWizard={isSetupWizard} /> ); break; case 'code_editor': renderControl = ( onChange(props.encode ? maybeEncode(val) : val) } /> ); if (props.allow_modal) { renderControlAfter = ( {props.description} ) : ( false ) } className={classnames( 'vpf-control-wrap', `vpf-control-wrap-${props.type}` )} >
{renderControl}
{props.classes_tree ? ( <>

{__('Classes Tree:', 'visual-portfolio')}

) : ( '' )}
); } break; case 'range': renderControl = ( onChange(parseFloat(val))} /> ); break; case 'toggle': renderControl = ( onChange(val)} /> ); break; case 'checkbox': renderControl = ( onChange(val)} /> ); break; case 'radio': renderControl = ( ({ label: props.options[val], value: val, }))} onChange={(option) => onChange(option)} /> ); renderControlLabel = false; break; case 'color': renderControl = ( onChange(val)} /> ); renderControlLabel = false; break; case 'date': renderControl = ( onChange(val)} /> ); break; case 'textarea': renderControl = ( onChange(val)} /> ); renderControlLabel = false; break; case 'url': renderControl = ( onChange(val)} /> ); renderControlLabel = false; break; case 'number': renderControl = ( onChange(parseFloat(val))} /> ); renderControlLabel = false; break; case 'unit': renderControl = ( onChange(val)} labelPosition="edge" __unstableInputWidth="70px" /> ); renderControlLabel = false; break; case 'hidden': renderControl = ( onChange(val)} /> ); break; case 'notice': renderControl = renderControlHelp ? ( {renderControlHelp} ) : ( '' ); renderControlHelp = false; break; case 'pro_note': renderControl = ( {renderControlHelp || ''} {__('Go Pro', 'visual-portfolio')} ); renderControlLabel = false; renderControlHelp = false; break; case 'sortable': renderControl = ( onChange(val)} /> ); break; default: renderControl = ( onChange(val)} /> ); renderControlLabel = false; } // Hint. if (props.hint) { renderControl = (
{renderControl}
); } // TODO: use this filter for custom controls. const data = applyFilters( 'vpf.editor.controls-render-inner-data', { renderControl, renderControlLabel, renderControlHelp, renderControlAfter, renderControlClassName, }, { props, controlVal } ); // Prevent rendering. if (data.renderControl === null) { return null; } return ( <> {positionInGroup === 'start' ? (
) : null}
{data.renderControl}
{data.renderControlHelp}
{data.renderControlAfter} {positionInGroup === 'end' ? (
) : null} ); }; /** * Check if control is allowed to rendering. * * @param props * @param isSetupWizard */ ControlsRender.AllowRender = function (props, isSetupWizard = false) { if (props.skip) { return false; } if ( props.condition && props.condition.length && !controlConditionCheck(props.condition, props.attributes) ) { return false; } if (isSetupWizard && !props.setup_wizard) { return false; } return true; }; /** * Check if category does not contains controls. * * @param props */ ControlsRender.isCategoryEmpty = function (props) { const { category, attributes, setAttributes, controls, isSetupWizard } = props; const usedControls = controls || registeredControls; let isEmpty = true; Object.keys(usedControls).forEach((name) => { if (!isEmpty) { return; } const control = usedControls[name]; if (category && (!control.category || category !== control.category)) { return; } const controlData = applyFilters('vpf.editor.controls-render-data', { attributes, setAttributes, onChange: (val) => { const newAttrs = applyFilters( 'vpf.editor.controls-on-change', { [control.name]: val }, control, val, attributes ); setAttributes(newAttrs); }, ...control, }); // Conditions check. if (!ControlsRender.AllowRender(controlData, isSetupWizard)) { return; } isEmpty = false; }); return isEmpty; }; export default ControlsRender;