96 lines
2.8 KiB
TypeScript
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>
|
|
);
|
|
}
|