feat: show seen indicator on particles
This commit is contained in:
@@ -2,11 +2,12 @@ import { Video, Mic } from "lucide-react";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { useMediaSettingsStore } from "@/stores/media-settings-store";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { PropsWithChildren } from "react";
|
||||
|
||||
interface ControlsIndicatorProps {
|
||||
interface ControlsIndicatorProps extends PropsWithChildren {
|
||||
type: "reply" | "new";
|
||||
}
|
||||
export default function ControlsIndicator({ type }: ControlsIndicatorProps) {
|
||||
export default function ControlsIndicator({ type, children }: ControlsIndicatorProps) {
|
||||
const recordingMode = useMediaSettingsStore((s) => s.recordingMode);
|
||||
const setRecordingMode = useMediaSettingsStore((s) => s.setRecordingMode);
|
||||
|
||||
@@ -39,7 +40,10 @@ export default function ControlsIndicator({ type }: ControlsIndicatorProps) {
|
||||
</>
|
||||
)}
|
||||
</Button>
|
||||
<div className="flex-1" />
|
||||
{children ? <div className="flex-1">{children}</div> :
|
||||
<div className="flex-1" />
|
||||
}
|
||||
{children && <span className="text-muted-foreground">·</span>}
|
||||
<div className="text-muted-foreground">
|
||||
Hold{" "}
|
||||
<kbd
|
||||
|
||||
@@ -9,9 +9,11 @@ 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 } from "@/components/ui/avatar";
|
||||
import { Avatar, AvatarFallback, AvatarGroup, AvatarGroupCount } 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";
|
||||
|
||||
// --- Playback reducer ---
|
||||
|
||||
@@ -382,8 +384,45 @@ export function StreamView({ path, streamParticle }: StreamViewProps) {
|
||||
|
||||
{/* Bottom overlay: stream info + reply */}
|
||||
<div className="absolute right-0 left-0 bottom-0 z-10">
|
||||
<ControlsIndicator type={"reply"} />
|
||||
<ControlsIndicator type={"reply"}>
|
||||
<SeenIndicator stream={streamParticle} currentParticle={currentParticle} networkId={networkId} />
|
||||
</ControlsIndicator>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// 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);
|
||||
const playbackMarkers = stream.playback_markers ?? {};
|
||||
|
||||
const seenUserIds = Object.entries(playbackMarkers)
|
||||
.filter(([_, timestamp]) => timestamp.getTime() >= currentParticle.created_at.getTime())
|
||||
.map(([userId, _]) => userId);
|
||||
|
||||
const seenUserEmails = seenUserIds
|
||||
.map((userId) => network?.humans?.find((h) => h.id === userId)?.email)
|
||||
.filter((email): email is string => !!email);
|
||||
|
||||
if (seenUserIds.length === 0) return null;
|
||||
|
||||
return (
|
||||
<AvatarGroup>
|
||||
{seenUserEmails.map((email) => (
|
||||
<Tooltip key={email}>
|
||||
<TooltipTrigger asChild>
|
||||
<Avatar size="sm">
|
||||
<AvatarFallback>
|
||||
{email.split("@")[0].slice(0, 2)}
|
||||
</AvatarFallback>
|
||||
</Avatar>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
<p>Seen by {email.split("@")[0]}</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
))}
|
||||
</AvatarGroup>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -7,3 +7,8 @@ export function useNetworks() {
|
||||
queryFn: () => apiClient.listNetworks(),
|
||||
});
|
||||
}
|
||||
|
||||
export function useNetwork(networkId: string) {
|
||||
const { data: networks } = useNetworks();
|
||||
return networks?.find((n) => n.id === networkId) || null;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user