Update files
This commit is contained in:
@@ -18,7 +18,7 @@ import {
|
|||||||
import { router, usePathname } from "expo-router";
|
import { router, usePathname } from "expo-router";
|
||||||
import { StyleSheet, useUnistyles } from "react-native-unistyles";
|
import { StyleSheet, useUnistyles } from "react-native-unistyles";
|
||||||
import { useQueries, useQueryClient, type UseQueryOptions } from "@tanstack/react-query";
|
import { useQueries, useQueryClient, type UseQueryOptions } from "@tanstack/react-query";
|
||||||
import { ChevronDown, ChevronRight } from "lucide-react-native";
|
import { ChevronDown, ChevronRight, Plus } from "lucide-react-native";
|
||||||
import { formatTimeAgo } from "@/utils/time";
|
import { formatTimeAgo } from "@/utils/time";
|
||||||
import {
|
import {
|
||||||
groupAgents,
|
groupAgents,
|
||||||
@@ -51,6 +51,8 @@ interface SectionData {
|
|||||||
firstAgentServerId?: string;
|
firstAgentServerId?: string;
|
||||||
/** For project sections, the first agent's id (to lookup checkout status) */
|
/** For project sections, the first agent's id (to lookup checkout status) */
|
||||||
firstAgentId?: string;
|
firstAgentId?: string;
|
||||||
|
/** Working directory for the project (from first agent) */
|
||||||
|
workingDir?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface GroupedAgentListProps {
|
interface GroupedAgentListProps {
|
||||||
@@ -66,14 +68,17 @@ interface SectionHeaderProps {
|
|||||||
section: SectionData;
|
section: SectionData;
|
||||||
isCollapsed: boolean;
|
isCollapsed: boolean;
|
||||||
onToggle: () => void;
|
onToggle: () => void;
|
||||||
|
onCreateAgent: (workingDir: string) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
function SectionHeader({
|
function SectionHeader({
|
||||||
section,
|
section,
|
||||||
isCollapsed,
|
isCollapsed,
|
||||||
onToggle,
|
onToggle,
|
||||||
|
onCreateAgent,
|
||||||
}: SectionHeaderProps) {
|
}: SectionHeaderProps) {
|
||||||
const { theme } = useUnistyles();
|
const { theme } = useUnistyles();
|
||||||
|
const [isHovered, setIsHovered] = useState(false);
|
||||||
|
|
||||||
// For project sections, try to get repo name from checkout status
|
// For project sections, try to get repo name from checkout status
|
||||||
const checkoutQuery = useCheckoutStatusCacheOnly({
|
const checkoutQuery = useCheckoutStatusCacheOnly({
|
||||||
@@ -98,13 +103,26 @@ function SectionHeader({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const handleCreatePress = useCallback(
|
||||||
|
(e: { stopPropagation: () => void }) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
if (section.workingDir) {
|
||||||
|
onCreateAgent(section.workingDir);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[onCreateAgent, section.workingDir]
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Pressable
|
<Pressable
|
||||||
style={({ hovered }) => [
|
style={[
|
||||||
styles.sectionHeader,
|
styles.sectionHeader,
|
||||||
hovered && styles.sectionHeaderHovered,
|
isHovered && styles.sectionHeaderHovered,
|
||||||
|
!isCollapsed && styles.sectionHeaderExpanded,
|
||||||
]}
|
]}
|
||||||
onPress={onToggle}
|
onPress={onToggle}
|
||||||
|
onHoverIn={() => setIsHovered(true)}
|
||||||
|
onHoverOut={() => setIsHovered(false)}
|
||||||
>
|
>
|
||||||
<View style={styles.sectionHeaderLeft}>
|
<View style={styles.sectionHeaderLeft}>
|
||||||
<Text style={styles.sectionTitle} numberOfLines={1}>
|
<Text style={styles.sectionTitle} numberOfLines={1}>
|
||||||
@@ -112,6 +130,24 @@ function SectionHeader({
|
|||||||
</Text>
|
</Text>
|
||||||
</View>
|
</View>
|
||||||
<View style={styles.sectionHeaderRight}>
|
<View style={styles.sectionHeaderRight}>
|
||||||
|
{section.workingDir && (
|
||||||
|
<Pressable
|
||||||
|
style={[
|
||||||
|
styles.createAgentButton,
|
||||||
|
{ opacity: isHovered ? 1 : 0 },
|
||||||
|
]}
|
||||||
|
onPress={handleCreatePress}
|
||||||
|
onHoverIn={() => setIsHovered(true)}
|
||||||
|
onHoverOut={() => setIsHovered(true)}
|
||||||
|
>
|
||||||
|
{({ hovered }) => (
|
||||||
|
<Plus
|
||||||
|
size={14}
|
||||||
|
color={hovered ? theme.colors.foreground : theme.colors.foregroundMuted}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</Pressable>
|
||||||
|
)}
|
||||||
{isCollapsed ? (
|
{isCollapsed ? (
|
||||||
<ChevronRight size={14} color={theme.colors.foregroundMuted} />
|
<ChevronRight size={14} color={theme.colors.foregroundMuted} />
|
||||||
) : (
|
) : (
|
||||||
@@ -206,6 +242,13 @@ export function GroupedAgentList({
|
|||||||
});
|
});
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
const handleCreateAgentInProject = useCallback(
|
||||||
|
(workingDir: string) => {
|
||||||
|
router.push(`/agent?workingDir=${encodeURIComponent(workingDir)}` as any);
|
||||||
|
},
|
||||||
|
[]
|
||||||
|
);
|
||||||
|
|
||||||
// Subscribe to checkout status cache entries so project grouping can react
|
// Subscribe to checkout status cache entries so project grouping can react
|
||||||
// to remote URL updates (e.g. git worktrees in different directories).
|
// to remote URL updates (e.g. git worktrees in different directories).
|
||||||
const checkoutCacheQueries = useQueries({
|
const checkoutCacheQueries = useQueries({
|
||||||
@@ -260,6 +303,7 @@ export function GroupedAgentList({
|
|||||||
data: isCollapsed ? [] : group.agents,
|
data: isCollapsed ? [] : group.agents,
|
||||||
firstAgentServerId: firstAgent?.serverId,
|
firstAgentServerId: firstAgent?.serverId,
|
||||||
firstAgentId: firstAgent?.id,
|
firstAgentId: firstAgent?.id,
|
||||||
|
workingDir: firstAgent?.cwd,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -386,10 +430,11 @@ export function GroupedAgentList({
|
|||||||
section={section}
|
section={section}
|
||||||
isCollapsed={isCollapsed}
|
isCollapsed={isCollapsed}
|
||||||
onToggle={() => toggleSection(section.key)}
|
onToggle={() => toggleSection(section.key)}
|
||||||
|
onCreateAgent={handleCreateAgentInProject}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
[collapsedSections, toggleSection]
|
[collapsedSections, toggleSection, handleCreateAgentInProject]
|
||||||
);
|
);
|
||||||
|
|
||||||
const keyExtractor = useCallback(
|
const keyExtractor = useCallback(
|
||||||
@@ -495,14 +540,17 @@ const styles = StyleSheet.create((theme) => ({
|
|||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
justifyContent: "space-between",
|
justifyContent: "space-between",
|
||||||
paddingVertical: theme.spacing[2],
|
paddingVertical: theme.spacing[2],
|
||||||
paddingHorizontal: theme.spacing[4] + theme.spacing[2],
|
paddingHorizontal: theme.spacing[3],
|
||||||
marginHorizontal: -theme.spacing[4],
|
marginHorizontal: -theme.spacing[2],
|
||||||
marginTop: theme.spacing[2],
|
marginTop: theme.spacing[2],
|
||||||
borderRadius: theme.borderRadius.md,
|
borderRadius: theme.borderRadius.md,
|
||||||
},
|
},
|
||||||
sectionHeaderHovered: {
|
sectionHeaderHovered: {
|
||||||
backgroundColor: theme.colors.surface1,
|
backgroundColor: theme.colors.surface1,
|
||||||
},
|
},
|
||||||
|
sectionHeaderExpanded: {
|
||||||
|
marginBottom: theme.spacing[1],
|
||||||
|
},
|
||||||
sectionHeaderLeft: {
|
sectionHeaderLeft: {
|
||||||
flexDirection: "row",
|
flexDirection: "row",
|
||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
@@ -511,13 +559,21 @@ const styles = StyleSheet.create((theme) => ({
|
|||||||
minWidth: 0,
|
minWidth: 0,
|
||||||
},
|
},
|
||||||
sectionHeaderRight: {
|
sectionHeaderRight: {
|
||||||
|
flexDirection: "row",
|
||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
justifyContent: "center",
|
justifyContent: "center",
|
||||||
|
gap: theme.spacing[2],
|
||||||
marginLeft: theme.spacing[2],
|
marginLeft: theme.spacing[2],
|
||||||
},
|
},
|
||||||
|
createAgentButton: {
|
||||||
|
width: 14,
|
||||||
|
height: 14,
|
||||||
|
alignItems: "center",
|
||||||
|
justifyContent: "center",
|
||||||
|
},
|
||||||
sectionTitle: {
|
sectionTitle: {
|
||||||
fontSize: theme.fontSize.sm,
|
fontSize: theme.fontSize.sm,
|
||||||
fontWeight: "500",
|
fontWeight: "normal",
|
||||||
color: theme.colors.foregroundMuted,
|
color: theme.colors.foregroundMuted,
|
||||||
flex: 1,
|
flex: 1,
|
||||||
textAlign: "left",
|
textAlign: "left",
|
||||||
@@ -525,6 +581,7 @@ const styles = StyleSheet.create((theme) => ({
|
|||||||
agentItem: {
|
agentItem: {
|
||||||
paddingVertical: theme.spacing[2],
|
paddingVertical: theme.spacing[2],
|
||||||
paddingHorizontal: theme.spacing[3],
|
paddingHorizontal: theme.spacing[3],
|
||||||
|
marginHorizontal: -theme.spacing[2],
|
||||||
borderRadius: theme.borderRadius.lg,
|
borderRadius: theme.borderRadius.lg,
|
||||||
marginBottom: theme.spacing[1],
|
marginBottom: theme.spacing[1],
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import Animated, {
|
|||||||
} from "react-native-reanimated";
|
} from "react-native-reanimated";
|
||||||
import { Gesture, GestureDetector } from "react-native-gesture-handler";
|
import { Gesture, GestureDetector } from "react-native-gesture-handler";
|
||||||
import { StyleSheet, UnistylesRuntime, useUnistyles } from "react-native-unistyles";
|
import { StyleSheet, UnistylesRuntime, useUnistyles } from "react-native-unistyles";
|
||||||
import { ChevronRight, Plus, Settings } from "lucide-react-native";
|
import { Plus, Settings, Users } from "lucide-react-native";
|
||||||
import { router } from "expo-router";
|
import { router } from "expo-router";
|
||||||
import { usePanelStore } from "@/stores/panel-store";
|
import { usePanelStore } from "@/stores/panel-store";
|
||||||
import { GroupedAgentList } from "./grouped-agent-list";
|
import { GroupedAgentList } from "./grouped-agent-list";
|
||||||
@@ -170,20 +170,6 @@ export function SlidingSidebar({ selectedAgentId }: SlidingSidebarProps) {
|
|||||||
pointerEvents: backdropOpacity.value > 0.01 ? "auto" : "none",
|
pointerEvents: backdropOpacity.value > 0.01 ? "auto" : "none",
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const viewMoreButton = (
|
|
||||||
<View style={styles.viewMoreContainer}>
|
|
||||||
<Pressable
|
|
||||||
style={({ hovered, pressed }) => [
|
|
||||||
styles.viewMoreButton,
|
|
||||||
(hovered || pressed) && styles.viewMoreButtonHovered,
|
|
||||||
]}
|
|
||||||
onPress={handleViewMore}
|
|
||||||
>
|
|
||||||
<Text style={styles.viewMoreButtonText}>All agents</Text>
|
|
||||||
<ChevronRight size={16} color={theme.colors.foregroundMuted} />
|
|
||||||
</Pressable>
|
|
||||||
</View>
|
|
||||||
);
|
|
||||||
|
|
||||||
// Render mobile sidebar
|
// Render mobile sidebar
|
||||||
// On web, use "auto" instead of "box-none" because web's pointer-events: none blocks scroll
|
// On web, use "auto" instead of "box-none" because web's pointer-events: none blocks scroll
|
||||||
@@ -228,17 +214,30 @@ export function SlidingSidebar({ selectedAgentId }: SlidingSidebarProps) {
|
|||||||
onRefresh={handleRefresh}
|
onRefresh={handleRefresh}
|
||||||
selectedAgentId={selectedAgentId}
|
selectedAgentId={selectedAgentId}
|
||||||
onAgentSelect={handleAgentSelectMobile}
|
onAgentSelect={handleAgentSelectMobile}
|
||||||
listFooterComponent={viewMoreButton}
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* Footer */}
|
{/* Footer */}
|
||||||
<View style={styles.sidebarFooter}>
|
<View style={styles.sidebarFooter}>
|
||||||
<Pressable
|
<Pressable
|
||||||
style={styles.settingsButton}
|
style={styles.footerButton}
|
||||||
|
onPress={handleViewMore}
|
||||||
|
>
|
||||||
|
{({ hovered }) => (
|
||||||
|
<>
|
||||||
|
<Users size={18} color={hovered ? theme.colors.foreground : theme.colors.foregroundMuted} />
|
||||||
|
<Text style={[styles.footerButtonText, hovered && styles.footerButtonTextHovered]}>
|
||||||
|
All agents
|
||||||
|
</Text>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Pressable>
|
||||||
|
<Pressable
|
||||||
|
style={styles.footerIconButton}
|
||||||
onPress={handleSettingsMobile}
|
onPress={handleSettingsMobile}
|
||||||
>
|
>
|
||||||
<Settings size={20} color={theme.colors.foregroundMuted} />
|
{({ hovered }) => (
|
||||||
<Text style={styles.settingsButtonText}>Settings</Text>
|
<Settings size={20} color={hovered ? theme.colors.foreground : theme.colors.foregroundMuted} />
|
||||||
|
)}
|
||||||
</Pressable>
|
</Pressable>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
@@ -279,17 +278,30 @@ export function SlidingSidebar({ selectedAgentId }: SlidingSidebarProps) {
|
|||||||
isRefreshing={isManualRefresh && isRevalidating}
|
isRefreshing={isManualRefresh && isRevalidating}
|
||||||
onRefresh={handleRefresh}
|
onRefresh={handleRefresh}
|
||||||
selectedAgentId={selectedAgentId}
|
selectedAgentId={selectedAgentId}
|
||||||
listFooterComponent={viewMoreButton}
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* Footer: Settings button */}
|
{/* Footer */}
|
||||||
<View style={styles.sidebarFooter}>
|
<View style={styles.sidebarFooter}>
|
||||||
<Pressable
|
<Pressable
|
||||||
style={styles.settingsButton}
|
style={styles.footerButton}
|
||||||
|
onPress={handleViewMore}
|
||||||
|
>
|
||||||
|
{({ hovered }) => (
|
||||||
|
<>
|
||||||
|
<Users size={18} color={hovered ? theme.colors.foreground : theme.colors.foregroundMuted} />
|
||||||
|
<Text style={[styles.footerButtonText, hovered && styles.footerButtonTextHovered]}>
|
||||||
|
All agents
|
||||||
|
</Text>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Pressable>
|
||||||
|
<Pressable
|
||||||
|
style={styles.footerIconButton}
|
||||||
onPress={handleSettingsDesktop}
|
onPress={handleSettingsDesktop}
|
||||||
>
|
>
|
||||||
<Settings size={20} color={theme.colors.foregroundMuted} />
|
{({ hovered }) => (
|
||||||
<Text style={styles.settingsButtonText}>Settings</Text>
|
<Settings size={20} color={hovered ? theme.colors.foreground : theme.colors.foregroundMuted} />
|
||||||
|
)}
|
||||||
</Pressable>
|
</Pressable>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
@@ -349,47 +361,32 @@ const styles = StyleSheet.create((theme) => ({
|
|||||||
fontWeight: theme.fontWeight.normal,
|
fontWeight: theme.fontWeight.normal,
|
||||||
color: theme.colors.foreground,
|
color: theme.colors.foreground,
|
||||||
},
|
},
|
||||||
viewMoreContainer: {
|
sidebarFooter: {
|
||||||
paddingTop: theme.spacing[2],
|
|
||||||
},
|
|
||||||
viewMoreButton: {
|
|
||||||
flexDirection: "row",
|
flexDirection: "row",
|
||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
justifyContent: "flex-start",
|
justifyContent: "space-between",
|
||||||
gap: theme.spacing[1],
|
|
||||||
paddingVertical: theme.spacing[2],
|
|
||||||
paddingHorizontal: 0,
|
|
||||||
borderWidth: 0,
|
|
||||||
backgroundColor: "transparent",
|
|
||||||
alignSelf: "flex-start",
|
|
||||||
transitionProperty: "opacity",
|
|
||||||
transitionDuration: "150ms",
|
|
||||||
},
|
|
||||||
viewMoreButtonHovered: {
|
|
||||||
opacity: 0.8,
|
|
||||||
},
|
|
||||||
viewMoreButtonText: {
|
|
||||||
fontSize: theme.fontSize.base,
|
|
||||||
fontWeight: theme.fontWeight.normal,
|
|
||||||
color: theme.colors.foregroundMuted,
|
|
||||||
},
|
|
||||||
sidebarFooter: {
|
|
||||||
paddingHorizontal: theme.spacing[4],
|
paddingHorizontal: theme.spacing[4],
|
||||||
paddingTop: theme.spacing[2],
|
paddingVertical: theme.spacing[3],
|
||||||
paddingBottom: theme.spacing[4],
|
|
||||||
borderTopWidth: 1,
|
borderTopWidth: 1,
|
||||||
borderTopColor: theme.colors.border,
|
borderTopColor: theme.colors.border,
|
||||||
},
|
},
|
||||||
settingsButton: {
|
footerButton: {
|
||||||
flexDirection: "row",
|
flexDirection: "row",
|
||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
gap: theme.spacing[3],
|
gap: theme.spacing[2],
|
||||||
paddingVertical: theme.spacing[2],
|
paddingVertical: theme.spacing[1],
|
||||||
paddingHorizontal: theme.spacing[2],
|
paddingHorizontal: theme.spacing[1],
|
||||||
},
|
},
|
||||||
settingsButtonText: {
|
footerIconButton: {
|
||||||
|
paddingVertical: theme.spacing[1],
|
||||||
|
paddingHorizontal: theme.spacing[1],
|
||||||
|
},
|
||||||
|
footerButtonText: {
|
||||||
fontSize: theme.fontSize.base,
|
fontSize: theme.fontSize.base,
|
||||||
fontWeight: theme.fontWeight.normal,
|
fontWeight: theme.fontWeight.normal,
|
||||||
color: theme.colors.foregroundMuted,
|
color: theme.colors.foregroundMuted,
|
||||||
},
|
},
|
||||||
|
footerButtonTextHovered: {
|
||||||
|
color: theme.colors.foreground,
|
||||||
|
},
|
||||||
}));
|
}));
|
||||||
|
|||||||
Reference in New Issue
Block a user