diff --git a/js/src/App.tsx b/js/src/App.tsx index cdd788b..d83c8b4 100644 --- a/js/src/App.tsx +++ b/js/src/App.tsx @@ -13,6 +13,7 @@ import NetworkSelector from "@/features/network-selector"; import NetworkRoot from "@/features/network-root"; import ParticleViewResolver from "@/features/particles/particle-view-resolver"; import Layout from "@/features/layout"; +import { Button } from "@/components/ui/button"; const queryClient = new QueryClient(); @@ -45,10 +46,10 @@ function AuthenticatedApp() { } /> - }> - } /> + + } /> - } /> + } /> } /> diff --git a/js/src/features/layout.tsx b/js/src/features/layout.tsx index b22a31e..aac81ea 100644 --- a/js/src/features/layout.tsx +++ b/js/src/features/layout.tsx @@ -15,6 +15,7 @@ import { useNetworks } from "@/hooks/use-networks"; import { particlePath } from "@/lib/particle-path"; import { useParticle } from "@/hooks/use-particle"; import type { Particle } from "@/api/types"; +import { PropsWithChildren } from "react"; function getParticleDisplayName(particle: Particle): string { switch (particle.type) { @@ -127,11 +128,11 @@ function TopBar() { ); } -export default function Layout() { +export default function Layout({ children }: PropsWithChildren) { return (
- + {children}
); } diff --git a/js/src/features/particles/stream-view.tsx b/js/src/features/particles/stream-view.tsx index 5058530..6ff9d0e 100644 --- a/js/src/features/particles/stream-view.tsx +++ b/js/src/features/particles/stream-view.tsx @@ -9,11 +9,33 @@ import { PlaybackPageIndicator } from "@/features/playback/playback-page-indicat import { MediaParticleView } from "@/features/playback/media-particle-view"; import { TextParticleView } from "@/features/playback/text-particle-view"; import { FallbackParticleView } from "@/features/playback/fallback-particle-view"; -import { Avatar, AvatarFallback, AvatarGroup, AvatarGroupCount } from "@/components/ui/avatar"; +import { Avatar, AvatarFallback, AvatarGroup } from "@/components/ui/avatar"; import ControlsIndicator from "@/features/compose/controls-indicator"; import { updateStreamPlaybackMarker } from "@/lib/firestore-particles"; import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip"; -import { useNetwork } from "@/hooks/use-networks"; +import { useNetwork, useNetworks } from "@/hooks/use-networks"; +import { Button } from "@/components/ui/button"; +import { Home, Settings } from "lucide-react"; +import { Breadcrumb, BreadcrumbItem, BreadcrumbLink, BreadcrumbList, BreadcrumbPage, BreadcrumbSeparator } from "@/components/ui/breadcrumb"; +import { WindowControls } from "@/components/window-controls"; + +function getParticleDisplayName(particle: Particle): string { + switch (particle.type) { + case "stream": + case "folder": + return particle.properties.name; + case "quest": + return particle.properties.title; + case "paper": + return particle.properties.title; + case "file": + return particle.properties.filename; + case "text": + return particle.properties.content.slice(0, 30); + case "media": + return particle.type; + } +} // --- Playback reducer --- @@ -334,7 +356,7 @@ export function StreamView({ path, streamParticle }: StreamViewProps) { } return ( -
+
{/* Progress indicator */}
- {/* Author overlay */} - {currentParticle && ( -
- - - {authorInitials} - - - - {authorEmail.split("@")[0]} - -
- )} +
+ + + + + + navigate("/")} + > + + + + + + navigate(`/${networkId}`)} + > + + + + + {streamParticle && ( + + + + {getParticleDisplayName(streamParticle)} + + + )} + + {currentParticle && ( + <> + + + + + + )} + + + + +
{/* Main playback area */}
@@ -381,7 +442,6 @@ export function StreamView({ path, streamParticle }: StreamViewProps) {
- Seen by {/* Exit countdown */} {exitRemainingMs !== null && ( @@ -396,6 +456,41 @@ export function StreamView({ path, streamParticle }: StreamViewProps) { ); } +function NetworkBreadcrumbContent({ networkId }: { networkId: string }) { + const { data: networks } = useNetworks(); + const network = networks?.find((n) => n.id === networkId); + const name = network?.name ?? networkId; + const initials = name.slice(0, 2).toUpperCase(); + + return ( + + + + {initials} + + + {name} + + ); +} + +function ParticleBreadcrumbContent({ particle }: { particle: Particle }) { + const createdByEmail = particle.created_by_email; + const prefix = createdByEmail.split('@')[0]; + const initials = createdByEmail.slice(0, 2).toUpperCase(); + + return ( + + + + {initials} + + + {prefix} + + ) +} + // Shows a list of avatars of users who have seen the current particle, based on playback markers in the stream particle. const SeenIndicator = ({ stream, currentParticle, networkId }: { stream: Particle & { type: "stream" }, currentParticle: Particle, networkId: string }) => { const network = useNetwork(networkId); @@ -412,21 +507,24 @@ const SeenIndicator = ({ stream, currentParticle, networkId }: { stream: Particl if (seenUserIds.length === 0) return null; return ( - - {seenUserEmails.map((email) => ( - - - - - {email.split("@")[0].slice(0, 2)} - - - - -

Seen by {email.split("@")[0]}

-
-
- ))} -
+ <> + {seenUserEmails.length > 0 && "Seen by"} + + {seenUserEmails.map((email) => ( + + + + + {email.split("@")[0].slice(0, 2)} + + + + +

Seen by {email.split("@")[0]}

+
+
+ ))} +
+ ); }