PluginProbe
BeyondWords – AI audio for publishers / 7.2.0
BeyondWords – AI audio for publishers v7.2.0
7.2.0 7.1.0 trunk 4.0.0 4.0.1 4.0.2 4.0.3 4.0.4 4.0.5 4.0.6 4.1.0 4.1.1 4.1.2 4.2.0 4.2.1 4.2.2 4.2.3 4.2.4 4.3.0 4.4.0 4.5.0 4.5.1 4.6.0 4.6.1 4.6.2 All 44 releases
speechkit / src / editor / components / settings-panel / voice-section.js

voice-section.js in BeyondWords – AI audio for publishers 7.2.0, at src/editor/components/settings-panel/voice-section.js

141 lines 3.9 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 /**
2 * WordPress dependencies
3 */
4 import { __ } from '@wordpress/i18n';
5 import { PanelBody, Spinner } from '@wordpress/components';
6 import { useEntityProp } from '@wordpress/core-data';
7 import { useRegistry, useSelect } from '@wordpress/data';
8 import { useEffect, useState } from '@wordpress/element';
9
10 /**
11 * Internal dependencies
12 */
13 import Stack from '../stack';
14 import Toggle from '../toggle';
15 import VoicePicker from '../voice-picker';
16
17 export function VoiceSection( { withPanel = true } ) {
18 const registry = useRegistry();
19
20 const postType = useSelect(
21 ( select ) => select( 'core/editor' ).getCurrentPostType(),
22 []
23 );
24
25 // `useEntityProp` yields undefined meta until the post entity record is hydrated.
26 const [ rawMeta, setMeta ] = useEntityProp( 'postType', postType, 'meta' );
27 const meta = rawMeta ?? {};
28
29 const languageCode = meta.beyondwords_language_code || '';
30 const voiceId = meta.beyondwords_body_voice_id || '';
31
32 // "Customize" is opt-in: a post counts as customised once it carries an explicit
33 // language or voice. Local state so toggling on reveals the pickers pre-choice.
34 const [ customize, setCustomize ] = useState(
35 () => !! ( languageCode || voiceId )
36 );
37
38 const projectId = useSelect(
39 ( select ) =>
40 select( 'core/editor' ).getEditedPostAttribute( 'meta' )
41 ?.beyondwords_project_id ||
42 select( 'beyondwords/settings' ).getSettings()?.projectId,
43 []
44 );
45
46 // Customize-on with no choice yet: fetch the project default language and seed
47 // only the Language (a project default voice can belong to another language).
48 const needsDefault = customize && ! languageCode && ! voiceId;
49
50 const project = useSelect(
51 ( select ) =>
52 needsDefault
53 ? select( 'beyondwords/settings' ).getProject( projectId )
54 : null,
55 [ needsDefault, projectId ]
56 );
57
58 // True once the project fetch has settled; keeps the spinner up until then to
59 // avoid a one-frame "empty dropdown then spinner" flicker.
60 const projectResolved = useSelect(
61 ( select ) =>
62 ! needsDefault ||
63 select( 'beyondwords/settings' ).hasFinishedResolution(
64 'getProject',
65 [ projectId ]
66 ),
67 [ needsDefault, projectId ]
68 );
69
70 // Apply the resolved default language. Reads the freshest meta — not the
71 // closure's — so a concurrent edit during the async fetch isn't clobbered.
72 useEffect( () => {
73 if ( needsDefault && project?.language ) {
74 const current = registry
75 .select( 'core/editor' )
76 .getEditedPostAttribute( 'meta' );
77 setMeta( {
78 ...current,
79 beyondwords_language_code: project.language,
80 } );
81 }
82 // eslint-disable-next-line react-hooks/exhaustive-deps
83 }, [ needsDefault, project ] );
84
85 const loadingProject = customize && needsDefault && ! projectResolved;
86
87 // Storing the pair together keeps the language and its voice consistent.
88 const setVoice = ( { languageCode: nextCode, voiceId: nextVoiceId } ) => {
89 setMeta( {
90 ...meta,
91 beyondwords_language_code: nextCode,
92 beyondwords_body_voice_id: nextVoiceId,
93 } );
94 };
95
96 // Customize off reverts to the project defaults by clearing both choices.
97 const toggleCustomize = () => {
98 const next = ! customize;
99 setCustomize( next );
100
101 if ( ! next ) {
102 setVoice( { languageCode: '', voiceId: '' } );
103 }
104 };
105
106 const fields = (
107 <Stack>
108 <Toggle
109 className="beyondwords--customize"
110 label={ __( 'Customize', 'speechkit' ) }
111 checked={ customize }
112 onChange={ toggleCustomize }
113 />
114 { loadingProject && (
115 <div className="beyondwords--languages-spinner">
116 <Spinner />
117 </div>
118 ) }
119 <VoicePicker
120 enabled={ customize && ! loadingProject }
121 languageCode={ languageCode }
122 voiceId={ voiceId }
123 onChange={ setVoice }
124 />
125 </Stack>
126 );
127
128 // Document/pre-publish panels render the fields without nesting another panel.
129 if ( ! withPanel ) {
130 return fields;
131 }
132
133 return (
134 <PanelBody title={ __( 'Voice', 'speechkit' ) } initialOpen={ true }>
135 { fields }
136 </PanelBody>
137 );
138 }
139
140 export default VoiceSection;
141