fix nits
This commit is contained in:
@@ -2,21 +2,25 @@ import { useCallback, useState } from 'react';
|
|||||||
import { toast } from 'sonner';
|
import { toast } from 'sonner';
|
||||||
import { ConfirmDestructiveOverlay } from '@/components/confirm-destructive-overlay';
|
import { ConfirmDestructiveOverlay } from '@/components/confirm-destructive-overlay';
|
||||||
import { softDeleteParticle } from '@/lib/firestore-particles';
|
import { softDeleteParticle } from '@/lib/firestore-particles';
|
||||||
import { particlePath, toFirestoreDocPath } from '@/lib/particle-path';
|
import {
|
||||||
|
particlePath,
|
||||||
|
parseParticlePath,
|
||||||
|
toFirestoreDocPath,
|
||||||
|
type ParticlePath,
|
||||||
|
} from '@/lib/particle-path';
|
||||||
import type { Particle } from '@/api/types';
|
import type { Particle } from '@/api/types';
|
||||||
import { useSuspendPlayback } from '@/hooks/use-suspend-playback';
|
import { useSuspendPlayback } from '@/hooks/use-suspend-playback';
|
||||||
|
|
||||||
interface DeleteParticleOverlayProps {
|
interface DeleteParticleOverlayProps {
|
||||||
networkId: string;
|
/** Path of the stream the particle lives in — may be nested. */
|
||||||
streamId: string;
|
streamPath: ParticlePath;
|
||||||
particle: Particle;
|
particle: Particle;
|
||||||
userId: string;
|
userId: string;
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function DeleteParticleOverlay({
|
export function DeleteParticleOverlay({
|
||||||
networkId,
|
streamPath,
|
||||||
streamId,
|
|
||||||
particle,
|
particle,
|
||||||
userId,
|
userId,
|
||||||
onClose,
|
onClose,
|
||||||
@@ -29,8 +33,9 @@ export function DeleteParticleOverlay({
|
|||||||
if (deleting) return;
|
if (deleting) return;
|
||||||
setDeleting(true);
|
setDeleting(true);
|
||||||
try {
|
try {
|
||||||
|
const { networkId, segments } = parseParticlePath(streamPath);
|
||||||
const docPath = toFirestoreDocPath(
|
const docPath = toFirestoreDocPath(
|
||||||
particlePath(networkId, [streamId, particle.id]),
|
particlePath(networkId, [...segments, particle.id]),
|
||||||
);
|
);
|
||||||
await softDeleteParticle(docPath, userId);
|
await softDeleteParticle(docPath, userId);
|
||||||
toast.success('Particle deleted');
|
toast.success('Particle deleted');
|
||||||
@@ -41,7 +46,7 @@ export function DeleteParticleOverlay({
|
|||||||
toast.error(message);
|
toast.error(message);
|
||||||
setDeleting(false);
|
setDeleting(false);
|
||||||
}
|
}
|
||||||
}, [deleting, networkId, onClose, particle.id, streamId, userId]);
|
}, [deleting, onClose, particle.id, streamPath, userId]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ConfirmDestructiveOverlay
|
<ConfirmDestructiveOverlay
|
||||||
|
|||||||
@@ -4,18 +4,18 @@ import { Input } from '@/components/ui/input';
|
|||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import { KeyHint } from '@/components/key-hint';
|
import { KeyHint } from '@/components/key-hint';
|
||||||
import { updateParticleProperties } from '@/lib/firestore-particles';
|
import { updateParticleProperties } from '@/lib/firestore-particles';
|
||||||
import { particlePath, toFirestoreDocPath } from '@/lib/particle-path';
|
import { toFirestoreDocPath, type ParticlePath } from '@/lib/particle-path';
|
||||||
import type { Particle } from '@/api/types';
|
import type { Particle } from '@/api/types';
|
||||||
import { useSuspendPlayback } from '@/hooks/use-suspend-playback';
|
import { useSuspendPlayback } from '@/hooks/use-suspend-playback';
|
||||||
|
|
||||||
interface RenameStreamOverlayProps {
|
interface RenameStreamOverlayProps {
|
||||||
networkId: string;
|
streamPath: ParticlePath;
|
||||||
streamParticle: Particle & { type: 'stream' };
|
streamParticle: Particle & { type: 'stream' };
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function RenameStreamOverlay({
|
export function RenameStreamOverlay({
|
||||||
networkId,
|
streamPath,
|
||||||
streamParticle,
|
streamParticle,
|
||||||
onClose,
|
onClose,
|
||||||
}: RenameStreamOverlayProps) {
|
}: RenameStreamOverlayProps) {
|
||||||
@@ -32,15 +32,13 @@ export function RenameStreamOverlay({
|
|||||||
if (!canSave) return;
|
if (!canSave) return;
|
||||||
setSaving(true);
|
setSaving(true);
|
||||||
try {
|
try {
|
||||||
const docPath = toFirestoreDocPath(
|
const docPath = toFirestoreDocPath(streamPath);
|
||||||
particlePath(networkId, [streamParticle.id]),
|
|
||||||
);
|
|
||||||
await updateParticleProperties<'stream'>(docPath, { name: trimmed });
|
await updateParticleProperties<'stream'>(docPath, { name: trimmed });
|
||||||
onClose();
|
onClose();
|
||||||
} finally {
|
} finally {
|
||||||
setSaving(false);
|
setSaving(false);
|
||||||
}
|
}
|
||||||
}, [canSave, networkId, onClose, streamParticle.id, trimmed]);
|
}, [canSave, streamPath, onClose, trimmed]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const handler = (e: KeyboardEvent) => {
|
const handler = (e: KeyboardEvent) => {
|
||||||
|
|||||||
@@ -103,8 +103,9 @@ function ChatRow({
|
|||||||
role="button"
|
role="button"
|
||||||
tabIndex={0}
|
tabIndex={0}
|
||||||
onClick={onClick}
|
onClick={onClick}
|
||||||
|
// Enter only — Space is reserved for play/pause in the stream view.
|
||||||
onKeyDown={(e) => {
|
onKeyDown={(e) => {
|
||||||
if (e.key === 'Enter' || e.key === ' ') onClick();
|
if (e.key === 'Enter') onClick();
|
||||||
}}
|
}}
|
||||||
className={cn(
|
className={cn(
|
||||||
'flex w-full cursor-pointer items-start gap-2.5 rounded-md px-2 py-2 text-left transition-colors',
|
'flex w-full cursor-pointer items-start gap-2.5 rounded-md px-2 py-2 text-left transition-colors',
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import {
|
|||||||
parseVisibleTo,
|
parseVisibleTo,
|
||||||
} from '@/lib/stream-visibility';
|
} from '@/lib/stream-visibility';
|
||||||
import { updateParticleVisibleTo } from '@/lib/firestore-particles';
|
import { updateParticleVisibleTo } from '@/lib/firestore-particles';
|
||||||
import { particlePath, toFirestoreDocPath } from '@/lib/particle-path';
|
import { toFirestoreDocPath, type ParticlePath } from '@/lib/particle-path';
|
||||||
import { useNetwork } from '@/hooks/use-networks';
|
import { useNetwork } from '@/hooks/use-networks';
|
||||||
import { cn, getInitials } from '@/lib/utils';
|
import { cn, getInitials } from '@/lib/utils';
|
||||||
import { resolveHumanDisplay } from '@/lib/humans';
|
import { resolveHumanDisplay } from '@/lib/humans';
|
||||||
@@ -19,6 +19,7 @@ import { useSuspendPlayback } from '@/hooks/use-suspend-playback';
|
|||||||
|
|
||||||
interface StreamMembersOverlayProps {
|
interface StreamMembersOverlayProps {
|
||||||
networkId: string;
|
networkId: string;
|
||||||
|
streamPath: ParticlePath;
|
||||||
streamParticle: Particle & { type: 'stream' };
|
streamParticle: Particle & { type: 'stream' };
|
||||||
isCreator: boolean;
|
isCreator: boolean;
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
@@ -26,6 +27,7 @@ interface StreamMembersOverlayProps {
|
|||||||
|
|
||||||
export function StreamMembersOverlay({
|
export function StreamMembersOverlay({
|
||||||
networkId,
|
networkId,
|
||||||
|
streamPath,
|
||||||
streamParticle,
|
streamParticle,
|
||||||
isCreator,
|
isCreator,
|
||||||
onClose,
|
onClose,
|
||||||
@@ -40,10 +42,7 @@ export function StreamMembersOverlay({
|
|||||||
[streamParticle.visible_to, networkId],
|
[streamParticle.visible_to, networkId],
|
||||||
);
|
);
|
||||||
|
|
||||||
const docPath = useMemo(
|
const docPath = useMemo(() => toFirestoreDocPath(streamPath), [streamPath]);
|
||||||
() => toFirestoreDocPath(particlePath(networkId, [streamParticle.id])),
|
|
||||||
[networkId, streamParticle.id],
|
|
||||||
);
|
|
||||||
|
|
||||||
const memberIds =
|
const memberIds =
|
||||||
visibility.mode === 'network'
|
visibility.mode === 'network'
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ import {
|
|||||||
CircleDot,
|
CircleDot,
|
||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
import { updateStreamStatus } from '@/lib/firestore-particles';
|
import { updateStreamStatus } from '@/lib/firestore-particles';
|
||||||
import { toFirestoreDocPath, particlePath } from '@/lib/particle-path';
|
import { toFirestoreDocPath, type ParticlePath } from '@/lib/particle-path';
|
||||||
import { RenameStreamOverlay } from '@/features/particles/rename-stream-overlay';
|
import { RenameStreamOverlay } from '@/features/particles/rename-stream-overlay';
|
||||||
import { DeleteParticleOverlay } from '@/features/particles/delete-particle-overlay';
|
import { DeleteParticleOverlay } from '@/features/particles/delete-particle-overlay';
|
||||||
import { StreamMembersOverlay } from '@/features/particles/stream-members-overlay';
|
import { StreamMembersOverlay } from '@/features/particles/stream-members-overlay';
|
||||||
@@ -51,9 +51,16 @@ interface TopBarProps {
|
|||||||
networkId: string;
|
networkId: string;
|
||||||
particle: Particle | null;
|
particle: Particle | null;
|
||||||
streamParticle: Particle & { type: 'stream' };
|
streamParticle: Particle & { type: 'stream' };
|
||||||
|
/** Resolved path of the stream — may be nested under a container. */
|
||||||
|
streamPath: ParticlePath;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function TopBar({ networkId, particle, streamParticle }: TopBarProps) {
|
export function TopBar({
|
||||||
|
networkId,
|
||||||
|
particle,
|
||||||
|
streamParticle,
|
||||||
|
streamPath,
|
||||||
|
}: TopBarProps) {
|
||||||
const network = useNetwork(networkId);
|
const network = useNetwork(networkId);
|
||||||
const userId = useAuthStore((s) => s.user?.id);
|
const userId = useAuthStore((s) => s.user?.id);
|
||||||
const isCreator = !!userId && userId === streamParticle.created_by_human_id;
|
const isCreator = !!userId && userId === streamParticle.created_by_human_id;
|
||||||
@@ -176,9 +183,7 @@ export function TopBar({ networkId, particle, streamParticle }: TopBarProps) {
|
|||||||
)}
|
)}
|
||||||
<DropdownMenuItem
|
<DropdownMenuItem
|
||||||
onSelect={async () => {
|
onSelect={async () => {
|
||||||
const docPath = toFirestoreDocPath(
|
const docPath = toFirestoreDocPath(streamPath);
|
||||||
particlePath(networkId, [streamParticle.id]),
|
|
||||||
);
|
|
||||||
await updateStreamStatus(
|
await updateStreamStatus(
|
||||||
docPath,
|
docPath,
|
||||||
isStreamOpen(streamParticle) ? 'closed' : 'open',
|
isStreamOpen(streamParticle) ? 'closed' : 'open',
|
||||||
@@ -211,7 +216,7 @@ export function TopBar({ networkId, particle, streamParticle }: TopBarProps) {
|
|||||||
|
|
||||||
{renameOpen && isCreator && (
|
{renameOpen && isCreator && (
|
||||||
<RenameStreamOverlay
|
<RenameStreamOverlay
|
||||||
networkId={networkId}
|
streamPath={streamPath}
|
||||||
streamParticle={streamParticle}
|
streamParticle={streamParticle}
|
||||||
onClose={() => setRenameOpen(false)}
|
onClose={() => setRenameOpen(false)}
|
||||||
/>
|
/>
|
||||||
@@ -219,8 +224,7 @@ export function TopBar({ networkId, particle, streamParticle }: TopBarProps) {
|
|||||||
|
|
||||||
{deleteOpen && canDeleteParticle && particle && userId && (
|
{deleteOpen && canDeleteParticle && particle && userId && (
|
||||||
<DeleteParticleOverlay
|
<DeleteParticleOverlay
|
||||||
networkId={networkId}
|
streamPath={streamPath}
|
||||||
streamId={streamParticle.id}
|
|
||||||
particle={particle}
|
particle={particle}
|
||||||
userId={userId}
|
userId={userId}
|
||||||
onClose={() => setDeleteOpen(false)}
|
onClose={() => setDeleteOpen(false)}
|
||||||
@@ -230,6 +234,7 @@ export function TopBar({ networkId, particle, streamParticle }: TopBarProps) {
|
|||||||
{membersOpen && (
|
{membersOpen && (
|
||||||
<StreamMembersOverlay
|
<StreamMembersOverlay
|
||||||
networkId={networkId}
|
networkId={networkId}
|
||||||
|
streamPath={streamPath}
|
||||||
streamParticle={streamParticle}
|
streamParticle={streamParticle}
|
||||||
isCreator={isCreator}
|
isCreator={isCreator}
|
||||||
onClose={() => setMembersOpen(false)}
|
onClose={() => setMembersOpen(false)}
|
||||||
|
|||||||
@@ -479,6 +479,7 @@ function StreamViewInner({ path, streamParticle }: StreamViewProps) {
|
|||||||
networkId={networkId}
|
networkId={networkId}
|
||||||
particle={currentParticle}
|
particle={currentParticle}
|
||||||
streamParticle={streamParticle}
|
streamParticle={streamParticle}
|
||||||
|
streamPath={path}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -101,8 +101,12 @@ export function TaskParticleView({
|
|||||||
}, [checklist]);
|
}, [checklist]);
|
||||||
|
|
||||||
const writeChecklist = useCallback(
|
const writeChecklist = useCallback(
|
||||||
(items: ChecklistItem[]) =>
|
(items: ChecklistItem[]) => {
|
||||||
updateParticleProperties<'task'>(docPath, { checklist: items }),
|
// Advance the local base before the write so a second edit issued before
|
||||||
|
// the next snapshot composes on top of this one instead of dropping it.
|
||||||
|
checklistRef.current = items;
|
||||||
|
return updateParticleProperties<'task'>(docPath, { checklist: items });
|
||||||
|
},
|
||||||
[docPath],
|
[docPath],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user