Update files
This commit is contained in:
@@ -416,7 +416,6 @@ export default function SettingsScreen() {
|
|||||||
const [pendingRemoveHost, setPendingRemoveHost] = useState<HostProfile | null>(null);
|
const [pendingRemoveHost, setPendingRemoveHost] = useState<HostProfile | null>(null);
|
||||||
const [isRemovingHost, setIsRemovingHost] = useState(false);
|
const [isRemovingHost, setIsRemovingHost] = useState(false);
|
||||||
const [editingDaemon, setEditingDaemon] = useState<HostProfile | null>(null);
|
const [editingDaemon, setEditingDaemon] = useState<HostProfile | null>(null);
|
||||||
const [editLabel, setEditLabel] = useState("");
|
|
||||||
const [isSavingEdit, setIsSavingEdit] = useState(false);
|
const [isSavingEdit, setIsSavingEdit] = useState(false);
|
||||||
const isLoading = settingsLoading || daemonLoading;
|
const isLoading = settingsLoading || daemonLoading;
|
||||||
const isMountedRef = useRef(true);
|
const isMountedRef = useRef(true);
|
||||||
@@ -460,13 +459,11 @@ export default function SettingsScreen() {
|
|||||||
|
|
||||||
const handleEditDaemon = useCallback((profile: HostProfile) => {
|
const handleEditDaemon = useCallback((profile: HostProfile) => {
|
||||||
setEditingDaemon(profile);
|
setEditingDaemon(profile);
|
||||||
setEditLabel(profile.label ?? "");
|
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const handleCloseEditDaemon = useCallback(() => {
|
const handleCloseEditDaemon = useCallback(() => {
|
||||||
if (isSavingEdit) return;
|
if (isSavingEdit) return;
|
||||||
setEditingDaemon(null);
|
setEditingDaemon(null);
|
||||||
setEditLabel("");
|
|
||||||
}, [isSavingEdit]);
|
}, [isSavingEdit]);
|
||||||
|
|
||||||
const closeAddConnectionFlow = useCallback(() => {
|
const closeAddConnectionFlow = useCallback(() => {
|
||||||
@@ -510,17 +507,11 @@ export default function SettingsScreen() {
|
|||||||
pendingEditReopenServerId,
|
pendingEditReopenServerId,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
useEffect(() => {
|
const handleSaveEditDaemon = useCallback(async (nextLabelRaw: string) => {
|
||||||
if (!editingDaemon) return;
|
|
||||||
if (editingDaemonLive) return;
|
|
||||||
handleCloseEditDaemon();
|
|
||||||
}, [editingDaemon, editingDaemonLive, handleCloseEditDaemon]);
|
|
||||||
|
|
||||||
const handleSaveEditDaemon = useCallback(async () => {
|
|
||||||
if (!editingDaemon) return;
|
if (!editingDaemon) return;
|
||||||
if (isSavingEdit) return;
|
if (isSavingEdit) return;
|
||||||
|
|
||||||
const nextLabel = editLabel.trim();
|
const nextLabel = nextLabelRaw.trim();
|
||||||
if (!nextLabel) {
|
if (!nextLabel) {
|
||||||
Alert.alert("Label required", "Enter a label for this host.");
|
Alert.alert("Label required", "Enter a label for this host.");
|
||||||
return;
|
return;
|
||||||
@@ -536,7 +527,7 @@ export default function SettingsScreen() {
|
|||||||
} finally {
|
} finally {
|
||||||
setIsSavingEdit(false);
|
setIsSavingEdit(false);
|
||||||
}
|
}
|
||||||
}, [editLabel, editingDaemon, handleCloseEditDaemon, isSavingEdit, updateHost]);
|
}, [editingDaemon, handleCloseEditDaemon, isSavingEdit, updateHost]);
|
||||||
|
|
||||||
const handleRemoveConnection = useCallback(
|
const handleRemoveConnection = useCallback(
|
||||||
async (serverId: string, connectionId: string) => {
|
async (serverId: string, connectionId: string) => {
|
||||||
@@ -757,84 +748,14 @@ export default function SettingsScreen() {
|
|||||||
</AdaptiveModalSheet>
|
</AdaptiveModalSheet>
|
||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
<AdaptiveModalSheet
|
<EditHostModal
|
||||||
title="Edit host"
|
|
||||||
visible={Boolean(editingDaemon)}
|
visible={Boolean(editingDaemon)}
|
||||||
|
host={editingDaemonLive ?? editingDaemon}
|
||||||
|
isSaving={isSavingEdit}
|
||||||
onClose={handleCloseEditDaemon}
|
onClose={handleCloseEditDaemon}
|
||||||
testID="edit-host-modal"
|
onSave={(label) => void handleSaveEditDaemon(label)}
|
||||||
>
|
onRemoveConnection={handleRemoveConnection}
|
||||||
<View style={styles.formField}>
|
/>
|
||||||
<Text style={styles.label}>Label</Text>
|
|
||||||
<AdaptiveTextInput
|
|
||||||
style={styles.input}
|
|
||||||
value={editLabel}
|
|
||||||
onChangeText={setEditLabel}
|
|
||||||
placeholder="My Host"
|
|
||||||
placeholderTextColor={defaultTheme.colors.mutedForeground}
|
|
||||||
/>
|
|
||||||
</View>
|
|
||||||
|
|
||||||
{editingDaemonLive ? (
|
|
||||||
<View style={styles.formField}>
|
|
||||||
<Text style={styles.label}>Connections</Text>
|
|
||||||
<View style={{ gap: 8 }}>
|
|
||||||
{editingDaemonLive.connections.map((conn) => {
|
|
||||||
const title =
|
|
||||||
conn.type === "relay"
|
|
||||||
? `Relay (${conn.relayEndpoint})`
|
|
||||||
: `Direct (${conn.endpoint})`;
|
|
||||||
return (
|
|
||||||
<View
|
|
||||||
key={conn.id}
|
|
||||||
style={{
|
|
||||||
flexDirection: "row",
|
|
||||||
alignItems: "center",
|
|
||||||
justifyContent: "space-between",
|
|
||||||
gap: 12,
|
|
||||||
paddingVertical: 10,
|
|
||||||
paddingHorizontal: 12,
|
|
||||||
borderRadius: 12,
|
|
||||||
borderWidth: 1,
|
|
||||||
borderColor: theme.colors.border,
|
|
||||||
backgroundColor: theme.colors.surface2,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Text style={{ color: theme.colors.foreground, fontSize: 12, flex: 1 }}>
|
|
||||||
{title}
|
|
||||||
</Text>
|
|
||||||
<Pressable
|
|
||||||
onPress={() => void handleRemoveConnection(editingDaemonLive.serverId, conn.id)}
|
|
||||||
>
|
|
||||||
<Text style={{ color: theme.colors.destructive, fontSize: 12, fontWeight: "500" }}>
|
|
||||||
Remove
|
|
||||||
</Text>
|
|
||||||
</Pressable>
|
|
||||||
</View>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</View>
|
|
||||||
</View>
|
|
||||||
) : null}
|
|
||||||
|
|
||||||
<View style={styles.formActionsRow}>
|
|
||||||
<Button
|
|
||||||
variant="secondary"
|
|
||||||
size="sm"
|
|
||||||
onPress={handleCloseEditDaemon}
|
|
||||||
disabled={isSavingEdit}
|
|
||||||
>
|
|
||||||
Cancel
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
variant="default"
|
|
||||||
size="sm"
|
|
||||||
onPress={() => void handleSaveEditDaemon()}
|
|
||||||
disabled={isSavingEdit}
|
|
||||||
>
|
|
||||||
{isSavingEdit ? "Saving..." : "Save"}
|
|
||||||
</Button>
|
|
||||||
</View>
|
|
||||||
</AdaptiveModalSheet>
|
|
||||||
|
|
||||||
{/* Appearance */}
|
{/* Appearance */}
|
||||||
<View style={styles.section}>
|
<View style={styles.section}>
|
||||||
@@ -895,6 +816,126 @@ export default function SettingsScreen() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface EditHostModalProps {
|
||||||
|
visible: boolean;
|
||||||
|
host: HostProfile | null;
|
||||||
|
isSaving: boolean;
|
||||||
|
onClose: () => void;
|
||||||
|
onSave: (label: string) => void;
|
||||||
|
onRemoveConnection: (serverId: string, connectionId: string) => Promise<void>;
|
||||||
|
}
|
||||||
|
|
||||||
|
function EditHostModal({
|
||||||
|
visible,
|
||||||
|
host,
|
||||||
|
isSaving,
|
||||||
|
onClose,
|
||||||
|
onSave,
|
||||||
|
onRemoveConnection,
|
||||||
|
}: EditHostModalProps) {
|
||||||
|
const [draftLabel, setDraftLabel] = useState("");
|
||||||
|
const activeServerIdRef = useRef<string | null>(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!visible || !host) return;
|
||||||
|
if (activeServerIdRef.current !== host.serverId) {
|
||||||
|
setDraftLabel(host.label ?? "");
|
||||||
|
activeServerIdRef.current = host.serverId;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!draftLabel.trim()) {
|
||||||
|
setDraftLabel(host.label ?? "");
|
||||||
|
}
|
||||||
|
}, [visible, host, draftLabel]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!visible) {
|
||||||
|
activeServerIdRef.current = null;
|
||||||
|
}
|
||||||
|
}, [visible]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<AdaptiveModalSheet
|
||||||
|
title="Edit host"
|
||||||
|
visible={visible}
|
||||||
|
onClose={onClose}
|
||||||
|
testID="edit-host-modal"
|
||||||
|
>
|
||||||
|
<View style={styles.formField}>
|
||||||
|
<Text style={styles.label}>Label</Text>
|
||||||
|
<AdaptiveTextInput
|
||||||
|
style={styles.input}
|
||||||
|
value={draftLabel}
|
||||||
|
onChangeText={setDraftLabel}
|
||||||
|
placeholder="My Host"
|
||||||
|
placeholderTextColor={defaultTheme.colors.mutedForeground}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
{host ? (
|
||||||
|
<View style={styles.formField}>
|
||||||
|
<Text style={styles.label}>Connections</Text>
|
||||||
|
<View style={{ gap: 8 }}>
|
||||||
|
{host.connections.map((conn) => {
|
||||||
|
const title =
|
||||||
|
conn.type === "relay"
|
||||||
|
? `Relay (${conn.relayEndpoint})`
|
||||||
|
: `Direct (${conn.endpoint})`;
|
||||||
|
return (
|
||||||
|
<View
|
||||||
|
key={conn.id}
|
||||||
|
style={{
|
||||||
|
flexDirection: "row",
|
||||||
|
alignItems: "center",
|
||||||
|
justifyContent: "space-between",
|
||||||
|
gap: 12,
|
||||||
|
paddingVertical: 10,
|
||||||
|
paddingHorizontal: 12,
|
||||||
|
borderRadius: 12,
|
||||||
|
borderWidth: 1,
|
||||||
|
borderColor: defaultTheme.colors.border,
|
||||||
|
backgroundColor: defaultTheme.colors.surface2,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Text style={{ color: defaultTheme.colors.foreground, fontSize: 12, flex: 1 }}>
|
||||||
|
{title}
|
||||||
|
</Text>
|
||||||
|
<Pressable
|
||||||
|
onPress={() => void onRemoveConnection(host.serverId, conn.id)}
|
||||||
|
>
|
||||||
|
<Text style={{ color: defaultTheme.colors.destructive, fontSize: 12, fontWeight: "500" }}>
|
||||||
|
Remove
|
||||||
|
</Text>
|
||||||
|
</Pressable>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
) : null}
|
||||||
|
|
||||||
|
<View style={styles.formActionsRow}>
|
||||||
|
<Button
|
||||||
|
variant="secondary"
|
||||||
|
size="sm"
|
||||||
|
onPress={onClose}
|
||||||
|
disabled={isSaving}
|
||||||
|
>
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="default"
|
||||||
|
size="sm"
|
||||||
|
onPress={() => onSave(draftLabel)}
|
||||||
|
disabled={isSaving}
|
||||||
|
>
|
||||||
|
{isSaving ? "Saving..." : "Save"}
|
||||||
|
</Button>
|
||||||
|
</View>
|
||||||
|
</AdaptiveModalSheet>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
interface DaemonCardProps {
|
interface DaemonCardProps {
|
||||||
daemon: HostProfile;
|
daemon: HostProfile;
|
||||||
connectionStatus: ConnectionStatus;
|
connectionStatus: ConnectionStatus;
|
||||||
|
|||||||
@@ -163,6 +163,15 @@ export function SelectField({
|
|||||||
[getWebKey, onPress, preventWebDefault]
|
[getWebKey, onPress, preventWebDefault]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const normalizedValue = (value ?? "").trim();
|
||||||
|
const normalizedPlaceholder = (placeholder ?? "").trim();
|
||||||
|
const hasConcreteValue =
|
||||||
|
normalizedValue.length > 0 &&
|
||||||
|
(normalizedPlaceholder.length === 0 || normalizedValue !== normalizedPlaceholder);
|
||||||
|
const displayText = hasConcreteValue
|
||||||
|
? normalizedValue
|
||||||
|
: (normalizedPlaceholder || "Select...");
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={styles.selectFieldContainer}>
|
<View style={styles.selectFieldContainer}>
|
||||||
<Pressable
|
<Pressable
|
||||||
@@ -423,6 +432,14 @@ export function FormSelectTrigger({
|
|||||||
},
|
},
|
||||||
[getWebKey, onPress, preventWebDefault]
|
[getWebKey, onPress, preventWebDefault]
|
||||||
);
|
);
|
||||||
|
const normalizedValue = (value ?? "").trim();
|
||||||
|
const normalizedPlaceholder = (placeholder ?? "").trim();
|
||||||
|
const hasConcreteValue =
|
||||||
|
normalizedValue.length > 0 &&
|
||||||
|
(normalizedPlaceholder.length === 0 || normalizedValue !== normalizedPlaceholder);
|
||||||
|
const displayText = hasConcreteValue
|
||||||
|
? normalizedValue
|
||||||
|
: (normalizedPlaceholder || "Select...");
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Pressable
|
<Pressable
|
||||||
@@ -451,11 +468,11 @@ export function FormSelectTrigger({
|
|||||||
<ActivityIndicator size="small" color={defaultTheme.colors.foregroundMuted} />
|
<ActivityIndicator size="small" color={defaultTheme.colors.foregroundMuted} />
|
||||||
) : (
|
) : (
|
||||||
<Text
|
<Text
|
||||||
style={value ? styles.compactSelectValue : styles.compactSelectPlaceholder}
|
style={hasConcreteValue ? styles.compactSelectValue : styles.compactSelectPlaceholder}
|
||||||
numberOfLines={1}
|
numberOfLines={1}
|
||||||
ellipsizeMode={valueEllipsizeMode}
|
ellipsizeMode={valueEllipsizeMode}
|
||||||
>
|
>
|
||||||
{value || placeholder || "Select..."}
|
{displayText}
|
||||||
</Text>
|
</Text>
|
||||||
)}
|
)}
|
||||||
</View>
|
</View>
|
||||||
@@ -1426,7 +1443,10 @@ const styles = StyleSheet.create((theme) => ({
|
|||||||
paddingVertical: theme.spacing[1],
|
paddingVertical: theme.spacing[1],
|
||||||
},
|
},
|
||||||
agentConfigRow: {
|
agentConfigRow: {
|
||||||
flexDirection: "row",
|
flexDirection: {
|
||||||
|
xs: "column",
|
||||||
|
md: "row",
|
||||||
|
},
|
||||||
gap: theme.spacing[2],
|
gap: theme.spacing[2],
|
||||||
},
|
},
|
||||||
agentConfigColumn: {
|
agentConfigColumn: {
|
||||||
|
|||||||
@@ -1206,7 +1206,10 @@ const stylesheet = StyleSheet.create((theme) => ({
|
|||||||
},
|
},
|
||||||
list: {
|
list: {
|
||||||
flex: 1,
|
flex: 1,
|
||||||
paddingHorizontal: theme.spacing[2],
|
paddingHorizontal: {
|
||||||
|
xs: theme.spacing[2],
|
||||||
|
md: theme.spacing[4],
|
||||||
|
},
|
||||||
},
|
},
|
||||||
streamItemWrapper: {
|
streamItemWrapper: {
|
||||||
width: "100%",
|
width: "100%",
|
||||||
|
|||||||
@@ -1050,24 +1050,18 @@ export function GitDiffPane({ serverId, agentId, cwd }: GitDiffPaneProps) {
|
|||||||
accessibilityRole="button"
|
accessibilityRole="button"
|
||||||
accessibilityLabel={gitActions.primary.label}
|
accessibilityLabel={gitActions.primary.label}
|
||||||
>
|
>
|
||||||
<View style={styles.splitButtonPrimaryInner}>
|
{gitActions.primary.status === "pending" ? (
|
||||||
<View
|
<ActivityIndicator
|
||||||
style={[
|
size="small"
|
||||||
styles.splitButtonContent,
|
color={theme.colors.foreground}
|
||||||
gitActions.primary.status === "pending" && styles.splitButtonContentHidden,
|
style={styles.splitButtonSpinnerOnly}
|
||||||
]}
|
/>
|
||||||
>
|
) : (
|
||||||
|
<View style={styles.splitButtonContent}>
|
||||||
{gitActions.primary.icon}
|
{gitActions.primary.icon}
|
||||||
<Text style={styles.splitButtonText}>{getActionDisplayLabel(gitActions.primary)}</Text>
|
<Text style={styles.splitButtonText}>{getActionDisplayLabel(gitActions.primary)}</Text>
|
||||||
</View>
|
</View>
|
||||||
{gitActions.primary.status === "pending" ? (
|
)}
|
||||||
<ActivityIndicator
|
|
||||||
size="small"
|
|
||||||
color={theme.colors.foreground}
|
|
||||||
style={styles.splitButtonSpinnerOverlay}
|
|
||||||
/>
|
|
||||||
) : null}
|
|
||||||
</View>
|
|
||||||
</Pressable>
|
</Pressable>
|
||||||
{gitActions.secondary.length > 0 ? (
|
{gitActions.secondary.length > 0 ? (
|
||||||
<DropdownMenu>
|
<DropdownMenu>
|
||||||
@@ -1306,11 +1300,6 @@ const styles = StyleSheet.create((theme) => ({
|
|||||||
justifyContent: "center",
|
justifyContent: "center",
|
||||||
position: "relative",
|
position: "relative",
|
||||||
},
|
},
|
||||||
splitButtonPrimaryInner: {
|
|
||||||
position: "relative",
|
|
||||||
alignItems: "center",
|
|
||||||
justifyContent: "center",
|
|
||||||
},
|
|
||||||
splitButtonPrimaryDisabled: {
|
splitButtonPrimaryDisabled: {
|
||||||
opacity: 0.6,
|
opacity: 0.6,
|
||||||
},
|
},
|
||||||
@@ -1326,11 +1315,7 @@ const styles = StyleSheet.create((theme) => ({
|
|||||||
justifyContent: "center",
|
justifyContent: "center",
|
||||||
gap: theme.spacing[2],
|
gap: theme.spacing[2],
|
||||||
},
|
},
|
||||||
splitButtonContentHidden: {
|
splitButtonSpinnerOnly: {
|
||||||
opacity: 0,
|
|
||||||
},
|
|
||||||
splitButtonSpinnerOverlay: {
|
|
||||||
position: "absolute",
|
|
||||||
transform: [{ scale: 0.8 }],
|
transform: [{ scale: 0.8 }],
|
||||||
},
|
},
|
||||||
splitButtonCaret: {
|
splitButtonCaret: {
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ import {
|
|||||||
import { useDaemonConnections } from "@/contexts/daemon-connections-context";
|
import { useDaemonConnections } from "@/contexts/daemon-connections-context";
|
||||||
import { useDaemonRegistry } from "@/contexts/daemon-registry-context";
|
import { useDaemonRegistry } from "@/contexts/daemon-registry-context";
|
||||||
import { formatConnectionStatus } from "@/utils/daemons";
|
import { formatConnectionStatus } from "@/utils/daemons";
|
||||||
|
import { shortenPath } from "@/utils/shorten-path";
|
||||||
import { usePanelStore } from "@/stores/panel-store";
|
import { usePanelStore } from "@/stores/panel-store";
|
||||||
import { useSessionStore } from "@/stores/session-store";
|
import { useSessionStore } from "@/stores/session-store";
|
||||||
import { useCreateFlowStore } from "@/stores/create-flow-store";
|
import { useCreateFlowStore } from "@/stores/create-flow-store";
|
||||||
@@ -199,11 +200,25 @@ export function DraftAgentScreen({
|
|||||||
isCreateFlow: true,
|
isCreateFlow: true,
|
||||||
onlineServerIds,
|
onlineServerIds,
|
||||||
});
|
});
|
||||||
|
const daemonLabelByServerId = useMemo(() => {
|
||||||
|
const map = new Map<string, string>();
|
||||||
|
for (const daemon of daemons) {
|
||||||
|
const label = daemon.label?.trim();
|
||||||
|
map.set(daemon.serverId, label && label.length > 0 ? label : daemon.serverId);
|
||||||
|
}
|
||||||
|
return map;
|
||||||
|
}, [daemons]);
|
||||||
const hostEntry = selectedServerId
|
const hostEntry = selectedServerId
|
||||||
? connectionStates.get(selectedServerId)
|
? connectionStates.get(selectedServerId)
|
||||||
: undefined;
|
: undefined;
|
||||||
|
const selectedDaemonLabel = selectedServerId
|
||||||
|
? daemonLabelByServerId.get(selectedServerId)
|
||||||
|
: null;
|
||||||
const hostLabel =
|
const hostLabel =
|
||||||
hostEntry?.daemon.label ?? selectedServerId ?? "Select host";
|
selectedDaemonLabel ??
|
||||||
|
hostEntry?.daemon.label ??
|
||||||
|
selectedServerId ??
|
||||||
|
"Select host";
|
||||||
const isMobile =
|
const isMobile =
|
||||||
UnistylesRuntime.breakpoint === "xs" || UnistylesRuntime.breakpoint === "sm";
|
UnistylesRuntime.breakpoint === "xs" || UnistylesRuntime.breakpoint === "sm";
|
||||||
const isSidebarOpen = isMobile ? mobileView === "agent-list" : desktopAgentListOpen;
|
const isSidebarOpen = isMobile ? mobileView === "agent-list" : desktopAgentListOpen;
|
||||||
@@ -561,9 +576,8 @@ export function DraftAgentScreen({
|
|||||||
const handleSelectWorktreePath = useCallback(
|
const handleSelectWorktreePath = useCallback(
|
||||||
(path: string) => {
|
(path: string) => {
|
||||||
setSelectedWorktreePath(path);
|
setSelectedWorktreePath(path);
|
||||||
setWorkingDirFromUser(path);
|
|
||||||
},
|
},
|
||||||
[setWorkingDirFromUser]
|
[]
|
||||||
);
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -591,35 +605,36 @@ export function DraftAgentScreen({
|
|||||||
|
|
||||||
const selectedWorktreeLabel =
|
const selectedWorktreeLabel =
|
||||||
worktreeOptions.find((option) => option.path === selectedWorktreePath)?.label ?? "";
|
worktreeOptions.find((option) => option.path === selectedWorktreePath)?.label ?? "";
|
||||||
|
const displayWorkingDir = shortenPath(workingDir);
|
||||||
const worktreeTriggerValue =
|
const worktreeTriggerValue =
|
||||||
worktreeMode === "create"
|
worktreeMode === "create"
|
||||||
? "Create new worktree"
|
? "Create new worktree"
|
||||||
: selectedWorktreeLabel || "Select worktree";
|
: selectedWorktreeLabel || "Select worktree";
|
||||||
const hostOptions = useMemo(
|
const hostOptions = useMemo(
|
||||||
() =>
|
() =>
|
||||||
Array.from(connectionStates.values()).map(({ daemon, status }) => ({
|
daemons.map((daemon) => ({
|
||||||
id: daemon.serverId,
|
id: daemon.serverId,
|
||||||
label: daemon.label?.trim() ? daemon.label : daemon.serverId,
|
label: daemonLabelByServerId.get(daemon.serverId) ?? daemon.serverId,
|
||||||
description: formatConnectionStatus(status),
|
description: formatConnectionStatus(
|
||||||
|
connectionStates.get(daemon.serverId)?.status ?? "idle"
|
||||||
|
),
|
||||||
})),
|
})),
|
||||||
[connectionStates]
|
[connectionStates, daemonLabelByServerId, daemons]
|
||||||
);
|
);
|
||||||
const worktreeComboOptions = useMemo(
|
const worktreeComboOptions = useMemo(
|
||||||
() => [
|
() => [
|
||||||
{
|
|
||||||
id: "__create_new__",
|
|
||||||
label: "Create new worktree",
|
|
||||||
description: "Create a new isolated worktree",
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
id: "__none__",
|
id: "__none__",
|
||||||
label: "None",
|
label: "None",
|
||||||
description: "Do not use a worktree",
|
},
|
||||||
|
{
|
||||||
|
id: "__create_new__",
|
||||||
|
label: "Create new worktree",
|
||||||
},
|
},
|
||||||
...worktreeOptions.map((option) => ({
|
...worktreeOptions.map((option) => ({
|
||||||
id: option.path,
|
id: option.path,
|
||||||
label: option.label,
|
label: option.label,
|
||||||
description: option.path,
|
description: shortenPath(option.path),
|
||||||
})),
|
})),
|
||||||
],
|
],
|
||||||
[worktreeOptions]
|
[worktreeOptions]
|
||||||
@@ -922,12 +937,12 @@ export function DraftAgentScreen({
|
|||||||
) : (
|
) : (
|
||||||
<ScrollView style={styles.scrollView} contentContainerStyle={styles.configScrollContent}>
|
<ScrollView style={styles.scrollView} contentContainerStyle={styles.configScrollContent}>
|
||||||
<View style={styles.configSection}>
|
<View style={styles.configSection}>
|
||||||
<View style={styles.topSelectorRow}>
|
<View style={isMobile ? styles.stackedSelectorGroup : styles.topSelectorRow}>
|
||||||
<FormSelectTrigger
|
<FormSelectTrigger
|
||||||
controlRef={workingDirAnchorRef}
|
controlRef={workingDirAnchorRef}
|
||||||
containerStyle={styles.topSelectorPrimary}
|
containerStyle={isMobile ? styles.fullSelector : styles.topSelectorPrimary}
|
||||||
label="Working directory"
|
label="Working directory"
|
||||||
value={workingDir}
|
value={displayWorkingDir}
|
||||||
placeholder="/path/to/project"
|
placeholder="/path/to/project"
|
||||||
onPress={() => setIsWorkingDirOpen(true)}
|
onPress={() => setIsWorkingDirOpen(true)}
|
||||||
icon={<Folder size={16} color={theme.colors.foregroundMuted} />}
|
icon={<Folder size={16} color={theme.colors.foregroundMuted} />}
|
||||||
@@ -936,7 +951,7 @@ export function DraftAgentScreen({
|
|||||||
/>
|
/>
|
||||||
<FormSelectTrigger
|
<FormSelectTrigger
|
||||||
controlRef={hostAnchorRef}
|
controlRef={hostAnchorRef}
|
||||||
containerStyle={styles.topSelectorSecondary}
|
containerStyle={isMobile ? styles.fullSelector : styles.topSelectorSecondary}
|
||||||
label="Host"
|
label="Host"
|
||||||
value={hostLabel}
|
value={hostLabel}
|
||||||
placeholder="Select host"
|
placeholder="Select host"
|
||||||
@@ -952,36 +967,7 @@ export function DraftAgentScreen({
|
|||||||
</Text>
|
</Text>
|
||||||
</View>
|
</View>
|
||||||
)}
|
)}
|
||||||
{trimmedWorkingDir.length > 0 && !isNonGitDirectory ? (
|
{isMobile ? <View style={styles.formSeparator} /> : null}
|
||||||
<View style={styles.topSelectorRow}>
|
|
||||||
<FormSelectTrigger
|
|
||||||
controlRef={worktreeAnchorRef}
|
|
||||||
containerStyle={
|
|
||||||
worktreeMode === "create" ? styles.halfSelector : styles.topSelectorPrimary
|
|
||||||
}
|
|
||||||
label="Worktree"
|
|
||||||
value={worktreeTriggerValue}
|
|
||||||
placeholder="Select worktree"
|
|
||||||
onPress={() => setIsWorktreePickerOpen(true)}
|
|
||||||
icon={<GitBranch size={16} color={theme.colors.foregroundMuted} />}
|
|
||||||
showLabel={false}
|
|
||||||
valueEllipsizeMode="middle"
|
|
||||||
/>
|
|
||||||
{worktreeMode === "create" ? (
|
|
||||||
<FormSelectTrigger
|
|
||||||
controlRef={branchAnchorRef}
|
|
||||||
containerStyle={styles.halfSelector}
|
|
||||||
label="Base branch"
|
|
||||||
value={baseBranch}
|
|
||||||
placeholder="From branch"
|
|
||||||
onPress={() => setIsBranchOpen(true)}
|
|
||||||
disabled={repoInfoStatus === "loading"}
|
|
||||||
icon={<GitBranch size={16} color={theme.colors.foregroundMuted} />}
|
|
||||||
showLabel={false}
|
|
||||||
/>
|
|
||||||
) : null}
|
|
||||||
</View>
|
|
||||||
) : null}
|
|
||||||
<AgentConfigRow
|
<AgentConfigRow
|
||||||
providerDefinitions={providerDefinitions}
|
providerDefinitions={providerDefinitions}
|
||||||
selectedProvider={selectedProvider}
|
selectedProvider={selectedProvider}
|
||||||
@@ -994,6 +980,43 @@ export function DraftAgentScreen({
|
|||||||
isModelLoading={isModelLoading}
|
isModelLoading={isModelLoading}
|
||||||
onSelectModel={setModelFromUser}
|
onSelectModel={setModelFromUser}
|
||||||
/>
|
/>
|
||||||
|
{isMobile && trimmedWorkingDir.length > 0 && !isNonGitDirectory ? (
|
||||||
|
<View style={styles.formSeparator} />
|
||||||
|
) : null}
|
||||||
|
{trimmedWorkingDir.length > 0 && !isNonGitDirectory ? (
|
||||||
|
<View style={isMobile ? styles.stackedSelectorGroup : styles.topSelectorRow}>
|
||||||
|
<FormSelectTrigger
|
||||||
|
controlRef={worktreeAnchorRef}
|
||||||
|
containerStyle={
|
||||||
|
isMobile
|
||||||
|
? styles.fullSelector
|
||||||
|
: worktreeMode === "create"
|
||||||
|
? styles.halfSelector
|
||||||
|
: styles.topSelectorPrimary
|
||||||
|
}
|
||||||
|
label="Worktree"
|
||||||
|
value={worktreeTriggerValue}
|
||||||
|
placeholder="Select worktree"
|
||||||
|
onPress={() => setIsWorktreePickerOpen(true)}
|
||||||
|
icon={<GitBranch size={16} color={theme.colors.foregroundMuted} />}
|
||||||
|
showLabel={false}
|
||||||
|
valueEllipsizeMode="middle"
|
||||||
|
/>
|
||||||
|
{worktreeMode === "create" ? (
|
||||||
|
<FormSelectTrigger
|
||||||
|
controlRef={branchAnchorRef}
|
||||||
|
containerStyle={isMobile ? styles.fullSelector : styles.halfSelector}
|
||||||
|
label="Base branch"
|
||||||
|
value={baseBranch}
|
||||||
|
placeholder="From branch"
|
||||||
|
onPress={() => setIsBranchOpen(true)}
|
||||||
|
disabled={repoInfoStatus === "loading"}
|
||||||
|
icon={<GitBranch size={16} color={theme.colors.foregroundMuted} />}
|
||||||
|
showLabel={false}
|
||||||
|
/>
|
||||||
|
) : null}
|
||||||
|
</View>
|
||||||
|
) : null}
|
||||||
{baseBranchError ? <Text style={styles.errorInlineText}>{baseBranchError}</Text> : null}
|
{baseBranchError ? <Text style={styles.errorInlineText}>{baseBranchError}</Text> : null}
|
||||||
{repoInfoError ? <Text style={styles.errorInlineText}>{repoInfoError}</Text> : null}
|
{repoInfoError ? <Text style={styles.errorInlineText}>{repoInfoError}</Text> : null}
|
||||||
{gitBlockingError ? <Text style={styles.errorInlineText}>{gitBlockingError}</Text> : null}
|
{gitBlockingError ? <Text style={styles.errorInlineText}>{gitBlockingError}</Text> : null}
|
||||||
@@ -1046,7 +1069,10 @@ export function DraftAgentScreen({
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<Combobox
|
<Combobox
|
||||||
options={agentWorkingDirSuggestions.map((path) => ({ id: path, label: path }))}
|
options={agentWorkingDirSuggestions.map((path) => ({
|
||||||
|
id: path,
|
||||||
|
label: shortenPath(path),
|
||||||
|
}))}
|
||||||
value={workingDir}
|
value={workingDir}
|
||||||
onSelect={setWorkingDirFromUser}
|
onSelect={setWorkingDirFromUser}
|
||||||
searchPlaceholder="/path/to/project"
|
searchPlaceholder="/path/to/project"
|
||||||
@@ -1136,7 +1162,10 @@ const styles = StyleSheet.create((theme) => ({
|
|||||||
justifyContent: "flex-end",
|
justifyContent: "flex-end",
|
||||||
},
|
},
|
||||||
configSection: {
|
configSection: {
|
||||||
paddingHorizontal: theme.spacing[0],
|
paddingHorizontal: {
|
||||||
|
xs: theme.spacing[4],
|
||||||
|
md: theme.spacing[0],
|
||||||
|
},
|
||||||
paddingTop: theme.spacing[3],
|
paddingTop: theme.spacing[3],
|
||||||
paddingBottom: theme.spacing[4],
|
paddingBottom: theme.spacing[4],
|
||||||
gap: theme.spacing[2],
|
gap: theme.spacing[2],
|
||||||
@@ -1149,6 +1178,22 @@ const styles = StyleSheet.create((theme) => ({
|
|||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
gap: theme.spacing[2],
|
gap: theme.spacing[2],
|
||||||
},
|
},
|
||||||
|
stackedSelectorGroup: {
|
||||||
|
gap: theme.spacing[2],
|
||||||
|
},
|
||||||
|
fullSelector: {
|
||||||
|
width: "100%",
|
||||||
|
},
|
||||||
|
formSeparator: {
|
||||||
|
height: theme.borderWidth[1],
|
||||||
|
backgroundColor: {
|
||||||
|
xs: theme.colors.surface1,
|
||||||
|
sm: theme.colors.surface1,
|
||||||
|
md: theme.colors.border,
|
||||||
|
},
|
||||||
|
marginHorizontal: theme.spacing[1],
|
||||||
|
marginVertical: theme.spacing[2],
|
||||||
|
},
|
||||||
topSelectorPrimary: {
|
topSelectorPrimary: {
|
||||||
flex: 7,
|
flex: 7,
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user