Skip to content

Commit bc76a14

Browse files
committed
handeling pc heif error, showing user it is not supported
1 parent b0bbc5a commit bc76a14

5 files changed

Lines changed: 177 additions & 34 deletions

File tree

package-lock.json

Lines changed: 4 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,4 +37,4 @@
3737
"typescript-eslint": "^8.3.0",
3838
"vite": "^5.4.2"
3939
}
40-
}
40+
}

src/components/PhotoUploader.tsx

Lines changed: 58 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,15 @@ const PhotoUploader: React.FC<PhotoUploaderProps> = ({ photos, onPhotosChange })
2121
setIsProcessing(true);
2222

2323
try {
24-
const fileArray = Array.from(files).filter(file =>
25-
file.type.startsWith('image/') || file.type.startsWith('video/')
26-
);
24+
// Filter files to include images, videos, and HEIF files (by extension)
25+
const fileArray = Array.from(files).filter(file => {
26+
const isImage = file.type.startsWith('image/');
27+
const isVideo = file.type.startsWith('video/');
28+
// Check for HEIF files by extension (some systems don't set proper MIME types)
29+
const isHeif = /\.(heic|heif)$/i.test(file.name);
30+
31+
return isImage || isVideo || isHeif;
32+
});
2733

2834
if (fileArray.length === 0) {
2935
alert(t('selectImageVideoOnly'));
@@ -55,6 +61,19 @@ const PhotoUploader: React.FC<PhotoUploaderProps> = ({ photos, onPhotosChange })
5561
// Create optimized photos with thumbnails
5662
const newOptimizedPhotos = await createOptimizedPhotos(filesToProcess);
5763

64+
// Check if any HEIF files were detected (not supported)
65+
const heifFiles = newOptimizedPhotos.filter(photo =>
66+
photo.name.includes('(HEIF not supported)')
67+
);
68+
69+
if (heifFiles.length > 0) {
70+
const heifNames = heifFiles.map(photo =>
71+
photo.name.replace(' (HEIF not supported)', '')
72+
).join(', ');
73+
74+
alert(`Info: ${heifFiles.length} HEIF file(s) detected: ${heifNames}. HEIF format is not supported by web browsers. Please convert these files to JPEG for best results.`);
75+
}
76+
5877
// Convert to Photo interface
5978
const newPhotos: Photo[] = newOptimizedPhotos.map(optimized => ({
6079
id: optimized.id,
@@ -71,7 +90,13 @@ const PhotoUploader: React.FC<PhotoUploaderProps> = ({ photos, onPhotosChange })
7190
onPhotosChange([...photos, ...newPhotos]);
7291
} catch (error) {
7392
console.error('Error processing photos:', error);
74-
alert('Error processing some photos. Please try again.');
93+
94+
// Provide specific error message for HEIF conversion issues
95+
const errorMessage = error instanceof Error && error.message.includes('HEIF')
96+
? 'Error converting HEIF/HEIC files. Please try converting them to JPEG first, or use a different image format.'
97+
: 'Error processing some photos. Please try again.';
98+
99+
alert(errorMessage);
75100
} finally {
76101
setIsProcessing(false);
77102
// Reset the input value
@@ -94,7 +119,7 @@ const PhotoUploader: React.FC<PhotoUploaderProps> = ({ photos, onPhotosChange })
94119
ref={fileInputRef}
95120
type="file"
96121
multiple
97-
accept="image/*,video/*"
122+
accept="image/*,video/*,.heic,.heif"
98123
onChange={handleFileSelect}
99124
className="hidden"
100125
/>
@@ -125,27 +150,35 @@ const PhotoUploader: React.FC<PhotoUploaderProps> = ({ photos, onPhotosChange })
125150
{t('selectedPhotos', { count: photos.length.toString() })}
126151
</div>
127152
<div className="grid grid-cols-4 sm:grid-cols-4 md:grid-cols-4 gap-3 max-h-80 overflow-y-auto overflow-x-hidden p-2">
128-
{photos.map((photo, index) => (
129-
<div key={photo.id} className="relative group">
130-
<div className="aspect-square bg-gray-100 dark:bg-gray-700 rounded-lg overflow-hidden">
131-
<img
132-
src={photo.thumbnailUrl || photo.url}
133-
alt={`Photo ${index + 1}`}
134-
className="w-full h-full object-cover"
135-
loading="lazy"
136-
/>
137-
</div>
138-
<button
139-
onClick={() => removePhoto(photo.id)}
140-
className="absolute top-1 right-1 p-1 bg-red-500 hover:bg-red-600 text-white rounded-full transition-colors duration-200 shadow-lg"
141-
>
142-
<X className="w-3 h-3" />
143-
</button>
144-
<div className="absolute bottom-1 left-1 bg-black bg-opacity-50 text-white text-xs px-1 py-0.5 rounded">
145-
#{index + 1}
153+
{photos.map((photo, index) => {
154+
const isHeifFile = photo.name.includes('(HEIF not supported)');
155+
return (
156+
<div key={photo.id} className="relative group">
157+
<div className={`aspect-square bg-gray-100 dark:bg-gray-700 rounded-lg overflow-hidden ${isHeifFile ? 'border-2 border-yellow-400' : ''}`}>
158+
<img
159+
src={photo.thumbnailUrl || photo.url}
160+
alt={`Photo ${index + 1}`}
161+
className="w-full h-full object-cover"
162+
loading="lazy"
163+
/>
164+
{isHeifFile && (
165+
<div className="absolute top-1 left-1 bg-yellow-500 text-white text-xs px-1 py-0.5 rounded">
166+
HEIF
167+
</div>
168+
)}
169+
</div>
170+
<button
171+
onClick={() => removePhoto(photo.id)}
172+
className="absolute top-1 right-1 p-1 bg-red-500 hover:bg-red-600 text-white rounded-full transition-colors duration-200 shadow-lg"
173+
>
174+
<X className="w-3 h-3" />
175+
</button>
176+
<div className="absolute bottom-1 left-1 bg-black bg-opacity-50 text-white text-xs px-1 py-0.5 rounded">
177+
#{index + 1}
178+
</div>
146179
</div>
147-
</div>
148-
))}
180+
);
181+
})}
149182
</div>
150183
</div>
151184
)}

src/types/heic2any.d.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
declare module 'heic2any' {
2+
interface ConvertOptions {
3+
blob: Blob;
4+
toType: string;
5+
quality?: number;
6+
}
7+
8+
function heic2any(options: ConvertOptions): Promise<Blob | Blob[]>;
9+
export default heic2any;
10+
}
11+
12+
declare module 'libheif-js' {
13+
interface LibHeif {
14+
HeifDecoder: any;
15+
}
16+
17+
const libheif: LibHeif;
18+
export default libheif;
19+
}

src/utils/imageOptimization.ts

Lines changed: 95 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,75 @@ export interface OptimizedPhoto {
1414
duration?: number;
1515
}
1616

17+
/**
18+
* Checks if a file is a HEIF/HEIC image
19+
*/
20+
function isHeifFile(file: File): boolean {
21+
const heifMimeTypes = ['image/heic', 'image/heif'];
22+
const heifExtensions = ['.heic', '.heif'];
23+
24+
// Check MIME type
25+
if (heifMimeTypes.includes(file.type.toLowerCase())) {
26+
return true;
27+
}
28+
29+
// Check file extension as fallback
30+
const fileName = file.name.toLowerCase();
31+
return heifExtensions.some(ext => fileName.endsWith(ext));
32+
}
33+
34+
/**
35+
* Creates a placeholder image for failed HEIF conversions
36+
*/
37+
function createHeifPlaceholderImage(fileName: string): Blob {
38+
const canvas = document.createElement('canvas');
39+
const ctx = canvas.getContext('2d');
40+
41+
// Set canvas size
42+
canvas.width = 400;
43+
canvas.height = 400;
44+
45+
if (ctx) {
46+
// Draw a gray background
47+
ctx.fillStyle = '#f3f4f6';
48+
ctx.fillRect(0, 0, canvas.width, canvas.height);
49+
50+
// Draw border
51+
ctx.strokeStyle = '#d1d5db';
52+
ctx.lineWidth = 2;
53+
ctx.strokeRect(10, 10, canvas.width - 20, canvas.height - 20);
54+
55+
// Draw text
56+
ctx.fillStyle = '#6b7280';
57+
ctx.font = 'bold 16px Arial';
58+
ctx.textAlign = 'center';
59+
ctx.fillText('HEIF Format', canvas.width / 2, 150);
60+
ctx.fillText('Not Supported', canvas.width / 2, 180);
61+
62+
ctx.font = '12px Arial';
63+
ctx.fillText('Please convert to JPEG', canvas.width / 2, 220);
64+
ctx.fillText('and re-upload', canvas.width / 2, 240);
65+
66+
// Draw file name (truncated if too long)
67+
const displayName = fileName.length > 30 ? fileName.substring(0, 27) + '...' : fileName;
68+
ctx.font = '10px Arial';
69+
ctx.fillText(displayName, canvas.width / 2, 280);
70+
}
71+
72+
// Convert to blob synchronously
73+
const dataUrl = canvas.toDataURL('image/jpeg', 0.8);
74+
const byteString = atob(dataUrl.split(',')[1]);
75+
const mimeString = dataUrl.split(',')[0].split(':')[1].split(';')[0];
76+
const ab = new ArrayBuffer(byteString.length);
77+
const ia = new Uint8Array(ab);
78+
79+
for (let i = 0; i < byteString.length; i++) {
80+
ia[i] = byteString.charCodeAt(i);
81+
}
82+
83+
return new Blob([ab], { type: mimeString });
84+
}
85+
1786
/**
1887
* Creates a thumbnail version of an image file
1988
*/
@@ -132,22 +201,44 @@ export function createVideoThumbnail(file: File, maxSize: number = 400, seekTime
132201
*/
133202
export async function createOptimizedPhotos(files: File[]): Promise<OptimizedPhoto[]> {
134203
const promises = files.map(async (file) => {
204+
const id = Math.random().toString(36).substr(2, 9);
205+
206+
// Check for HEIF files and immediately create placeholder (no conversion attempt)
207+
if (isHeifFile(file)) {
208+
console.log(`HEIF file detected: ${file.name} - Creating placeholder (no conversion attempted)`);
209+
210+
const placeholderBlob = createHeifPlaceholderImage(file.name);
211+
const placeholderUrl = URL.createObjectURL(placeholderBlob);
212+
213+
return {
214+
id,
215+
file: new File([placeholderBlob], file.name.replace(/\.(heic|heif)$/i, '_heif_not_supported.jpg'), {
216+
type: 'image/jpeg',
217+
lastModified: file.lastModified
218+
}),
219+
url: placeholderUrl,
220+
thumbnailUrl: placeholderUrl,
221+
name: file.name + ' (HEIF not supported)',
222+
type: 'image' as const
223+
};
224+
}
225+
226+
// Process regular image/video files
135227
const isImage = file.type.startsWith('image/');
136228
const isVideo = file.type.startsWith('video/');
137229

138230
if (!isImage && !isVideo) {
139231
throw new Error(`Invalid file type: ${file.type}`);
140232
}
141233

142-
const id = Math.random().toString(36).substr(2, 9);
143234
const url = URL.createObjectURL(file);
144235

145236
try {
146237
if (isImage) {
147238
const thumbnailUrl = await createThumbnail(file, 400);
148239
return {
149240
id,
150-
file,
241+
file: file,
151242
url,
152243
thumbnailUrl,
153244
name: file.name,
@@ -158,7 +249,7 @@ export async function createOptimizedPhotos(files: File[]): Promise<OptimizedPho
158249
const { thumbnailUrl, duration } = await createVideoThumbnail(file, 400);
159250
return {
160251
id,
161-
file,
252+
file: file,
162253
url,
163254
thumbnailUrl,
164255
name: file.name,
@@ -171,7 +262,7 @@ export async function createOptimizedPhotos(files: File[]): Promise<OptimizedPho
171262
console.warn('Thumbnail creation failed for', file.name, error);
172263
return {
173264
id,
174-
file,
265+
file: file,
175266
url,
176267
thumbnailUrl: url,
177268
name: file.name,

0 commit comments

Comments
 (0)