diff options
author | polwex <polwex@sortug.com> | 2025-05-15 20:32:25 +0700 |
---|---|---|
committer | polwex <polwex@sortug.com> | 2025-05-15 20:32:25 +0700 |
commit | fd86dc15734f3b7126d88f0130897c597100e30a (patch) | |
tree | 253890a5f0bde7bc460904ce1743581f53a23d5b /src/components | |
parent | 3d4b740e5a512db8fbdd934af2fbc9585fa00f0f (diff) |
m
Diffstat (limited to 'src/components')
-rw-r--r-- | src/components/Main.tsx | 25 | ||||
-rw-r--r-- | src/components/Modal.tsx | 55 | ||||
-rw-r--r-- | src/components/ui/dialog.tsx | 133 | ||||
-rw-r--r-- | src/components/zoom/Entry.tsx | 13 |
4 files changed, 208 insertions, 18 deletions
diff --git a/src/components/Main.tsx b/src/components/Main.tsx index 2157a91..3e6f3e7 100644 --- a/src/components/Main.tsx +++ b/src/components/Main.tsx @@ -19,6 +19,7 @@ import { CardTitle, } from "@/components/ui/card"; import { Loader2 } from "lucide-react"; // Loading spinner +import { useRouter } from "waku"; const SorlangPage: React.FC = () => { const [textValue, setTextValue] = useState<string>(""); @@ -82,24 +83,30 @@ const SorlangPage: React.FC = () => { }; }, [handlePaste]); - const handleProcessText = async () => { - setIsAnalyzing(true); - const text = textValue.trim(); - if (!text) { - alert("Text area is empty!"); - return; - } + const router = useRouter(); + async function fetchNLP(text: string, app: "spacy" | "stanza") { const opts = { method: "POST", headers: { "Content-type": "application/json" }, - body: JSON.stringify({ text, app: "spacy" }), + body: JSON.stringify({ text, app }), }; const res = await fetch("/api/nlp", opts); const j = await res.json(); console.log("j", j); if ("ok" in j) { - console.log("good"); + sessionStorage.setItem(`${app}res`, JSON.stringify(j.ok)); + } + } + + const handleProcessText = async () => { + setIsAnalyzing(true); + const text = textValue.trim(); + if (!text) { + alert("Text area is empty!"); + return; } + await Promise.all([fetchNLP(text, "spacy"), fetchNLP(text, "stanza")]); + router.push("/zoom"); setIsAnalyzing(false); }; diff --git a/src/components/Modal.tsx b/src/components/Modal.tsx new file mode 100644 index 0000000..4c52caa --- /dev/null +++ b/src/components/Modal.tsx @@ -0,0 +1,55 @@ +"use client"; + +import { useState, ReactNode, useEffect } from "react"; + +interface ClientModalProps { + isOpen: boolean; + onClose: () => void; + children: ReactNode; // This will receive the Server Component's output + title?: string; +} + +export default function ClientModal({ + isOpen, + onClose, + children, +}: ClientModalProps) { + // Optional: Prevent body scroll when modal is open + useEffect(() => { + if (isOpen) { + document.body.style.overflow = "hidden"; + } else { + document.body.style.overflow = "unset"; + } + return () => { + document.body.style.overflow = "unset"; + }; + }, [isOpen]); + + if (!isOpen) { + return null; + } + + return ( + <div + className="fixed inset-0 z-50 flex items-center justify-center bg-black bg-opacity-50" + onClick={onClose} // Close on overlay click + > + <div + className="p-6 bg-white rounded-lg shadow-xl w-11/12 max-w-lg" + onClick={(e) => e.stopPropagation()} // Prevent click from closing modal if clicking inside content + > + <button + onClick={onClose} + className="text-gray-500 hover:text-gray-700" + aria-label="Close modal" + > + × {/* A simple 'X' close button */} + </button> + <div> + {children} {/* Server Component content will be rendered here */} + </div> + </div> + </div> + ); +} diff --git a/src/components/ui/dialog.tsx b/src/components/ui/dialog.tsx new file mode 100644 index 0000000..981e999 --- /dev/null +++ b/src/components/ui/dialog.tsx @@ -0,0 +1,133 @@ +import * as React from "react" +import * as DialogPrimitive from "@radix-ui/react-dialog" +import { XIcon } from "lucide-react" + +import { cn } from "@/lib/utils" + +function Dialog({ + ...props +}: React.ComponentProps<typeof DialogPrimitive.Root>) { + return <DialogPrimitive.Root data-slot="dialog" {...props} /> +} + +function DialogTrigger({ + ...props +}: React.ComponentProps<typeof DialogPrimitive.Trigger>) { + return <DialogPrimitive.Trigger data-slot="dialog-trigger" {...props} /> +} + +function DialogPortal({ + ...props +}: React.ComponentProps<typeof DialogPrimitive.Portal>) { + return <DialogPrimitive.Portal data-slot="dialog-portal" {...props} /> +} + +function DialogClose({ + ...props +}: React.ComponentProps<typeof DialogPrimitive.Close>) { + return <DialogPrimitive.Close data-slot="dialog-close" {...props} /> +} + +function DialogOverlay({ + className, + ...props +}: React.ComponentProps<typeof DialogPrimitive.Overlay>) { + return ( + <DialogPrimitive.Overlay + data-slot="dialog-overlay" + className={cn( + "data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/50", + className + )} + {...props} + /> + ) +} + +function DialogContent({ + className, + children, + ...props +}: React.ComponentProps<typeof DialogPrimitive.Content>) { + return ( + <DialogPortal data-slot="dialog-portal"> + <DialogOverlay /> + <DialogPrimitive.Content + data-slot="dialog-content" + className={cn( + "bg-background data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 fixed top-[50%] left-[50%] z-50 grid w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-4 rounded-lg border p-6 shadow-lg duration-200 sm:max-w-lg", + className + )} + {...props} + > + {children} + <DialogPrimitive.Close className="ring-offset-background focus:ring-ring data-[state=open]:bg-accent data-[state=open]:text-muted-foreground absolute top-4 right-4 rounded-xs opacity-70 transition-opacity hover:opacity-100 focus:ring-2 focus:ring-offset-2 focus:outline-hidden disabled:pointer-events-none [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4"> + <XIcon /> + <span className="sr-only">Close</span> + </DialogPrimitive.Close> + </DialogPrimitive.Content> + </DialogPortal> + ) +} + +function DialogHeader({ className, ...props }: React.ComponentProps<"div">) { + return ( + <div + data-slot="dialog-header" + className={cn("flex flex-col gap-2 text-center sm:text-left", className)} + {...props} + /> + ) +} + +function DialogFooter({ className, ...props }: React.ComponentProps<"div">) { + return ( + <div + data-slot="dialog-footer" + className={cn( + "flex flex-col-reverse gap-2 sm:flex-row sm:justify-end", + className + )} + {...props} + /> + ) +} + +function DialogTitle({ + className, + ...props +}: React.ComponentProps<typeof DialogPrimitive.Title>) { + return ( + <DialogPrimitive.Title + data-slot="dialog-title" + className={cn("text-lg leading-none font-semibold", className)} + {...props} + /> + ) +} + +function DialogDescription({ + className, + ...props +}: React.ComponentProps<typeof DialogPrimitive.Description>) { + return ( + <DialogPrimitive.Description + data-slot="dialog-description" + className={cn("text-muted-foreground text-sm", className)} + {...props} + /> + ) +} + +export { + Dialog, + DialogClose, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogOverlay, + DialogPortal, + DialogTitle, + DialogTrigger, +} diff --git a/src/components/zoom/Entry.tsx b/src/components/zoom/Entry.tsx index a60c75c..9e6eed9 100644 --- a/src/components/zoom/Entry.tsx +++ b/src/components/zoom/Entry.tsx @@ -1,14 +1,9 @@ -"use client"; -import { Zoom } from "prosody-ui"; +import * as Zoom from "@/zoom"; +import { useEffect, useState } from "react"; import { NLP } from "sortug-ai"; -type Props = { text: string; doc: NLP.Spacy.SpacyRes }; -function ZoomText(props: Props) { - return ( - <div> - <Zoom.FullText {...props} /> - </div> - ); +function ZoomText() { + return <div></div>; } export default ZoomText; |