/**
* WordPress dependencies
*/
import { __ } from '@wordpress/i18n';
import { PanelBody, Spinner } from '@wordpress/components';
import { useEntityProp } from '@wordpress/core-data';
import { useRegistry, useSelect } from '@wordpress/data';
import { useEffect, useState } from '@wordpress/element';
/**
* Internal dependencies
*/
import Stack from '../stack';
import Toggle from '../toggle';
import VoicePicker from '../voice-picker';
export function VoiceSection( { withPanel = true } ) {
const registry = useRegistry();
const postType = useSelect(
( select ) => select( 'core/editor' ).getCurrentPostType(),
[]
);
// `useEntityProp` yields undefined meta until the post entity record is hydrated.
const [ rawMeta, setMeta ] = useEntityProp( 'postType', postType, 'meta' );
const meta = rawMeta ?? {};
const languageCode = meta.beyondwords_language_code || '';
const voiceId = meta.beyondwords_body_voice_id || '';
// "Customize" is opt-in: a post counts as customised once it carries an explicit
// language or voice. Local state so toggling on reveals the pickers pre-choice.
const [ customize, setCustomize ] = useState(
() => !! ( languageCode || voiceId )
);
const projectId = useSelect(
( select ) =>
select( 'core/editor' ).getEditedPostAttribute( 'meta' )
?.beyondwords_project_id ||
select( 'beyondwords/settings' ).getSettings()?.projectId,
[]
);
// Customize-on with no choice yet: fetch the project default language and seed
// only the Language (a project default voice can belong to another language).
const needsDefault = customize && ! languageCode && ! voiceId;
const project = useSelect(
( select ) =>
needsDefault
? select( 'beyondwords/settings' ).getProject( projectId )
: null,
[ needsDefault, projectId ]
);
// True once the project fetch has settled; keeps the spinner up until then to
// avoid a one-frame "empty dropdown then spinner" flicker.
const projectResolved = useSelect(
( select ) =>
! needsDefault ||
select( 'beyondwords/settings' ).hasFinishedResolution(
'getProject',
[ projectId ]
),
[ needsDefault, projectId ]
);
// Apply the resolved default language. Reads the freshest meta — not the
// closure's — so a concurrent edit during the async fetch isn't clobbered.
useEffect( () => {
if ( needsDefault && project?.language ) {
const current = registry
.select( 'core/editor' )
.getEditedPostAttribute( 'meta' );
setMeta( {
...current,
beyondwords_language_code: project.language,
} );
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [ needsDefault, project ] );
const loadingProject = customize && needsDefault && ! projectResolved;
// Storing the pair together keeps the language and its voice consistent.
const setVoice = ( { languageCode: nextCode, voiceId: nextVoiceId } ) => {
setMeta( {
...meta,
beyondwords_language_code: nextCode,
beyondwords_body_voice_id: nextVoiceId,
} );
};
// Customize off reverts to the project defaults by clearing both choices.
const toggleCustomize = () => {
const next = ! customize;
setCustomize( next );
if ( ! next ) {
setVoice( { languageCode: '', voiceId: '' } );
}
};
const fields = (
{ loadingProject && (
) }
);
// Document/pre-publish panels render the fields without nesting another panel.
if ( ! withPanel ) {
return fields;
}
return (
{ fields }
);
}
export default VoiceSection;