mirror of
https://github.com/getpaseo/paseo.git
synced 2026-07-29 12:01:31 +00:00
chore(lint): hoist/memoize inline arrays and objects in app JSX
This commit is contained in:
@@ -55,20 +55,69 @@ export function ToolCallDetailsContent({
|
||||
detail?.type === "sub_agent");
|
||||
const codeBlockStyle = isFullBleed ? styles.fullBleedBlock : styles.diffContainer;
|
||||
|
||||
const sectionFillStyle = useMemo(
|
||||
() => [styles.section, shouldFill && styles.fillHeight],
|
||||
[shouldFill],
|
||||
);
|
||||
const codeBlockFillStyle = useMemo(
|
||||
() => [codeBlockStyle, shouldFill && styles.fillHeight],
|
||||
[codeBlockStyle, shouldFill],
|
||||
);
|
||||
const codeVerticalScrollStyle = useMemo(
|
||||
() => [
|
||||
styles.codeVerticalScroll,
|
||||
resolvedMaxHeight !== undefined && { maxHeight: resolvedMaxHeight },
|
||||
shouldFill && styles.fillHeight,
|
||||
webScrollbarStyle,
|
||||
],
|
||||
[resolvedMaxHeight, shouldFill, webScrollbarStyle],
|
||||
);
|
||||
const scrollAreaFillStyle = useMemo(
|
||||
() => [
|
||||
styles.scrollArea,
|
||||
resolvedMaxHeight !== undefined && { maxHeight: resolvedMaxHeight },
|
||||
shouldFill && styles.fillHeight,
|
||||
webScrollbarStyle,
|
||||
],
|
||||
[resolvedMaxHeight, shouldFill, webScrollbarStyle],
|
||||
);
|
||||
const scrollAreaStyle = useMemo(
|
||||
() => [
|
||||
styles.scrollArea,
|
||||
resolvedMaxHeight !== undefined && { maxHeight: resolvedMaxHeight },
|
||||
webScrollbarStyle,
|
||||
],
|
||||
[resolvedMaxHeight, webScrollbarStyle],
|
||||
);
|
||||
const jsonScrollCombined = useMemo(
|
||||
() => [styles.jsonScroll, webScrollbarStyle],
|
||||
[webScrollbarStyle],
|
||||
);
|
||||
const jsonScrollErrorCombined = useMemo(
|
||||
() => [styles.jsonScroll, styles.jsonScrollError, webScrollbarStyle],
|
||||
[webScrollbarStyle],
|
||||
);
|
||||
const fullBleedContainerStyle = useMemo(
|
||||
() => [
|
||||
isFullBleed ? styles.fullBleedContainer : styles.paddedContainer,
|
||||
shouldFill && styles.fillHeight,
|
||||
],
|
||||
[isFullBleed, shouldFill],
|
||||
);
|
||||
const loadingContainerStyle = useMemo(
|
||||
() => [styles.loadingContainer, fillAvailableHeight && styles.fillHeight],
|
||||
[fillAvailableHeight],
|
||||
);
|
||||
|
||||
if (detail?.type === "shell") {
|
||||
const command = detail.command.replace(/\n+$/, "");
|
||||
const commandOutput = (detail.output ?? "").replace(/^\n+/, "");
|
||||
const hasOutput = commandOutput.length > 0;
|
||||
sections.push(
|
||||
<View key="shell" style={[styles.section, shouldFill && styles.fillHeight]}>
|
||||
<View style={[codeBlockStyle, shouldFill && styles.fillHeight]}>
|
||||
<View key="shell" style={sectionFillStyle}>
|
||||
<View style={codeBlockFillStyle}>
|
||||
<ScrollView
|
||||
style={[
|
||||
styles.codeVerticalScroll,
|
||||
resolvedMaxHeight !== undefined && { maxHeight: resolvedMaxHeight },
|
||||
shouldFill && styles.fillHeight,
|
||||
webScrollbarStyle,
|
||||
]}
|
||||
style={codeVerticalScrollStyle}
|
||||
contentContainerStyle={styles.codeVerticalContent}
|
||||
nestedScrollEnabled
|
||||
showsVerticalScrollIndicator
|
||||
@@ -96,15 +145,10 @@ export function ToolCallDetailsContent({
|
||||
const setupLog = detail.log.replace(/^\n+/, "");
|
||||
const hasLog = setupLog.length > 0;
|
||||
sections.push(
|
||||
<View key="worktree-setup" style={[styles.section, shouldFill && styles.fillHeight]}>
|
||||
<View style={[codeBlockStyle, shouldFill && styles.fillHeight]}>
|
||||
<View key="worktree-setup" style={sectionFillStyle}>
|
||||
<View style={codeBlockFillStyle}>
|
||||
<ScrollView
|
||||
style={[
|
||||
styles.codeVerticalScroll,
|
||||
resolvedMaxHeight !== undefined && { maxHeight: resolvedMaxHeight },
|
||||
shouldFill && styles.fillHeight,
|
||||
webScrollbarStyle,
|
||||
]}
|
||||
style={codeVerticalScrollStyle}
|
||||
contentContainerStyle={styles.codeVerticalContent}
|
||||
nestedScrollEnabled
|
||||
showsVerticalScrollIndicator
|
||||
@@ -136,15 +180,10 @@ export function ToolCallDetailsContent({
|
||||
? `${detail.subAgentType}: ${detail.description}`
|
||||
: (detail.subAgentType ?? detail.description ?? "Sub-agent activity");
|
||||
sections.push(
|
||||
<View key="sub-agent" style={[styles.section, shouldFill && styles.fillHeight]}>
|
||||
<View style={[codeBlockStyle, shouldFill && styles.fillHeight]}>
|
||||
<View key="sub-agent" style={sectionFillStyle}>
|
||||
<View style={codeBlockFillStyle}>
|
||||
<ScrollView
|
||||
style={[
|
||||
styles.codeVerticalScroll,
|
||||
resolvedMaxHeight !== undefined && { maxHeight: resolvedMaxHeight },
|
||||
shouldFill && styles.fillHeight,
|
||||
webScrollbarStyle,
|
||||
]}
|
||||
style={codeVerticalScrollStyle}
|
||||
contentContainerStyle={styles.codeVerticalContent}
|
||||
nestedScrollEnabled
|
||||
showsVerticalScrollIndicator
|
||||
@@ -168,9 +207,9 @@ export function ToolCallDetailsContent({
|
||||
);
|
||||
} else if (detail?.type === "edit") {
|
||||
sections.push(
|
||||
<View key="edit" style={[styles.section, shouldFill && styles.fillHeight]}>
|
||||
<View key="edit" style={sectionFillStyle}>
|
||||
{diffLines ? (
|
||||
<View style={[codeBlockStyle, shouldFill && styles.fillHeight]}>
|
||||
<View style={codeBlockFillStyle}>
|
||||
<DiffViewer
|
||||
diffLines={diffLines}
|
||||
maxHeight={resolvedMaxHeight}
|
||||
@@ -182,15 +221,10 @@ export function ToolCallDetailsContent({
|
||||
);
|
||||
} else if (detail?.type === "write") {
|
||||
sections.push(
|
||||
<View key="write" style={[styles.section, shouldFill && styles.fillHeight]}>
|
||||
<View key="write" style={sectionFillStyle}>
|
||||
{detail.content ? (
|
||||
<ScrollView
|
||||
style={[
|
||||
styles.scrollArea,
|
||||
resolvedMaxHeight !== undefined && { maxHeight: resolvedMaxHeight },
|
||||
shouldFill && styles.fillHeight,
|
||||
webScrollbarStyle,
|
||||
]}
|
||||
style={scrollAreaFillStyle}
|
||||
contentContainerStyle={styles.scrollContent}
|
||||
nestedScrollEnabled
|
||||
showsVerticalScrollIndicator={true}
|
||||
@@ -212,14 +246,9 @@ export function ToolCallDetailsContent({
|
||||
} else if (detail?.type === "read") {
|
||||
if (detail.content) {
|
||||
sections.push(
|
||||
<View key="read" style={[styles.section, shouldFill && styles.fillHeight]}>
|
||||
<View key="read" style={sectionFillStyle}>
|
||||
<ScrollView
|
||||
style={[
|
||||
styles.scrollArea,
|
||||
resolvedMaxHeight !== undefined && { maxHeight: resolvedMaxHeight },
|
||||
shouldFill && styles.fillHeight,
|
||||
webScrollbarStyle,
|
||||
]}
|
||||
style={scrollAreaFillStyle}
|
||||
contentContainerStyle={styles.scrollContent}
|
||||
nestedScrollEnabled
|
||||
showsVerticalScrollIndicator={true}
|
||||
@@ -253,11 +282,7 @@ export function ToolCallDetailsContent({
|
||||
searchSections.push(
|
||||
<View key="search-content" style={styles.section}>
|
||||
<ScrollView
|
||||
style={[
|
||||
styles.scrollArea,
|
||||
resolvedMaxHeight !== undefined && { maxHeight: resolvedMaxHeight },
|
||||
webScrollbarStyle,
|
||||
]}
|
||||
style={scrollAreaStyle}
|
||||
contentContainerStyle={styles.scrollContent}
|
||||
nestedScrollEnabled
|
||||
showsVerticalScrollIndicator
|
||||
@@ -306,14 +331,9 @@ export function ToolCallDetailsContent({
|
||||
sections.push(...searchSections);
|
||||
} else if (detail?.type === "fetch") {
|
||||
sections.push(
|
||||
<View key="fetch" style={[styles.section, shouldFill && styles.fillHeight]}>
|
||||
<View key="fetch" style={sectionFillStyle}>
|
||||
<ScrollView
|
||||
style={[
|
||||
styles.scrollArea,
|
||||
resolvedMaxHeight !== undefined && { maxHeight: resolvedMaxHeight },
|
||||
shouldFill && styles.fillHeight,
|
||||
webScrollbarStyle,
|
||||
]}
|
||||
style={scrollAreaFillStyle}
|
||||
contentContainerStyle={styles.scrollContent}
|
||||
nestedScrollEnabled
|
||||
showsVerticalScrollIndicator
|
||||
@@ -388,7 +408,7 @@ export function ToolCallDetailsContent({
|
||||
<ScrollView
|
||||
horizontal
|
||||
nestedScrollEnabled
|
||||
style={[styles.jsonScroll, webScrollbarStyle]}
|
||||
style={jsonScrollCombined}
|
||||
contentContainerStyle={styles.jsonContent}
|
||||
showsHorizontalScrollIndicator={true}
|
||||
>
|
||||
@@ -406,15 +426,15 @@ export function ToolCallDetailsContent({
|
||||
if (errorText) {
|
||||
sections.push(
|
||||
<View key="error" style={styles.section}>
|
||||
<Text style={[styles.sectionTitle, styles.errorText]}>Error</Text>
|
||||
<Text style={SECTION_TITLE_ERROR_STYLE}>Error</Text>
|
||||
<ScrollView
|
||||
horizontal
|
||||
nestedScrollEnabled
|
||||
style={[styles.jsonScroll, styles.jsonScrollError, webScrollbarStyle]}
|
||||
style={jsonScrollErrorCombined}
|
||||
contentContainerStyle={styles.jsonContent}
|
||||
showsHorizontalScrollIndicator={true}
|
||||
>
|
||||
<Text selectable style={[styles.scrollText, styles.errorText]}>
|
||||
<Text selectable style={SCROLL_TEXT_ERROR_STYLE}>
|
||||
{errorText}
|
||||
</Text>
|
||||
</ScrollView>
|
||||
@@ -425,7 +445,7 @@ export function ToolCallDetailsContent({
|
||||
if (sections.length === 0) {
|
||||
if (showLoadingSkeleton) {
|
||||
return (
|
||||
<View style={[styles.loadingContainer, fillAvailableHeight && styles.fillHeight]}>
|
||||
<View style={loadingContainerStyle}>
|
||||
<View style={styles.loadingLineWide} />
|
||||
<View style={styles.loadingLineMedium} />
|
||||
<View style={styles.loadingLineShort} />
|
||||
@@ -435,16 +455,7 @@ export function ToolCallDetailsContent({
|
||||
return <Text style={styles.emptyStateText}>No additional details available</Text>;
|
||||
}
|
||||
|
||||
return (
|
||||
<View
|
||||
style={[
|
||||
isFullBleed ? styles.fullBleedContainer : styles.paddedContainer,
|
||||
shouldFill && styles.fillHeight,
|
||||
]}
|
||||
>
|
||||
{sections}
|
||||
</View>
|
||||
);
|
||||
return <View style={fullBleedContainerStyle}>{sections}</View>;
|
||||
}
|
||||
|
||||
// ---- Styles ----
|
||||
@@ -598,3 +609,6 @@ const styles = StyleSheet.create((theme) => {
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
const SECTION_TITLE_ERROR_STYLE = [styles.sectionTitle, styles.errorText];
|
||||
const SCROLL_TEXT_ERROR_STYLE = [styles.scrollText, styles.errorText];
|
||||
|
||||
@@ -126,18 +126,18 @@ function ThemeIcon({
|
||||
}
|
||||
|
||||
function ThemeSwatch({ color, size }: { color: string; size: number }) {
|
||||
return (
|
||||
<View
|
||||
style={{
|
||||
width: size,
|
||||
height: size,
|
||||
borderRadius: size / 2,
|
||||
backgroundColor: color,
|
||||
borderWidth: 1,
|
||||
borderColor: "rgba(255,255,255,0.15)",
|
||||
}}
|
||||
/>
|
||||
const swatchStyle = useMemo(
|
||||
() => ({
|
||||
width: size,
|
||||
height: size,
|
||||
borderRadius: size / 2,
|
||||
backgroundColor: color,
|
||||
borderWidth: 1,
|
||||
borderColor: "rgba(255,255,255,0.15)",
|
||||
}),
|
||||
[color, size],
|
||||
);
|
||||
return <View style={swatchStyle} />;
|
||||
}
|
||||
|
||||
function themeTriggerStyle({ pressed }: PressableStateCallbackType) {
|
||||
@@ -166,6 +166,18 @@ const THEME_LABELS: Record<AppSettings["theme"], string> = {
|
||||
auto: "System",
|
||||
};
|
||||
|
||||
const ROW_WITH_BORDER_STYLE = [settingsStyles.row, settingsStyles.rowBorder];
|
||||
|
||||
const SEND_BEHAVIOR_OPTIONS = [
|
||||
{ value: "interrupt" as const, label: "Interrupt" },
|
||||
{ value: "queue" as const, label: "Queue" },
|
||||
];
|
||||
|
||||
const RELEASE_CHANNEL_OPTIONS = [
|
||||
{ value: "stable" as const, label: "Stable" },
|
||||
{ value: "beta" as const, label: "Beta" },
|
||||
];
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Section components
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -252,7 +264,7 @@ function GeneralSection({
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</View>
|
||||
<View style={[settingsStyles.row, settingsStyles.rowBorder]}>
|
||||
<View style={ROW_WITH_BORDER_STYLE}>
|
||||
<View style={settingsStyles.rowContent}>
|
||||
<Text style={settingsStyles.rowTitle}>Default send</Text>
|
||||
<Text style={settingsStyles.rowHint}>
|
||||
@@ -263,10 +275,7 @@ function GeneralSection({
|
||||
size="sm"
|
||||
value={settings.sendBehavior}
|
||||
onValueChange={handleSendBehaviorChange}
|
||||
options={[
|
||||
{ value: "interrupt", label: "Interrupt" },
|
||||
{ value: "queue", label: "Queue" },
|
||||
]}
|
||||
options={SEND_BEHAVIOR_OPTIONS}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
@@ -401,7 +410,7 @@ function DesktopAppUpdateRow() {
|
||||
|
||||
return (
|
||||
<>
|
||||
<View style={[settingsStyles.row, settingsStyles.rowBorder]}>
|
||||
<View style={ROW_WITH_BORDER_STYLE}>
|
||||
<View style={settingsStyles.rowContent}>
|
||||
<Text style={settingsStyles.rowTitle}>Release channel</Text>
|
||||
<Text style={settingsStyles.rowHint}>
|
||||
@@ -412,13 +421,10 @@ function DesktopAppUpdateRow() {
|
||||
size="sm"
|
||||
value={settings.releaseChannel}
|
||||
onValueChange={handleReleaseChannelChange}
|
||||
options={[
|
||||
{ value: "stable", label: "Stable" },
|
||||
{ value: "beta", label: "Beta" },
|
||||
]}
|
||||
options={RELEASE_CHANNEL_OPTIONS}
|
||||
/>
|
||||
</View>
|
||||
<View style={[settingsStyles.row, settingsStyles.rowBorder]}>
|
||||
<View style={ROW_WITH_BORDER_STYLE}>
|
||||
<View style={settingsStyles.rowContent}>
|
||||
<Text style={settingsStyles.rowTitle}>App updates</Text>
|
||||
<Text style={settingsStyles.rowHint}>{statusText}</Text>
|
||||
@@ -503,10 +509,15 @@ function SidebarSectionButton({
|
||||
const handlePress = useCallback(() => {
|
||||
onSelect(itemId);
|
||||
}, [onSelect, itemId]);
|
||||
const accessibilityState = useMemo(() => ({ selected: isSelected }), [isSelected]);
|
||||
const labelStyle = useMemo(
|
||||
() => [sidebarStyles.label, isSelected && { color: theme.colors.foreground }],
|
||||
[isSelected, theme.colors.foreground],
|
||||
);
|
||||
return (
|
||||
<Pressable
|
||||
accessibilityRole="button"
|
||||
accessibilityState={{ selected: isSelected }}
|
||||
accessibilityState={accessibilityState}
|
||||
onPress={handlePress}
|
||||
style={isSelected ? selectedSidebarItemStyle : sidebarItemStyle}
|
||||
>
|
||||
@@ -514,10 +525,7 @@ function SidebarSectionButton({
|
||||
size={theme.iconSize.md}
|
||||
color={isSelected ? theme.colors.foreground : theme.colors.foregroundMuted}
|
||||
/>
|
||||
<Text
|
||||
style={[sidebarStyles.label, isSelected && { color: theme.colors.foreground }]}
|
||||
numberOfLines={1}
|
||||
>
|
||||
<Text style={labelStyle} numberOfLines={1}>
|
||||
{label}
|
||||
</Text>
|
||||
</Pressable>
|
||||
@@ -537,10 +545,15 @@ function SidebarHostItem({ serverId, label, isSelected, isLocal, onSelect }: Sid
|
||||
const handlePress = useCallback(() => {
|
||||
onSelect(serverId);
|
||||
}, [onSelect, serverId]);
|
||||
const accessibilityState = useMemo(() => ({ selected: isSelected }), [isSelected]);
|
||||
const labelStyle = useMemo(
|
||||
() => [sidebarStyles.label, isSelected && { color: theme.colors.foreground }],
|
||||
[isSelected, theme.colors.foreground],
|
||||
);
|
||||
return (
|
||||
<Pressable
|
||||
accessibilityRole="button"
|
||||
accessibilityState={{ selected: isSelected }}
|
||||
accessibilityState={accessibilityState}
|
||||
onPress={handlePress}
|
||||
testID={`settings-host-entry-${serverId}`}
|
||||
style={isSelected ? selectedSidebarItemStyle : sidebarItemStyle}
|
||||
@@ -549,10 +562,7 @@ function SidebarHostItem({ serverId, label, isSelected, isLocal, onSelect }: Sid
|
||||
size={theme.iconSize.md}
|
||||
color={isSelected ? theme.colors.foreground : theme.colors.foregroundMuted}
|
||||
/>
|
||||
<Text
|
||||
style={[sidebarStyles.label, isSelected && { color: theme.colors.foreground }]}
|
||||
numberOfLines={1}
|
||||
>
|
||||
<Text style={labelStyle} numberOfLines={1}>
|
||||
{label}
|
||||
</Text>
|
||||
{isLocal ? (
|
||||
@@ -604,13 +614,14 @@ function SettingsSidebar({
|
||||
const containerStyle = isDesktop ? sidebarStyles.desktopContainer : sidebarStyles.mobileContainer;
|
||||
const selectedSectionId = view.kind === "section" ? view.section : null;
|
||||
const selectedServerId = view.kind === "host" ? view.serverId : null;
|
||||
const paddingTopStyle = useMemo(() => ({ height: padding.top }), [padding.top]);
|
||||
|
||||
return (
|
||||
<View style={containerStyle} testID="settings-sidebar">
|
||||
{isDesktop ? (
|
||||
<>
|
||||
<TitlebarDragRegion />
|
||||
{padding.top > 0 ? <View style={{ height: padding.top }} /> : null}
|
||||
{padding.top > 0 ? <View style={paddingTopStyle} /> : null}
|
||||
</>
|
||||
) : null}
|
||||
{isDesktop ? (
|
||||
@@ -685,6 +696,7 @@ export default function SettingsScreen({ view }: SettingsScreenProps) {
|
||||
const appVersionText = formatVersionWithPrefix(appVersion);
|
||||
const isCompactLayout = useIsCompactFormFactor();
|
||||
const insets = useSafeAreaInsets();
|
||||
const insetBottomStyle = useMemo(() => ({ paddingBottom: insets.bottom }), [insets.bottom]);
|
||||
const hosts = useHosts();
|
||||
const hostServerIds = useMemo(() => hosts.map((host) => host.serverId), [hosts]);
|
||||
const anyOnlineServerId = useAnyOnlineHostServerId(hostServerIds);
|
||||
@@ -935,10 +947,7 @@ export default function SettingsScreen({ view }: SettingsScreenProps) {
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
<BackHeader title="Settings" onBack={handleBackToWorkspace} />
|
||||
<ScrollView
|
||||
style={styles.scrollView}
|
||||
contentContainerStyle={{ paddingBottom: insets.bottom }}
|
||||
>
|
||||
<ScrollView style={styles.scrollView} contentContainerStyle={insetBottomStyle}>
|
||||
<SettingsSidebar
|
||||
view={view}
|
||||
onSelectSection={handleSelectSection}
|
||||
@@ -962,10 +971,7 @@ export default function SettingsScreen({ view }: SettingsScreenProps) {
|
||||
titleAccessory={detailHeader?.titleAccessory}
|
||||
onBack={handleBackToRoot}
|
||||
/>
|
||||
<ScrollView
|
||||
style={styles.scrollView}
|
||||
contentContainerStyle={{ paddingBottom: insets.bottom }}
|
||||
>
|
||||
<ScrollView style={styles.scrollView} contentContainerStyle={insetBottomStyle}>
|
||||
<View style={styles.content}>{content}</View>
|
||||
</ScrollView>
|
||||
{addHostModals}
|
||||
@@ -1009,10 +1015,7 @@ export default function SettingsScreen({ view }: SettingsScreenProps) {
|
||||
}
|
||||
leftStyle={desktopStyles.detailLeft}
|
||||
/>
|
||||
<ScrollView
|
||||
style={styles.scrollView}
|
||||
contentContainerStyle={{ paddingBottom: insets.bottom }}
|
||||
>
|
||||
<ScrollView style={styles.scrollView} contentContainerStyle={insetBottomStyle}>
|
||||
<View style={styles.content}>{content}</View>
|
||||
</ScrollView>
|
||||
</View>
|
||||
|
||||
Reference in New Issue
Block a user