Skip to content

Commit 5ab171b

Browse files
antoniomtzclaude
andcommitted
feat: allow replacing uploaded image by clicking on it
Clicking the image preview now opens the file picker to replace the image without resetting locale, fields, or other settings. A hover overlay with "Click to replace image" provides visual affordance. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 72b0900 commit 5ab171b

2 files changed

Lines changed: 48 additions & 10 deletions

File tree

src/ui/app/page.tsx

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,14 @@ function Home() {
5858
return;
5959
}
6060

61+
// Clear image-dependent results when replacing, but preserve settings
62+
setAugmentedData(null);
63+
setGeneratedImages([null, null]);
64+
setQualityScores([null, null]);
65+
setQualityIssues([null, null]);
66+
setGenerated3DModel(null);
67+
setModel3DError(null);
68+
6169
setIsUploading(true);
6270
setUploadedFile(file);
6371

@@ -343,6 +351,7 @@ function Home() {
343351
onChange={(e) => {
344352
const file = e.target.files?.[0];
345353
if (file) handleFileUpload(file);
354+
if (fileInputRef.current) fileInputRef.current.value = '';
346355
}}
347356
style={{ display: 'none' }}
348357
/>

src/ui/components/ImageUploadCard.tsx

Lines changed: 39 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ export function ImageUploadCard({
3333
}: Props) {
3434
const [isHovered, setIsHovered] = useState(false);
3535
const [isDragging, setIsDragging] = useState(false);
36+
const [isImageHovered, setIsImageHovered] = useState(false);
3637

3738
const handleDragOver = (e: React.DragEvent) => {
3839
onDragOver(e);
@@ -71,29 +72,57 @@ export function ImageUploadCard({
7172
</div>
7273
) : uploadedImage ? (
7374
<>
74-
<div
75-
className="relative rounded-lg overflow-hidden nvidia-green-border"
76-
style={{
75+
<div
76+
className="relative rounded-lg overflow-hidden nvidia-green-border"
77+
style={{
7778
minHeight: '400px',
7879
backgroundColor: 'var(--color-gray-1000)',
7980
borderWidth: '2px',
8081
display: 'flex',
8182
alignItems: 'center',
82-
justifyContent: 'center'
83+
justifyContent: 'center',
84+
cursor: isAnalyzingFields || isGeneratingImage ? 'default' : 'pointer',
8385
}}
86+
onClick={() => {
87+
if (!isAnalyzingFields && !isGeneratingImage) onFileSelect();
88+
}}
89+
onMouseEnter={() => setIsImageHovered(true)}
90+
onMouseLeave={() => setIsImageHovered(false)}
8491
>
85-
<img
86-
src={uploadedImage}
87-
alt="Uploaded preview"
88-
style={{
89-
maxWidth: '100%',
92+
<img
93+
src={uploadedImage}
94+
alt="Uploaded preview"
95+
style={{
96+
maxWidth: '100%',
9097
maxHeight: '400px',
9198
width: 'auto',
9299
height: 'auto',
93100
objectFit: 'contain',
94-
display: 'block'
101+
display: 'block',
102+
transition: 'opacity 0.2s',
103+
opacity: isImageHovered && !isAnalyzingFields && !isGeneratingImage ? 0.6 : 1,
95104
}}
96105
/>
106+
{isImageHovered && !isAnalyzingFields && !isGeneratingImage && (
107+
<div
108+
style={{
109+
position: 'absolute',
110+
inset: 0,
111+
display: 'flex',
112+
flexDirection: 'column',
113+
alignItems: 'center',
114+
justifyContent: 'center',
115+
pointerEvents: 'none',
116+
}}
117+
>
118+
<svg width="32" height="32" fill="none" viewBox="0 0 24 24" stroke="#76B900">
119+
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5} d="M7 16a4 4 0 01-.88-7.903A5 5 0 1115.9 6L16 6a5 5 0 011 9.9M15 13l-3-3m0 0l-3 3m3-3v12" />
120+
</svg>
121+
<Text kind="body/semibold/sm" style={{ color: '#76B900', marginTop: '8px' }}>
122+
Click to replace image
123+
</Text>
124+
</div>
125+
)}
97126
</div>
98127

99128
<Flex gap="3" align="center">

0 commit comments

Comments
 (0)