🎛️ fix: Improve Frontend Practices for Audio Settings (#3624)

* refactor: do not call await inside useCallbacks, rely on updates for dropdown

* fix: remember last selected voice

* refactor: Update Speech component to use TypeScript in useCallback

* refactor: Update Dropdown component styles to match header theme
This commit is contained in:
Danny Avila 2024-08-13 02:42:49 -04:00 committed by GitHub
parent 8cbb6ba166
commit 05696233a9
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
20 changed files with 436 additions and 367 deletions

View file

@ -25,11 +25,11 @@ export default function ExportModal({
const [recursive, setRecursive] = useState<boolean | 'indeterminate'>(true);
const typeOptions = [
{ value: 'screenshot', display: 'screenshot (.png)' },
{ value: 'text', display: 'text (.txt)' },
{ value: 'markdown', display: 'markdown (.md)' },
{ value: 'json', display: 'json (.json)' },
{ value: 'csv', display: 'csv (.csv)' },
{ value: 'screenshot', label: 'screenshot (.png)' },
{ value: 'text', label: 'text (.txt)' },
{ value: 'markdown', label: 'markdown (.md)' },
{ value: 'json', label: 'json (.json)' },
{ value: 'csv', label: 'csv (.csv)' },
];
useEffect(() => {

View file

@ -14,11 +14,11 @@ export default function FontSizeSelector() {
};
const options = [
{ value: 'text-xs', display: localize('com_nav_font_size_xs') },
{ value: 'text-sm', display: localize('com_nav_font_size_sm') },
{ value: 'text-base', display: localize('com_nav_font_size_base') },
{ value: 'text-lg', display: localize('com_nav_font_size_lg') },
{ value: 'text-xl', display: localize('com_nav_font_size_xl') },
{ value: 'text-xs', label: localize('com_nav_font_size_xs') },
{ value: 'text-sm', label: localize('com_nav_font_size_sm') },
{ value: 'text-base', label: localize('com_nav_font_size_base') },
{ value: 'text-lg', label: localize('com_nav_font_size_lg') },
{ value: 'text-xl', label: localize('com_nav_font_size_xl') },
];
return (

View file

@ -12,9 +12,9 @@ export const ForkSettings = () => {
const [remember, setRemember] = useRecoilState<boolean>(store.rememberForkOption);
const forkOptions = [
{ value: ForkOptions.DIRECT_PATH, display: localize('com_ui_fork_visible') },
{ value: ForkOptions.INCLUDE_BRANCHES, display: localize('com_ui_fork_branches') },
{ value: ForkOptions.TARGET_LEVEL, display: localize('com_ui_fork_all_target') },
{ value: ForkOptions.DIRECT_PATH, label: localize('com_ui_fork_visible') },
{ value: ForkOptions.INCLUDE_BRANCHES, label: localize('com_ui_fork_branches') },
{ value: ForkOptions.TARGET_LEVEL, label: localize('com_ui_fork_all_target') },
];
return (

View file

@ -21,9 +21,9 @@ export const ThemeSelector = ({
const localize = useLocalize();
const themeOptions = [
{ value: 'system', display: localize('com_nav_theme_system') },
{ value: 'dark', display: localize('com_nav_theme_dark') },
{ value: 'light', display: localize('com_nav_theme_light') },
{ value: 'system', label: localize('com_nav_theme_system') },
{ value: 'dark', label: localize('com_nav_theme_dark') },
{ value: 'light', label: localize('com_nav_theme_light') },
];
return (
@ -81,27 +81,27 @@ export const LangSelector = ({
// Create an array of options for the Dropdown
const languageOptions = [
{ value: 'auto', display: localize('com_nav_lang_auto') },
{ value: 'en-US', display: localize('com_nav_lang_english') },
{ value: 'zh-CN', display: localize('com_nav_lang_chinese') },
{ value: 'zh-TW', display: localize('com_nav_lang_traditionalchinese') },
{ value: 'ar-EG', display: localize('com_nav_lang_arabic') },
{ value: 'de-DE', display: localize('com_nav_lang_german') },
{ value: 'es-ES', display: localize('com_nav_lang_spanish') },
{ value: 'fr-FR', display: localize('com_nav_lang_french') },
{ value: 'it-IT', display: localize('com_nav_lang_italian') },
{ value: 'pl-PL', display: localize('com_nav_lang_polish') },
{ value: 'pt-BR', display: localize('com_nav_lang_brazilian_portuguese') },
{ value: 'ru-RU', display: localize('com_nav_lang_russian') },
{ value: 'ja-JP', display: localize('com_nav_lang_japanese') },
{ value: 'sv-SE', display: localize('com_nav_lang_swedish') },
{ value: 'ko-KR', display: localize('com_nav_lang_korean') },
{ value: 'vi-VN', display: localize('com_nav_lang_vietnamese') },
{ value: 'tr-TR', display: localize('com_nav_lang_turkish') },
{ value: 'nl-NL', display: localize('com_nav_lang_dutch') },
{ value: 'id-ID', display: localize('com_nav_lang_indonesia') },
{ value: 'he-HE', display: localize('com_nav_lang_hebrew') },
{ value: 'fi-FI', display: localize('com_nav_lang_finnish') },
{ value: 'auto', label: localize('com_nav_lang_auto') },
{ value: 'en-US', label: localize('com_nav_lang_english') },
{ value: 'zh-CN', label: localize('com_nav_lang_chinese') },
{ value: 'zh-TW', label: localize('com_nav_lang_traditionalchinese') },
{ value: 'ar-EG', label: localize('com_nav_lang_arabic') },
{ value: 'de-DE', label: localize('com_nav_lang_german') },
{ value: 'es-ES', label: localize('com_nav_lang_spanish') },
{ value: 'fr-FR', label: localize('com_nav_lang_french') },
{ value: 'it-IT', label: localize('com_nav_lang_italian') },
{ value: 'pl-PL', label: localize('com_nav_lang_polish') },
{ value: 'pt-BR', label: localize('com_nav_lang_brazilian_portuguese') },
{ value: 'ru-RU', label: localize('com_nav_lang_russian') },
{ value: 'ja-JP', label: localize('com_nav_lang_japanese') },
{ value: 'sv-SE', label: localize('com_nav_lang_swedish') },
{ value: 'ko-KR', label: localize('com_nav_lang_korean') },
{ value: 'vi-VN', label: localize('com_nav_lang_vietnamese') },
{ value: 'tr-TR', label: localize('com_nav_lang_turkish') },
{ value: 'nl-NL', label: localize('com_nav_lang_dutch') },
{ value: 'id-ID', label: localize('com_nav_lang_indonesia') },
{ value: 'he-HE', label: localize('com_nav_lang_hebrew') },
{ value: 'fi-FI', label: localize('com_nav_lang_finnish') },
];
return (

View file

@ -14,10 +14,10 @@ const EngineSTTDropdown: React.FC<EngineSTTDropdownProps> = ({ external }) => {
const endpointOptions = external
? [
{ value: 'browser', display: localize('com_nav_browser') },
{ value: 'external', display: localize('com_nav_external') },
{ value: 'browser', label: localize('com_nav_browser') },
{ value: 'external', label: localize('com_nav_external') },
]
: [{ value: 'browser', display: localize('com_nav_browser') }];
: [{ value: 'browser', label: localize('com_nav_browser') }];
const handleSelect = (value: string) => {
setEngineSTT(value);

View file

@ -8,83 +8,83 @@ export default function LanguageSTTDropdown() {
const [languageSTT, setLanguageSTT] = useRecoilState<string>(store.languageSTT);
const languageOptions = [
{ value: 'af', display: 'Afrikaans' },
{ value: 'eu', display: 'Basque' },
{ value: 'bg', display: 'Bulgarian' },
{ value: 'ca', display: 'Catalan' },
{ value: 'ar-EG', display: 'Arabic (Egypt)' },
{ value: 'ar-JO', display: 'Arabic (Jordan)' },
{ value: 'ar-KW', display: 'Arabic (Kuwait)' },
{ value: 'ar-LB', display: 'Arabic (Lebanon)' },
{ value: 'ar-QA', display: 'Arabic (Qatar)' },
{ value: 'ar-AE', display: 'Arabic (UAE)' },
{ value: 'ar-MA', display: 'Arabic (Morocco)' },
{ value: 'ar-IQ', display: 'Arabic (Iraq)' },
{ value: 'ar-DZ', display: 'Arabic (Algeria)' },
{ value: 'ar-BH', display: 'Arabic (Bahrain)' },
{ value: 'ar-LY', display: 'Arabic (Libya)' },
{ value: 'ar-OM', display: 'Arabic (Oman)' },
{ value: 'ar-SA', display: 'Arabic (Saudi Arabia)' },
{ value: 'ar-TN', display: 'Arabic (Tunisia)' },
{ value: 'ar-YE', display: 'Arabic (Yemen)' },
{ value: 'cs', display: 'Czech' },
{ value: 'nl-NL', display: 'Dutch' },
{ value: 'en-AU', display: 'English (Australia)' },
{ value: 'en-CA', display: 'English (Canada)' },
{ value: 'en-IN', display: 'English (India)' },
{ value: 'en-NZ', display: 'English (New Zealand)' },
{ value: 'en-ZA', display: 'English (South Africa)' },
{ value: 'en-GB', display: 'English (UK)' },
{ value: 'en-US', display: 'English (US)' },
{ value: 'fi', display: 'Finnish' },
{ value: 'fr-FR', display: 'French' },
{ value: 'gl', display: 'Galician' },
{ value: 'de-DE', display: 'German' },
{ value: 'el-GR', display: 'Greek' },
{ value: 'he', display: 'Hebrew' },
{ value: 'hu', display: 'Hungarian' },
{ value: 'is', display: 'Icelandic' },
{ value: 'it-IT', display: 'Italian' },
{ value: 'id', display: 'Indonesian' },
{ value: 'ja', display: 'Japanese' },
{ value: 'ko', display: 'Korean' },
{ value: 'la', display: 'Latin' },
{ value: 'zh-CN', display: 'Mandarin Chinese' },
{ value: 'zh-TW', display: 'Taiwanese' },
{ value: 'zh-HK', display: 'Cantonese' },
{ value: 'ms-MY', display: 'Malaysian' },
{ value: 'no-NO', display: 'Norwegian' },
{ value: 'pl', display: 'Polish' },
{ value: 'xx-piglatin', display: 'Pig Latin' },
{ value: 'pt-PT', display: 'Portuguese' },
{ value: 'pt-br', display: 'Portuguese (Brasil)' },
{ value: 'ro-RO', display: 'Romanian' },
{ value: 'ru', display: 'Russian' },
{ value: 'sr-SP', display: 'Serbian' },
{ value: 'sk', display: 'Slovak' },
{ value: 'es-AR', display: 'Spanish (Argentina)' },
{ value: 'es-BO', display: 'Spanish (Bolivia)' },
{ value: 'es-CL', display: 'Spanish (Chile)' },
{ value: 'es-CO', display: 'Spanish (Colombia)' },
{ value: 'es-CR', display: 'Spanish (Costa Rica)' },
{ value: 'es-DO', display: 'Spanish (Dominican Republic)' },
{ value: 'es-EC', display: 'Spanish (Ecuador)' },
{ value: 'es-SV', display: 'Spanish (El Salvador)' },
{ value: 'es-GT', display: 'Spanish (Guatemala)' },
{ value: 'es-HN', display: 'Spanish (Honduras)' },
{ value: 'es-MX', display: 'Spanish (Mexico)' },
{ value: 'es-NI', display: 'Spanish (Nicaragua)' },
{ value: 'es-PA', display: 'Spanish (Panama)' },
{ value: 'es-PY', display: 'Spanish (Paraguay)' },
{ value: 'es-PE', display: 'Spanish (Peru)' },
{ value: 'es-PR', display: 'Spanish (Puerto Rico)' },
{ value: 'es-ES', display: 'Spanish (Spain)' },
{ value: 'es-US', display: 'Spanish (US)' },
{ value: 'es-UY', display: 'Spanish (Uruguay)' },
{ value: 'es-VE', display: 'Spanish (Venezuela)' },
{ value: 'sv-SE', display: 'Swedish' },
{ value: 'tr', display: 'Turkish' },
{ value: 'zu', display: 'Zulu' },
{ value: 'af', label: 'Afrikaans' },
{ value: 'eu', label: 'Basque' },
{ value: 'bg', label: 'Bulgarian' },
{ value: 'ca', label: 'Catalan' },
{ value: 'ar-EG', label: 'Arabic (Egypt)' },
{ value: 'ar-JO', label: 'Arabic (Jordan)' },
{ value: 'ar-KW', label: 'Arabic (Kuwait)' },
{ value: 'ar-LB', label: 'Arabic (Lebanon)' },
{ value: 'ar-QA', label: 'Arabic (Qatar)' },
{ value: 'ar-AE', label: 'Arabic (UAE)' },
{ value: 'ar-MA', label: 'Arabic (Morocco)' },
{ value: 'ar-IQ', label: 'Arabic (Iraq)' },
{ value: 'ar-DZ', label: 'Arabic (Algeria)' },
{ value: 'ar-BH', label: 'Arabic (Bahrain)' },
{ value: 'ar-LY', label: 'Arabic (Libya)' },
{ value: 'ar-OM', label: 'Arabic (Oman)' },
{ value: 'ar-SA', label: 'Arabic (Saudi Arabia)' },
{ value: 'ar-TN', label: 'Arabic (Tunisia)' },
{ value: 'ar-YE', label: 'Arabic (Yemen)' },
{ value: 'cs', label: 'Czech' },
{ value: 'nl-NL', label: 'Dutch' },
{ value: 'en-AU', label: 'English (Australia)' },
{ value: 'en-CA', label: 'English (Canada)' },
{ value: 'en-IN', label: 'English (India)' },
{ value: 'en-NZ', label: 'English (New Zealand)' },
{ value: 'en-ZA', label: 'English (South Africa)' },
{ value: 'en-GB', label: 'English (UK)' },
{ value: 'en-US', label: 'English (US)' },
{ value: 'fi', label: 'Finnish' },
{ value: 'fr-FR', label: 'French' },
{ value: 'gl', label: 'Galician' },
{ value: 'de-DE', label: 'German' },
{ value: 'el-GR', label: 'Greek' },
{ value: 'he', label: 'Hebrew' },
{ value: 'hu', label: 'Hungarian' },
{ value: 'is', label: 'Icelandic' },
{ value: 'it-IT', label: 'Italian' },
{ value: 'id', label: 'Indonesian' },
{ value: 'ja', label: 'Japanese' },
{ value: 'ko', label: 'Korean' },
{ value: 'la', label: 'Latin' },
{ value: 'zh-CN', label: 'Mandarin Chinese' },
{ value: 'zh-TW', label: 'Taiwanese' },
{ value: 'zh-HK', label: 'Cantonese' },
{ value: 'ms-MY', label: 'Malaysian' },
{ value: 'no-NO', label: 'Norwegian' },
{ value: 'pl', label: 'Polish' },
{ value: 'xx-piglatin', label: 'Pig Latin' },
{ value: 'pt-PT', label: 'Portuguese' },
{ value: 'pt-br', label: 'Portuguese (Brasil)' },
{ value: 'ro-RO', label: 'Romanian' },
{ value: 'ru', label: 'Russian' },
{ value: 'sr-SP', label: 'Serbian' },
{ value: 'sk', label: 'Slovak' },
{ value: 'es-AR', label: 'Spanish (Argentina)' },
{ value: 'es-BO', label: 'Spanish (Bolivia)' },
{ value: 'es-CL', label: 'Spanish (Chile)' },
{ value: 'es-CO', label: 'Spanish (Colombia)' },
{ value: 'es-CR', label: 'Spanish (Costa Rica)' },
{ value: 'es-DO', label: 'Spanish (Dominican Republic)' },
{ value: 'es-EC', label: 'Spanish (Ecuador)' },
{ value: 'es-SV', label: 'Spanish (El Salvador)' },
{ value: 'es-GT', label: 'Spanish (Guatemala)' },
{ value: 'es-HN', label: 'Spanish (Honduras)' },
{ value: 'es-MX', label: 'Spanish (Mexico)' },
{ value: 'es-NI', label: 'Spanish (Nicaragua)' },
{ value: 'es-PA', label: 'Spanish (Panama)' },
{ value: 'es-PY', label: 'Spanish (Paraguay)' },
{ value: 'es-PE', label: 'Spanish (Peru)' },
{ value: 'es-PR', label: 'Spanish (Puerto Rico)' },
{ value: 'es-ES', label: 'Spanish (Spain)' },
{ value: 'es-US', label: 'Spanish (US)' },
{ value: 'es-UY', label: 'Spanish (Uruguay)' },
{ value: 'es-VE', label: 'Spanish (Venezuela)' },
{ value: 'sv-SE', label: 'Swedish' },
{ value: 'tr', label: 'Turkish' },
{ value: 'zu', label: 'Zulu' },
];
const handleSelect = (value: string) => {

View file

@ -44,7 +44,7 @@ function Speech() {
const [decibelValue, setDecibelValue] = useRecoilState(store.decibelValue);
const [autoSendText, setAutoSendText] = useRecoilState(store.autoSendText);
const [engineTTS, setEngineTTS] = useRecoilState<string>(store.engineTTS);
const [voice, setVoice] = useRecoilState<string>(store.voice);
const [voice, setVoice] = useRecoilState(store.voice);
const [cloudBrowserVoices, setCloudBrowserVoices] = useRecoilState<boolean>(
store.cloudBrowserVoices,
);
@ -53,7 +53,7 @@ function Speech() {
const [playbackRate, setPlaybackRate] = useRecoilState(store.playbackRate);
const updateSetting = useCallback(
(key, newValue) => {
(key: string, newValue: string | number) => {
const settings = {
sttExternal: { value: sttExternal, setFunc: setSttExternal },
ttsExternal: { value: ttsExternal, setFunc: setTtsExternal },

View file

@ -14,13 +14,13 @@ const EngineTTSDropdown: React.FC<EngineTTSDropdownProps> = ({ external }) => {
const endpointOptions = external
? [
{ value: 'browser', display: localize('com_nav_browser') },
{ value: 'edge', display: localize('com_nav_edge') },
{ value: 'external', display: localize('com_nav_external') },
{ value: 'browser', label: localize('com_nav_browser') },
{ value: 'edge', label: localize('com_nav_edge') },
{ value: 'external', label: localize('com_nav_external') },
]
: [
{ value: 'browser', display: localize('com_nav_browser') },
{ value: 'edge', display: localize('com_nav_edge') },
{ value: 'browser', label: localize('com_nav_browser') },
{ value: 'edge', label: localize('com_nav_edge') },
];
const handleSelect = (value: string) => {

View file

@ -1,39 +1,33 @@
import React, { useEffect, useState, useMemo } from 'react';
import { useRecoilState } from 'recoil';
import Dropdown from '~/components/ui/DropdownNoState';
import React from 'react';
import { useRecoilState, useRecoilValue } from 'recoil';
import type { Option } from '~/common';
import DropdownNoState from '~/components/ui/DropdownNoState';
import { useLocalize, useTextToSpeech } from '~/hooks';
import { logger } from '~/utils';
import store from '~/store';
export default function VoiceDropdown() {
const localize = useLocalize();
const { voices = [] } = useTextToSpeech();
const [voice, setVoice] = useRecoilState(store.voice);
const { voices } = useTextToSpeech();
const [voiceOptions, setVoiceOptions] = useState([]);
const [engineTTS] = useRecoilState(store.engineTTS);
const engineTTS = useRecoilValue<string>(store.engineTTS);
useEffect(() => {
async function fetchVoices() {
const options = await voices();
setVoiceOptions(options);
if (!voice && options.length > 0) {
setVoice(options[0]);
}
const handleVoiceChange = (newValue?: string | Option) => {
logger.log('Voice changed:', newValue);
const newVoice = typeof newValue === 'string' ? newValue : newValue?.value;
if (newVoice != null) {
return setVoice(newVoice.toString());
}
fetchVoices();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [engineTTS]);
const memoizedVoiceOptions = useMemo(() => voiceOptions, [voiceOptions]);
};
return (
<div className="flex items-center justify-between">
<div>{localize('com_nav_voice_select')}</div>
<Dropdown
<DropdownNoState
key={`voice-dropdown-${engineTTS}-${voices.length}`}
value={voice}
onChange={setVoice}
options={memoizedVoiceOptions}
options={voices}
onChange={handleVoiceChange}
sizeClasses="min-w-[200px] !max-w-[400px] [--anchor-max-width:400px]"
anchor="bottom start"
testId="VoiceDropdown"