"use client";
import { type RobotPlugin } from "~/lib/plugin-store/types";
import { Badge } from "~/components/ui/badge";
import { Button } from "~/components/ui/button";
import { Bot, Download, Info, Zap, Battery, Scale, Ruler } from "lucide-react";
import Image from "next/image";
import { cn } from "~/lib/utils";
import { useState, useRef, useEffect } from "react";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "~/components/ui/tabs";
import { PageHeader } from "~/components/layout/page-header";
import { PageContent } from "~/components/layout/page-content";
import { api } from "~/trpc/react";
import { useToast } from "~/hooks/use-toast";
import { useRouter } from "next/navigation";
interface RobotListProps {
plugins: RobotPlugin[];
}
function RobotListItem({
plugin,
isSelected,
onSelect
}: {
plugin: RobotPlugin;
isSelected: boolean;
onSelect: () => void;
}) {
return (
{plugin.assets.logo ? (
) : plugin.assets.thumbnailUrl ? (
) : (
)}
{plugin.name}
{plugin.platform}
{plugin.description}
{plugin.specs.maxSpeed}m/s
{plugin.specs.batteryLife}h
);
}
function RobotHeader({ robot }: { robot: RobotPlugin }) {
const router = useRouter();
const { toast } = useToast();
const [isInstalling, setIsInstalling] = useState(false);
const utils = api.useUtils();
const installPlugin = api.pluginStore.installPlugin.useMutation({
onSuccess: () => {
toast({
title: "Success",
description: `${robot.name} installed successfully`,
});
// Invalidate both queries to refresh the data
utils.pluginStore.getInstalledPlugins.invalidate();
utils.pluginStore.getPlugins.invalidate();
},
onError: (error) => {
console.error("Failed to install plugin:", error);
toast({
title: "Error",
description: error.message || "Failed to install plugin",
variant: "destructive",
});
},
});
const handleInstall = async () => {
if (isInstalling) return;
try {
setIsInstalling(true);
await installPlugin.mutateAsync({
robotId: robot.robotId,
repositoryId: "hristudio-official", // TODO: Get from context
});
} finally {
setIsInstalling(false);
}
};
return (
{robot.name}
{robot.description}
{robot.specs.maxSpeed}m/s
{robot.specs.batteryLife}h
{robot.specs.dimensions.weight}kg
);
}
function RobotImages({ robot }: { robot: RobotPlugin }) {
const [showLeftFade, setShowLeftFade] = useState(false);
const [showRightFade, setShowRightFade] = useState(false);
const scrollRef = useRef(null);
useEffect(() => {
const el = scrollRef.current;
if (!el) return;
const checkScroll = () => {
const hasLeftScroll = el.scrollLeft > 0;
const hasRightScroll = el.scrollLeft < (el.scrollWidth - el.clientWidth);
setShowLeftFade(hasLeftScroll);
setShowRightFade(hasRightScroll);
};
// Check initial scroll
checkScroll();
// Add scroll listener
el.addEventListener('scroll', checkScroll);
// Add resize listener to handle window changes
window.addEventListener('resize', checkScroll);
return () => {
el.removeEventListener('scroll', checkScroll);
window.removeEventListener('resize', checkScroll);
};
}, []);
return (
{/* Main Image */}
{/* Angle Images */}
{robot.assets.images.angles && (
{Object.entries(robot.assets.images.angles).map(([angle, url]) => url && (
))}
)}
{/* Fade indicators */}
{showLeftFade && (
)}
{showRightFade && (
)}
);
}
function RobotSpecs({ robot }: { robot: RobotPlugin }) {
return (
Physical Specifications
{robot.specs.dimensions.length}m × {robot.specs.dimensions.width}m × {robot.specs.dimensions.height}m
{robot.specs.dimensions.weight}kg
{robot.specs.maxSpeed}m/s
{robot.specs.batteryLife}h
Capabilities
{robot.specs.capabilities.map((capability) => (
{capability}
))}
ROS 2 Configuration
Namespace:
{robot.ros2Config.namespace}
Node Prefix:
{robot.ros2Config.nodePrefix}
Default Topics:
{Object.entries(robot.ros2Config.defaultTopics).map(([name, topic]) => (
{name}:
{topic}
))}
);
}
function RobotActions({ robot }: { robot: RobotPlugin }) {
return (
{robot.actions.map((action) => (
{action.title}
{action.type}
{action.description}
Parameters:
{Object.entries(action.parameters.properties).map(([name, prop]) => (
{prop.title}
{prop.unit && (
({prop.unit})
)}
{prop.description && (
{prop.description}
)}
))}
))}
);
}
export function RobotList({ plugins }: RobotListProps) {
const [selectedRobot, setSelectedRobot] = useState(plugins[0] ?? null);
if (!plugins.length) {
return (
);
}
return (
{/* Left Pane - Robot List */}
{plugins.map((plugin) => (
setSelectedRobot(plugin)}
/>
))}
{/* Right Pane - Robot Details */}
{selectedRobot && (
Overview
Specifications
Actions
)}
);
}