| 128 | } |
| 129 | |
| 130 | function fileToDataUrl(file) { |
| 131 | return new Promise((resolve, reject) => { |
| 132 | const objectUrl = URL.createObjectURL(file); |
| 133 | const img = new Image(); |
| 134 | img.onerror = () => { |
| 135 | try { URL.revokeObjectURL(objectUrl); } catch {} |
| 136 | reject(new Error('Failed to decode image preview')); |
| 137 | }; |
| 138 | img.onload = () => { |
| 139 | try { |
| 140 | const maxW = 480; |
| 141 | const maxH = 320; |
| 142 | const scale = Math.min(1, maxW / img.width, maxH / img.height); |
| 143 | const width = Math.max(1, Math.round(img.width * scale)); |
| 144 | const height = Math.max(1, Math.round(img.height * scale)); |
| 145 | const canvas = document.createElement('canvas'); |
| 146 | canvas.width = width; |
| 147 | canvas.height = height; |
| 148 | const ctx = canvas.getContext('2d'); |
| 149 | if (!ctx) throw new Error('Canvas unavailable'); |
| 150 | ctx.drawImage(img, 0, 0, width, height); |
| 151 | resolve(canvas.toDataURL('image/jpeg', 0.82)); |
| 152 | } catch (err) { |
| 153 | reject(err); |
| 154 | } finally { |
| 155 | try { URL.revokeObjectURL(objectUrl); } catch {} |
| 156 | } |
| 157 | }; |
| 158 | img.src = objectUrl; |
| 159 | }); |
| 160 | } |
| 161 | |
| 162 | export async function submitPendingImageUpload() { |
| 163 | const currentImage = pendingImage; |