forked from lightningpixel/modly
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathImageUpload.tsx
More file actions
112 lines (102 loc) · 4.54 KB
/
Copy pathImageUpload.tsx
File metadata and controls
112 lines (102 loc) · 4.54 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
import { useState, useCallback } from 'react'
import { useAppStore } from '@shared/stores/appStore'
import { useGeneration } from '@shared/hooks/useGeneration'
export default function ImageUpload(): JSX.Element {
const { currentJob } = useGeneration()
const { setSelectedImagePath, selectedImagePreviewUrl, setSelectedImagePreviewUrl, setSelectedImageData } = useAppStore()
const [isDragging, setIsDragging] = useState(false)
const isGenerating = currentJob?.status === 'uploading' || currentJob?.status === 'generating'
const handleFileSelect = useCallback(async () => {
const path = await window.electron.fs.selectImage()
if (!path) return
setSelectedImageData(null)
setSelectedImagePath(path)
// Read via IPC → blob URL (file:// blocked when served from localhost in dev)
const base64 = await window.electron.fs.readFileBase64(path)
const byteArray = Uint8Array.from(atob(base64), (c) => c.charCodeAt(0))
const blob = new Blob([byteArray], { type: 'image/png' })
setSelectedImagePreviewUrl(URL.createObjectURL(blob))
}, [setSelectedImagePath, setSelectedImagePreviewUrl, setSelectedImageData])
const handleDrop = useCallback((e: React.DragEvent) => {
e.preventDefault()
setIsDragging(false)
const file = e.dataTransfer.files[0]
if (!file || !file.type.startsWith('image/')) return
setSelectedImagePreviewUrl(URL.createObjectURL(file))
const filePath = (file as File & { path?: string }).path
if (filePath) {
setSelectedImageData(null)
setSelectedImagePath(filePath)
} else {
// file.path unavailable (some Electron configs) — read directly via FileReader
const reader = new FileReader()
reader.onload = (ev) => {
const dataUrl = ev.target?.result as string
const base64 = dataUrl.split(',')[1]
setSelectedImageData(base64)
setSelectedImagePath('__blob__')
}
reader.readAsDataURL(file)
}
}, [setSelectedImagePath, setSelectedImagePreviewUrl, setSelectedImageData])
return (
<div className="flex flex-col p-4 gap-3">
<h2 className="text-xs font-semibold uppercase tracking-widest text-zinc-500">Input Image</h2>
{/* Drop zone */}
<div
onClick={isGenerating ? undefined : handleFileSelect}
onDrop={handleDrop}
onDragOver={(e) => { e.preventDefault(); setIsDragging(true) }}
onDragLeave={() => setIsDragging(false)}
className={`
relative aspect-square rounded-xl border-2 border-dashed
flex items-center justify-center overflow-hidden
transition-colors cursor-pointer
${isDragging ? 'border-accent bg-accent/10' : 'border-zinc-700 hover:border-zinc-500'}
${isGenerating ? 'cursor-not-allowed opacity-60' : ''}
`}
>
{selectedImagePreviewUrl ? (
<>
<img
src={selectedImagePreviewUrl}
alt="Input"
className="w-full h-full object-cover"
/>
{!isGenerating && (
<button
onClick={(e) => {
e.stopPropagation()
setSelectedImagePath(null)
setSelectedImagePreviewUrl(null)
setSelectedImageData(null)
}}
className="absolute top-2 right-2 w-6 h-6 rounded-full bg-black/70 hover:bg-black/90 text-zinc-300 hover:text-white flex items-center justify-center transition-colors"
>
<svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5">
<line x1="18" y1="6" x2="6" y2="18" />
<line x1="6" y1="6" x2="18" y2="18" />
</svg>
</button>
)}
</>
) : (
<div className="flex flex-col items-center gap-2 text-zinc-600">
<svg width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1">
<rect x="3" y="3" width="18" height="18" rx="2" />
<circle cx="8.5" cy="8.5" r="1.5" />
<polyline points="21 15 16 10 5 21" />
</svg>
<p className="text-xs text-center">Drop image here<br />or click to browse</p>
</div>
)}
{/* Generating overlay */}
{isGenerating && (
<div className="absolute inset-0 bg-surface-500/80 flex items-center justify-center">
<div className="w-8 h-8 border-2 border-accent border-t-transparent rounded-full animate-spin" />
</div>
)}
</div>
</div>
)
}