summaryrefslogtreecommitdiff
path: root/src/components/Flashcard
diff options
context:
space:
mode:
authorpolwex <polwex@sortug.com>2025-05-29 12:10:22 +0700
committerpolwex <polwex@sortug.com>2025-05-29 12:10:22 +0700
commita3f24ea79b14394b24c4b60a010651eb29eeb872 (patch)
treecb1c4937084116f66a59727ee752afd974714c8e /src/components/Flashcard
parent7abf2227438362ad30820ee236405ec1b57a40b6 (diff)
glorious new db
Diffstat (limited to 'src/components/Flashcard')
-rw-r--r--src/components/Flashcard/ServerCard.tsx43
-rw-r--r--src/components/Flashcard/Syllable.tsx44
-rw-r--r--src/components/Flashcard/SyllableModal.tsx110
-rw-r--r--src/components/Flashcard/SyllableSpan.tsx45
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;