diff --git a/src/components/icons/ResetIcon.tsx b/src/components/icons/ResetIcon.tsx index 4136118..09f2e46 100644 --- a/src/components/icons/ResetIcon.tsx +++ b/src/components/icons/ResetIcon.tsx @@ -1,16 +1,7 @@ -import React from "react"; -const ResetIcon = ({ className = "" }: { className?: string }) => { +const ResetIcon = () => { return ( - - - + ); }; diff --git a/src/components/settings/AlwaysOnMicrophone.tsx b/src/components/settings/AlwaysOnMicrophone.tsx index c4c40c1..48fdae0 100644 --- a/src/components/settings/AlwaysOnMicrophone.tsx +++ b/src/components/settings/AlwaysOnMicrophone.tsx @@ -7,7 +7,7 @@ interface AlwaysOnMicrophoneProps { grouped?: boolean; } -export const AlwaysOnMicrophone: React.FC = ({ +export const AlwaysOnMicrophone: React.FC = React.memo(({ descriptionMode = "tooltip", grouped = false, }) => { @@ -26,4 +26,4 @@ export const AlwaysOnMicrophone: React.FC = ({ grouped={grouped} /> ); -}; +}); diff --git a/src/components/settings/AudioFeedback.tsx b/src/components/settings/AudioFeedback.tsx index dfe73e5..36524c1 100644 --- a/src/components/settings/AudioFeedback.tsx +++ b/src/components/settings/AudioFeedback.tsx @@ -7,7 +7,7 @@ interface AudioFeedbackProps { grouped?: boolean; } -export const AudioFeedback: React.FC = ({ +export const AudioFeedback: React.FC = React.memo(({ descriptionMode = "tooltip", grouped = false, }) => { @@ -26,4 +26,4 @@ export const AudioFeedback: React.FC = ({ grouped={grouped} /> ); -}; +}); diff --git a/src/components/settings/CorrectWords.tsx b/src/components/settings/CorrectWords.tsx index 0d87d2c..1bba17d 100644 --- a/src/components/settings/CorrectWords.tsx +++ b/src/components/settings/CorrectWords.tsx @@ -7,7 +7,7 @@ interface CorrectWordsProps { grouped?: boolean; } -export const CorrectWords: React.FC = ({ +export const CorrectWords: React.FC = React.memo(({ descriptionMode = "tooltip", grouped = false, }) => { @@ -17,8 +17,9 @@ export const CorrectWords: React.FC = ({ const handleAddWord = () => { const trimmedWord = newWord.trim(); - if (trimmedWord && !trimmedWord.includes(' ') && !correctWords.includes(trimmedWord)) { - updateSetting("correct_words", [...correctWords, trimmedWord]); + const sanitizedWord = trimmedWord.replace(/[<>"'&]/g, ''); + if (sanitizedWord && !sanitizedWord.includes(' ') && sanitizedWord.length <= 50 && !correctWords.includes(sanitizedWord)) { + updateSetting("correct_words", [...correctWords, sanitizedWord]); setNewWord(""); } }; @@ -54,7 +55,7 @@ export const CorrectWords: React.FC = ({ /> + /> ); })()} diff --git a/src/components/settings/LanguageSelector.tsx b/src/components/settings/LanguageSelector.tsx index c29db8c..c0ec705 100644 --- a/src/components/settings/LanguageSelector.tsx +++ b/src/components/settings/LanguageSelector.tsx @@ -1,55 +1,14 @@ -import React, { useState, useRef, useEffect } from "react"; +import React, { useState, useRef, useEffect, useMemo } from "react"; import { SettingContainer } from "../ui/SettingContainer"; -import ResetIcon from "../icons/ResetIcon"; +import { ResetButton } from "../ui/ResetButton"; import { useSettings } from "../../hooks/useSettings"; +import { LANGUAGES } from "../../lib/constants/languages"; interface LanguageSelectorProps { descriptionMode?: "inline" | "tooltip"; grouped?: boolean; } -const LANGUAGES = [ - { code: "auto", name: "Auto" }, - { code: "en", name: "English" }, - { code: "zh", name: "Chinese" }, - { code: "hi", name: "Hindi" }, - { code: "es", name: "Spanish" }, - { code: "fr", name: "French" }, - { code: "ar", name: "Arabic" }, - { code: "pt", name: "Portuguese" }, - { code: "ru", name: "Russian" }, - { code: "ja", name: "Japanese" }, - { code: "de", name: "German" }, - { code: "ko", name: "Korean" }, - { code: "it", name: "Italian" }, - { code: "tr", name: "Turkish" }, - { code: "vi", name: "Vietnamese" }, - { code: "pl", name: "Polish" }, - { code: "nl", name: "Dutch" }, - { code: "uk", name: "Ukrainian" }, - { code: "fa", name: "Persian" }, - { code: "th", name: "Thai" }, - { code: "ro", name: "Romanian" }, - { code: "el", name: "Greek" }, - { code: "cs", name: "Czech" }, - { code: "sv", name: "Swedish" }, - { code: "hu", name: "Hungarian" }, - { code: "he", name: "Hebrew" }, - { code: "da", name: "Danish" }, - { code: "fi", name: "Finnish" }, - { code: "no", name: "Norwegian" }, - { code: "sk", name: "Slovak" }, - { code: "bg", name: "Bulgarian" }, - { code: "hr", name: "Croatian" }, - { code: "lt", name: "Lithuanian" }, - { code: "sl", name: "Slovenian" }, - { code: "lv", name: "Latvian" }, - { code: "et", name: "Estonian" }, - { code: "sr", name: "Serbian" }, - { code: "is", name: "Icelandic" }, - { code: "id", name: "Indonesian" }, -]; - export const LanguageSelector: React.FC = ({ descriptionMode = "tooltip", grouped = false, @@ -85,12 +44,15 @@ export const LanguageSelector: React.FC = ({ } }, [isOpen]); - const filteredLanguages = LANGUAGES.filter((language) => - language.name.toLowerCase().includes(searchQuery.toLowerCase()), + const filteredLanguages = useMemo( + () => LANGUAGES.filter((language) => + language.label.toLowerCase().includes(searchQuery.toLowerCase()), + ), + [searchQuery] ); const selectedLanguageName = - LANGUAGES.find((lang) => lang.code === selectedLanguage)?.name || "Auto"; + LANGUAGES.find((lang) => lang.value === selectedLanguage)?.label || "Auto"; const handleLanguageSelect = async (languageCode: string) => { await updateSetting("selected_language", languageCode); @@ -114,7 +76,7 @@ export const LanguageSelector: React.FC = ({ const handleKeyDown = (event: React.KeyboardEvent) => { if (event.key === "Enter" && filteredLanguages.length > 0) { // Select first filtered language on Enter - handleLanguageSelect(filteredLanguages[0].code); + handleLanguageSelect(filteredLanguages[0].value); } else if (event.key === "Escape") { setIsOpen(false); setSearchQuery(""); @@ -181,17 +143,17 @@ export const LanguageSelector: React.FC = ({ ) : ( filteredLanguages.map((language) => ( )) @@ -200,13 +162,10 @@ export const LanguageSelector: React.FC = ({ )} - + /> {isUpdating("selected_language") && (
diff --git a/src/components/settings/MicrophoneSelector.tsx b/src/components/settings/MicrophoneSelector.tsx index a958719..dc9bccf 100644 --- a/src/components/settings/MicrophoneSelector.tsx +++ b/src/components/settings/MicrophoneSelector.tsx @@ -1,33 +1,15 @@ import React from "react"; import { Dropdown } from "../ui/Dropdown"; import { SettingContainer } from "../ui/SettingContainer"; -import ResetIcon from "../icons/ResetIcon"; +import { ResetButton } from "../ui/ResetButton"; import { useSettings } from "../../hooks/useSettings"; -// Simple refresh icon component -const RefreshIcon: React.FC<{ className?: string }> = ({ className = "" }) => ( - - - -); - interface MicrophoneSelectorProps { descriptionMode?: "inline" | "tooltip"; grouped?: boolean; } -export const MicrophoneSelector: React.FC = ({ +export const MicrophoneSelector: React.FC = React.memo(({ descriptionMode = "tooltip", grouped = false, }) => { @@ -45,14 +27,10 @@ export const MicrophoneSelector: React.FC = ({ const handleMicrophoneSelect = async (deviceName: string) => { await updateSetting("selected_microphone", deviceName); - console.log( - `Microphone changed to: ${audioDevices.find((d) => d.name === deviceName)?.name}`, - ); }; const handleReset = async () => { await resetSetting("selected_microphone"); - console.log("Microphone reset to default"); }; const microphoneOptions = audioDevices.map(device => ({ @@ -76,19 +54,11 @@ export const MicrophoneSelector: React.FC = ({ disabled={isUpdating("selected_microphone") || isLoading} onRefresh={refreshAudioDevices} /> - + />
- {isUpdating("selected_microphone") && ( -
-
-
- )} ); -}; +}); diff --git a/src/components/settings/OutputDeviceSelector.tsx b/src/components/settings/OutputDeviceSelector.tsx index f3b3776..156805e 100644 --- a/src/components/settings/OutputDeviceSelector.tsx +++ b/src/components/settings/OutputDeviceSelector.tsx @@ -1,7 +1,7 @@ import React from "react"; import { Dropdown } from "../ui/Dropdown"; import { SettingContainer } from "../ui/SettingContainer"; -import ResetIcon from "../icons/ResetIcon"; +import { ResetButton } from "../ui/ResetButton"; import { useSettings } from "../../hooks/useSettings"; interface OutputDeviceSelectorProps { @@ -9,7 +9,7 @@ interface OutputDeviceSelectorProps { grouped?: boolean; } -export const OutputDeviceSelector: React.FC = ({ +export const OutputDeviceSelector: React.FC = React.memo(({ descriptionMode = "tooltip", grouped = false, }) => { @@ -28,14 +28,10 @@ export const OutputDeviceSelector: React.FC = ({ const handleOutputDeviceSelect = async (deviceName: string) => { await updateSetting("selected_output_device", deviceName); - console.log( - `Output device changed to: ${outputDevices.find((d) => d.name === deviceName)?.name}`, - ); }; const handleReset = async () => { await resetSetting("selected_output_device"); - console.log("Output device reset to default"); }; const outputDeviceOptions = outputDevices.map(device => ({ @@ -59,19 +55,11 @@ export const OutputDeviceSelector: React.FC = ({ disabled={isUpdating("selected_output_device") || isLoading} onRefresh={refreshOutputDevices} /> - + /> - {isUpdating("selected_output_device") && ( -
-
-
- )} ); -}; +}); diff --git a/src/components/settings/PushToTalk.tsx b/src/components/settings/PushToTalk.tsx index 848e504..01ed019 100644 --- a/src/components/settings/PushToTalk.tsx +++ b/src/components/settings/PushToTalk.tsx @@ -7,7 +7,7 @@ interface PushToTalkProps { grouped?: boolean; } -export const PushToTalk: React.FC = ({ +export const PushToTalk: React.FC = React.memo(({ descriptionMode = "tooltip", grouped = false, }) => { @@ -26,4 +26,4 @@ export const PushToTalk: React.FC = ({ grouped={grouped} /> ); -}; +}); diff --git a/src/components/settings/ShowOverlay.tsx b/src/components/settings/ShowOverlay.tsx index e177e6d..d606e74 100644 --- a/src/components/settings/ShowOverlay.tsx +++ b/src/components/settings/ShowOverlay.tsx @@ -15,7 +15,7 @@ const overlayOptions = [ { value: "top", label: "Top" }, ]; -export const ShowOverlay: React.FC = ({ +export const ShowOverlay: React.FC = React.memo(({ descriptionMode = "tooltip", grouped = false, }) => { @@ -41,4 +41,4 @@ export const ShowOverlay: React.FC = ({ /> ); -}; +}); diff --git a/src/components/settings/TranslateToEnglish.tsx b/src/components/settings/TranslateToEnglish.tsx index 92917c3..00f4599 100644 --- a/src/components/settings/TranslateToEnglish.tsx +++ b/src/components/settings/TranslateToEnglish.tsx @@ -7,7 +7,7 @@ interface TranslateToEnglishProps { grouped?: boolean; } -export const TranslateToEnglish: React.FC = ({ +export const TranslateToEnglish: React.FC = React.memo(({ descriptionMode = "tooltip", grouped = false, }) => { @@ -26,4 +26,4 @@ export const TranslateToEnglish: React.FC = ({ grouped={grouped} /> ); -}; +}); diff --git a/src/components/ui/ResetButton.tsx b/src/components/ui/ResetButton.tsx new file mode 100644 index 0000000..eb98b4e --- /dev/null +++ b/src/components/ui/ResetButton.tsx @@ -0,0 +1,22 @@ +import React from "react"; +import ResetIcon from "../icons/ResetIcon"; + +interface ResetButtonProps { + onClick: () => void; + disabled?: boolean; + className?: string; +} + +export const ResetButton: React.FC = React.memo(({ + onClick, + disabled = false, + className = "" +}) => ( + +)); \ No newline at end of file diff --git a/src/lib/constants/languages.ts b/src/lib/constants/languages.ts new file mode 100644 index 0000000..8e661fd --- /dev/null +++ b/src/lib/constants/languages.ts @@ -0,0 +1,59 @@ +export interface Language { + value: string; + label: string; +} + +export const LANGUAGES: Language[] = [ + { value: "auto", label: "Auto Detect" }, + { value: "en", label: "English" }, + { value: "zh", label: "Chinese" }, + { value: "de", label: "German" }, + { value: "es", label: "Spanish" }, + { value: "ru", label: "Russian" }, + { value: "ko", label: "Korean" }, + { value: "fr", label: "French" }, + { value: "ja", label: "Japanese" }, + { value: "pt", label: "Portuguese" }, + { value: "tr", label: "Turkish" }, + { value: "pl", label: "Polish" }, + { value: "ca", label: "Catalan" }, + { value: "nl", label: "Dutch" }, + { value: "ar", label: "Arabic" }, + { value: "sv", label: "Swedish" }, + { value: "it", label: "Italian" }, + { value: "id", label: "Indonesian" }, + { value: "hi", label: "Hindi" }, + { value: "fi", label: "Finnish" }, + { value: "vi", label: "Vietnamese" }, + { value: "he", label: "Hebrew" }, + { value: "uk", label: "Ukrainian" }, + { value: "el", label: "Greek" }, + { value: "ms", label: "Malay" }, + { value: "cs", label: "Czech" }, + { value: "ro", label: "Romanian" }, + { value: "da", label: "Danish" }, + { value: "hu", label: "Hungarian" }, + { value: "ta", label: "Tamil" }, + { value: "no", label: "Norwegian" }, + { value: "th", label: "Thai" }, + { value: "ur", label: "Urdu" }, + { value: "hr", label: "Croatian" }, + { value: "bg", label: "Bulgarian" }, + { value: "lt", label: "Lithuanian" }, + { value: "la", label: "Latin" }, + { value: "mi", label: "Maori" }, + { value: "ml", label: "Malayalam" }, + { value: "cy", label: "Welsh" }, + { value: "sk", label: "Slovak" }, + { value: "te", label: "Telugu" }, + { value: "fa", label: "Persian" }, + { value: "lv", label: "Latvian" }, + { value: "bn", label: "Bengali" }, + { value: "sr", label: "Serbian" }, + { value: "az", label: "Azerbaijani" }, + { value: "sl", label: "Slovenian" }, + { value: "kn", label: "Kannada" }, + { value: "et", label: "Estonian" }, + { value: "mk", label: "Macedonian" }, + { value: "br", label: "Breton" }, +]; \ No newline at end of file