From 175ddca375cef765cec8ca5bbc527a205c40bf25 Mon Sep 17 00:00:00 2001 From: polwex Date: Tue, 3 Jun 2025 15:41:31 +0700 Subject: preeeeettty much done FUCK YES --- src/components/tones/ToneSelectorClient.tsx | 405 ++++++++++++++++++---------- 1 file changed, 262 insertions(+), 143 deletions(-) (limited to 'src/components/tones/ToneSelectorClient.tsx') diff --git a/src/components/tones/ToneSelectorClient.tsx b/src/components/tones/ToneSelectorClient.tsx index 0ee9433..8a0327c 100644 --- a/src/components/tones/ToneSelectorClient.tsx +++ b/src/components/tones/ToneSelectorClient.tsx @@ -1,52 +1,240 @@ -'use client'; +"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 +import { useState, useEffect, useTransition, useRef } from "react"; +import { WordData } from "@/zoom/logic/types"; +import { + fetchWordsByToneAndSyllables, + mutateToneSelection, +} 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 +import { MutationOrder, ToneQuery } from "@/lib/types/phonetics"; +import { ProsodySyllable } from "@/lib/types/cards"; +import { ArrowLeft, ArrowRight, Loader2, Volume2 } from "lucide-react"; +function getColorByTone(tone: string): string { + if (tone === "mid") return "blue"; + if (tone === "low") return "green"; + if (tone === "falling") return "gold"; + if (tone === "high") return "purple"; + if (tone === "rising") return "black"; + else return "black"; +} // Helper to display tones prominently -const ProminentToneDisplay = ({ wordData }: { wordData: WordData }) => { - if (!wordData.prosody || !Array.isArray(wordData.prosody)) { - return

No prosody data

; +const ProminentToneDisplay = ({ word }: { word: any }) => { + const tones: string[] = word.tone_sequence.split(","); + const syls: string[] = word.syl_seq.split(","); + const [isPending, startTransition] = useTransition(); + function mutateWord(idx: number) { + console.log("changing", idx); + const mutationOrder: MutationOrder = syls.map((s, i) => { + if (idx === i) return { change: tones[idx]! }; + else return { keep: syls[i]! }; + }); + console.log("hey hey", word); + startTransition(async () => { + const words = await mutateToneSelection(mutationOrder); + console.log({ words }); + // setCurrentWord(word); + }); + } + // playing audio + // const sourceRef = useRef(null); + const audioRef = useRef(null); + + async function playAudio() { + // setLoading(true); + // const audioContext = new (window.AudioContext || + // (window as any).webkitAudioContext)(); + // const response = await fetch(audioUrl); + // const arrayBuffer = await response.arrayBuffer(); + // const audioBuffer = await audioContext.decodeAudioData(arrayBuffer); + // if (audioContext && audioBuffer) { + // setLoading(false); + // const source = audioContext.createBufferSource(); + // source.buffer = audioBuffer; + // source.connect(audioContext.destination); + // source.start(); + // sourceRef.current = source; + // } + const res = await fetch(`/api/tts?word=${word.spelling}&lang=thai`); + const audioBlob = await res.blob(); + const audioURL = URL.createObjectURL(audioBlob); + if (audioRef.current) { + audioRef.current.src = audioURL; + audioRef.current.play(); + } } return (
-

{wordData.spelling}

-
- {wordData.prosody.map((p, index) => ( -
-

Syllable {index + 1}

-

{p.tone ?? '?'}

-
+

+ {syls.map((syl: string, idx: number) => ( + mutateWord(idx)} + style={{ color: getColorByTone(tones[idx]!) }} + className="cursor-pointer hover:text-gray-700" + > + {syl} + ))} +

+
+

{word.ipa}

+ + {isPending && } +
- {wordData.ipa && wordData.ipa.length > 0 && ( -

- {wordData.ipa.map(i => i.ipa).join(' / ')} -

- )}
); }; +export default function ToneSelectorClient({ + initialData, + initialTones, +}: { + initialData: any[]; + initialTones: ToneQuery; +}) { + const [data, setData] = useState(initialData); + const [currentIdx, setCurrentIdx] = useState(0); + const [isLoading, startTransition] = useTransition(); + const [selectedTones, setTones] = useState(initialTones); + + function goPrev() { + setCurrentIdx((i) => (i === 0 ? 0 : i - 1)); + } + function goNext() { + setCurrentIdx((i) => (i === data.length - 1 ? data.length - 1 : i + 1)); + } + + const handleFetch = () => { + startTransition(async () => { + const words = await fetchWordsByToneAndSyllables(selectedTones); + setData(words); + }); + }; -export default function ToneSelectorClient({ initialWord }: { initialWord: WordData | null }) { - const [currentWord, setCurrentWord] = useState(initialWord); - const [syllableCount, setSyllableCount] = useState(initialWord?.syllables || 1); - const [selectedTones, setSelectedTones] = useState<(number | null)[]>( - initialWord?.prosody?.map(p => p.tone ?? null) || [null] + return ( +
+ + + +
); - const [isLoading, startTransition] = useTransition(); +} +type IProps = { + isLoading: boolean; + currentWord: any; + goPrev: () => void; + goNext: () => void; +}; +function Inner({ isLoading, currentWord, goPrev, goNext }: IProps) { + return isLoading ? ( + + + + + + + + + + + ) : currentWord ? ( + + + Current Word + + + + {/* You can add more details from WordData here if needed, like definitions */} + + + + + + + ) : ( + + + No Word Found + + +

+ Could not find a Thai word matching your criteria. Try different + selections. +

+
+
+ ); +} + +type ToneFormProps = { + isLoading: boolean; + handleFetch: (tones: ToneQuery) => void; + selectedTones: ToneQuery; + setTones: React.Dispatch>; +}; +function ToneForm({ + selectedTones, + setTones, + isLoading, + handleFetch, +}: ToneFormProps) { + const thaiTones = [ + { value: "mid", label: "1 (Mid)" }, + { value: "low", label: "2 (Low)" }, + { value: "falling", label: "3 (Falling)" }, + { value: "high", label: "4 (High)" }, + { value: "rising", label: "5 (Rising)" }, + ]; + const [syllableCount, setSyllableCount] = useState(2); + function decrSyl() { + setSyllableCount((s) => (s <= 1 ? 1 : s - 1)); + } + function incrSyl() { + setSyllableCount((s) => (s >= 5 ? 5 : s + 1)); + } useEffect(() => { // Adjust selectedTones array length when syllableCount changes - setSelectedTones(prevTones => { + setTones((prevTones) => { const newTones = Array(syllableCount).fill(null); for (let i = 0; i < Math.min(prevTones.length, syllableCount); i++) { newTones[i] = prevTones[i]; @@ -55,79 +243,51 @@ export default function ToneSelectorClient({ initialWord }: { initialWord: WordD }); }, [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 + 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 tone = value === "any" ? null : value; + setTones((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 ( -
- - - Thai Tone Explorer - Select syllable count and tones to find Thai words. - - -
- - -
- + + + Thai Tone Explorer + + Select syllable count and tones to find Thai words. + + + +
{Array.from({ length: syllableCount }).map((_, index) => ( -
- +
))} - - - - - - - {isLoading && !currentWord && ( - - - - - - - - - )} - - {!isLoading && currentWord && ( - - - Current Word - - - - {/* You can add more details from WordData here if needed, like definitions */} - {currentWord.senses && currentWord.senses.length > 0 && ( -
-

Meanings:

- {currentWord.senses.map((sense, sIdx) => ( -
-

{sense.pos}

- {sense.senses && Array.isArray(sense.senses) && sense.senses.map((subSense, ssIdx) => ( - subSense.glosses && Array.isArray(subSense.glosses) && subSense.glosses.map((gloss: string, gIdx: number) => ( -

- {gloss}

- )) - ))} -
- ))} -
- )} -
-
- )} - - {!isLoading && !currentWord && ( - - - No Word Found - - -

- Could not find a Thai word matching your criteria. Try different selections. -

-
-
- )} -
+
+
+ + + + + +
); } -- cgit v1.2.3