/** * WordPress Dependencies */ import { __ } from '@wordpress/i18n'; import { InspectorControls, BlockControls } from '@wordpress/block-editor'; import { PanelBody, ToolbarButton, ToolbarGroup } from '@wordpress/components'; import { createHigherOrderComponent } from '@wordpress/compose'; import { useSelect } from '@wordpress/data'; import { useState } from '@wordpress/element'; import { addFilter } from '@wordpress/hooks'; /** * Internal dependencies */ import { isBeyondwordsSupportedBlock } from './isBeyondwordsSupportedBlock'; import Stack from '../stack'; import Toggle from '../toggle'; import VoicePicker from '../voice-picker'; import { BeyondwordsTitle } from '../icon'; /** * Add BeyondWords controls to Gutenberg Blocks. * * @since 6.0.1 Skip internal/UI blocks to prevent breaking the block inserter. * @since 7.1.0 Add the per-block Language and Voice pickers. * * @param {Function} BlockEdit Block edit component. * * @return {Function} BlockEdit Modified block edit component. */ const withBeyondwordsBlockControls = createHigherOrderComponent( ( BlockEdit ) => { // A component of its own, so the unsupported-block check can return // before any hook runs. const BeyondwordsControls = ( props ) => { const { attributes, setAttributes } = props; const { beyondwordsAudio, beyondwordsLanguageCode, beyondwordsVoiceId, } = attributes; // "Customize" is opt-in: a block counts as customised once it carries // an explicit language or voice. const [ customize, setCustomize ] = useState( () => !! ( beyondwordsLanguageCode || beyondwordsVoiceId ) ); // What this block already reads aloud with: the post's own choice // where it has one, otherwise the project's. Fetched only once // Customize is on, so an untouched block still costs no requests. const seed = useSelect( ( select ) => { const meta = select( 'core/editor' ).getEditedPostAttribute( 'meta' ) || {}; if ( meta.beyondwords_language_code ) { return { languageCode: meta.beyondwords_language_code, voiceId: meta.beyondwords_body_voice_id || '', }; } const projectId = meta.beyondwords_project_id || select( 'beyondwords/settings' ).getSettings() ?.projectId; const project = customize && projectId ? select( 'beyondwords/settings' ).getProject( projectId ) : null; return { languageCode: project?.language || '', voiceId: String( project?.body?.voice?.id ?? '' ), }; }, [ customize ] ); const icon = beyondwordsAudio ? 'controls-volumeon' : 'controls-volumeoff'; const buttonLabel = beyondwordsAudio ? __( 'Disable generation', 'speechkit' ) : __( 'Enable generation', 'speechkit' ); const toggleLabel = beyondwordsAudio ? __( 'Generation enabled', 'speechkit' ) : __( 'Generation disabled', 'speechkit' ); const toggleBeyondwordsAudio = () => { setAttributes( { beyondwordsAudio: ! beyondwordsAudio } ); }; const setVoice = ( { languageCode, voiceId } ) => { setAttributes( { beyondwordsLanguageCode: languageCode, beyondwordsVoiceId: voiceId, } ); }; // Customize off returns the block to the post's language and voice. const toggleCustomize = () => { const next = ! customize; setCustomize( next ); if ( ! next ) { setVoice( { languageCode: '', voiceId: '' } ); } }; return ( <> } initialOpen={ true } > { !! beyondwordsAudio && ( ) } { !! beyondwordsAudio && ( ) } ); }; return ( props ) => { if ( ! isBeyondwordsSupportedBlock( props.name ) ) { return ; } return ; }; }, 'withBeyondwordsBlockControls' ); addFilter( 'editor.BlockEdit', 'beyondwords/block-controls', withBeyondwordsBlockControls );