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/Flashcard | |
parent | 7abf2227438362ad30820ee236405ec1b57a40b6 (diff) |
glorious new db
Diffstat (limited to 'src/components/Flashcard')
-rw-r--r-- | src/components/Flashcard/ServerCard.tsx | 43 | ||||
-rw-r--r-- | src/components/Flashcard/Syllable.tsx | 44 | ||||
-rw-r--r-- | src/components/Flashcard/SyllableModal.tsx | 110 | ||||
-rw-r--r-- | src/components/Flashcard/SyllableSpan.tsx | 45 |
4 files changed, 230 insertions, 12 deletions
diff --git a/src/components/Flashcard/ServerCard.tsx b/src/components/Flashcard/ServerCard.tsx index d377dce..df37ba8 100644 --- a/src/components/Flashcard/ServerCard.tsx +++ b/src/components/Flashcard/ServerCard.tsx @@ -23,13 +23,21 @@ import { WordData, } from "@/zoom/logic/types"; import { CardResponse } from "@/lib/types/cards"; -import { thaiData } from "@/pages/api/nlp"; +import { thaiData } from "@/lib/calls/nlp"; import { getRandomHexColor } from "@/lib/utils"; import { BookmarkIconito } from "./BookmarkButton"; +import SyllableSpan from "./SyllableSpan"; +import SyllableCard from "./Syllable"; -export async function CardFront({ data }: { data: CardResponse }) { +export async function CardFront({ + data, + needFetch = true, +}: { + data: CardResponse; + needFetch?: boolean; +}) { // const extraData = data.expression.lang - const extraData = await thaiData(data.expression.spelling); + const extraData = needFetch ? await thaiData(data.expression.spelling) : []; // console.log({ extraData }); return ( @@ -42,15 +50,26 @@ export async function CardFront({ data }: { data: CardResponse }) { } > <p className="text-5xl cursor-pointer font-semibold text-slate-800 dark:text-slate-100 text-center"> - {extraData[0]?.syllables.map((syl, i) => ( - <span - key={syl + i} - style={{ color: getRandomHexColor() }} - className="m-1 hover:text-6xl" - > - {syl} - </span> - ))} + {needFetch ? ( + extraData[0]?.syllables.map((syl, i) => ( + // <span + // key={syl + i} + // style={{ color: getRandomHexColor() }} + // className="m-1 hover:text-6xl" + // > + // {syl} + // </span> + <SyllableSpan + key={syl + i} + spelling={syl} + lang={data.expression.lang} + /> + )) + ) : ( + <p className="text-5xl cursor-pointer hover:text-blue-700 font-semibold text-slate-800 dark:text-slate-100 text-center"> + {data.expression.spelling} + </p> + )} </p> </Suspense> <IpaDisplay ipaEntries={data.expression.ipa} /> diff --git a/src/components/Flashcard/Syllable.tsx b/src/components/Flashcard/Syllable.tsx new file mode 100644 index 0000000..e470a4b --- /dev/null +++ b/src/components/Flashcard/Syllable.tsx @@ -0,0 +1,44 @@ +"use client"; + +import { syllableAction, thaiAnalysis } from "@/actions/lang"; +import { CardResponse } from "@/lib/types/cards"; +import { ReactNode, useState, useTransition } from "react"; +import { Spinner } from "../ui/spinner"; +import Modal from "@/components/Modal"; +import { getRandomHexColor } from "@/lib/utils"; + +const SyllableCard: React.FC<{ data: CardResponse }> = ({ data }) => { + return ( + <div className="absolute w-full h-full bg-white dark:bg-slate-800 rounded-xl backface-hidden flex flex-col justify-center gap-8 items-center p-6"> + <p className="text-5xl cursor-pointer hover:text-blue-700 font-semibold text-slate-800 dark:text-slate-100 text-center"> + {data.expression.spelling} + </p> + <IpaDisplay ipaEntries={data.expression.ipa} /> + </div> + ); +}; + +export default SyllableCard; + +const IpaDisplay = ({ + ipaEntries, +}: { + ipaEntries: Array<{ ipa: string; tags?: string[] }>; +}) => { + if (!ipaEntries || ipaEntries.length === 0) return null; + return ( + <div className="flex items-center space-x-2 flex-wrap"> + {ipaEntries.map((entry, index) => { + const tags = entry.tags ? entry.tags : []; + return ( + <span key={index} className="text-lg text-blue-600 font-serif"> + {entry.ipa}{" "} + {tags.length > 0 && ( + <span className="text-xs text-gray-500">({tags.join(", ")})</span> + )} + </span> + ); + })} + </div> + ); +}; diff --git a/src/components/Flashcard/SyllableModal.tsx b/src/components/Flashcard/SyllableModal.tsx new file mode 100644 index 0000000..a00fd10 --- /dev/null +++ b/src/components/Flashcard/SyllableModal.tsx @@ -0,0 +1,110 @@ +// This is a Server Component +import React from "react"; +import db from "@/lib/db"; +import { + Card, + CardHeader, + CardDescription, + CardContent, + CardFooter, + CardTitle, +} from "@/components/ui/card"; +import { NLP } from "sortug-ai"; +import { + BookOpen, + Volume2, + Link as LinkIcon, + ChevronDown, + ChevronUp, + Search, + Info, + MessageSquareQuote, + Tags, + ListTree, + Lightbulb, +} from "lucide-react"; +import { + Example, + SubSense, + RelatedEntry, + Sense, + WordData, +} from "@/zoom/logic/types"; +import { isTonal } from "@/lib/lang/utils"; + +type WordProps = { text: string; lang: string }; +export default async function (props: WordProps) { + const { text, lang } = props; + const data = db.fetchWordBySpelling(text, lang); + + if (!data) return <p>oh...</p>; + console.log(data.senses[0]); + return ( + <Card className="overflow-y-scroll max-h-[80vh]"> + <CardHeader> + <CardTitle> + <h1 className="text-5xl">{text}</h1> + </CardTitle> + <CardDescription> + <IpaDisplay ipaEntries={data.ipa} /> + </CardDescription> + </CardHeader> + <CardContent> + {isTonal(text) ? <Tones {...props} /> : <NotTones {...props} />} + </CardContent> + <CardFooter></CardFooter> + </Card> + ); + // return ( + // <div className="p-6"> + // <h3 className="mb-2 text-2xl font-bold">{word}</h3> + // <p className="mb-1 text-xl text-green-600">${word.}</p> + // <p className="text-gray-700">{word}</p> + // <p className="mt-4 text-xs text-gray-500"> + // Content rendered on the server at: {new Date().toLocaleTimeString()} + // </p> + // </div> + // ); +} + +// Helper component for IPA display +const IpaDisplay = ({ + ipaEntries, +}: { + ipaEntries: Array<{ ipa: string; tags?: string[] }>; +}) => { + if (!ipaEntries || ipaEntries.length === 0) return null; + return ( + <div className="flex items-center space-x-2 flex-wrap"> + {ipaEntries.map((entry, index) => { + const tags = entry.tags ? entry.tags : []; + return ( + <span key={index} className="text-lg text-blue-600 font-serif"> + {entry.ipa}{" "} + {tags.length > 0 && ( + <span className="text-xs text-gray-500">({tags.join(", ")})</span> + )} + </span> + ); + })} + <button + className="p-1 text-blue-500 hover:text-blue-700 transition-colors" + title="Pronounce" + // onClick={() => { + // /* Pronunciation logic would be client-side or a server roundtrip for audio file. */ alert( + // "Pronunciation feature not implemented for server component.", + // ); + // }} + > + <Volume2 size={20} /> + </button> + </div> + ); +}; + +function Tones({ text, lang }: WordProps) { + return <div></div>; +} +function NotTones({ text, lang }: WordProps) { + return <div></div>; +} diff --git a/src/components/Flashcard/SyllableSpan.tsx b/src/components/Flashcard/SyllableSpan.tsx new file mode 100644 index 0000000..445895e --- /dev/null +++ b/src/components/Flashcard/SyllableSpan.tsx @@ -0,0 +1,45 @@ +"use client"; + +import { syllableAction, thaiAnalysis } from "@/actions/lang"; +import { CardResponse } from "@/lib/types/cards"; +import { ReactNode, useState, useTransition } from "react"; +import { Spinner } from "../ui/spinner"; +import Modal from "@/components/Modal"; +import { getRandomHexColor } from "@/lib/utils"; + +const SyllableSpan: React.FC<{ spelling: string; lang: string }> = ({ + spelling, + lang, +}) => { + const [modalContent, setModalContent] = useState<ReactNode | null>(null); + + const closeModal = () => setModalContent(null); + + const [isPending, startTransition] = useTransition(); + const handleClick = (e: React.MouseEvent) => { + e.stopPropagation(); + startTransition(async () => { + const modal = await syllableAction(spelling, lang); + setModalContent(modal); + }); + }; + + return ( + <> + <span + onClick={handleClick} + className="m-1 hover:text-6xl" + style={{ color: getRandomHexColor() }} + > + {spelling} + </span> + {modalContent && ( + <Modal onClose={closeModal} isOpen={!!modalContent}> + {modalContent} + </Modal> + )} + </> + ); +}; + +export default SyllableSpan; |