/** * WordPress dependencies */ import { CheckboxControl, SelectControl } from '@safe-wordpress/components'; import { useState } from '@safe-wordpress/element'; import { _x } from '@safe-wordpress/i18n'; import { select as doSelect, useDispatch, useSelect, } from '@safe-wordpress/data'; /** * External dependencies */ import clsx from 'clsx'; import { filter, map } from 'lodash'; import { PostSearcher } from '@nab/components'; import { store as NAB_DATA } from '@nab/data'; import { createAlternative, isEmpty, isMultiArray, isSingleton, } from '@nab/utils'; import type { PostSearcherProps } from '@nab/components'; import type { Alternative, ExperimentEditProps, EntityKind, Maybe, } from '@nab/types'; // NOTE. Dependency loop with @nab package. import type { store as editorStore } from '@nab/editor'; const NAB_EDITOR = 'nab/editor' as unknown as typeof editorStore; /** * Internal dependencies */ import './style.scss'; import { usePostTypes } from '../../hooks'; import type { ControlAttributes } from '../../types'; type Attributes< T extends string > = { [ K in keyof ControlAttributes ]: K extends 'postType' ? T : ControlAttributes[ K ]; }; export type OriginalProps< T extends string > = ExperimentEditProps< Attributes< T > > & { readonly disableExistingContentSelect?: boolean; readonly validPostFilter?: PostSearcherProps[ 'filter' ]; }; export const Original = < T extends string >( { attributes, setAttributes, disableExistingContentSelect, validPostFilter, experimentType, }: OriginalProps< T > ): JSX.Element => { const isSubscribed = !! useSubscription(); const isPaused = useIsPaused(); const toggleExistingContent = useExistingContentToggler( setAttributes ); const { postTypes, showSinglePostType, isLoadingPostTypes } = usePostTypes( experimentType ); const defaultPostType = postTypes[ 0 ]?.name; const postType = attributes.postType || defaultPostType; const selectablePostTypes = postTypes.map( ( type ) => ( { value: type.name, label: type.labels.singular_name, } ) ); return (
{ showSinglePostType && isSingleton( postTypes ) && ( ) } { isMultiArray( postTypes ) && ( setAttributes( { postId: undefined, postType: value as T, } ) } /> ) } setAttributes( { // @ts-expect-error Post ID has the appropriate type based on current postType. postId: value, postType: postType as T, } ) } filter={ validPostFilter } placeholder={ getPlaceholder( postTypes, isLoadingPostTypes ) } />
{ ! disableExistingContentSelect && ( <>
{ !! attributes.testAgainstExistingContent && (
setAttributes( { useControlUrl } ) } />
) } ) }
); }; // ===== // HOOKS // ===== const useSubscription = () => useSelect( ( select ) => select( NAB_DATA ).getPluginSetting( 'subscription' ) || false, [] ); const useIsPaused = () => useSelect( ( select ) => `${ select( NAB_EDITOR ).getExperimentAttribute( 'status' ) }`.includes( 'paused' ), [] ); const useExistingContentToggler = ( setAttributes: ( props: Partial< Pick< ControlAttributes, 'testAgainstExistingContent' > > ) => void ) => { const [ prevAlts, setPrevAlts ] = useState( [ createAlternative() ] ); const { replaceAlternatives } = useDispatch( NAB_EDITOR ); return ( checked: boolean ) => { const alts = filter( doSelect( NAB_EDITOR ).getAlternatives(), ( { id }: Alternative ) => 'control' !== id ); void replaceAlternatives( map( alts, 'id' ), prevAlts ); setPrevAlts( alts ); setAttributes( { testAgainstExistingContent: checked ? true : undefined, ...( ! checked ? { useControlUrl: undefined } : {} ), } ); }; }; // ======= // HELPERS // ======= function getExistingContentLabel( type: Maybe< string >, isSubscribed: boolean ): string { switch ( type ) { case 'page': return isSubscribed ? _x( 'Test against already existing pages', 'command', 'nelio-ab-testing' ) : _x( 'Test against an already existing page', 'command', 'nelio-ab-testing' ); case 'post': return isSubscribed ? _x( 'Test against already existing posts', 'command', 'nelio-ab-testing' ) : _x( 'Test against an already existing post', 'command', 'nelio-ab-testing' ); default: return _x( 'Test against already existing content', 'command', 'nelio-ab-testing' ); } } function getPlaceholder( postTypes: ReadonlyArray< EntityKind >, isLoading: boolean ): Maybe< string > { if ( isLoading ) { return _x( 'Loading compatible post types…', 'text', 'nelio-ab-testing' ); } if ( isEmpty( postTypes ) ) { return _x( 'There are no compatible post types to select.', 'text', 'nelio-ab-testing' ); } if ( postTypes?.length > 1 ) { return _x( 'First select the post type and then the actual content…', 'user', 'nelio-ab-testing' ); } return undefined; }