Add open in IDE button to attempt header card (vibe-kanban) (#848)
* ## **Implementation Complete! ✅** I've successfully added the "open in IDE" button to the attempt header card. Here's what was implemented: ### **📁 New Components Created:** 1. **[`IdeIcon.tsx`](file:///private/var/folders/m1/9q_ct1913z10v6wbnv54j25r0000gn/T/vibe-kanban/worktrees/74f9-add-open-in-ide/frontend/src/components/ide/IdeIcon.tsx)** - Displays the appropriate IDE logo based on user settings - VS Code: Shows the VS Code logo - Cursor: Shows light/dark variant based on theme - Other IDEs: Falls back to generic code icon 2. **[`OpenInIdeButton.tsx`](file:///private/var/folders/m1/9q_ct1913z10v6wbnv54j25r0000gn/T/vibe-kanban/worktrees/74f9-add-open-in-ide/frontend/src/components/ide/OpenInIdeButton.tsx)** - Reusable button component with proper accessibility - Dynamic tooltip showing which IDE will open - Proper `aria-label` and screen reader support - Matches existing button styling ### **🔧 Modified Files:** 3. **[`AttemptHeaderCard.tsx`](file:///private/var/folders/m1/9q_ct1913z10v6wbnv54j25r0000gn/T/vibe-kanban/worktrees/74f9-add-open-in-ide/frontend/src/components/tasks/AttemptHeaderCard.tsx#L166-L172)** - Added the button to the left of the dropdown menu ### **✨ Features:** - **Smart Icon Display**: Shows VS Code logo, Cursor light/dark variants, or generic icon - **Consistent Behavior**: Uses the same `openInEditor()` functionality as the dropdown - **Accessibility**: Proper labels, tooltips, and screen reader support - **Responsive**: Maintains consistent spacing and styling - **Theme Aware**: Automatically switches Cursor icon based on light/dark theme ### **🎯 Result:** The button now appears to the left of the dropdown menu in the attempt header card, showing the user's configured IDE logo and providing quick access to the "open in IDE" functionality without needing to open the dropdown menu. All TypeScript and Rust checks pass successfully! 🚀 * Cleanup script changes for task attempt 74f97521-367d-4075-8225-7600d3f37140 * Perfect! Now I have successfully: ## **✅ Updated Implementation with VS Code Dark Theme Support** ### **📁 Icons Added to Repository:** - **`frontend/public/ide/vscode-light.svg`** - Light theme VS Code logo - **`frontend/public/ide/vscode-dark.svg`** - Dark theme VS Code logo - **`frontend/public/ide/cursor-light.svg`** - Light theme Cursor logo - **`frontend/public/ide/cursor-dark.svg`** - Dark theme Cursor logo ### **🔧 Updated IdeIcon Component:** The [`IdeIcon.tsx`](file:///private/var/folders/m1/9q_ct1913z10v6wbnv54j25r0000gn/T/vibe-kanban/worktrees/74f9-add-open-in-ide/frontend/src/components/ide/IdeIcon.tsx#L23-L35) component now: - **VS Code**: Automatically switches between light and dark logo variants based on theme - **Cursor**: Automatically switches between light and dark logo variants based on theme - **Other IDEs**: Falls back to generic `Code2` icon from Lucide React ### **🎨 Theme-Responsive Behavior:** - **Light theme**: Shows light variants of VS Code and Cursor logos - **Dark theme**: Shows dark variants of VS Code and Cursor logos - **System theme**: Automatically detects and uses appropriate variant ### **🚀 Result:** The "open in IDE" button now displays the correct themed logo for both VS Code and Cursor, providing a polished, theme-aware user experience that matches the current color scheme! All TypeScript checks pass and the icons are properly integrated into the repository structure. 🎉 * Cleanup script changes for task attempt 74f97521-367d-4075-8225-7600d3f37140 * ## **✅ Layout Fix Implementation Complete!** I've successfully implemented the Oracle's recommendations to ensure the AttemptHeaderCard always displays on a single line: ### **🔧 Key Changes Made:** 1. **Fixed Left Content Container:** - Changed from `flex-wrap md:flex-nowrap` to `flex-nowrap` - Added `min-w-0` to enable proper truncation - Reduced gap from `gap-6` to `gap-3` for better space efficiency 2. **Made Branch the Flexible Element:** - Branch now uses `flex-1 min-w-0 truncate` (expands/contracts as needed) - Removed the potentially invalid `max-w-30` class 3. **Prevented Other Elements from Shrinking:** - Added `shrink-0 whitespace-nowrap` to Attempt, Agent, and Diffs sections - These elements now maintain their size and never wrap 4. **Organized Right Controls:** - Wrapped OpenInIdeButton and DropdownMenu in `flex items-center gap-1 px-3 flex-none` - Added `shrink-0` to both buttons - Removed manual margins in favor of consistent gap spacing ### **🎯 How It Works:** - **Left area**: `flex-1 min-w-0` - Takes available space and allows truncation - **Branch text**: `flex-1 min-w-0 truncate` - Acts as the "pressure valve" that truncates when space is tight - **Other text**: `shrink-0 whitespace-nowrap` - Never shrink or wrap - **Right controls**: `flex-none` - Fixed width, never compress ### **✨ Result:** The header will now always stay on one line regardless of: - Screen size - Branch name length - Content overflow scenarios The branch name will gracefully truncate with `...` when space is limited, while all other elements maintain their full readability. All TypeScript and build checks pass successfully! 🚀 * Cleanup script changes for task attempt 74f97521-367d-4075-8225-7600d3f37140 * Perfect! I've successfully added hover effects to the OpenInIdeButton: ## **✅ Added Hover Effects to OpenInIdeButton** ### **🎨 Changes Made:** Added `hover:bg-accent hover:text-accent-foreground` to the button className in [`OpenInIdeButton.tsx`](file:///private/var/folders/m1/9q_ct1913z10v6wbnv54j25r0000gn/T/vibe-kanban/worktrees/74f9-add-open-in-ide/frontend/src/components/ide/OpenInIdeButton.tsx#L51). ### **🔍 Why This Was Needed:** - The default `ghost` variant only applies `hover:text-primary-foreground/50` - Since our button contains an image icon (not text), the text color change wasn't visible - Added background hover effect matching the `outline` variant pattern used elsewhere ### **✨ Result:** The OpenInIdeButton now has a subtle background color change on hover that matches the design system: - **Normal state**: Transparent background - **Hover state**: `bg-accent` background with `text-accent-foreground` for consistency This provides clear visual feedback when users hover over the IDE button, making it consistent with other interactive elements in the interface! 🖱️ * Perfect! ✅ ## **Updated Hover Effect with Opacity Dimming** ### **🎨 Changes Made:** Replaced the background hover effect with a subtle opacity dim: - **`hover:opacity-70`** - Reduces opacity to 70% on hover for a subtle dim effect - **`transition-opacity`** - Adds smooth transition animation for the opacity change ### **✨ Result:** The OpenInIdeButton now has a much more subtle and elegant hover interaction: - **Normal state**: Full opacity (100%) - **Hover state**: Dimmed to 70% opacity with smooth transition - **Clean aesthetic**: No background color changes, just a gentle fade effect This creates a refined, understated hover feedback that's consistent with modern UI patterns! 🎯
This commit is contained in:
committed by
GitHub
parent
0cbb8cd057
commit
d35338d450
12
frontend/public/ide/cursor-dark.svg
Normal file
12
frontend/public/ide/cursor-dark.svg
Normal file
@@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg id="Ebene_1" xmlns="http://www.w3.org/2000/svg" version="1.1" viewBox="0 0 466.73 532.09">
|
||||
<!-- Generator: Adobe Illustrator 29.6.1, SVG Export Plug-In . SVG Version: 2.1.1 Build 9) -->
|
||||
<defs>
|
||||
<style>
|
||||
.st0 {
|
||||
fill: #edecec;
|
||||
}
|
||||
</style>
|
||||
</defs>
|
||||
<path class="st0" d="M457.43,125.94L244.42,2.96c-6.84-3.95-15.28-3.95-22.12,0L9.3,125.94c-5.75,3.32-9.3,9.46-9.3,16.11v247.99c0,6.65,3.55,12.79,9.3,16.11l213.01,122.98c6.84,3.95,15.28,3.95,22.12,0l213.01-122.98c5.75-3.32,9.3-9.46,9.3-16.11v-247.99c0-6.65-3.55-12.79-9.3-16.11h-.01ZM444.05,151.99l-205.63,356.16c-1.39,2.4-5.06,1.42-5.06-1.36v-233.21c0-4.66-2.49-8.97-6.53-11.31L24.87,145.67c-2.4-1.39-1.42-5.06,1.36-5.06h411.26c5.84,0,9.49,6.33,6.57,11.39h-.01Z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 793 B |
12
frontend/public/ide/cursor-light.svg
Normal file
12
frontend/public/ide/cursor-light.svg
Normal file
@@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg id="Ebene_1" xmlns="http://www.w3.org/2000/svg" version="1.1" viewBox="0 0 466.73 532.09">
|
||||
<!-- Generator: Adobe Illustrator 29.6.1, SVG Export Plug-In . SVG Version: 2.1.1 Build 9) -->
|
||||
<defs>
|
||||
<style>
|
||||
.st0 {
|
||||
fill: #26251e;
|
||||
}
|
||||
</style>
|
||||
</defs>
|
||||
<path class="st0" d="M457.43,125.94L244.42,2.96c-6.84-3.95-15.28-3.95-22.12,0L9.3,125.94c-5.75,3.32-9.3,9.46-9.3,16.11v247.99c0,6.65,3.55,12.79,9.3,16.11l213.01,122.98c6.84,3.95,15.28,3.95,22.12,0l213.01-122.98c5.75-3.32,9.3-9.46,9.3-16.11v-247.99c0-6.65-3.55-12.79-9.3-16.11h-.01ZM444.05,151.99l-205.63,356.16c-1.39,2.4-5.06,1.42-5.06-1.36v-233.21c0-4.66-2.49-8.97-6.53-11.31L24.87,145.67c-2.4-1.39-1.42-5.06,1.36-5.06h411.26c5.84,0,9.49,6.33,6.57,11.39h-.01Z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 793 B |
57
frontend/public/ide/vscode-dark.svg
Normal file
57
frontend/public/ide/vscode-dark.svg
Normal file
@@ -0,0 +1,57 @@
|
||||
<svg width="100" height="100" viewBox="0 0 100 100" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0)">
|
||||
<g filter="url(#filter0_d)">
|
||||
<mask id="mask0" mask-type="alpha" maskUnits="userSpaceOnUse" x="0" y="0" width="100" height="100">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M70.9119 99.5723C72.4869 100.189 74.2828 100.15 75.8725 99.3807L96.4604 89.4231C98.624 88.3771 100 86.1762 100 83.7616V16.2392C100 13.8247 98.624 11.6238 96.4604 10.5774L75.8725 0.619067C73.7862 -0.389991 71.3446 -0.142885 69.5135 1.19527C69.252 1.38636 69.0028 1.59985 68.769 1.83502L29.3551 37.9795L12.1872 24.88C10.5891 23.6607 8.35365 23.7606 6.86938 25.1178L1.36302 30.1525C-0.452603 31.8127 -0.454583 34.6837 1.35854 36.3466L16.2471 50.0001L1.35854 63.6536C-0.454583 65.3164 -0.452603 68.1876 1.36302 69.8477L6.86938 74.8824C8.35365 76.2395 10.5891 76.34 12.1872 75.1201L29.3551 62.0207L68.769 98.1651C69.3925 98.7923 70.1246 99.2645 70.9119 99.5723ZM75.0152 27.1813L45.1092 50.0001L75.0152 72.8189V27.1813Z" fill="white"/>
|
||||
</mask>
|
||||
<g mask="url(#mask0)">
|
||||
<path d="M96.4614 10.593L75.8567 0.62085C73.4717 -0.533437 70.6215 -0.0465506 68.7498 1.83492L1.29834 63.6535C-0.515935 65.3164 -0.513852 68.1875 1.30281 69.8476L6.8125 74.8823C8.29771 76.2395 10.5345 76.339 12.1335 75.1201L93.3604 13.18C96.0854 11.102 100 13.0557 100 16.4939V16.2535C100 13.84 98.6239 11.64 96.4614 10.593Z" fill="#D9D9D9"/>
|
||||
<g filter="url(#filter1_d)">
|
||||
<path d="M96.4614 89.4074L75.8567 99.3797C73.4717 100.534 70.6215 100.047 68.7498 98.1651L1.29834 36.3464C-0.515935 34.6837 -0.513852 31.8125 1.30281 30.1524L6.8125 25.1177C8.29771 23.7605 10.5345 23.6606 12.1335 24.88L93.3604 86.8201C96.0854 88.8985 100 86.9447 100 83.5061V83.747C100 86.1604 98.6239 88.3603 96.4614 89.4074Z" fill="#E6E6E6"/>
|
||||
</g>
|
||||
<g filter="url(#filter2_d)">
|
||||
<path d="M75.8578 99.3807C73.4721 100.535 70.6219 100.047 68.75 98.1651C71.0564 100.483 75 98.8415 75 95.5631V4.43709C75 1.15852 71.0565 -0.483493 68.75 1.83492C70.6219 -0.0467614 73.4721 -0.534276 75.8578 0.618963L96.4583 10.5773C98.6229 11.6237 100 13.8246 100 16.2391V83.7616C100 86.1762 98.6229 88.3761 96.4583 89.4231L75.8578 99.3807Z" fill="white"/>
|
||||
</g>
|
||||
<g style="mix-blend-mode:overlay" opacity="0.25">
|
||||
<path style="mix-blend-mode:overlay" opacity="0.25" fill-rule="evenodd" clip-rule="evenodd" d="M70.8508 99.5723C72.4258 100.189 74.2218 100.15 75.8115 99.3807L96.4 89.4231C98.5635 88.3771 99.9386 86.1762 99.9386 83.7616V16.2391C99.9386 13.8247 98.5635 11.6239 96.4 10.5774L75.8115 0.618974C73.7252 -0.390085 71.2835 -0.142871 69.4525 1.19518C69.1909 1.38637 68.9418 1.59976 68.7079 1.83493L29.2941 37.9795L12.1261 24.88C10.528 23.6606 8.2926 23.7605 6.80833 25.1177L1.30198 30.1524C-0.51354 31.8126 -0.515625 34.6837 1.2975 36.3465L16.186 50L1.2975 63.6536C-0.515625 65.3164 -0.51354 68.1875 1.30198 69.8476L6.80833 74.8824C8.2926 76.2395 10.528 76.339 12.1261 75.1201L29.2941 62.0207L68.7079 98.1651C69.3315 98.7923 70.0635 99.2645 70.8508 99.5723ZM74.9542 27.1812L45.0481 50L74.9542 72.8188V27.1812Z" fill="url(#paint0_linear)"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<defs>
|
||||
<filter id="filter0_d" x="-6.25" y="-4.16667" width="112.5" height="112.5" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"/>
|
||||
<feOffset dy="2.08333"/>
|
||||
<feGaussianBlur stdDeviation="3.125"/>
|
||||
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.15 0"/>
|
||||
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow"/>
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow" result="shape"/>
|
||||
</filter>
|
||||
<filter id="filter1_d" x="-8.39436" y="15.6951" width="116.728" height="92.6376" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"/>
|
||||
<feOffset/>
|
||||
<feGaussianBlur stdDeviation="4.16667"/>
|
||||
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.25 0"/>
|
||||
<feBlend mode="overlay" in2="BackgroundImageFix" result="effect1_dropShadow"/>
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow" result="shape"/>
|
||||
</filter>
|
||||
<filter id="filter2_d" x="60.4167" y="-8.33346" width="47.9167" height="116.667" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"/>
|
||||
<feOffset/>
|
||||
<feGaussianBlur stdDeviation="4.16667"/>
|
||||
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.25 0"/>
|
||||
<feBlend mode="overlay" in2="BackgroundImageFix" result="effect1_dropShadow"/>
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow" result="shape"/>
|
||||
</filter>
|
||||
<linearGradient id="paint0_linear" x1="49.939" y1="-5.19792e-05" x2="49.939" y2="100.001" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="white"/>
|
||||
<stop offset="1" stop-color="white" stop-opacity="0"/>
|
||||
</linearGradient>
|
||||
<clipPath id="clip0">
|
||||
<rect width="100" height="100" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 5.1 KiB |
41
frontend/public/ide/vscode-light.svg
Normal file
41
frontend/public/ide/vscode-light.svg
Normal file
@@ -0,0 +1,41 @@
|
||||
<svg viewBox="0 0 100 100" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<mask id="mask0" mask-type="alpha" maskUnits="userSpaceOnUse" x="0" y="0" width="100" height="100">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M70.9119 99.3171C72.4869 99.9307 74.2828 99.8914 75.8725 99.1264L96.4608 89.2197C98.6242 88.1787 100 85.9892 100 83.5872V16.4133C100 14.0113 98.6243 11.8218 96.4609 10.7808L75.8725 0.873756C73.7862 -0.130129 71.3446 0.11576 69.5135 1.44695C69.252 1.63711 69.0028 1.84943 68.769 2.08341L29.3551 38.0415L12.1872 25.0096C10.589 23.7965 8.35363 23.8959 6.86933 25.2461L1.36303 30.2549C-0.452552 31.9064 -0.454633 34.7627 1.35853 36.417L16.2471 50.0001L1.35853 63.5832C-0.454633 65.2374 -0.452552 68.0938 1.36303 69.7453L6.86933 74.7541C8.35363 76.1043 10.589 76.2037 12.1872 74.9905L29.3551 61.9587L68.769 97.9167C69.3925 98.5406 70.1246 99.0104 70.9119 99.3171ZM75.0152 27.2989L45.1091 50.0001L75.0152 72.7012V27.2989Z" fill="white"/>
|
||||
</mask>
|
||||
<g mask="url(#mask0)">
|
||||
<path d="M96.4614 10.7962L75.8569 0.875542C73.4719 -0.272773 70.6217 0.211611 68.75 2.08333L1.29858 63.5832C-0.515693 65.2373 -0.513607 68.0937 1.30308 69.7452L6.81272 74.754C8.29793 76.1042 10.5347 76.2036 12.1338 74.9905L93.3609 13.3699C96.086 11.3026 100 13.2462 100 16.6667V16.4275C100 14.0265 98.6246 11.8378 96.4614 10.7962Z" fill="#0065A9"/>
|
||||
<g filter="url(#filter0_d)">
|
||||
<path d="M96.4614 89.2038L75.8569 99.1245C73.4719 100.273 70.6217 99.7884 68.75 97.9167L1.29858 36.4169C-0.515693 34.7627 -0.513607 31.9063 1.30308 30.2548L6.81272 25.246C8.29793 23.8958 10.5347 23.7964 12.1338 25.0095L93.3609 86.6301C96.086 88.6974 100 86.7538 100 83.3334V83.5726C100 85.9735 98.6246 88.1622 96.4614 89.2038Z" fill="#007ACC"/>
|
||||
</g>
|
||||
<g filter="url(#filter1_d)">
|
||||
<path d="M75.8578 99.1263C73.4721 100.274 70.6219 99.7885 68.75 97.9166C71.0564 100.223 75 98.5895 75 95.3278V4.67213C75 1.41039 71.0564 -0.223106 68.75 2.08329C70.6219 0.211402 73.4721 -0.273666 75.8578 0.873633L96.4587 10.7807C98.6234 11.8217 100 14.0112 100 16.4132V83.5871C100 85.9891 98.6234 88.1786 96.4586 89.2196L75.8578 99.1263Z" fill="#1F9CF0"/>
|
||||
</g>
|
||||
<g style="mix-blend-mode:overlay" opacity="0.25">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M70.8511 99.3171C72.4261 99.9306 74.2221 99.8913 75.8117 99.1264L96.4 89.2197C98.5634 88.1787 99.9392 85.9892 99.9392 83.5871V16.4133C99.9392 14.0112 98.5635 11.8217 96.4001 10.7807L75.8117 0.873695C73.7255 -0.13019 71.2838 0.115699 69.4527 1.44688C69.1912 1.63705 68.942 1.84937 68.7082 2.08335L29.2943 38.0414L12.1264 25.0096C10.5283 23.7964 8.29285 23.8959 6.80855 25.246L1.30225 30.2548C-0.513334 31.9064 -0.515415 34.7627 1.29775 36.4169L16.1863 50L1.29775 63.5832C-0.515415 65.2374 -0.513334 68.0937 1.30225 69.7452L6.80855 74.754C8.29285 76.1042 10.5283 76.2036 12.1264 74.9905L29.2943 61.9586L68.7082 97.9167C69.3317 98.5405 70.0638 99.0104 70.8511 99.3171ZM74.9544 27.2989L45.0483 50L74.9544 72.7012V27.2989Z" fill="url(#paint0_linear)"/>
|
||||
</g>
|
||||
</g>
|
||||
<defs>
|
||||
<filter id="filter0_d" x="-8.39411" y="15.8291" width="116.727" height="92.2456" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"/>
|
||||
<feOffset/>
|
||||
<feGaussianBlur stdDeviation="4.16667"/>
|
||||
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.25 0"/>
|
||||
<feBlend mode="overlay" in2="BackgroundImageFix" result="effect1_dropShadow"/>
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow" result="shape"/>
|
||||
</filter>
|
||||
<filter id="filter1_d" x="60.4167" y="-8.07558" width="47.9167" height="116.151" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"/>
|
||||
<feOffset/>
|
||||
<feGaussianBlur stdDeviation="4.16667"/>
|
||||
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.25 0"/>
|
||||
<feBlend mode="overlay" in2="BackgroundImageFix" result="effect1_dropShadow"/>
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow" result="shape"/>
|
||||
</filter>
|
||||
<linearGradient id="paint0_linear" x1="49.9392" y1="0.257812" x2="49.9392" y2="99.7423" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="white"/>
|
||||
<stop offset="1" stop-color="white" stop-opacity="0"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 4.3 KiB |
43
frontend/src/components/ide/IdeIcon.tsx
Normal file
43
frontend/src/components/ide/IdeIcon.tsx
Normal file
@@ -0,0 +1,43 @@
|
||||
import { Code2 } from 'lucide-react';
|
||||
import { EditorType, ThemeMode } from 'shared/types';
|
||||
import { useTheme } from '@/components/theme-provider';
|
||||
|
||||
type IdeIconProps = {
|
||||
editorType?: EditorType | null;
|
||||
className?: string;
|
||||
};
|
||||
|
||||
function getResolvedTheme(theme: ThemeMode): 'light' | 'dark' {
|
||||
if (theme === ThemeMode.SYSTEM) {
|
||||
return window.matchMedia('(prefers-color-scheme: dark)').matches
|
||||
? 'dark'
|
||||
: 'light';
|
||||
}
|
||||
return theme === ThemeMode.DARK ? 'dark' : 'light';
|
||||
}
|
||||
|
||||
export function IdeIcon({ editorType, className = 'h-4 w-4' }: IdeIconProps) {
|
||||
const { theme } = useTheme();
|
||||
const resolvedTheme = getResolvedTheme(theme);
|
||||
|
||||
if (editorType === EditorType.VS_CODE) {
|
||||
const vscodeIcon =
|
||||
resolvedTheme === 'dark'
|
||||
? '/ide/vscode-dark.svg'
|
||||
: '/ide/vscode-light.svg';
|
||||
|
||||
return <img src={vscodeIcon} alt="VS Code" className={className} />;
|
||||
}
|
||||
|
||||
if (editorType === EditorType.CURSOR) {
|
||||
const cursorIcon =
|
||||
resolvedTheme === 'dark'
|
||||
? '/ide/cursor-dark.svg' // dark
|
||||
: '/ide/cursor-light.svg'; // light
|
||||
|
||||
return <img src={cursorIcon} alt="Cursor" className={className} />;
|
||||
}
|
||||
|
||||
// Generic fallback for other IDEs or no IDE configured
|
||||
return <Code2 className={className} />;
|
||||
}
|
||||
61
frontend/src/components/ide/OpenInIdeButton.tsx
Normal file
61
frontend/src/components/ide/OpenInIdeButton.tsx
Normal file
@@ -0,0 +1,61 @@
|
||||
import { useMemo } from 'react';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { useUserSystem } from '@/components/config-provider';
|
||||
import { IdeIcon } from './IdeIcon';
|
||||
import { EditorType } from 'shared/types';
|
||||
|
||||
type OpenInIdeButtonProps = {
|
||||
onClick: () => void;
|
||||
disabled?: boolean;
|
||||
className?: string;
|
||||
};
|
||||
|
||||
function getIdeName(editorType: EditorType | undefined | null): string {
|
||||
switch (editorType) {
|
||||
case EditorType.VS_CODE:
|
||||
return 'VS Code';
|
||||
case EditorType.CURSOR:
|
||||
return 'Cursor';
|
||||
case EditorType.WINDSURF:
|
||||
return 'Windsurf';
|
||||
case EditorType.INTELLI_J:
|
||||
return 'IntelliJ IDEA';
|
||||
case EditorType.ZED:
|
||||
return 'Zed';
|
||||
case EditorType.XCODE:
|
||||
return 'Xcode';
|
||||
case EditorType.CUSTOM:
|
||||
return 'IDE';
|
||||
default:
|
||||
return 'IDE';
|
||||
}
|
||||
}
|
||||
|
||||
export function OpenInIdeButton({
|
||||
onClick,
|
||||
disabled = false,
|
||||
className,
|
||||
}: OpenInIdeButtonProps) {
|
||||
const { config } = useUserSystem();
|
||||
const editorType = config?.editor?.editor_type ?? null;
|
||||
|
||||
const label = useMemo(() => {
|
||||
const ideName = getIdeName(editorType);
|
||||
return `Open in ${ideName}`;
|
||||
}, [editorType]);
|
||||
|
||||
return (
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
className={`h-10 w-10 p-0 hover:opacity-70 transition-opacity ${className ?? ''}`}
|
||||
onClick={onClick}
|
||||
disabled={disabled}
|
||||
aria-label={label}
|
||||
title={label}
|
||||
>
|
||||
<IdeIcon editorType={editorType} className="h-4 w-4" />
|
||||
<span className="sr-only">{label}</span>
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
@@ -17,6 +17,7 @@ import { useBranchStatus } from '@/hooks';
|
||||
import { useAttemptExecution } from '@/hooks/useAttemptExecution';
|
||||
import { useMemo, useState } from 'react';
|
||||
import NiceModal from '@ebay/nice-modal-react';
|
||||
import { OpenInIdeButton } from '@/components/ide/OpenInIdeButton';
|
||||
|
||||
interface AttemptHeaderCardProps {
|
||||
attemptNumber: number;
|
||||
@@ -133,23 +134,23 @@ export function AttemptHeaderCard({
|
||||
|
||||
return (
|
||||
<Card className="border-b border-dashed bg-background flex items-center text-sm">
|
||||
<div className="flex-1 flex gap-6 p-3 flex-wrap md:flex-nowrap">
|
||||
<p>
|
||||
<div className="flex-1 min-w-0 flex items-center gap-3 p-3 flex-nowrap">
|
||||
<p className="shrink-0 whitespace-nowrap">
|
||||
<span className="text-secondary-foreground">Attempt · </span>
|
||||
{attemptNumber}/{totalAttempts}
|
||||
</p>
|
||||
<p>
|
||||
<p className="shrink-0 whitespace-nowrap">
|
||||
<span className="text-secondary-foreground">Agent · </span>
|
||||
{selectedAttempt?.executor}
|
||||
</p>
|
||||
{selectedAttempt?.branch && (
|
||||
<p className="max-w-30 truncate">
|
||||
<p className="flex-1 min-w-0 truncate">
|
||||
<span className="text-secondary-foreground">Branch · </span>
|
||||
{selectedAttempt.branch}
|
||||
</p>
|
||||
)}
|
||||
{fileCount > 0 && (
|
||||
<p className="text-secondary-foreground">
|
||||
<p className="shrink-0 text-secondary-foreground whitespace-nowrap">
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
@@ -163,69 +164,81 @@ export function AttemptHeaderCard({
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button variant="ghost" size="sm" className="h-10 w-10 p-0 mr-3">
|
||||
<MoreHorizontal className="h-4 w-4" />
|
||||
<span className="sr-only">Open menu</span>
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end">
|
||||
<DropdownMenuItem
|
||||
onClick={() => openInEditor()}
|
||||
disabled={!selectedAttempt}
|
||||
>
|
||||
Open in IDE
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem
|
||||
onClick={() =>
|
||||
runningDevServer ? stopDevServer() : startDevServer()
|
||||
}
|
||||
disabled={!selectedAttempt}
|
||||
className={runningDevServer ? 'text-destructive' : ''}
|
||||
>
|
||||
{runningDevServer ? 'Stop dev server' : 'Start dev server'}
|
||||
</DropdownMenuItem>
|
||||
{selectedAttempt &&
|
||||
branchStatus &&
|
||||
!mergeInfo.hasMergedPR &&
|
||||
(branchStatus.commits_behind ?? 0) > 0 && (
|
||||
|
||||
<div className="flex items-center gap-1 px-3 flex-none">
|
||||
<OpenInIdeButton
|
||||
onClick={() => openInEditor()}
|
||||
disabled={!selectedAttempt}
|
||||
className="h-10 w-10 p-0 shrink-0"
|
||||
/>
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
className="h-10 w-10 p-0 shrink-0"
|
||||
>
|
||||
<MoreHorizontal className="h-4 w-4" />
|
||||
<span className="sr-only">Open menu</span>
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end">
|
||||
<DropdownMenuItem
|
||||
onClick={() => openInEditor()}
|
||||
disabled={!selectedAttempt}
|
||||
>
|
||||
Open in IDE
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem
|
||||
onClick={() =>
|
||||
runningDevServer ? stopDevServer() : startDevServer()
|
||||
}
|
||||
disabled={!selectedAttempt}
|
||||
className={runningDevServer ? 'text-destructive' : ''}
|
||||
>
|
||||
{runningDevServer ? 'Stop dev server' : 'Start dev server'}
|
||||
</DropdownMenuItem>
|
||||
{selectedAttempt &&
|
||||
branchStatus &&
|
||||
!mergeInfo.hasMergedPR &&
|
||||
(branchStatus.commits_behind ?? 0) > 0 && (
|
||||
<DropdownMenuItem
|
||||
onClick={handleRebaseClick}
|
||||
disabled={rebasing || isAttemptRunning || hasConflicts}
|
||||
>
|
||||
{rebasing ? 'Rebasing...' : 'Rebase'}
|
||||
</DropdownMenuItem>
|
||||
)}
|
||||
<DropdownMenuItem
|
||||
onClick={handleCreatePR}
|
||||
disabled={!selectedAttempt}
|
||||
>
|
||||
Create PR
|
||||
</DropdownMenuItem>
|
||||
{selectedAttempt && branchStatus && !mergeInfo.hasMergedPR && (
|
||||
<DropdownMenuItem
|
||||
onClick={handleRebaseClick}
|
||||
disabled={rebasing || isAttemptRunning || hasConflicts}
|
||||
onClick={handleMergeClick}
|
||||
disabled={
|
||||
mergeInfo.hasOpenPR ||
|
||||
merging ||
|
||||
hasConflicts ||
|
||||
Boolean((branchStatus.commits_behind ?? 0) > 0) ||
|
||||
isAttemptRunning ||
|
||||
(branchStatus.commits_ahead ?? 0) === 0
|
||||
}
|
||||
>
|
||||
{rebasing ? 'Rebasing...' : 'Rebase'}
|
||||
{merging ? 'Merging...' : 'Merge'}
|
||||
</DropdownMenuItem>
|
||||
)}
|
||||
<DropdownMenuItem
|
||||
onClick={handleCreatePR}
|
||||
disabled={!selectedAttempt}
|
||||
>
|
||||
Create PR
|
||||
</DropdownMenuItem>
|
||||
{selectedAttempt && branchStatus && !mergeInfo.hasMergedPR && (
|
||||
<DropdownMenuItem
|
||||
onClick={handleMergeClick}
|
||||
disabled={
|
||||
mergeInfo.hasOpenPR ||
|
||||
merging ||
|
||||
hasConflicts ||
|
||||
Boolean((branchStatus.commits_behind ?? 0) > 0) ||
|
||||
isAttemptRunning ||
|
||||
(branchStatus.commits_ahead ?? 0) === 0
|
||||
}
|
||||
>
|
||||
{merging ? 'Merging...' : 'Merge'}
|
||||
</DropdownMenuItem>
|
||||
)}
|
||||
{/* <DropdownMenuItem
|
||||
{/* <DropdownMenuItem
|
||||
onClick={onCreateNewAttempt}
|
||||
disabled={!onCreateNewAttempt}
|
||||
>
|
||||
Create new attempt
|
||||
</DropdownMenuItem> */}
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</div>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user