diff --git a/crates/app/package-lock.json b/crates/app/package-lock.json index f100842442..c94ca8710f 100644 --- a/crates/app/package-lock.json +++ b/crates/app/package-lock.json @@ -9,11 +9,12 @@ "version": "0.1.0", "dependencies": { "@hookform/resolvers": "^3.9.1", - "@radix-ui/react-alert-dialog": "^1.1.4", + "@radix-ui/react-alert-dialog": "^1.1.5", + "@radix-ui/react-avatar": "^1.1.2", "@radix-ui/react-checkbox": "^1.1.3", "@radix-ui/react-collapsible": "^1.1.2", - "@radix-ui/react-dialog": "^1.1.4", - "@radix-ui/react-dropdown-menu": "^2.1.4", + "@radix-ui/react-dialog": "^1.1.5", + "@radix-ui/react-dropdown-menu": "^2.1.5", "@radix-ui/react-label": "^2.1.1", "@radix-ui/react-popover": "^1.1.4", "@radix-ui/react-radio-group": "^1.2.2", @@ -23,7 +24,7 @@ "@radix-ui/react-slot": "^1.1.1", "@radix-ui/react-tabs": "^1.1.2", "@radix-ui/react-toast": "^1.2.4", - "@radix-ui/react-tooltip": "^1.1.6", + "@radix-ui/react-tooltip": "^1.1.7", "@tauri-apps/api": "^2", "@tauri-apps/plugin-fs": "^2.2.0", "@tauri-apps/plugin-http": "^2.2.0", @@ -1364,15 +1365,15 @@ "license": "MIT" }, "node_modules/@radix-ui/react-alert-dialog": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/@radix-ui/react-alert-dialog/-/react-alert-dialog-1.1.4.tgz", - "integrity": "sha512-A6Kh23qZDLy3PSU4bh2UJZznOrUdHImIXqF8YtUa6CN73f8EOO9XlXSCd9IHyPvIquTaa/kwaSWzZTtUvgXVGw==", + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@radix-ui/react-alert-dialog/-/react-alert-dialog-1.1.5.tgz", + "integrity": "sha512-1Y2sI17QzSZP58RjGtrklfSGIf3AF7U/HkD3aAcAnhOUJrm7+7GG1wRDFaUlSe0nW5B/t4mYd/+7RNbP2Wexug==", "license": "MIT", "dependencies": { "@radix-ui/primitive": "1.1.1", "@radix-ui/react-compose-refs": "1.1.1", "@radix-ui/react-context": "1.1.1", - "@radix-ui/react-dialog": "1.1.4", + "@radix-ui/react-dialog": "1.1.5", "@radix-ui/react-primitive": "2.0.1", "@radix-ui/react-slot": "1.1.1" }, @@ -1414,6 +1415,32 @@ } } }, + "node_modules/@radix-ui/react-avatar": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-avatar/-/react-avatar-1.1.2.tgz", + "integrity": "sha512-GaC7bXQZ5VgZvVvsJ5mu/AEbjYLnhhkoidOboC50Z6FFlLA03wG2ianUoH+zgDQ31/9gCF59bE4+2bBgTyMiig==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-context": "1.1.1", + "@radix-ui/react-primitive": "2.0.1", + "@radix-ui/react-use-callback-ref": "1.1.0", + "@radix-ui/react-use-layout-effect": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-checkbox": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/@radix-ui/react-checkbox/-/react-checkbox-1.1.3.tgz", @@ -1531,15 +1558,15 @@ } }, "node_modules/@radix-ui/react-dialog": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/@radix-ui/react-dialog/-/react-dialog-1.1.4.tgz", - "integrity": "sha512-Ur7EV1IwQGCyaAuyDRiOLA5JIUZxELJljF+MbM/2NC0BYwfuRrbpS30BiQBJrVruscgUkieKkqXYDOoByaxIoA==", + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dialog/-/react-dialog-1.1.5.tgz", + "integrity": "sha512-LaO3e5h/NOEL4OfXjxD43k9Dx+vn+8n+PCFt6uhX/BADFflllyv3WJG6rgvvSVBxpTch938Qq/LGc2MMxipXPw==", "license": "MIT", "dependencies": { "@radix-ui/primitive": "1.1.1", "@radix-ui/react-compose-refs": "1.1.1", "@radix-ui/react-context": "1.1.1", - "@radix-ui/react-dismissable-layer": "1.1.3", + "@radix-ui/react-dismissable-layer": "1.1.4", "@radix-ui/react-focus-guards": "1.1.1", "@radix-ui/react-focus-scope": "1.1.1", "@radix-ui/react-id": "1.1.0", @@ -1548,8 +1575,35 @@ "@radix-ui/react-primitive": "2.0.1", "@radix-ui/react-slot": "1.1.1", "@radix-ui/react-use-controllable-state": "1.1.0", - "aria-hidden": "^1.1.1", - "react-remove-scroll": "^2.6.1" + "aria-hidden": "^1.2.4", + "react-remove-scroll": "^2.6.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-dismissable-layer": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.4.tgz", + "integrity": "sha512-XDUI0IVYVSwjMXxM6P4Dfti7AH+Y4oS/TB+sglZ/EXc7cqLwGAmp1NlMrcUjj7ks6R5WTZuWKv44FBbLpwU3sA==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.1", + "@radix-ui/react-compose-refs": "1.1.1", + "@radix-ui/react-primitive": "2.0.1", + "@radix-ui/react-use-callback-ref": "1.1.0", + "@radix-ui/react-use-escape-keydown": "1.1.0" }, "peerDependencies": { "@types/react": "*", @@ -1609,16 +1663,16 @@ } }, "node_modules/@radix-ui/react-dropdown-menu": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/@radix-ui/react-dropdown-menu/-/react-dropdown-menu-2.1.4.tgz", - "integrity": "sha512-iXU1Ab5ecM+yEepGAWK8ZhMyKX4ubFdCNtol4sT9D0OVErG9PNElfx3TQhjw7n7BC5nFVz68/5//clWy+8TXzA==", + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dropdown-menu/-/react-dropdown-menu-2.1.5.tgz", + "integrity": "sha512-50ZmEFL1kOuLalPKHrLWvPFMons2fGx9TqQCWlPwDVpbAnaUJ1g4XNcKqFNMQymYU0kKWR4MDDi+9vUQBGFgcQ==", "license": "MIT", "dependencies": { "@radix-ui/primitive": "1.1.1", "@radix-ui/react-compose-refs": "1.1.1", "@radix-ui/react-context": "1.1.1", "@radix-ui/react-id": "1.1.0", - "@radix-ui/react-menu": "2.1.4", + "@radix-ui/react-menu": "2.1.5", "@radix-ui/react-primitive": "2.0.1", "@radix-ui/react-use-controllable-state": "1.1.0" }, @@ -1719,9 +1773,9 @@ } }, "node_modules/@radix-ui/react-menu": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/@radix-ui/react-menu/-/react-menu-2.1.4.tgz", - "integrity": "sha512-BnOgVoL6YYdHAG6DtXONaR29Eq4nvbi8rutrV/xlr3RQCMMb3yqP85Qiw/3NReozrSW+4dfLkK+rc1hb4wPU/A==", + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@radix-ui/react-menu/-/react-menu-2.1.5.tgz", + "integrity": "sha512-uH+3w5heoMJtqVCgYOtYVMECk1TOrkUn0OG0p5MqXC0W2ppcuVeESbou8PTHoqAjbdTEK19AGXBWcEtR5WpEQg==", "license": "MIT", "dependencies": { "@radix-ui/primitive": "1.1.1", @@ -1729,7 +1783,7 @@ "@radix-ui/react-compose-refs": "1.1.1", "@radix-ui/react-context": "1.1.1", "@radix-ui/react-direction": "1.1.0", - "@radix-ui/react-dismissable-layer": "1.1.3", + "@radix-ui/react-dismissable-layer": "1.1.4", "@radix-ui/react-focus-guards": "1.1.1", "@radix-ui/react-focus-scope": "1.1.1", "@radix-ui/react-id": "1.1.0", @@ -1740,8 +1794,35 @@ "@radix-ui/react-roving-focus": "1.1.1", "@radix-ui/react-slot": "1.1.1", "@radix-ui/react-use-callback-ref": "1.1.0", - "aria-hidden": "^1.1.1", - "react-remove-scroll": "^2.6.1" + "aria-hidden": "^1.2.4", + "react-remove-scroll": "^2.6.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-dismissable-layer": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.4.tgz", + "integrity": "sha512-XDUI0IVYVSwjMXxM6P4Dfti7AH+Y4oS/TB+sglZ/EXc7cqLwGAmp1NlMrcUjj7ks6R5WTZuWKv44FBbLpwU3sA==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.1", + "@radix-ui/react-compose-refs": "1.1.1", + "@radix-ui/react-primitive": "2.0.1", + "@radix-ui/react-use-callback-ref": "1.1.0", + "@radix-ui/react-use-escape-keydown": "1.1.0" }, "peerDependencies": { "@types/react": "*", @@ -2141,15 +2222,15 @@ } }, "node_modules/@radix-ui/react-tooltip": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/@radix-ui/react-tooltip/-/react-tooltip-1.1.6.tgz", - "integrity": "sha512-TLB5D8QLExS1uDn7+wH/bjEmRurNMTzNrtq7IjaS4kjion9NtzsTGkvR5+i7yc9q01Pi2KMM2cN3f8UG4IvvXA==", + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-tooltip/-/react-tooltip-1.1.7.tgz", + "integrity": "sha512-ss0s80BC0+g0+Zc53MvilcnTYSOi4mSuFWBPYPuTOFGjx+pUU+ZrmamMNwS56t8MTFlniA5ocjd4jYm/CdhbOg==", "license": "MIT", "dependencies": { "@radix-ui/primitive": "1.1.1", "@radix-ui/react-compose-refs": "1.1.1", "@radix-ui/react-context": "1.1.1", - "@radix-ui/react-dismissable-layer": "1.1.3", + "@radix-ui/react-dismissable-layer": "1.1.4", "@radix-ui/react-id": "1.1.0", "@radix-ui/react-popper": "1.2.1", "@radix-ui/react-portal": "1.1.3", @@ -2174,6 +2255,33 @@ } } }, + "node_modules/@radix-ui/react-tooltip/node_modules/@radix-ui/react-dismissable-layer": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.4.tgz", + "integrity": "sha512-XDUI0IVYVSwjMXxM6P4Dfti7AH+Y4oS/TB+sglZ/EXc7cqLwGAmp1NlMrcUjj7ks6R5WTZuWKv44FBbLpwU3sA==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.1", + "@radix-ui/react-compose-refs": "1.1.1", + "@radix-ui/react-primitive": "2.0.1", + "@radix-ui/react-use-callback-ref": "1.1.0", + "@radix-ui/react-use-escape-keydown": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-use-callback-ref": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.0.tgz", diff --git a/crates/app/package.json b/crates/app/package.json index aafba7be26..1221d2fc01 100644 --- a/crates/app/package.json +++ b/crates/app/package.json @@ -11,11 +11,12 @@ }, "dependencies": { "@hookform/resolvers": "^3.9.1", - "@radix-ui/react-alert-dialog": "^1.1.4", + "@radix-ui/react-alert-dialog": "^1.1.5", + "@radix-ui/react-avatar": "^1.1.2", "@radix-ui/react-checkbox": "^1.1.3", "@radix-ui/react-collapsible": "^1.1.2", - "@radix-ui/react-dialog": "^1.1.4", - "@radix-ui/react-dropdown-menu": "^2.1.4", + "@radix-ui/react-dialog": "^1.1.5", + "@radix-ui/react-dropdown-menu": "^2.1.5", "@radix-ui/react-label": "^2.1.1", "@radix-ui/react-popover": "^1.1.4", "@radix-ui/react-radio-group": "^1.2.2", @@ -25,7 +26,7 @@ "@radix-ui/react-slot": "^1.1.1", "@radix-ui/react-tabs": "^1.1.2", "@radix-ui/react-toast": "^1.2.4", - "@radix-ui/react-tooltip": "^1.1.6", + "@radix-ui/react-tooltip": "^1.1.7", "@tauri-apps/api": "^2", "@tauri-apps/plugin-fs": "^2.2.0", "@tauri-apps/plugin-http": "^2.2.0", diff --git a/crates/app/src/App.tsx b/crates/app/src/App.tsx index 85e7e30021..66152ea0dc 100644 --- a/crates/app/src/App.tsx +++ b/crates/app/src/App.tsx @@ -32,6 +32,7 @@ import CreateDeployment from "@/pages/deployment/create"; import {ApiLayout} from "./pages/api/details/api-layout"; import ComponentInvoke from "@/pages/components/details/invoke"; import Plugins from "@/pages/components/details/plugin.tsx"; +import {ComponentLayout} from "@/pages/components/details/component-layout.tsx"; function App() { return ( @@ -47,41 +48,21 @@ function App() { }/> } - /> - } - /> - } - /> - } - /> - } - /> - } - /> - - } - /> - } - /> - } - /> + element={} + > + }/> + }/> + }/> + }/> + }/> + }/> + }/> + }/> + } + /> + } diff --git a/crates/app/src/app/dashboard/page.tsx b/crates/app/src/app/dashboard/page.tsx new file mode 100644 index 0000000000..23034abef3 --- /dev/null +++ b/crates/app/src/app/dashboard/page.tsx @@ -0,0 +1,52 @@ +import { AppSidebar } from "@/components/app-sidebar" +import { + Breadcrumb, + BreadcrumbItem, + BreadcrumbLink, + BreadcrumbList, + BreadcrumbPage, + BreadcrumbSeparator, +} from "@/components/ui/breadcrumb" +import { Separator } from "@/components/ui/separator" +import { + SidebarInset, + SidebarProvider, + SidebarTrigger, +} from "@/components/ui/sidebar" + +export default function Page() { + return ( + + + +
+
+ + + + + + + Building Your Application + + + + + Data Fetching + + + +
+
+
+
+
+
+
+
+
+
+ + + ) +} diff --git a/crates/app/src/components/app-sidebar.tsx b/crates/app/src/components/app-sidebar.tsx new file mode 100644 index 0000000000..dd5740a62b --- /dev/null +++ b/crates/app/src/components/app-sidebar.tsx @@ -0,0 +1,173 @@ +import * as React from "react" +import { + AudioWaveform, + BookOpen, + Bot, + Command, + Frame, + GalleryVerticalEnd, + Map, + PieChart, + Settings2, + SquareTerminal, +} from "lucide-react" + +import { NavMain } from "@/components/nav-main" +import { NavProjects } from "@/components/nav-projects" +import { NavUser } from "@/components/nav-user" +import { TeamSwitcher } from "@/components/team-switcher" +import { + Sidebar, + SidebarContent, + SidebarFooter, + SidebarHeader, + SidebarRail, +} from "@/components/ui/sidebar" + +// This is sample data. +const data = { + user: { + name: "shadcn", + email: "m@example.com", + avatar: "/avatars/shadcn.jpg", + }, + teams: [ + { + name: "Acme Inc", + logo: GalleryVerticalEnd, + plan: "Enterprise", + }, + { + name: "Acme Corp.", + logo: AudioWaveform, + plan: "Startup", + }, + { + name: "Evil Corp.", + logo: Command, + plan: "Free", + }, + ], + navMain: [ + { + title: "Playground", + url: "#", + icon: SquareTerminal, + isActive: true, + items: [ + { + title: "History", + url: "#", + }, + { + title: "Starred", + url: "#", + }, + { + title: "Settings", + url: "#", + }, + ], + }, + { + title: "Models", + url: "#", + icon: Bot, + items: [ + { + title: "Genesis", + url: "#", + }, + { + title: "Explorer", + url: "#", + }, + { + title: "Quantum", + url: "#", + }, + ], + }, + { + title: "Documentation", + url: "#", + icon: BookOpen, + items: [ + { + title: "Introduction", + url: "#", + }, + { + title: "Get Started", + url: "#", + }, + { + title: "Tutorials", + url: "#", + }, + { + title: "Changelog", + url: "#", + }, + ], + }, + { + title: "Settings", + url: "#", + icon: Settings2, + items: [ + { + title: "General", + url: "#", + }, + { + title: "Team", + url: "#", + }, + { + title: "Billing", + url: "#", + }, + { + title: "Limits", + url: "#", + }, + ], + }, + ], + projects: [ + { + name: "Design Engineering", + url: "#", + icon: Frame, + }, + { + name: "Sales & Marketing", + url: "#", + icon: PieChart, + }, + { + name: "Travel", + url: "#", + icon: Map, + }, + ], +} + +export function AppSidebar({ ...props }: React.ComponentProps) { + return ( + + + + + + + + + + + + + + ) +} diff --git a/crates/app/src/components/nav-main.tsx b/crates/app/src/components/nav-main.tsx new file mode 100644 index 0000000000..26f41f82c0 --- /dev/null +++ b/crates/app/src/components/nav-main.tsx @@ -0,0 +1,87 @@ +"use client"; + +import {ChevronRight, type LucideIcon} from "lucide-react"; + +import {Collapsible, CollapsibleContent, CollapsibleTrigger,} from "@/components/ui/collapsible"; +import { + SidebarGroup, + SidebarGroupLabel, + SidebarMenu, + SidebarMenuButton, + SidebarMenuItem, + SidebarMenuSub, + SidebarMenuSubButton, + SidebarMenuSubItem, +} from "@/components/ui/sidebar"; +import {useNavigate} from "react-router-dom"; + +export interface SidebarMenuProps { + title: string + url: string + icon?: LucideIcon + isActive?: boolean + isHidden?: boolean + items?: SidebarMenuProps[] +} + +export function NavMain({ + items, setActiveItem + }: { + items: SidebarMenuProps[]; + setActiveItem?: (item: string) => void +}) { + const navigate = useNavigate(); + + const onSelect = (item: SidebarMenuProps) => { + if (setActiveItem) setActiveItem(item.title); + navigate(item.url); + }; + return ( + + Menu + + {items.map((item) => + !item.isHidden && ( // If there are sub-items, render a collapsible menu + item.items ? ( // If there are sub-items, render a collapsible menu + + + + + {item.icon && } {/* Ensure icon exists before rendering */} + {item.title} + + + + + + {item.items.map((subItem) => ( + + onSelect(subItem)}> + {subItem.title} + + + ))} + + + + + ) : ( // If no sub-items, render a simple link + + onSelect(item)}> + {item.icon && } + {item.title} + + + ) + ) + )} + + + ); +} \ No newline at end of file diff --git a/crates/app/src/components/nav-projects.tsx b/crates/app/src/components/nav-projects.tsx new file mode 100644 index 0000000000..442e411232 --- /dev/null +++ b/crates/app/src/components/nav-projects.tsx @@ -0,0 +1,87 @@ +import { + Folder, + Forward, + MoreHorizontal, + Trash2, + type LucideIcon, +} from "lucide-react" + +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuSeparator, + DropdownMenuTrigger, +} from "@/components/ui/dropdown-menu" +import { + SidebarGroup, + SidebarGroupLabel, + SidebarMenu, + SidebarMenuAction, + SidebarMenuButton, + SidebarMenuItem, + useSidebar, +} from "@/components/ui/sidebar" + +export function NavProjects({ + projects, +}: { + projects: { + name: string + url: string + icon: LucideIcon + }[] +}) { + const { isMobile } = useSidebar() + + return ( + + Projects + + {projects.map((item) => ( + + + + + {item.name} + + + + + + + More + + + + + + View Project + + + + Share Project + + + + + Delete Project + + + + + ))} + + + + More + + + + + ) +} diff --git a/crates/app/src/components/nav-user.tsx b/crates/app/src/components/nav-user.tsx new file mode 100644 index 0000000000..d12ef780c2 --- /dev/null +++ b/crates/app/src/components/nav-user.tsx @@ -0,0 +1,114 @@ +"use client" + +import { + BadgeCheck, + Bell, + ChevronsUpDown, + CreditCard, + LogOut, + Sparkles, +} from "lucide-react" + +import { + Avatar, + AvatarFallback, + AvatarImage, +} from "@/components/ui/avatar" +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuGroup, + DropdownMenuItem, + DropdownMenuLabel, + DropdownMenuSeparator, + DropdownMenuTrigger, +} from "@/components/ui/dropdown-menu" +import { + SidebarMenu, + SidebarMenuButton, + SidebarMenuItem, + useSidebar, +} from "@/components/ui/sidebar" + +export function NavUser({ + user, +}: { + user: { + name: string + email: string + avatar: string + } +}) { + const { isMobile } = useSidebar() + + return ( + + + + + + + + CN + +
+ {user.name} + {user.email} +
+ +
+
+ + +
+ + + CN + +
+ {user.name} + {user.email} +
+
+
+ + + + + Upgrade to Pro + + + + + + + Account + + + + Billing + + + + Notifications + + + + + + Log out + +
+
+
+
+ ) +} diff --git a/crates/app/src/components/sidebar.tsx b/crates/app/src/components/sidebar.tsx new file mode 100644 index 0000000000..f9ef3bd15a --- /dev/null +++ b/crates/app/src/components/sidebar.tsx @@ -0,0 +1,54 @@ +import {ArrowLeft, type LucideIcon,} from "lucide-react" +import { + Sidebar, + SidebarContent, + SidebarHeader, + SidebarMenu as SidebarMenuM, + SidebarMenuButton, + SidebarMenuItem, + SidebarRail, +} from "@/components/ui/sidebar" +import {NavMain, SidebarMenuProps} from "@/components/nav-main.tsx"; +import {useNavigate} from "react-router-dom"; + +export interface SidebarProps { + headers?: SidebarHeaderProps[] + menus: SidebarMenuProps[] + footer?: { name: string; url: string }[] + side?: "left" | "right" + variant?: "sidebar" | "floating" | "inset" + collapsible?: "offcanvas" | "icon" | "none" + className?: string + setActiveItem?: (item: string) => void +} + +export interface SidebarHeaderProps { + name?: string + logo?: LucideIcon + plan?: string +} + +export function SidebarMenu({...props}: SidebarProps) { + + const navigate = useNavigate(); + return ( + + + + + navigate(-1)}> + + Component + + + + {/**/} + + + + {/**/} + + + + ) +} diff --git a/crates/app/src/components/team-switcher.tsx b/crates/app/src/components/team-switcher.tsx new file mode 100644 index 0000000000..e3de66acc9 --- /dev/null +++ b/crates/app/src/components/team-switcher.tsx @@ -0,0 +1,87 @@ +import * as React from "react" +import { ChevronsUpDown, Plus } from "lucide-react" + +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuLabel, + DropdownMenuSeparator, + DropdownMenuShortcut, + DropdownMenuTrigger, +} from "@/components/ui/dropdown-menu" +import { + SidebarMenu, + SidebarMenuButton, + SidebarMenuItem, + useSidebar, +} from "@/components/ui/sidebar" + +export function TeamSwitcher({ + teams, +}: { + teams: { + name: string + logo: React.ElementType + plan: string + }[] +}) { + const { isMobile } = useSidebar() + const [activeTeam, setActiveTeam] = React.useState(teams[0]) + + return ( + + + + + +
+ +
+
+ + {activeTeam.name} + + {activeTeam.plan} +
+ +
+
+ + + Teams + + {teams.map((team, index) => ( + setActiveTeam(team)} + className="gap-2 p-2" + > +
+ +
+ {team.name} + ⌘{index + 1} +
+ ))} + + +
+ +
+
Add team
+
+
+
+
+
+ ) +} diff --git a/crates/app/src/components/ui/avatar.tsx b/crates/app/src/components/ui/avatar.tsx new file mode 100644 index 0000000000..991f56ecb1 --- /dev/null +++ b/crates/app/src/components/ui/avatar.tsx @@ -0,0 +1,48 @@ +import * as React from "react" +import * as AvatarPrimitive from "@radix-ui/react-avatar" + +import { cn } from "@/lib/utils" + +const Avatar = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +Avatar.displayName = AvatarPrimitive.Root.displayName + +const AvatarImage = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +AvatarImage.displayName = AvatarPrimitive.Image.displayName + +const AvatarFallback = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +AvatarFallback.displayName = AvatarPrimitive.Fallback.displayName + +export { Avatar, AvatarImage, AvatarFallback } diff --git a/crates/app/src/components/ui/breadcrumb.tsx b/crates/app/src/components/ui/breadcrumb.tsx new file mode 100644 index 0000000000..60e6c96f72 --- /dev/null +++ b/crates/app/src/components/ui/breadcrumb.tsx @@ -0,0 +1,115 @@ +import * as React from "react" +import { Slot } from "@radix-ui/react-slot" +import { ChevronRight, MoreHorizontal } from "lucide-react" + +import { cn } from "@/lib/utils" + +const Breadcrumb = React.forwardRef< + HTMLElement, + React.ComponentPropsWithoutRef<"nav"> & { + separator?: React.ReactNode + } +>(({ ...props }, ref) => - - ); -}; - -export default ComponentLeftNav; diff --git a/crates/app/src/pages/components/details/export.tsx b/crates/app/src/pages/components/details/export.tsx index fa1662f954..26c72ef0a3 100644 --- a/crates/app/src/pages/components/details/export.tsx +++ b/crates/app/src/pages/components/details/export.tsx @@ -1,226 +1,189 @@ -import { Search } from "lucide-react"; -import { Input } from "@/components/ui/input"; -import { - Table, - TableBody, - TableCell, - TableHead, - TableHeader, - TableRow, -} from "@/components/ui/table"; -import { - Select, - SelectContent, - SelectItem, - SelectTrigger, - SelectValue, -} from "@/components/ui/select"; -import ComponentLeftNav from "./componentsLeftNav"; -import { useEffect, useState } from "react"; -import { API } from "@/service"; -import { useParams } from "react-router-dom"; -import { - ComponentExportFunction, - ComponentList, - Export, - Field, - Parameter, - Result, - Typ, -} from "@/types/component.ts"; -import ErrorBoundary from "@/components/errorBoundary"; -import { calculateExportFunctions } from "@/lib/utils"; +import {Search} from "lucide-react"; +import {Input} from "@/components/ui/input"; +import {Table, TableBody, TableCell, TableHead, TableHeader, TableRow,} from "@/components/ui/table"; +import {Select, SelectContent, SelectItem, SelectTrigger, SelectValue,} from "@/components/ui/select"; +import {useEffect, useState} from "react"; +import {API} from "@/service"; +import {useParams} from "react-router-dom"; +import {ComponentExportFunction, ComponentList, Export, Field, Parameter, Result, Typ,} from "@/types/component.ts"; +import {calculateExportFunctions} from "@/lib/utils"; function parseType(typ: Typ): string { - if (typ.type) { - if (typ.type === "Record" && typ.fields) { - return `{ + if (typ.type) { + if (typ.type === "Record" && typ.fields) { + return `{ ${typ.fields - .map((field: Field) => `${field.name}: ${parseType(field.typ)}`) - .join(", \n ")} + .map((field: Field) => `${field.name}: ${parseType(field.typ)}`) + .join(", \n ")} }`; - } else if (typ.type === "Array" && typ.fields) { - return `Array<{ + } else if (typ.type === "Array" && typ.fields) { + return `Array<{ ${typ.fields - .map((field: Field) => `${field.name}: ${parseType(field.typ)}`) - .join(", \n ")} + .map((field: Field) => `${field.name}: ${parseType(field.typ)}`) + .join(", \n ")} }>`; - } else if (typ.type === "Variant" && typ.cases) { - return typ.cases - .map((caseItem) => { - if (caseItem.typ.fields) { - return `${caseItem.name}: { + } else if (typ.type === "Variant" && typ.cases) { + return typ.cases + .map((caseItem) => { + if (caseItem.typ.fields) { + return `${caseItem.name}: { ${caseItem.typ.fields - .map((field) => `${field.name}: ${parseType(field.typ)}`) - .join(", \n ")} + .map((field) => `${field.name}: ${parseType(field.typ)}`) + .join(", \n ")} }`; - } - return `${caseItem.name}: ${parseType(caseItem.typ)}`; - }) - .join(" | "); + } + return `${caseItem.name}: ${parseType(caseItem.typ)}`; + }) + .join(" | "); + } + return typ.type; } - return typ.type; - } - return "Unknown"; + return "Unknown"; } // Main function to convert JSON to function structure function convertJsonToFunctionStructure(json: Parameter[] | Result[]) { - return json.map((entry) => { - const name = entry.name; - const fieldsStructure = parseType(entry.typ); - return `(${name}: ${fieldsStructure})`; - }); + return json.map((entry) => { + const name = entry.name; + const fieldsStructure = parseType(entry.typ); + return `(${name}: ${fieldsStructure})`; + }); } export default function Exports() { - const { componentId = "" } = useParams(); - const [component, setComponent] = useState({}); - const [versionList, setVersionList] = useState([] as number[]); - const [versionChange, setVersionChange] = useState(0 as number); - const [functions, setFunctions] = useState([] as ComponentExportFunction[]); + const {componentId = ""} = useParams(); + const [component, setComponent] = useState({}); + const [versionList, setVersionList] = useState([] as number[]); + const [versionChange, setVersionChange] = useState(0 as number); + const [functions, setFunctions] = useState([] as ComponentExportFunction[]); + + useEffect(() => { + if (componentId) { + API.getComponentByIdAsKey().then((response) => { + setVersionList(response[componentId].versionList || []); + setVersionChange( + response[componentId].versionList?.[ + response[componentId].versionList?.length - 1 + ] || 0 + ); + setComponent(response[componentId]); + }); + } + }, [componentId]); - useEffect(() => { - if (componentId) { - API.getComponentByIdAsKey().then((response) => { - setVersionList(response[componentId].versionList || []); - setVersionChange( - response[componentId].versionList?.[ - response[componentId].versionList?.length - 1 - ] || 0 + useEffect(() => { + const componentDetails = component.versions?.find( + (data) => data.versionedComponentId?.version === versionChange ); - setComponent(response[componentId]); - }); - } - }, [componentId]); + if (componentDetails) { + const functions = + componentDetails.metadata?.exports.reduce( + (acc: ComponentExportFunction[], curr: Export) => { + const updatedFunctions = curr.functions.map( + (func: ComponentExportFunction) => ({ + ...func, + exportName: curr.name, + }) + ); - useEffect(() => { - const componentDetails = component.versions?.find( - (data) => data.versionedComponentId?.version === versionChange - ); - if (componentDetails) { - const functions = - componentDetails.metadata?.exports.reduce( - (acc: ComponentExportFunction[], curr: Export) => { - const updatedFunctions = curr.functions.map( - (func: ComponentExportFunction) => ({ - ...func, - exportName: curr.name, - }) - ); + return acc.concat(updatedFunctions); + }, + [] + ) || []; + setFunctions(functions); + } + }, [component, versionChange]); - return acc.concat(updatedFunctions); - }, - [] - ) || []; - setFunctions(functions); - } - }, [component, versionChange]); + const handleVersionChange = (version: number) => { + setVersionChange(version); + }; - const handleVersionChange = (version: number) => { - setVersionChange(version); - }; + const handleSearch = (e: React.ChangeEvent) => { + const value = e.target.value; - const handleSearch = (e: React.ChangeEvent) => { - const value = e.target.value; + const searchResult = calculateExportFunctions( + component.versions?.find( + (data) => data.versionedComponentId?.version === versionChange + )?.metadata?.exports || [] + ).filter((fn: ComponentExportFunction) => { + return fn.name.includes(value); + }); + setFunctions(searchResult || ([] as ComponentExportFunction[])); + }; - const searchResult = calculateExportFunctions( - component.versions?.find( - (data) => data.versionedComponentId?.version === versionChange - )?.metadata?.exports || [] - ).filter((fn: ComponentExportFunction) => { - return fn.name.includes(value); - }); - setFunctions(searchResult || ([] as ComponentExportFunction[])); - }; + return ( +
+
+
+
+

Exports

+
+
+
+ + handleSearch(e)} + /> +
+ {versionList.length > 0 && ( + + )} +
- return ( - -
- -
-
-
-
-

- {component.componentName} -

-
-
-
-
-
-
-

Exports

-
-
-
- - handleSearch(e)} - /> +
+ + + + Package + Function + Parameters + Return Value + + + + {functions?.length > 0 ? ( + functions.map((fn: ComponentExportFunction) => ( + + + {fn.exportName} + + + {fn.name} + + + {convertJsonToFunctionStructure(fn.parameters)} + + + {convertJsonToFunctionStructure(fn.results)} + + + )) + ) : ( +
+ No exports found. +
+ )} +
+
+
- {versionList.length > 0 && ( - - )} -
- -
- - - - Package - Function - Parameters - Return Value - - - - {functions?.length > 0 ? ( - functions.map((fn: ComponentExportFunction) => ( - - - {fn.exportName} - - - {fn.name} - - - {convertJsonToFunctionStructure(fn.parameters)} - - - {convertJsonToFunctionStructure(fn.results)} - - - )) - ) : ( -
- No exports found. -
- )} -
-
-
-
-
-
- ); + ); } diff --git a/crates/app/src/pages/components/details/index.tsx b/crates/app/src/pages/components/details/index.tsx index 53c8432219..d78a778d74 100644 --- a/crates/app/src/pages/components/details/index.tsx +++ b/crates/app/src/pages/components/details/index.tsx @@ -1,94 +1,78 @@ -import { useParams } from "react-router-dom"; -import { MetricCard } from "./widgets/metrixCard"; -import { ExportsList } from "./widgets/exportsList"; -import { WorkerStatus } from "./widgets/workerStatus"; -import ComponentLeftNav from "./componentsLeftNav"; -import { useEffect, useState } from "react"; -import { API } from "@/service"; -import { ComponentList } from "@/types/component.ts"; -import { Worker, WorkerStatus as IWorkerStatus } from "@/types/worker.ts"; -import ErrorBoundary from "@/components/errorBoundary"; +import {useParams} from "react-router-dom"; +import {MetricCard} from "./widgets/metrixCard"; +import {ExportsList} from "./widgets/exportsList"; +import {WorkerStatus} from "./widgets/workerStatus"; +import {useEffect, useState} from "react"; +import {API} from "@/service"; +import {ComponentList} from "@/types/component.ts"; +import {Worker, WorkerStatus as IWorkerStatus} from "@/types/worker.ts"; export const ComponentDetails = () => { - const { componentId = "" } = useParams(); - const [component, setComponent] = useState({} as ComponentList); - const [workerStatus, setWorkerStatus] = useState({} as IWorkerStatus); + const {componentId = ""} = useParams(); + const [component, setComponent] = useState({} as ComponentList); + const [workerStatus, setWorkerStatus] = useState({} as IWorkerStatus); - useEffect(() => { - API.getComponentByIdAsKey().then((response) => { - setComponent(response[componentId]); - }); + useEffect(() => { + API.getComponentByIdAsKey().then((response) => { + setComponent(response[componentId]); + }); - API.findWorker(componentId!).then((res) => { - const status: Record = {}; - res.workers.forEach((worker: Worker) => { - status[worker.status] = (status[worker.status] || 0) + 1; - }); - setWorkerStatus(status); - }); - }, [componentId]); + API.findWorker(componentId!).then((res) => { + const status: Record = {}; + res.workers.forEach((worker: Worker) => { + status[worker.status] = (status[worker.status] || 0) + 1; + }); + setWorkerStatus(status); + }); + }, [componentId]); - return ( - -
- -
-
-
-
-

- {component.componentName} -

-
-
-
-
-
-
- - - - -
+ return ( +
+
+
+
+ + + + +
-
- - -
+
+ + +
+
-
-
- - ); + ); }; diff --git a/crates/app/src/pages/components/details/info.tsx b/crates/app/src/pages/components/details/info.tsx index f403038afa..c20bf0ee23 100644 --- a/crates/app/src/pages/components/details/info.tsx +++ b/crates/app/src/pages/components/details/info.tsx @@ -1,184 +1,154 @@ -import { useParams } from "react-router-dom"; +import {useParams} from "react-router-dom"; -import { Button } from "@/components/ui/button"; -import { - Card, - CardContent, - CardDescription, - CardHeader, - CardTitle, -} from "@/components/ui/card"; -import { - Select, - SelectContent, - SelectItem, - SelectTrigger, - SelectValue, -} from "@/components/ui/select"; -import { Download } from "lucide-react"; -import ComponentLeftNav from "./componentsLeftNav"; -import ErrorBoundary from "@/components/errorBoundary"; -import { writeFile } from "@tauri-apps/plugin-fs"; -import { BaseDirectory } from "@tauri-apps/api/path"; -import { useEffect, useState } from "react"; -import { ComponentList } from "@/types/component"; -import { API } from "@/service"; -import { formatRelativeTime } from "@/lib/utils"; -import { toast } from "@/hooks/use-toast"; +import {Button} from "@/components/ui/button"; +import {Card, CardContent, CardDescription, CardHeader, CardTitle,} from "@/components/ui/card"; +import {Select, SelectContent, SelectItem, SelectTrigger, SelectValue,} from "@/components/ui/select"; +import {Download} from "lucide-react"; +import {writeFile} from "@tauri-apps/plugin-fs"; +import {BaseDirectory} from "@tauri-apps/api/path"; +import {useEffect, useState} from "react"; +import {ComponentList} from "@/types/component"; +import {API} from "@/service"; +import {formatRelativeTime} from "@/lib/utils"; +import {toast} from "@/hooks/use-toast"; export default function ComponentInfo() { - const { componentId = "" } = useParams(); - const [componentList, setComponentList] = useState<{ - [key: string]: ComponentList; - }>({}); - const [versionList, setVersionList] = useState([] as number[]); - const [versionChange, setVersionChange] = useState(0 as number); + const {componentId = ""} = useParams(); + const [componentList, setComponentList] = useState<{ + [key: string]: ComponentList; + }>({}); + const [versionList, setVersionList] = useState([] as number[]); + const [versionChange, setVersionChange] = useState(0 as number); - useEffect(() => { - if (componentId) { - API.getComponentByIdAsKey().then((response) => { - const componentData = response[componentId]; - const versionList = componentData?.versionList || []; - setVersionList(versionList); - setComponentList(response); - if (versionList.length > 0) { - setVersionChange(versionList[versionList.length - 1]); + useEffect(() => { + if (componentId) { + API.getComponentByIdAsKey().then((response) => { + const componentData = response[componentId]; + const versionList = componentData?.versionList || []; + setVersionList(versionList); + setComponentList(response); + if (versionList.length > 0) { + setVersionChange(versionList[versionList.length - 1]); + } + }); } - }); - } - }, [componentId]); + }, [componentId]); - const handleVersionChange = (version: number) => { - setVersionChange(version); - }; + const handleVersionChange = (version: number) => { + setVersionChange(version); + }; - async function downloadFile() { - try { - API.downloadComponent(componentId!, versionChange).then( - async (response) => { - const blob = await response.blob(); - const arrayBuffer = await blob.arrayBuffer(); + async function downloadFile() { + try { + API.downloadComponent(componentId!, versionChange).then( + async (response) => { + const blob = await response.blob(); + const arrayBuffer = await blob.arrayBuffer(); - // Specify the file name and path - const fileName = `${componentId}.wasm`; - await writeFile(fileName, new Uint8Array(arrayBuffer), { - baseDir: BaseDirectory.Download, - }); - toast({ - title: "File downloaded successfully", - duration: 3000, - }); + // Specify the file name and path + const fileName = `${componentId}.wasm`; + await writeFile(fileName, new Uint8Array(arrayBuffer), { + baseDir: BaseDirectory.Download, + }); + toast({ + title: "File downloaded successfully", + duration: 3000, + }); + } + ); + } catch (error) { + console.error("Error downloading the file:", error); } - ); - } catch (error) { - console.error("Error downloading the file:", error); } - } - const componentDetails = - componentList[componentId]?.versions?.[versionChange] || {}; + const componentDetails = + componentList[componentId]?.versions?.[versionChange] || {}; - return ( - -
- -
-
-
-
-

- {componentList[componentId]?.componentName} -

-
+ return ( +
+
+ + +
+ + Component Information + + + View metadata about this component + +
+
+ {versionList.length > 0 && ( + + )} + +
+
+ +
+
+
+ Component ID +
+
+ {componentDetails.versionedComponentId?.componentId} +
+
+
+
+ Version +
+
{versionChange}
+
+
+
+ Name +
+
+ {componentDetails.componentName} +
+
+
+
+ Size +
+
+ {Math.round( + (componentDetails?.componentSize || 0) / 1024 + )}{" "} + KB +
+
+
+
+ Created At +
+
+ {componentDetails.createdAt + ? formatRelativeTime(componentDetails.createdAt) + : "NA"} +
+
+
+
+
-
-
- - -
- - Component Information - - - View metadata about this component - -
-
- {versionList.length > 0 && ( - - )} - -
-
- -
-
-
- Component ID -
-
- {componentDetails.versionedComponentId?.componentId} -
-
-
-
- Version -
-
{versionChange}
-
-
-
- Name -
-
- {componentDetails.componentName} -
-
-
-
- Size -
-
- {Math.round( - (componentDetails?.componentSize || 0) / 1024 - )}{" "} - KB -
-
-
-
- Created At -
-
- {componentDetails.createdAt - ? formatRelativeTime(componentDetails.createdAt) - : "NA"} -
-
-
-
-
-
-
-
- ); + ); } diff --git a/crates/app/src/pages/components/details/invoke.tsx b/crates/app/src/pages/components/details/invoke.tsx index d2e3415dc5..d64b4e729c 100644 --- a/crates/app/src/pages/components/details/invoke.tsx +++ b/crates/app/src/pages/components/details/invoke.tsx @@ -1,230 +1,215 @@ -import { useCallback, useEffect, useState } from "react"; -import { useNavigate, useParams, useSearchParams } from "react-router-dom"; -import { API } from "@/service"; -import { - ComponentList, - ComponentExportFunction, - Export, -} from "@/types/component.ts"; -import ErrorBoundary from "@/components/errorBoundary"; -import ComponentLeftNav from "./componentsLeftNav"; -import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; -import { Button } from "@/components/ui/button"; -import { ClipboardCopy } from "lucide-react"; -import { cn, sanitizeInput } from "@/lib/utils"; +import {useCallback, useEffect, useState} from "react"; +import {useNavigate, useParams, useSearchParams} from "react-router-dom"; +import {API} from "@/service"; +import {ComponentExportFunction, ComponentList, Export,} from "@/types/component.ts"; +import {Card, CardContent, CardHeader, CardTitle} from "@/components/ui/card"; +import {Button} from "@/components/ui/button"; +import {ClipboardCopy} from "lucide-react"; +import {cn, sanitizeInput} from "@/lib/utils"; import ReactJson from "react-json-view"; -import { Textarea } from "@/components/ui/textarea"; -import { - parseToApiPayload, - parseToJsonEditor, - safeFormatJSON, -} from "@/lib/worker"; +import {Textarea} from "@/components/ui/textarea"; +import {parseToApiPayload, parseToJsonEditor, safeFormatJSON,} from "@/lib/worker"; export default function ComponentInvoke() { - const { componentId = "" } = useParams(); - const [searchParams] = useSearchParams(); - const navigate = useNavigate(); + const {componentId = ""} = useParams(); + const [searchParams] = useSearchParams(); + const navigate = useNavigate(); - const name = searchParams.get("name") || ""; - const urlFn = searchParams.get("fn") || ""; + const name = searchParams.get("name") || ""; + const urlFn = searchParams.get("fn") || ""; - const [functionDetails, setFunctionDetails] = - useState(null); - const [value, setValue] = useState("{}"); - const [resultValue, setResultValue] = useState(""); - const [error, setError] = useState(null); - const [componentList, setComponentList] = useState<{ - [key: string]: ComponentList; - }>({}); + const [functionDetails, setFunctionDetails] = + useState(null); + const [value, setValue] = useState("{}"); + const [resultValue, setResultValue] = useState(""); + const [error, setError] = useState(null); + const [componentList, setComponentList] = useState<{ + [key: string]: ComponentList; + }>({}); - /** Fetch function details based on URL params. */ - const fetchFunctionDetails = useCallback(async () => { - try { - const data = await API.getComponentByIdAsKey(); - setComponentList(data); - const matchingComponent = - data?.[componentId].versions?.[data?.[componentId].versions.length - 1]; - if (!matchingComponent) { - throw new Error("Component not found."); - } - if (name && urlFn) { - const exportItem = matchingComponent.metadata?.exports?.find( - (e: Export) => e.name === name - ); - if (!exportItem) { - throw new Error("Export item not found."); - } + /** Fetch function details based on URL params. */ + const fetchFunctionDetails = useCallback(async () => { + try { + const data = await API.getComponentByIdAsKey(); + setComponentList(data); + const matchingComponent = + data?.[componentId].versions?.[data?.[componentId].versions.length - 1]; + if (!matchingComponent) { + throw new Error("Component not found."); + } + if (name && urlFn) { + const exportItem = matchingComponent.metadata?.exports?.find( + (e: Export) => e.name === name + ); + if (!exportItem) { + throw new Error("Export item not found."); + } - const fnDetails = exportItem.functions?.find( - (f: ComponentExportFunction) => f.name === urlFn - ); - if (!fnDetails) { - throw new Error("Function details not found."); + const fnDetails = exportItem.functions?.find( + (f: ComponentExportFunction) => f.name === urlFn + ); + if (!fnDetails) { + throw new Error("Function details not found."); + } + setFunctionDetails(fnDetails); + const initialJson = parseToJsonEditor(fnDetails); + // Pre-format the JSON so it looks nice in the textarea + setValue(JSON.stringify(initialJson, null, 2)); + } else if ( + !name && + !urlFn && + matchingComponent?.metadata?.exports?.[0]?.functions?.[0] + ) { + navigate( + `/components/${componentId}/invoke?name=${matchingComponent.metadata.exports[0].name}&&fn=${matchingComponent.metadata.exports[0].functions[0].name}` + ); + } + } catch (error: unknown) { + if (error instanceof Error) { + setError(error.message); + } else { + setError("Unable to fetch function details."); + } } - setFunctionDetails(fnDetails); - const initialJson = parseToJsonEditor(fnDetails); - // Pre-format the JSON so it looks nice in the textarea - setValue(JSON.stringify(initialJson, null, 2)); - } else if ( - !name && - !urlFn && - matchingComponent?.metadata?.exports?.[0]?.functions?.[0] - ) { - navigate( - `/components/${componentId}/invoke?name=${matchingComponent.metadata.exports[0].name}&&fn=${matchingComponent.metadata.exports[0].functions[0].name}` - ); - } - } catch (error: unknown) { - if (error instanceof Error) { - setError(error.message); - } else { - setError("Unable to fetch function details."); - } - } - }, [componentId, urlFn, name]); + }, [componentId, urlFn, name]); - useEffect(() => { - if (componentId) { - setError(null); - setResultValue(""); - fetchFunctionDetails(); - } - }, [componentId, name, urlFn, fetchFunctionDetails]); + useEffect(() => { + if (componentId) { + setError(null); + setResultValue(""); + fetchFunctionDetails(); + } + }, [componentId, name, urlFn, fetchFunctionDetails]); - const handleValueChange = (newValue: string) => { - const formatted = safeFormatJSON(newValue); - setValue(formatted); - setResultValue(""); - setError(null); - }; + const handleValueChange = (newValue: string) => { + const formatted = safeFormatJSON(newValue); + setValue(formatted); + setResultValue(""); + setError(null); + }; - const onInvoke = async () => { - try { - setError(null); - const sanitizedValue = sanitizeInput(value); - const parsedValue = JSON.parse(sanitizedValue); + const onInvoke = async () => { + try { + setError(null); + const sanitizedValue = sanitizeInput(value); + const parsedValue = JSON.parse(sanitizedValue); - if (!functionDetails) { - throw new Error("No function details loaded."); - } + if (!functionDetails) { + throw new Error("No function details loaded."); + } - const apiData = parseToApiPayload(parsedValue, functionDetails); + const apiData = parseToApiPayload(parsedValue, functionDetails); - const functionName = `${encodeURIComponent(name)}.${encodeURIComponent( - `{${urlFn}}` - )}`; - const response = await API.invokeEphemeralAwait( - componentId, - functionName, - apiData - ); + const functionName = `${encodeURIComponent(name)}.${encodeURIComponent( + `{${urlFn}}` + )}`; + const response = await API.invokeEphemeralAwait( + componentId, + functionName, + apiData + ); - const newValue = JSON.stringify(response?.result?.value, null, 2); - setResultValue(newValue); - } catch (error: unknown) { - if (error instanceof Error) { - setError(error.message); - } else { - setError("Invalid JSON data. Please correct it before invoking."); - } - } - }; + const newValue = JSON.stringify(response?.result?.value, null, 2); + setResultValue(newValue); + } catch (error: unknown) { + if (error instanceof Error) { + setError(error.message); + } else { + setError("Invalid JSON data. Please correct it before invoking."); + } + } + }; - const copyToClipboard = () => { - navigator.clipboard.writeText(value); - }; + const copyToClipboard = () => { + navigator.clipboard.writeText(value); + }; - const componentDetails = - componentList[componentId]?.versions?.[ - componentList[componentId]?.versions.length - 1 - ] || {}; + const componentDetails = + componentList[componentId]?.versions?.[ + componentList[componentId]?.versions.length - 1 + ] || {}; - return ( - -
- -
-
-
-
- {componentDetails?.metadata?.exports?.map((exportItem) => ( -
-
+ return ( +
+
+
+
+
+ {componentDetails?.metadata?.exports?.map((exportItem) => ( +
+
{exportItem.name} +
+
    + {exportItem?.functions?.length > 0 && + exportItem.functions.map( + (fn: ComponentExportFunction) => ( +
  • + +
  • + ) + )} +
+
+ ))} +
-
    - {exportItem?.functions?.length > 0 && - exportItem.functions.map( - (fn: ComponentExportFunction) => ( -
  • - -
  • - ) - )} -
-
- ))} -
-
-
-
-

- {name} - {urlFn} -

-
+
+
+

+ {name} - {urlFn} +

+
-
-
- +
+
+ -
- -
+
+ +
- {resultValue && ( - - )} -
-
+ {resultValue && ( + + )} +
+
+
+
-
-
- - ); + ); } /* ---------------------------------- @@ -232,64 +217,64 @@ export default function ComponentInvoke() { * ---------------------------------- */ interface SectionCardProps { - title: string; - description: string; - value: string; - onValueChange?: (value: string) => void; - copyToClipboard?: () => void; - error?: string | null; - readOnly?: boolean; + title: string; + description: string; + value: string; + onValueChange?: (value: string) => void; + copyToClipboard?: () => void; + error?: string | null; + readOnly?: boolean; } function SectionCard({ - title, - description, - value, - onValueChange, - copyToClipboard, - error, - readOnly = false, -}: SectionCardProps) { - return ( - - -
-
- {title} -

{description}

-
- {copyToClipboard && ( - - )} -
-
- - {readOnly ? ( - - ) : ( -