Files
llink/js/src/features/particles/particle-list-view.tsx
T
2026-03-18 15:44:30 -07:00

96 lines
2.8 KiB
TypeScript

import { useMemo } from "react";
import { useComposeKeyboard } from "@/features/compose/use-compose-keyboard";
import { useNavigate } from "react-router-dom";
import { Radio } from "lucide-react";
import { useLiveParticleChildren } from "@/hooks/use-particle";
import { parseParticlePath, type ParticlePath } from "@/lib/particle-path";
import { Avatar, AvatarFallback } from "@/components/ui/avatar";
import { ScrollArea } from "@/components/ui/scroll-area";
import { Separator } from "@/components/ui/separator";
import { Progress } from "@/components/ui/progress";
import { Small } from "@/components/ui/typography";
import ControlsIndicator from "@/features/compose/controls-indicator";
import type { Particle, StreamProperties } from "@/api/types";
function StreamRow({
particle,
onClick,
}: {
particle: Particle & { type: "stream"; properties: StreamProperties };
onClick: () => void;
}) {
const initials = particle.properties.name.slice(0, 2).toUpperCase();
return (
<button
type="button"
onClick={onClick}
className="flex w-full items-center gap-3 px-4 py-3 text-left transition-colors hover:bg-accent"
>
<Avatar>
<AvatarFallback className="bg-primary/10 text-primary font-medium">
{initials}
</AvatarFallback>
</Avatar>
<div className="min-w-0 flex-1">
<p className="truncate text-sm font-medium">
{particle.properties.name}
</p>
<div className="text-muted-foreground flex items-center gap-1">
<Radio className="size-3" />
<Small className="text-muted-foreground">
{particle.properties.status}
</Small>
</div>
</div>
</button>
);
}
interface ParticleListViewProps {
path: ParticlePath;
}
/**
* List of stream particles for a container (network root, folder, etc.).
*/
export function ParticleListView({ path }: ParticleListViewProps) {
useComposeKeyboard();
const { children, isLoading } = useLiveParticleChildren(path);
const { networkId } = parseParticlePath(path);
const navigate = useNavigate();
const streams = useMemo(
() => children.filter((c) => c.type === "stream"),
[children],
);
if (isLoading) {
return <Progress />;
}
if (streams.length === 0) {
return (
<div className="flex flex-col h-full items-center justify-center gap-2">
<ControlsIndicator type={"new"} />
</div>
);
}
return (
<ScrollArea className="h-full">
<div className="py-1">
{streams.map((stream, index) => (
<div key={stream.id}>
<StreamRow
particle={stream}
onClick={() => navigate(`/${networkId}/${stream.id}`)}
/>
{index < streams.length - 1 && <Separator className="mx-4" />}
</div>
))}
</div>
</ScrollArea>
);
}