@@ -2,7 +2,7 @@ import { useState, useEffect, useEffectEvent, useCallback, useRef } from "react"
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { useAuthStore } from "@/stores/auth-store";
|
||||
import { apiClient } from "@/api/client";
|
||||
import type { Particle } from "@/api/types";
|
||||
import { type Particle, REACTION_EMOJIS } from "@/api/types";
|
||||
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";
|
||||
@@ -22,7 +22,8 @@ import {
|
||||
DropdownMenuTrigger,
|
||||
} from "@/components/ui/dropdown-menu";
|
||||
import { Settings, CircleCheckBig, CircleDot, EllipsisVertical } from "lucide-react";
|
||||
import { updateStreamStatus } from "@/lib/firestore-particles";
|
||||
import { updateStreamStatus, toggleParticleReaction } from "@/lib/firestore-particles";
|
||||
import { ReactionBar } from "@/features/particles/reaction-bar";
|
||||
import { Breadcrumb, BreadcrumbItem, BreadcrumbList, BreadcrumbPage, BreadcrumbSeparator } from "@/components/ui/breadcrumb";
|
||||
import { WindowControls } from "@/components/window-controls";
|
||||
import { RelativeTimestamp } from "@/components/relative-timestamp";
|
||||
@@ -32,6 +33,11 @@ import { usePresencePositions } from "@/hooks/use-presence-positions";
|
||||
import { cn, getInitials } from "@/lib/utils";
|
||||
import { useMount } from "react-use";
|
||||
|
||||
function getReactions(particle: Particle): Record<string, string[]> | undefined {
|
||||
if (particle.type === "media" || particle.type === "text") return particle.reactions;
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function getParticleDisplayName(particle: Particle): string {
|
||||
switch (particle.type) {
|
||||
case "stream":
|
||||
@@ -128,6 +134,12 @@ const STREAM_VIEW_KEYBINDINGS: KeybindingGroup[] = [
|
||||
{ keys: ["H"], description: "Join huddle" },
|
||||
],
|
||||
},
|
||||
{
|
||||
label: "Reactions",
|
||||
bindings: [
|
||||
{ keys: ["1-6"], description: "Toggle emoji reaction" },
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
// --- StreamView ---
|
||||
@@ -171,6 +183,19 @@ export function StreamView({ path, streamParticle }: StreamViewProps) {
|
||||
|
||||
const mediaRef = useRef<MediaParticleHandle>(null);
|
||||
|
||||
const handleToggleReaction = useCallback((emoji: string) => {
|
||||
if (!authedUser || !currentParticle) return;
|
||||
|
||||
|
||||
const currentParticleDocPath = currentParticle
|
||||
? toFirestoreDocPath(particlePath(networkId, [streamParticle.id, currentParticle.id]))
|
||||
: null;
|
||||
if (!currentParticleDocPath) return;
|
||||
|
||||
const reactions = getReactions(currentParticle);
|
||||
toggleParticleReaction(currentParticleDocPath, emoji, authedUser.id, reactions);
|
||||
}, [authedUser, currentParticle]);
|
||||
|
||||
const [composeActive, setComposeActive] = useState(false);
|
||||
const [progress, setProgress] = useState(0);
|
||||
const [fastPlayback, setFastPlayback] = useState(false);
|
||||
@@ -262,6 +287,10 @@ export function StreamView({ path, streamParticle }: StreamViewProps) {
|
||||
e.preventDefault();
|
||||
setShowKeybindings((v) => !v);
|
||||
break;
|
||||
case "1": case "2": case "3": case "4": case "5": case "6":
|
||||
e.preventDefault();
|
||||
handleToggleReaction(REACTION_EMOJIS[parseInt(e.key) - 1]);
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -286,7 +315,7 @@ export function StreamView({ path, streamParticle }: StreamViewProps) {
|
||||
window.removeEventListener("keyup", handleKeyUp);
|
||||
};
|
||||
},
|
||||
[composeActive, next, prev, pause, resume, navigate, networkId, streamParticle.id, setShowKeybindings],
|
||||
[composeActive, next, prev, pause, resume, navigate, networkId, streamParticle.id, setShowKeybindings, handleToggleReaction],
|
||||
);
|
||||
|
||||
// Click-to-navigate: left 30% = prev, right 70% = next
|
||||
@@ -387,6 +416,18 @@ export function StreamView({ path, streamParticle }: StreamViewProps) {
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Reaction bar — always visible */}
|
||||
{currentParticle && (
|
||||
<div className="absolute bottom-16 left-4 z-10">
|
||||
<ReactionBar
|
||||
reactions={getReactions(currentParticle)}
|
||||
currentHumanId={authedUser?.id ?? ""}
|
||||
humans={network?.humans}
|
||||
onToggle={handleToggleReaction}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<ComposeOverlay
|
||||
networkId={networkId}
|
||||
targetPath={path}
|
||||
|
||||
Reference in New Issue
Block a user