Skip to main content
Glama

PopUI

by kelnishi
file-list.tsx7.53 kB
import * as React from "react" import { View, BrainCircuit, Trash, ChevronDown, ChevronRight, FileSearch} from "lucide-react" import * as AlertDialog from "@radix-ui/react-alert-dialog" import * as Collapsible from "@radix-ui/react-collapsible" type File = { name: string, path: string, schema: object } export function FileList() { const [files, setFiles] = React.useState<File[]>([]) const [openItems, setOpenItems] = React.useState<Record<string, boolean>>({}) const toggleItem = (id: string) => { setOpenItems((prev) => ({ ...prev, [id]: !prev[id], })) } React.useEffect(() => { window.api.listFiles().then((files) => { setFiles(files) }).catch((err) => { console.error("Error fetching files:", err) }) }, [files]); const handleShow = (file: File) => { // Implement file preview logic here window.api.openFile(file.path); } const handleOpen = (file: File) => { // Implement file opening logic here window.api.sendToHost(`Use the PopUI tool to <show> the '${file.name}' user interface, <describe> it, and <get> its state.`); } const handleReveal = (file: File) => { // Implement file reveal logic here window.api.showFile(file.path); } const handleDelete = (file: File) => { window.api.deleteFile(file.path).then((deletedFile) => { setFiles(files.filter((file) => file.name !== file.name)) // Also clean up the openItems state setOpenItems((prev) => { const newState = { ...prev } delete newState[file.name] return newState }) }).catch((err) => { console.error("Error deleting file:", err) }) } return ( <div className="border rounded-md overflow-hidden"> <table className="min-w-full divide-y divide-gray-200"> <thead className="bg-gray-50"> <tr> <th scope="col" className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"> Name </th> <th scope="col" className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"> Actions </th> </tr> </thead> <tbody className="bg-white divide-y divide-gray-200"> {files.map((file) => ( <React.Fragment key={file.name}> <tr> <td className="px-6 py-4 whitespace-nowrap text-sm text-gray-900"> <Collapsible.Root open={openItems[file.name] || false} onOpenChange={() => toggleItem(file.name)} className="flex items-center" > <Collapsible.Trigger asChild> <button className="flex items-center focus:outline-none mr-2"> {openItems[file.name] ? ( <ChevronDown className="h-4 w-4 text-gray-500" /> ) : ( <ChevronRight className="h-4 w-4 text-gray-500" /> )} </button> </Collapsible.Trigger> {file.name} </Collapsible.Root> </td> <td className="px-6 py-4 whitespace-nowrap text-sm font-medium"> <div className="flex space-x-2"> <button onClick={() => handleShow(file)} className="p-1 rounded-md text-gray-400 hover:text-gray-500 focus:outline-none focus:ring-2 focus:ring-blue-500" aria-label="Preview User Interface" title="Preview User Interface" > <View className="h-4 w-4" /> </button> <button onClick={() => handleOpen(file)} className="p-1 rounded-md text-gray-400 hover:text-gray-500 focus:outline-none focus:ring-2 focus:ring-blue-500" aria-label="Load User Interface" title="Load User Interface in Host" > <BrainCircuit className="h-4 w-4" /> </button> <button onClick={() => handleReveal(file)} className="p-1 rounded-md text-gray-400 hover:text-gray-500 focus:outline-none focus:ring-2 focus:ring-blue-500" aria-label="Reveal File" title="Reveal File in Directory" > <FileSearch className="h-4 w-4" /> </button> <DeleteFileDialog file={file} onDelete={() => handleDelete(file)} /> </div> </td> </tr> {openItems[file.name] && ( <tr className="bg-gray-50"> <td colSpan={3} className="px-6 py-4"> <div className="text-sm text-gray-900"> <pre className="bg-gray-100 p-3 rounded-md overflow-auto max-h-60"> {JSON.stringify(file.schema, null, 2)} </pre> </div> </td> </tr> )} </React.Fragment> ))} </tbody> </table> </div> ) } type DeleteFileDialogProps = { file: File onDelete: () => void } function DeleteFileDialog({ file, onDelete }: DeleteFileDialogProps) { return ( <AlertDialog.Root> <AlertDialog.Trigger asChild> <button className="p-1 rounded-md text-gray-400 hover:text-red-500 focus:outline-none focus:ring-2 focus:ring-red-500" aria-label="Delete File" title="Delete File"> <Trash className="h-4 w-4" /> </button> </AlertDialog.Trigger> <AlertDialog.Portal> <AlertDialog.Overlay className="fixed inset-0 bg-black/50 data-[state=open]:animate-fadeIn" /> <AlertDialog.Content className="fixed top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 bg-white rounded-lg p-6 shadow-xl max-w-md w-full data-[state=open]:animate-contentShow"> <AlertDialog.Title className="text-lg font-medium text-gray-900"> Are you sure you want to delete this file? </AlertDialog.Title> <AlertDialog.Description className="mt-2 text-sm text-gray-500"> This action cannot be undone. This will permanently delete <span className="font-medium">{file.name}</span>. </AlertDialog.Description> <div className="mt-6 flex justify-end space-x-4"> <AlertDialog.Cancel asChild> <button className="px-4 py-2 text-sm font-medium text-gray-700 bg-gray-100 rounded-md hover:bg-gray-200 focus:outline-none focus:ring-2 focus:ring-gray-500"> Cancel </button> </AlertDialog.Cancel> <AlertDialog.Action asChild> <button onClick={onDelete} className="px-4 py-2 text-sm font-medium text-white bg-red-600 rounded-md hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-red-500" > Delete </button> </AlertDialog.Action> </div> </AlertDialog.Content> </AlertDialog.Portal> </AlertDialog.Root> ) }

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/kelnishi/PopUI'

If you have feedback or need assistance with the MCP directory API, please join our Discord server