show stream state in stream-view

This commit is contained in:
talksik
2026-04-07 15:31:07 -07:00
parent d4b0545598
commit 4a4e09ae0b
3 changed files with 77 additions and 11 deletions
@@ -23,6 +23,8 @@ interface ComposeOverlayProps {
targetPath?: ParticlePath;
onActiveChange?: (active: boolean) => void;
onParticleCreated?: (particleId: string) => void;
/** When true, composing is blocked (e.g. stream is closed). */
disabled?: boolean;
}
@@ -37,6 +39,7 @@ export function ComposeOverlay({
targetPath,
onActiveChange,
onParticleCreated,
disabled,
}: ComposeOverlayProps) {
const [step, setStep] = useState<ComposeStep>("idle");
const [error, setError] = useState<string | null>(null);
@@ -56,6 +59,8 @@ export function ComposeOverlay({
// Refs for synchronous reads in keyboard handlers
const stepRef = useRef(step);
const recordStartRef = useRef(0);
const disabledRef = useRef(disabled);
disabledRef.current = disabled;
const setStepSync = useCallback((next: ComposeStep) => {
stepRef.current = next;
@@ -341,6 +346,13 @@ export function ComposeOverlay({
switch (currentStep) {
case "idle": {
if (disabledRef.current) {
if ((e.key === "`" && !e.repeat) || e.key === "t" || e.key === "T") {
e.preventDefault();
toast.info("This stream is closed");
}
break;
}
if (e.key === "`" && !e.repeat) {
e.preventDefault();
recordStartRef.current = Date.now();
@@ -4,6 +4,7 @@ import {
ContextMenuItem,
ContextMenuTrigger,
} from "@/components/ui/context-menu";
import { CircleCheckBig, CircleDot } from "lucide-react";
import { updateStreamStatus } from "@/lib/firestore-particles";
import { toFirestoreDocPath, particlePath } from "@/lib/particle-path";
import type { StreamParticle } from "@/hooks/use-stream-particles";
@@ -27,7 +28,17 @@ export function StreamContextMenu({ particle, networkId, children }: StreamConte
<ContextMenuTrigger asChild>{children}</ContextMenuTrigger>
<ContextMenuContent>
<ContextMenuItem onSelect={toggleStatus}>
{isOpen ? "Close stream" : "Open stream"}
{isOpen ? (
<>
<CircleCheckBig className="size-4" />
Close stream
</>
) : (
<>
<CircleDot className="size-4 text-green-500" />
Open stream
</>
)}
</ContextMenuItem>
</ContextMenuContent>
</ContextMenu>
+53 -10
View File
@@ -3,7 +3,7 @@ import { useNavigate } from "react-router-dom";
import { useAuthStore } from "@/stores/auth-store";
import { apiClient } from "@/api/client";
import type { Particle } from "@/api/types";
import { parseParticlePath, type ParticlePath } from "@/lib/particle-path";
import { parseParticlePath, particlePath, toFirestoreDocPath, type ParticlePath } from "@/lib/particle-path";
import { ComposeOverlay } from "@/features/compose/compose-overlay";
import { PlaybackPageIndicator } from "@/features/particles/playback-page-indicator";
import { MediaParticleView } from "@/features/particles/media-particle-view";
@@ -14,7 +14,14 @@ import ControlsIndicator from "@/features/compose/controls-indicator";
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip";
import { useNetwork, useNetworks } from "@/hooks/use-networks";
import { Button } from "@/components/ui/button";
import { Settings } from "lucide-react";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
import { Settings, CircleCheckBig, CircleDot, EllipsisVertical } from "lucide-react";
import { updateStreamStatus } from "@/lib/firestore-particles";
import { Breadcrumb, BreadcrumbItem, BreadcrumbList, BreadcrumbPage, BreadcrumbSeparator } from "@/components/ui/breadcrumb";
import { WindowControls } from "@/components/window-controls";
import { RelativeTimestamp } from "@/components/relative-timestamp";
@@ -248,6 +255,7 @@ export function StreamView({ path, streamParticle }: StreamViewProps) {
networkId={networkId}
targetPath={path}
onActiveChange={setComposeActive}
disabled={streamParticle.status === "closed"}
/>
</div>
);
@@ -314,6 +322,7 @@ export function StreamView({ path, streamParticle }: StreamViewProps) {
targetPath={path}
onActiveChange={setComposeActive}
onParticleCreated={onLocalParticleCreated}
disabled={streamParticle.status === "closed"}
/>
{/* BottomBar */}
@@ -461,14 +470,48 @@ function TopBar({ networkId, particle, streamParticle }: { networkId: string; pa
</button>
)}
<Button
variant="ghost"
size="sm"
className="no-drag text-muted-foreground"
onClick={() => navigate("/settings")}
>
<Settings className="size-3.5" />
</Button>
{streamParticle.status === "closed" && (
<span className="no-drag flex items-center gap-1 rounded-full bg-white/10 px-2 py-0.5 text-xs text-muted-foreground backdrop-blur-sm">
<CircleCheckBig className="size-3" />
Closed
</span>
)}
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button
variant="ghost"
size="sm"
className="no-drag text-muted-foreground"
>
<EllipsisVertical className="size-3.5" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuItem
onSelect={async () => {
const docPath = toFirestoreDocPath(particlePath(networkId, [streamParticle.id]));
await updateStreamStatus(docPath, streamParticle.status === "open" ? "closed" : "open");
}}
>
{streamParticle.status === "open" ? (
<>
<CircleCheckBig className="size-4" />
Close stream
</>
) : (
<>
<CircleDot className="size-4 text-green-500" />
Open stream
</>
)}
</DropdownMenuItem>
<DropdownMenuItem onSelect={() => navigate("/settings")}>
<Settings className="size-4" />
Settings
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</div>
);
}