diff options
author | polwex <polwex@sortug.com> | 2025-08-16 13:18:51 +0700 |
---|---|---|
committer | polwex <polwex@sortug.com> | 2025-08-16 13:18:51 +0700 |
commit | 9a1e8af707acec9bfabd4c5be9ba6595d7f14c3e (patch) | |
tree | ba345111027c359cf76501781236b4d8f1581ade | |
parent | f6f8e791fc95d5efb585177071ba0328d3c3b17f (diff) |
pretty fast gotta say
-rw-r--r-- | src/components/Main.tsx | 6 | ||||
-rw-r--r-- | src/components/tones/ToneSelectorClient.tsx | 65 | ||||
-rw-r--r-- | src/pages/tones.tsx | 1 | ||||
-rw-r--r-- | src/picker/App.tsx | 5 | ||||
-rw-r--r-- | src/styles/globals.css | 32 | ||||
-rw-r--r-- | src/zoom/App.tsx | 4 | ||||
-rw-r--r-- | src/zoom/zoom.css | 32 |
7 files changed, 95 insertions, 50 deletions
diff --git a/src/components/Main.tsx b/src/components/Main.tsx index 3e6f3e7..6be61a3 100644 --- a/src/components/Main.tsx +++ b/src/components/Main.tsx @@ -18,8 +18,9 @@ import { CardHeader, CardTitle, } from "@/components/ui/card"; -import { Loader2 } from "lucide-react"; // Loading spinner import { useRouter } from "waku"; +import { Spinner } from "./ui/spinner"; +import { Loader2 } from "lucide-react"; const SorlangPage: React.FC = () => { const [textValue, setTextValue] = useState<string>(""); @@ -192,7 +193,8 @@ const SorlangPage: React.FC = () => { > {isExtracting ? ( <> - <Loader2 className="mr-2 h-4 w-4 animate-spin" /> + {/*<Loader2 className="mr-2 h-4 w-4 animate-spin" />*/} + <Spinner /> Extracting... </> ) : ( diff --git a/src/components/tones/ToneSelectorClient.tsx b/src/components/tones/ToneSelectorClient.tsx index 8a0327c..48580a4 100644 --- a/src/components/tones/ToneSelectorClient.tsx +++ b/src/components/tones/ToneSelectorClient.tsx @@ -1,6 +1,7 @@ "use client"; -import { useState, useEffect, useTransition, useRef } from "react"; +import { Spinner } from "@/components/ui/spinner"; +import { useState, useEffect, useTransition, useRef, useCallback } from "react"; import { WordData } from "@/zoom/logic/types"; import { fetchWordsByToneAndSyllables, @@ -26,7 +27,7 @@ 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"; +import { ArrowLeft, ArrowRight, Volume2 } from "lucide-react"; function getColorByTone(tone: string): string { if (tone === "mid") return "blue"; @@ -38,6 +39,7 @@ function getColorByTone(tone: string): string { } // Helper to display tones prominently const ProminentToneDisplay = ({ word }: { word: any }) => { + const [isLoading, setLoading] = useState(false); const tones: string[] = word.tone_sequence.split(","); const syls: string[] = word.syl_seq.split(","); const [isPending, startTransition] = useTransition(); @@ -59,7 +61,7 @@ const ProminentToneDisplay = ({ word }: { word: any }) => { const audioRef = useRef<HTMLAudioElement>(null); async function playAudio() { - // setLoading(true); + setLoading(true); // const audioContext = new (window.AudioContext || // (window as any).webkitAudioContext)(); // const response = await fetch(audioUrl); @@ -76,11 +78,22 @@ const ProminentToneDisplay = ({ word }: { word: any }) => { const res = await fetch(`/api/tts?word=${word.spelling}&lang=thai`); const audioBlob = await res.blob(); const audioURL = URL.createObjectURL(audioBlob); + setLoading(false); if (audioRef.current) { audioRef.current.src = audioURL; audioRef.current.play(); } } + useEffect(() => { + const onKeyDown = (e: KeyboardEvent) => { + if (e.key === " ") { + e.preventDefault(); + playAudio(); + } + }; + window.addEventListener("keydown", onKeyDown); + return () => window.removeEventListener("keydown", onKeyDown); + }, [playAudio]); return ( <div className="flex flex-col items-center mb-4"> @@ -105,7 +118,7 @@ const ProminentToneDisplay = ({ word }: { word: any }) => { > <Volume2 size={20} /> </button> - {isPending && <Loader2 />} + {(isPending || isLoading) && <Spinner />} <audio ref={audioRef} /> <p className="ipa text-xl text-gray-700 mt-2">{word.frequency}</p> <p className="ipa text-xl text-gray-700 mt-2">{word.word_id}</p> @@ -126,12 +139,26 @@ export default function ToneSelectorClient({ const [isLoading, startTransition] = useTransition(); const [selectedTones, setTones] = useState<ToneQuery>(initialTones); - function goPrev() { + const goPrev = useCallback(() => { setCurrentIdx((i) => (i === 0 ? 0 : i - 1)); - } - function goNext() { + }, []); + const goNext = useCallback(() => { setCurrentIdx((i) => (i === data.length - 1 ? data.length - 1 : i + 1)); - } + }, [data.length]); + + useEffect(() => { + const onKeyDown = (e: KeyboardEvent) => { + if (e.key === "ArrowLeft") { + e.preventDefault(); + goPrev(); + } else if (e.key === "ArrowRight") { + e.preventDefault(); + goNext(); + } + }; + window.addEventListener("keydown", onKeyDown); + return () => window.removeEventListener("keydown", onKeyDown); + }, [goPrev, goNext]); const handleFetch = () => { startTransition(async () => { @@ -225,12 +252,12 @@ function ToneForm({ { value: "rising", label: "5 (Rising)" }, ]; const [syllableCount, setSyllableCount] = useState<number>(2); - function decrSyl() { + const decrSyl = useCallback(() => { setSyllableCount((s) => (s <= 1 ? 1 : s - 1)); - } - function incrSyl() { + }, []); + const incrSyl = useCallback(() => { setSyllableCount((s) => (s >= 5 ? 5 : s + 1)); - } + }, []); useEffect(() => { // Adjust selectedTones array length when syllableCount changes @@ -251,6 +278,20 @@ function ToneForm({ } }; + useEffect(() => { + const onKeyDown = (e: KeyboardEvent) => { + if (e.key === "ArrowUp") { + e.preventDefault(); + incrSyl(); + } else if (e.key === "ArrowDown") { + e.preventDefault(); + decrSyl(); + } + }; + window.addEventListener("keydown", onKeyDown); + return () => window.removeEventListener("keydown", onKeyDown); + }, [incrSyl, decrSyl]); + const handleToneChange = (syllableIndex: number, value: string) => { const tone = value === "any" ? null : value; setTones((prevTones) => { diff --git a/src/pages/tones.tsx b/src/pages/tones.tsx index 732ebd1..8658401 100644 --- a/src/pages/tones.tsx +++ b/src/pages/tones.tsx @@ -1,3 +1,4 @@ +import "@/styles/globals.css"; import { Suspense } from "react"; import { fetchWordsByToneAndSyllables } from "@/actions/tones"; import ToneSelectorClient from "@/components/tones/ToneSelectorClient"; diff --git a/src/picker/App.tsx b/src/picker/App.tsx index 0b4d46f..4b17a1e 100644 --- a/src/picker/App.tsx +++ b/src/picker/App.tsx @@ -10,12 +10,13 @@ import React, { startTransition, useTransition, } from "react"; -import { Brain, Zap, Loader2 } from "lucide-react"; +import { Brain, Zap } from "lucide-react"; import { NLP } from "sortug-ai"; import GranularityMenu, { GranularityId } from "./LevelPicker"; import TextViewer from "./TextViewer"; import { wordAction } from "@/actions/lang"; import Modal from "@/components/Modal"; +import { Spinner } from "@/components/ui/spinner"; // --- Granularity Definition --- type AnalysisEngine = "spacy" | "stanza"; @@ -171,7 +172,7 @@ export default function NlpTextAnalysisScreen({ onElementSelect={handleElementSelect} /> ) : ( - <Loader2 /> + <Spinner /> )} </main> </div> diff --git a/src/styles/globals.css b/src/styles/globals.css index 4bc2c75..d22c133 100644 --- a/src/styles/globals.css +++ b/src/styles/globals.css @@ -121,4 +121,36 @@ body { @apply bg-background text-foreground; } +} + +/* Loaders */ +.spinner { + border: 4px solid rgba(0, 0, 0, 0.1); + width: 24px; + height: 24px; + border-radius: 50%; + border-left-color: #09f; + margin: 5px auto; + animation: spin 1s ease infinite; +} + +.mini-spinner { + display: inline-block; + border: 2px solid rgba(0, 0, 0, 0.1); + width: 12px; + height: 12px; + border-radius: 50%; + border-left-color: #09f; + margin: 0 3px; + animation: spin 1s ease infinite; +} + +@keyframes spin { + 0% { + transform: rotate(0deg); + } + + 100% { + transform: rotate(360deg); + } }
\ No newline at end of file diff --git a/src/zoom/App.tsx b/src/zoom/App.tsx index d41dd7f..8ea3007 100644 --- a/src/zoom/App.tsx +++ b/src/zoom/App.tsx @@ -5,7 +5,7 @@ import FullText from "./FullText"; import "./zoom.css"; import { useZoom, ZoomProvider } from "./hooks/useZoom"; import { NLP } from "sortug-ai"; -import { Loader2 } from "lucide-react"; +import { Spinner } from "@/components/ui/spinner"; const App: React.FC = () => { const { viewState } = useZoom(); @@ -70,7 +70,7 @@ const App: React.FC = () => { <main> {isLoading ? ( - <Loader2 /> + <Spinner /> ) : spacy ? ( <FullText text={spacy.input} doc={spacy} stanza={stanza} /> ) : ( diff --git a/src/zoom/zoom.css b/src/zoom/zoom.css index 2c743bd..2887ca2 100644 --- a/src/zoom/zoom.css +++ b/src/zoom/zoom.css @@ -409,38 +409,6 @@ main { background-color: #e0e0e0; } -/* Loaders */ -.spinner { - border: 4px solid rgba(0, 0, 0, 0.1); - width: 24px; - height: 24px; - border-radius: 50%; - border-left-color: #09f; - margin: 5px auto; - animation: spin 1s ease infinite; -} - -.mini-spinner { - display: inline-block; - border: 2px solid rgba(0, 0, 0, 0.1); - width: 12px; - height: 12px; - border-radius: 50%; - border-left-color: #09f; - margin: 0 3px; - animation: spin 1s ease infinite; -} - -@keyframes spin { - 0% { - transform: rotate(0deg); - } - - 100% { - transform: rotate(360deg); - } -} - /* Responsive adjustments */ @media (max-width: 768px) { |