diff options
author | polwex <polwex@sortug.com> | 2025-05-29 12:10:22 +0700 |
---|---|---|
committer | polwex <polwex@sortug.com> | 2025-05-29 12:10:22 +0700 |
commit | a3f24ea79b14394b24c4b60a010651eb29eeb872 (patch) | |
tree | cb1c4937084116f66a59727ee752afd974714c8e /src/components/tones/ToneSelectorClient.tsx | |
parent | 7abf2227438362ad30820ee236405ec1b57a40b6 (diff) |
glorious new db
Diffstat (limited to 'src/components/tones/ToneSelectorClient.tsx')
-rw-r--r-- | src/components/tones/ToneSelectorClient.tsx | 199 |
1 files changed, 199 insertions, 0 deletions
diff --git a/src/components/tones/ToneSelectorClient.tsx b/src/components/tones/ToneSelectorClient.tsx new file mode 100644 index 0000000..0ee9433 --- /dev/null +++ b/src/components/tones/ToneSelectorClient.tsx @@ -0,0 +1,199 @@ +'use client'; + +import { useState, useEffect, useTransition } from 'react'; +import { WordData } from '@/zoom/logic/types'; +import { fetchWordsByToneAndSyllables } from '@/actions/tones'; +import { Button } from '@/components/ui/button'; +import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'; +import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from '@/components/ui/card'; +import { Label } from '@/components/ui/label'; +import { Skeleton } from '@/components/ui/skeleton'; // For loading state + +// Helper to display tones prominently +const ProminentToneDisplay = ({ wordData }: { wordData: WordData }) => { + if (!wordData.prosody || !Array.isArray(wordData.prosody)) { + return <p className="text-gray-500">No prosody data</p>; + } + + return ( + <div className="flex flex-col items-center mb-4"> + <h1 className="text-6xl font-bold text-blue-600 mb-2">{wordData.spelling}</h1> + <div className="flex space-x-4"> + {wordData.prosody.map((p, index) => ( + <div key={index} className="text-center"> + <p className="text-sm text-gray-500">Syllable {index + 1}</p> + <p className="text-5xl font-semibold text-indigo-500">{p.tone ?? '?'}</p> + </div> + ))} + </div> + {wordData.ipa && wordData.ipa.length > 0 && ( + <p className="text-xl text-gray-700 mt-2"> + {wordData.ipa.map(i => i.ipa).join(' / ')} + </p> + )} + </div> + ); +}; + + +export default function ToneSelectorClient({ initialWord }: { initialWord: WordData | null }) { + const [currentWord, setCurrentWord] = useState<WordData | null>(initialWord); + const [syllableCount, setSyllableCount] = useState<number>(initialWord?.syllables || 1); + const [selectedTones, setSelectedTones] = useState<(number | null)[]>( + initialWord?.prosody?.map(p => p.tone ?? null) || [null] + ); + const [isLoading, startTransition] = useTransition(); + + useEffect(() => { + // Adjust selectedTones array length when syllableCount changes + setSelectedTones(prevTones => { + const newTones = Array(syllableCount).fill(null); + for (let i = 0; i < Math.min(prevTones.length, syllableCount); i++) { + newTones[i] = prevTones[i]; + } + return newTones; + }); + }, [syllableCount]); + + const handleFetchWord = () => { + startTransition(async () => { + const word = await fetchWordsByToneAndSyllables(syllableCount, selectedTones); + setCurrentWord(word); + }); + }; + + const handleSyllableCountChange = (value: string) => { + const count = parseInt(value, 10); + if (!isNaN(count) && count > 0 && count <= 5) { // Max 5 syllables for simplicity + setSyllableCount(count); + } + }; + + const handleToneChange = (syllableIndex: number, value: string) => { + const tone = value === 'any' ? null : parseInt(value, 10); + setSelectedTones(prevTones => { + const newTones = [...prevTones]; + newTones[syllableIndex] = tone; + return newTones; + }); + }; + + const thaiTones = [ + { value: '1', label: '1 (Mid)' }, + { value: '2', label: '2 (Low)' }, + { value: '3', label: '3 (Falling)' }, + { value: '4', label: '4 (High)' }, + { value: '5', label: '5 (Rising)' }, + ]; + + return ( + <div className="container mx-auto p-4 max-w-2xl"> + <Card className="mb-6"> + <CardHeader> + <CardTitle>Thai Tone Explorer</CardTitle> + <CardDescription>Select syllable count and tones to find Thai words.</CardDescription> + </CardHeader> + <CardContent className="space-y-6"> + <div> + <Label htmlFor="syllable-count" className="text-lg font-medium">Number of Syllables</Label> + <Select + value={syllableCount.toString()} + onValueChange={handleSyllableCountChange} + > + <SelectTrigger id="syllable-count" className="w-full md:w-1/2 mt-1"> + <SelectValue placeholder="Select number of syllables" /> + </SelectTrigger> + <SelectContent> + {[1, 2, 3, 4, 5].map(num => ( + <SelectItem key={num} value={num.toString()}> + {num} Syllable{num > 1 ? 's' : ''} + </SelectItem> + ))} + </SelectContent> + </Select> + </div> + + {Array.from({ length: syllableCount }).map((_, index) => ( + <div key={index}> + <Label htmlFor={`tone-select-${index}`} className="text-lg font-medium"> + Tone for Syllable {index + 1} + </Label> + <Select + value={selectedTones[index]?.toString() || 'any'} + onValueChange={(value) => handleToneChange(index, value)} + > + <SelectTrigger id={`tone-select-${index}`} className="w-full md:w-1/2 mt-1"> + <SelectValue placeholder={`Select tone for syllable ${index + 1}`} /> + </SelectTrigger> + <SelectContent> + <SelectItem value="any">Any Tone</SelectItem> + {thaiTones.map(tone => ( + <SelectItem key={tone.value} value={tone.value}> + {tone.label} + </SelectItem> + ))} + </SelectContent> + </Select> + </div> + ))} + </CardContent> + <CardFooter> + <Button onClick={handleFetchWord} disabled={isLoading} className="w-full md:w-auto"> + {isLoading ? 'Searching...' : 'Find Word'} + </Button> + </CardFooter> + </Card> + + {isLoading && !currentWord && ( + <Card> + <CardHeader><Skeleton className="h-12 w-3/4" /></CardHeader> + <CardContent className="space-y-4"> + <Skeleton className="h-8 w-1/2" /> + <Skeleton className="h-20 w-full" /> + <Skeleton className="h-6 w-full" /> + </CardContent> + </Card> + )} + + {!isLoading && currentWord && ( + <Card> + <CardHeader> + <CardTitle className="text-center">Current Word</CardTitle> + </CardHeader> + <CardContent> + <ProminentToneDisplay wordData={currentWord} /> + {/* You can add more details from WordData here if needed, like definitions */} + {currentWord.senses && currentWord.senses.length > 0 && ( + <div className="mt-4 pt-4 border-t"> + <h3 className="text-lg font-semibold mb-2">Meanings:</h3> + {currentWord.senses.map((sense, sIdx) => ( + <div key={sIdx} className="mb-2 p-2 border rounded bg-gray-50"> + <p className="font-medium text-indigo-600">{sense.pos}</p> + {sense.senses && Array.isArray(sense.senses) && sense.senses.map((subSense, ssIdx) => ( + subSense.glosses && Array.isArray(subSense.glosses) && subSense.glosses.map((gloss: string, gIdx: number) => ( + <p key={`${ssIdx}-${gIdx}`} className="text-sm text-gray-700 ml-2">- {gloss}</p> + )) + ))} + </div> + ))} + </div> + )} + </CardContent> + </Card> + )} + + {!isLoading && !currentWord && ( + <Card> + <CardHeader> + <CardTitle className="text-center">No Word Found</CardTitle> + </CardHeader> + <CardContent> + <p className="text-center text-gray-600"> + Could not find a Thai word matching your criteria. Try different selections. + </p> + </CardContent> + </Card> + )} + </div> + ); +} |