cleanup and add toggle for sidebar
This commit is contained in:
@@ -48,8 +48,7 @@ export type ComposeStep =
|
|||||||
|
|
||||||
type RecordingSource = 'media' | 'screen';
|
type RecordingSource = 'media' | 'screen';
|
||||||
|
|
||||||
type PendingArtifact =
|
type PendingArtifact = { type: 'task'; properties: TaskProperties };
|
||||||
| { type: 'task'; properties: TaskProperties };
|
|
||||||
|
|
||||||
interface ComposeOverlayProps {
|
interface ComposeOverlayProps {
|
||||||
networkId: string;
|
networkId: string;
|
||||||
@@ -466,7 +465,7 @@ export function ComposeOverlay({
|
|||||||
// --- Compose intent handlers ---
|
// --- Compose intent handlers ---
|
||||||
// Single source of truth for the step transitions triggered by the user.
|
// Single source of truth for the step transitions triggered by the user.
|
||||||
// Both the keyboard handler and the intent store dispatch into these so
|
// Both the keyboard handler and the intent store dispatch into these so
|
||||||
// guards (disabled, quota) and screen-vs-media branching live in one place.
|
// guards (quota) and screen-vs-media branching live in one place.
|
||||||
|
|
||||||
const guardIdle = useCallback((): boolean => {
|
const guardIdle = useCallback((): boolean => {
|
||||||
if (stepRef.current !== 'idle') return false;
|
if (stepRef.current !== 'idle') return false;
|
||||||
@@ -497,7 +496,6 @@ export function ComposeOverlay({
|
|||||||
setStepSync('task');
|
setStepSync('task');
|
||||||
}, [guardIdle, setStepSync]);
|
}, [guardIdle, setStepSync]);
|
||||||
|
|
||||||
|
|
||||||
// Artifact submit: capture the artifact, then reuse the standard flow —
|
// Artifact submit: capture the artifact, then reuse the standard flow —
|
||||||
// reply mode creates it under targetPath, root mode configures a stream
|
// reply mode creates it under targetPath, root mode configures a stream
|
||||||
// that will hold it as its first child.
|
// that will hold it as its first child.
|
||||||
|
|||||||
@@ -1,4 +1,11 @@
|
|||||||
import { Fragment, useMemo, useRef, useEffect, memo, createElement } from 'react';
|
import {
|
||||||
|
Fragment,
|
||||||
|
useMemo,
|
||||||
|
useRef,
|
||||||
|
useEffect,
|
||||||
|
memo,
|
||||||
|
createElement,
|
||||||
|
} from 'react';
|
||||||
import { Headphones, Radio, FolderIcon } from 'lucide-react';
|
import { Headphones, Radio, FolderIcon } from 'lucide-react';
|
||||||
import { cn, getInitials } from '@/lib/utils';
|
import { cn, getInitials } from '@/lib/utils';
|
||||||
import { useLiveLatestChild } from '@/hooks/use-particle';
|
import { useLiveLatestChild } from '@/hooks/use-particle';
|
||||||
|
|||||||
@@ -1,18 +1,12 @@
|
|||||||
import { useEffect, useRef } from 'react';
|
import { useEffect, useRef } from 'react';
|
||||||
import {
|
import { CircleCheck, FileText, Image, List, Mic, Video } from 'lucide-react';
|
||||||
CircleCheck,
|
|
||||||
FileText,
|
|
||||||
Image,
|
|
||||||
List,
|
|
||||||
Mic,
|
|
||||||
Video,
|
|
||||||
} from 'lucide-react';
|
|
||||||
import { isParticleDeleted, type Human, type Particle } from '@/api/types';
|
import { isParticleDeleted, type Human, type Particle } from '@/api/types';
|
||||||
import { cn } from '@/lib/utils';
|
import { cn } from '@/lib/utils';
|
||||||
import { useNetwork } from '@/hooks/use-networks';
|
import { useNetwork } from '@/hooks/use-networks';
|
||||||
import { resolveHumanDisplay } from '@/lib/humans';
|
import { resolveHumanDisplay } from '@/lib/humans';
|
||||||
import { RelativeTimestamp } from '@/components/relative-timestamp';
|
import { RelativeTimestamp } from '@/components/relative-timestamp';
|
||||||
import { HumanAvatar } from '@/components/human-avatar';
|
import { HumanAvatar } from '@/components/human-avatar';
|
||||||
|
import { KeyHint } from '@/components/key-hint';
|
||||||
import { ScrollArea } from '@/components/ui/scroll-area';
|
import { ScrollArea } from '@/components/ui/scroll-area';
|
||||||
|
|
||||||
interface StreamListSidebarProps {
|
interface StreamListSidebarProps {
|
||||||
@@ -21,6 +15,7 @@ interface StreamListSidebarProps {
|
|||||||
networkId: string;
|
networkId: string;
|
||||||
currentIndex: number;
|
currentIndex: number;
|
||||||
onSelect: (index: number) => void;
|
onSelect: (index: number) => void;
|
||||||
|
onToggle: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -34,6 +29,7 @@ export function StreamListSidebar({
|
|||||||
networkId,
|
networkId,
|
||||||
currentIndex,
|
currentIndex,
|
||||||
onSelect,
|
onSelect,
|
||||||
|
onToggle,
|
||||||
}: StreamListSidebarProps) {
|
}: StreamListSidebarProps) {
|
||||||
const network = useNetwork(networkId);
|
const network = useNetwork(networkId);
|
||||||
const rowRefs = useRef<Array<HTMLDivElement | null>>([]);
|
const rowRefs = useRef<Array<HTMLDivElement | null>>([]);
|
||||||
@@ -52,6 +48,14 @@ export function StreamListSidebar({
|
|||||||
{streamName}
|
{streamName}
|
||||||
</span>
|
</span>
|
||||||
<span className="ml-auto text-xs text-white/40">{items.length}</span>
|
<span className="ml-auto text-xs text-white/40">{items.length}</span>
|
||||||
|
<KeyHint
|
||||||
|
keys="L"
|
||||||
|
onClick={onToggle}
|
||||||
|
title="Hide list (or press L)"
|
||||||
|
className="text-xs text-white/40"
|
||||||
|
>
|
||||||
|
to close
|
||||||
|
</KeyHint>
|
||||||
</div>
|
</div>
|
||||||
<ScrollArea className="min-h-0 flex-1">
|
<ScrollArea className="min-h-0 flex-1">
|
||||||
<div className="flex flex-col gap-0.5 px-2 py-2">
|
<div className="flex flex-col gap-0.5 px-2 py-2">
|
||||||
|
|||||||
@@ -68,6 +68,18 @@ import { requireDesktop } from '@/lib/platform/desktop-only';
|
|||||||
|
|
||||||
const noop = () => {};
|
const noop = () => {};
|
||||||
|
|
||||||
|
// Compose step → the composing-presence mode broadcast to other viewers.
|
||||||
|
const STEP_TO_COMPOSING_MODE: Record<ComposeStep, ComposingMode | null> = {
|
||||||
|
idle: null,
|
||||||
|
submitting: null,
|
||||||
|
recording: 'recording',
|
||||||
|
typing: 'typing',
|
||||||
|
task: 'typing',
|
||||||
|
reviewing: 'typing',
|
||||||
|
configuring: 'typing',
|
||||||
|
picking: 'screen',
|
||||||
|
};
|
||||||
|
|
||||||
function getReactions(
|
function getReactions(
|
||||||
particle: Particle,
|
particle: Particle,
|
||||||
): Record<string, string[]> | undefined {
|
): Record<string, string[]> | undefined {
|
||||||
@@ -321,18 +333,7 @@ function StreamViewInner({ path, streamParticle }: StreamViewProps) {
|
|||||||
|
|
||||||
// Broadcast composing state to other viewers
|
// Broadcast composing state to other viewers
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const stepToMode: Record<string, ComposingMode | null> = {
|
const mode = STEP_TO_COMPOSING_MODE[composeStep] ?? null;
|
||||||
idle: null,
|
|
||||||
submitting: null,
|
|
||||||
recording: 'recording',
|
|
||||||
typing: 'typing',
|
|
||||||
task: 'typing',
|
|
||||||
event: 'typing',
|
|
||||||
reviewing: 'typing',
|
|
||||||
configuring: 'typing',
|
|
||||||
picking: 'screen',
|
|
||||||
};
|
|
||||||
const mode = stepToMode[composeStep] ?? null;
|
|
||||||
if (mode) {
|
if (mode) {
|
||||||
startComposing(mode);
|
startComposing(mode);
|
||||||
} else {
|
} else {
|
||||||
@@ -575,6 +576,7 @@ function StreamViewInner({ path, streamParticle }: StreamViewProps) {
|
|||||||
networkId={networkId}
|
networkId={networkId}
|
||||||
currentIndex={currentIndex}
|
currentIndex={currentIndex}
|
||||||
onSelect={goTo}
|
onSelect={goTo}
|
||||||
|
onToggle={toggleViewMode}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -89,4 +89,3 @@ export function getParticleDisplayName(particle: Particle): string {
|
|||||||
return 'Unsupported particle';
|
return 'Unsupported particle';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user