Switch to double quotes and reformat codebase with Biome
This commit is contained in:
@@ -6,107 +6,107 @@ import {
|
||||
RefreshControl,
|
||||
FlatList,
|
||||
type ListRenderItem,
|
||||
} from 'react-native'
|
||||
import { useSafeAreaInsets } from 'react-native-safe-area-context'
|
||||
import { useCallback, useMemo, useState, type ReactElement } from 'react'
|
||||
import { router } from 'expo-router'
|
||||
import { StyleSheet, UnistylesRuntime, useUnistyles } from 'react-native-unistyles'
|
||||
import { formatTimeAgo } from '@/utils/time'
|
||||
import { shortenPath } from '@/utils/shorten-path'
|
||||
import { type AggregatedAgent } from '@/hooks/use-aggregated-agents'
|
||||
import { useSessionStore } from '@/stores/session-store'
|
||||
import { Archive } from 'lucide-react-native'
|
||||
import { prepareWorkspaceTab } from '@/utils/workspace-navigation'
|
||||
} from "react-native";
|
||||
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
||||
import { useCallback, useMemo, useState, type ReactElement } from "react";
|
||||
import { router } from "expo-router";
|
||||
import { StyleSheet, UnistylesRuntime, useUnistyles } from "react-native-unistyles";
|
||||
import { formatTimeAgo } from "@/utils/time";
|
||||
import { shortenPath } from "@/utils/shorten-path";
|
||||
import { type AggregatedAgent } from "@/hooks/use-aggregated-agents";
|
||||
import { useSessionStore } from "@/stores/session-store";
|
||||
import { Archive } from "lucide-react-native";
|
||||
import { prepareWorkspaceTab } from "@/utils/workspace-navigation";
|
||||
|
||||
interface AgentListProps {
|
||||
agents: AggregatedAgent[]
|
||||
showCheckoutInfo?: boolean
|
||||
isRefreshing?: boolean
|
||||
onRefresh?: () => void
|
||||
selectedAgentId?: string
|
||||
onAgentSelect?: () => void
|
||||
listFooterComponent?: ReactElement | null
|
||||
showAttentionIndicator?: boolean
|
||||
agents: AggregatedAgent[];
|
||||
showCheckoutInfo?: boolean;
|
||||
isRefreshing?: boolean;
|
||||
onRefresh?: () => void;
|
||||
selectedAgentId?: string;
|
||||
onAgentSelect?: () => void;
|
||||
listFooterComponent?: ReactElement | null;
|
||||
showAttentionIndicator?: boolean;
|
||||
}
|
||||
|
||||
type FlatListItem =
|
||||
| { type: 'header'; key: string; title: string }
|
||||
| { type: 'agent'; key: string; agent: AggregatedAgent }
|
||||
| { type: "header"; key: string; title: string }
|
||||
| { type: "agent"; key: string; agent: AggregatedAgent };
|
||||
|
||||
function deriveDateSectionLabel(lastActivityAt: Date): string {
|
||||
const now = new Date()
|
||||
const todayStart = new Date(now.getFullYear(), now.getMonth(), now.getDate())
|
||||
const yesterdayStart = new Date(todayStart.getTime() - 24 * 60 * 60 * 1000)
|
||||
const now = new Date();
|
||||
const todayStart = new Date(now.getFullYear(), now.getMonth(), now.getDate());
|
||||
const yesterdayStart = new Date(todayStart.getTime() - 24 * 60 * 60 * 1000);
|
||||
const activityStart = new Date(
|
||||
lastActivityAt.getFullYear(),
|
||||
lastActivityAt.getMonth(),
|
||||
lastActivityAt.getDate()
|
||||
)
|
||||
lastActivityAt.getDate(),
|
||||
);
|
||||
|
||||
if (activityStart.getTime() >= todayStart.getTime()) {
|
||||
return 'Today'
|
||||
return "Today";
|
||||
}
|
||||
if (activityStart.getTime() >= yesterdayStart.getTime()) {
|
||||
return 'Yesterday'
|
||||
return "Yesterday";
|
||||
}
|
||||
|
||||
const diffTime = todayStart.getTime() - activityStart.getTime()
|
||||
const diffDays = Math.floor(diffTime / (1000 * 60 * 60 * 24))
|
||||
const diffTime = todayStart.getTime() - activityStart.getTime();
|
||||
const diffDays = Math.floor(diffTime / (1000 * 60 * 60 * 24));
|
||||
if (diffDays <= 7) {
|
||||
return 'This week'
|
||||
return "This week";
|
||||
}
|
||||
if (diffDays <= 30) {
|
||||
return 'This month'
|
||||
return "This month";
|
||||
}
|
||||
return 'Older'
|
||||
return "Older";
|
||||
}
|
||||
|
||||
function formatStatusLabel(status: AggregatedAgent['status']): string {
|
||||
function formatStatusLabel(status: AggregatedAgent["status"]): string {
|
||||
switch (status) {
|
||||
case 'initializing':
|
||||
return 'Starting'
|
||||
case 'idle':
|
||||
return 'Idle'
|
||||
case 'running':
|
||||
return 'Running'
|
||||
case 'error':
|
||||
return 'Error'
|
||||
case 'closed':
|
||||
return 'Closed'
|
||||
case "initializing":
|
||||
return "Starting";
|
||||
case "idle":
|
||||
return "Idle";
|
||||
case "running":
|
||||
return "Running";
|
||||
case "error":
|
||||
return "Error";
|
||||
case "closed":
|
||||
return "Closed";
|
||||
default:
|
||||
return status
|
||||
return status;
|
||||
}
|
||||
}
|
||||
|
||||
function SessionBadge({
|
||||
label,
|
||||
icon,
|
||||
tone = 'neutral',
|
||||
tone = "neutral",
|
||||
}: {
|
||||
label: string
|
||||
icon?: ReactElement
|
||||
tone?: 'neutral' | 'warning' | 'danger'
|
||||
label: string;
|
||||
icon?: ReactElement;
|
||||
tone?: "neutral" | "warning" | "danger";
|
||||
}) {
|
||||
return (
|
||||
<View
|
||||
style={[
|
||||
styles.badge,
|
||||
tone === 'warning' && styles.badgeWarning,
|
||||
tone === 'danger' && styles.badgeDanger,
|
||||
tone === "warning" && styles.badgeWarning,
|
||||
tone === "danger" && styles.badgeDanger,
|
||||
]}
|
||||
>
|
||||
{icon}
|
||||
<Text
|
||||
style={[
|
||||
styles.badgeText,
|
||||
tone === 'warning' && styles.badgeTextWarning,
|
||||
tone === 'danger' && styles.badgeTextDanger,
|
||||
tone === "warning" && styles.badgeTextWarning,
|
||||
tone === "danger" && styles.badgeTextDanger,
|
||||
]}
|
||||
>
|
||||
{label}
|
||||
</Text>
|
||||
</View>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
function SessionRow({
|
||||
@@ -117,19 +117,19 @@ function SessionRow({
|
||||
onPress,
|
||||
onLongPress,
|
||||
}: {
|
||||
agent: AggregatedAgent
|
||||
isMobile: boolean
|
||||
selectedAgentId?: string
|
||||
showAttentionIndicator: boolean
|
||||
onPress: (agent: AggregatedAgent) => void
|
||||
onLongPress: (agent: AggregatedAgent) => void
|
||||
agent: AggregatedAgent;
|
||||
isMobile: boolean;
|
||||
selectedAgentId?: string;
|
||||
showAttentionIndicator: boolean;
|
||||
onPress: (agent: AggregatedAgent) => void;
|
||||
onLongPress: (agent: AggregatedAgent) => void;
|
||||
}) {
|
||||
const { theme } = useUnistyles()
|
||||
const timeAgo = formatTimeAgo(agent.lastActivityAt)
|
||||
const agentKey = `${agent.serverId}:${agent.id}`
|
||||
const isSelected = selectedAgentId === agentKey
|
||||
const statusLabel = formatStatusLabel(agent.status)
|
||||
const projectPath = shortenPath(agent.cwd)
|
||||
const { theme } = useUnistyles();
|
||||
const timeAgo = formatTimeAgo(agent.lastActivityAt);
|
||||
const agentKey = `${agent.serverId}:${agent.id}`;
|
||||
const isSelected = selectedAgentId === agentKey;
|
||||
const statusLabel = formatStatusLabel(agent.status);
|
||||
const projectPath = shortenPath(agent.cwd);
|
||||
|
||||
return (
|
||||
<Pressable
|
||||
@@ -149,7 +149,7 @@ function SessionRow({
|
||||
style={[styles.sessionTitle, isSelected && styles.sessionTitleHighlighted]}
|
||||
numberOfLines={1}
|
||||
>
|
||||
{agent.title || 'New session'}
|
||||
{agent.title || "New session"}
|
||||
</Text>
|
||||
{agent.archivedAt ? (
|
||||
<SessionBadge
|
||||
@@ -199,10 +199,9 @@ function SessionRow({
|
||||
</View>
|
||||
) : null}
|
||||
</Pressable>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
export function AgentList({
|
||||
agents,
|
||||
isRefreshing = false,
|
||||
@@ -212,88 +211,88 @@ export function AgentList({
|
||||
listFooterComponent,
|
||||
showAttentionIndicator = true,
|
||||
}: AgentListProps) {
|
||||
const { theme } = useUnistyles()
|
||||
const insets = useSafeAreaInsets()
|
||||
const [actionAgent, setActionAgent] = useState<AggregatedAgent | null>(null)
|
||||
const isMobile = UnistylesRuntime.breakpoint === 'xs' || UnistylesRuntime.breakpoint === 'sm'
|
||||
const { theme } = useUnistyles();
|
||||
const insets = useSafeAreaInsets();
|
||||
const [actionAgent, setActionAgent] = useState<AggregatedAgent | null>(null);
|
||||
const isMobile = UnistylesRuntime.breakpoint === "xs" || UnistylesRuntime.breakpoint === "sm";
|
||||
|
||||
const actionClient = useSessionStore((state) =>
|
||||
actionAgent?.serverId ? (state.sessions[actionAgent.serverId]?.client ?? null) : null
|
||||
)
|
||||
actionAgent?.serverId ? (state.sessions[actionAgent.serverId]?.client ?? null) : null,
|
||||
);
|
||||
|
||||
const isActionSheetVisible = actionAgent !== null
|
||||
const isActionDaemonUnavailable = Boolean(actionAgent?.serverId && !actionClient)
|
||||
const isActionSheetVisible = actionAgent !== null;
|
||||
const isActionDaemonUnavailable = Boolean(actionAgent?.serverId && !actionClient);
|
||||
|
||||
const handleAgentPress = useCallback(
|
||||
(agent: AggregatedAgent) => {
|
||||
if (isActionSheetVisible) {
|
||||
return
|
||||
return;
|
||||
}
|
||||
|
||||
const serverId = agent.serverId
|
||||
const agentId = agent.id
|
||||
const serverId = agent.serverId;
|
||||
const agentId = agent.id;
|
||||
|
||||
onAgentSelect?.()
|
||||
onAgentSelect?.();
|
||||
|
||||
const route = prepareWorkspaceTab({
|
||||
serverId,
|
||||
workspaceId: agent.cwd,
|
||||
target: { kind: 'agent', agentId },
|
||||
target: { kind: "agent", agentId },
|
||||
pin: Boolean(agent.archivedAt),
|
||||
})
|
||||
router.navigate(route as any)
|
||||
});
|
||||
router.navigate(route as any);
|
||||
},
|
||||
[isActionSheetVisible, onAgentSelect]
|
||||
)
|
||||
[isActionSheetVisible, onAgentSelect],
|
||||
);
|
||||
|
||||
const handleAgentLongPress = useCallback((agent: AggregatedAgent) => {
|
||||
setActionAgent(agent)
|
||||
}, [])
|
||||
setActionAgent(agent);
|
||||
}, []);
|
||||
|
||||
const handleCloseActionSheet = useCallback(() => {
|
||||
setActionAgent(null)
|
||||
}, [])
|
||||
setActionAgent(null);
|
||||
}, []);
|
||||
|
||||
const handleArchiveAgent = useCallback(() => {
|
||||
if (!actionAgent || !actionClient) {
|
||||
return
|
||||
return;
|
||||
}
|
||||
void actionClient.archiveAgent(actionAgent.id)
|
||||
setActionAgent(null)
|
||||
}, [actionAgent, actionClient])
|
||||
void actionClient.archiveAgent(actionAgent.id);
|
||||
setActionAgent(null);
|
||||
}, [actionAgent, actionClient]);
|
||||
|
||||
const flatItems = useMemo((): FlatListItem[] => {
|
||||
const order = ['Today', 'Yesterday', 'This week', 'This month', 'Older'] as const
|
||||
const buckets = new Map<string, AggregatedAgent[]>()
|
||||
const order = ["Today", "Yesterday", "This week", "This month", "Older"] as const;
|
||||
const buckets = new Map<string, AggregatedAgent[]>();
|
||||
for (const agent of agents) {
|
||||
const label = deriveDateSectionLabel(agent.lastActivityAt)
|
||||
const existing = buckets.get(label) ?? []
|
||||
existing.push(agent)
|
||||
buckets.set(label, existing)
|
||||
const label = deriveDateSectionLabel(agent.lastActivityAt);
|
||||
const existing = buckets.get(label) ?? [];
|
||||
existing.push(agent);
|
||||
buckets.set(label, existing);
|
||||
}
|
||||
|
||||
const result: FlatListItem[] = []
|
||||
const result: FlatListItem[] = [];
|
||||
for (const label of order) {
|
||||
const data = buckets.get(label)
|
||||
const data = buckets.get(label);
|
||||
if (!data || data.length === 0) {
|
||||
continue
|
||||
continue;
|
||||
}
|
||||
result.push({ type: 'header', key: `header:${label}`, title: label })
|
||||
result.push({ type: "header", key: `header:${label}`, title: label });
|
||||
for (const agent of data) {
|
||||
result.push({ type: 'agent', key: `${agent.serverId}:${agent.id}`, agent })
|
||||
result.push({ type: "agent", key: `${agent.serverId}:${agent.id}`, agent });
|
||||
}
|
||||
}
|
||||
return result
|
||||
}, [agents])
|
||||
return result;
|
||||
}, [agents]);
|
||||
|
||||
const renderItem: ListRenderItem<FlatListItem> = useCallback(
|
||||
({ item }) => {
|
||||
if (item.type === 'header') {
|
||||
if (item.type === "header") {
|
||||
return (
|
||||
<View style={styles.sectionHeading}>
|
||||
<Text style={styles.sectionTitle}>{item.title}</Text>
|
||||
</View>
|
||||
)
|
||||
);
|
||||
}
|
||||
return (
|
||||
<SessionRow
|
||||
@@ -304,12 +303,12 @@ export function AgentList({
|
||||
onPress={handleAgentPress}
|
||||
onLongPress={handleAgentLongPress}
|
||||
/>
|
||||
)
|
||||
);
|
||||
},
|
||||
[handleAgentLongPress, handleAgentPress, isMobile, selectedAgentId, showAttentionIndicator]
|
||||
)
|
||||
[handleAgentLongPress, handleAgentPress, isMobile, selectedAgentId, showAttentionIndicator],
|
||||
);
|
||||
|
||||
const keyExtractor = useCallback((item: FlatListItem) => item.key, [])
|
||||
const keyExtractor = useCallback((item: FlatListItem) => item.key, []);
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -350,7 +349,7 @@ export function AgentList({
|
||||
>
|
||||
<View style={styles.sheetHandle} />
|
||||
<Text style={styles.sheetTitle}>
|
||||
{isActionDaemonUnavailable ? 'Host offline' : 'Archive this session?'}
|
||||
{isActionDaemonUnavailable ? "Host offline" : "Archive this session?"}
|
||||
</Text>
|
||||
<View style={styles.sheetButtonRow}>
|
||||
<Pressable
|
||||
@@ -380,7 +379,7 @@ export function AgentList({
|
||||
</View>
|
||||
</Modal>
|
||||
</>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create((theme) => ({
|
||||
@@ -399,8 +398,8 @@ const styles = StyleSheet.create((theme) => ({
|
||||
},
|
||||
sectionHeading: {
|
||||
marginTop: theme.spacing[2],
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
gap: theme.spacing[3],
|
||||
paddingHorizontal: theme.spacing[3],
|
||||
marginBottom: theme.spacing[2],
|
||||
@@ -411,8 +410,8 @@ const styles = StyleSheet.create((theme) => ({
|
||||
color: theme.colors.foregroundMuted,
|
||||
},
|
||||
row: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
paddingVertical: theme.spacing[2],
|
||||
paddingHorizontal: theme.spacing[3],
|
||||
borderRadius: {
|
||||
@@ -429,15 +428,15 @@ const styles = StyleSheet.create((theme) => ({
|
||||
minWidth: 0,
|
||||
},
|
||||
rowTitleRow: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
flexWrap: 'wrap',
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
flexWrap: "wrap",
|
||||
gap: theme.spacing[2],
|
||||
},
|
||||
rowMetaRow: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
flexWrap: 'wrap',
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
flexWrap: "wrap",
|
||||
gap: theme.spacing[1],
|
||||
marginTop: 2,
|
||||
},
|
||||
@@ -456,7 +455,7 @@ const styles = StyleSheet.create((theme) => ({
|
||||
sessionTitle: {
|
||||
flexShrink: 1,
|
||||
fontSize: theme.fontSize.sm,
|
||||
fontWeight: '400',
|
||||
fontWeight: "400",
|
||||
color: theme.colors.foreground,
|
||||
opacity: 0.86,
|
||||
},
|
||||
@@ -464,7 +463,7 @@ const styles = StyleSheet.create((theme) => ({
|
||||
opacity: 1,
|
||||
},
|
||||
sessionMetaText: {
|
||||
maxWidth: '100%',
|
||||
maxWidth: "100%",
|
||||
fontSize: theme.fontSize.sm,
|
||||
color: theme.colors.foregroundMuted,
|
||||
},
|
||||
@@ -486,11 +485,11 @@ const styles = StyleSheet.create((theme) => ({
|
||||
color: theme.colors.foregroundMuted,
|
||||
flexShrink: 0,
|
||||
width: 72,
|
||||
textAlign: 'right' as const,
|
||||
textAlign: "right" as const,
|
||||
},
|
||||
badge: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
gap: theme.spacing[1],
|
||||
paddingHorizontal: theme.spacing[2],
|
||||
paddingVertical: theme.spacing[1],
|
||||
@@ -498,10 +497,10 @@ const styles = StyleSheet.create((theme) => ({
|
||||
backgroundColor: theme.colors.surface2,
|
||||
},
|
||||
badgeWarning: {
|
||||
backgroundColor: 'rgba(245, 158, 11, 0.12)',
|
||||
backgroundColor: "rgba(245, 158, 11, 0.12)",
|
||||
},
|
||||
badgeDanger: {
|
||||
backgroundColor: 'rgba(239, 68, 68, 0.14)',
|
||||
backgroundColor: "rgba(239, 68, 68, 0.14)",
|
||||
},
|
||||
badgeText: {
|
||||
fontSize: theme.fontSize.xs,
|
||||
@@ -516,26 +515,26 @@ const styles = StyleSheet.create((theme) => ({
|
||||
},
|
||||
sheetOverlay: {
|
||||
flex: 1,
|
||||
justifyContent: 'flex-end',
|
||||
justifyContent: "flex-end",
|
||||
},
|
||||
sheetBackdrop: {
|
||||
position: 'absolute',
|
||||
position: "absolute",
|
||||
top: 0,
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
left: 0,
|
||||
backgroundColor: 'rgba(0,0,0,0.35)',
|
||||
backgroundColor: "rgba(0,0,0,0.35)",
|
||||
},
|
||||
sheetContainer: {
|
||||
backgroundColor: theme.colors.surface2,
|
||||
borderTopLeftRadius: theme.borderRadius['2xl'],
|
||||
borderTopRightRadius: theme.borderRadius['2xl'],
|
||||
borderTopLeftRadius: theme.borderRadius["2xl"],
|
||||
borderTopRightRadius: theme.borderRadius["2xl"],
|
||||
paddingHorizontal: theme.spacing[6],
|
||||
paddingTop: theme.spacing[4],
|
||||
gap: theme.spacing[4],
|
||||
},
|
||||
sheetHandle: {
|
||||
alignSelf: 'center',
|
||||
alignSelf: "center",
|
||||
width: 40,
|
||||
height: 4,
|
||||
borderRadius: theme.borderRadius.full,
|
||||
@@ -546,18 +545,18 @@ const styles = StyleSheet.create((theme) => ({
|
||||
fontSize: theme.fontSize.lg,
|
||||
fontWeight: theme.fontWeight.semibold,
|
||||
color: theme.colors.foreground,
|
||||
textAlign: 'center',
|
||||
textAlign: "center",
|
||||
},
|
||||
sheetButtonRow: {
|
||||
flexDirection: 'row',
|
||||
flexDirection: "row",
|
||||
gap: theme.spacing[3],
|
||||
},
|
||||
sheetButton: {
|
||||
flex: 1,
|
||||
borderRadius: theme.borderRadius.lg,
|
||||
paddingVertical: theme.spacing[4],
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
},
|
||||
sheetArchiveButton: {
|
||||
backgroundColor: theme.colors.primary,
|
||||
@@ -578,4 +577,4 @@ const styles = StyleSheet.create((theme) => ({
|
||||
fontWeight: theme.fontWeight.semibold,
|
||||
fontSize: theme.fontSize.base,
|
||||
},
|
||||
}))
|
||||
}));
|
||||
|
||||
Reference in New Issue
Block a user