MogensR commited on
Commit
e510a3d
·
1 Parent(s): e6e3409

Create examples/javascript/browser/react_components.jsx

Browse files
examples/javascript/browser/react_components.jsx ADDED
@@ -0,0 +1,475 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * BackgroundFX Pro - React Component Example
3
+ *
4
+ * A complete React component for background removal with drag-and-drop,
5
+ * progress tracking, and result preview.
6
+ */
7
+
8
+ import React, { useState, useCallback, useRef } from 'react';
9
+ import axios from 'axios';
10
+
11
+ // Configuration
12
+ const API_BASE_URL = process.env.REACT_APP_BACKGROUNDFX_API_URL || 'https://api.backgroundfx.pro/v1';
13
+ const API_KEY = process.env.REACT_APP_BACKGROUNDFX_API_KEY;
14
+
15
+ /**
16
+ * BackgroundFX API Client Hook
17
+ */
18
+ const useBackgroundFX = (apiKey) => {
19
+ const [isProcessing, setIsProcessing] = useState(false);
20
+ const [progress, setProgress] = useState(0);
21
+ const [error, setError] = useState(null);
22
+
23
+ const client = useRef(
24
+ axios.create({
25
+ baseURL: API_BASE_URL,
26
+ headers: {
27
+ 'Authorization': `Bearer ${apiKey}`,
28
+ },
29
+ })
30
+ );
31
+
32
+ const removeBackground = useCallback(async (file, options = {}) => {
33
+ setIsProcessing(true);
34
+ setError(null);
35
+ setProgress(0);
36
+
37
+ const formData = new FormData();
38
+ formData.append('file', file);
39
+ Object.keys(options).forEach(key => {
40
+ formData.append(key, options[key]);
41
+ });
42
+
43
+ try {
44
+ const response = await client.current.post('/process/remove-background', formData, {
45
+ onUploadProgress: (progressEvent) => {
46
+ const percentCompleted = Math.round((progressEvent.loaded * 100) / progressEvent.total);
47
+ setProgress(percentCompleted);
48
+ },
49
+ });
50
+
51
+ setIsProcessing(false);
52
+ setProgress(100);
53
+ return response.data;
54
+ } catch (err) {
55
+ setError(err.response?.data?.message || err.message);
56
+ setIsProcessing(false);
57
+ throw err;
58
+ }
59
+ }, []);
60
+
61
+ return {
62
+ removeBackground,
63
+ isProcessing,
64
+ progress,
65
+ error,
66
+ };
67
+ };
68
+
69
+ /**
70
+ * Drag and Drop Zone Component
71
+ */
72
+ const DropZone = ({ onDrop, disabled }) => {
73
+ const [isDragging, setIsDragging] = useState(false);
74
+ const fileInputRef = useRef(null);
75
+
76
+ const handleDragEnter = useCallback((e) => {
77
+ e.preventDefault();
78
+ e.stopPropagation();
79
+ setIsDragging(true);
80
+ }, []);
81
+
82
+ const handleDragLeave = useCallback((e) => {
83
+ e.preventDefault();
84
+ e.stopPropagation();
85
+ setIsDragging(false);
86
+ }, []);
87
+
88
+ const handleDragOver = useCallback((e) => {
89
+ e.preventDefault();
90
+ e.stopPropagation();
91
+ }, []);
92
+
93
+ const handleDrop = useCallback((e) => {
94
+ e.preventDefault();
95
+ e.stopPropagation();
96
+ setIsDragging(false);
97
+
98
+ const files = e.dataTransfer.files;
99
+ if (files && files[0]) {
100
+ onDrop(files[0]);
101
+ }
102
+ }, [onDrop]);
103
+
104
+ const handleFileSelect = useCallback((e) => {
105
+ const files = e.target.files;
106
+ if (files && files[0]) {
107
+ onDrop(files[0]);
108
+ }
109
+ }, [onDrop]);
110
+
111
+ return (
112
+ <div
113
+ className={`dropzone ${isDragging ? 'dragging' : ''} ${disabled ? 'disabled' : ''}`}
114
+ onDragEnter={handleDragEnter}
115
+ onDragLeave={handleDragLeave}
116
+ onDragOver={handleDragOver}
117
+ onDrop={handleDrop}
118
+ onClick={() => !disabled && fileInputRef.current?.click()}
119
+ style={{
120
+ border: '2px dashed #ccc',
121
+ borderRadius: '8px',
122
+ padding: '40px',
123
+ textAlign: 'center',
124
+ cursor: disabled ? 'not-allowed' : 'pointer',
125
+ backgroundColor: isDragging ? '#f0f0f0' : 'white',
126
+ transition: 'all 0.3s ease',
127
+ }}
128
+ >
129
+ <input
130
+ ref={fileInputRef}
131
+ type="file"
132
+ accept="image/*"
133
+ onChange={handleFileSelect}
134
+ style={{ display: 'none' }}
135
+ disabled={disabled}
136
+ />
137
+
138
+ <div className="dropzone-content">
139
+ <svg width="64" height="64" viewBox="0 0 24 24" fill="none" stroke="currentColor">
140
+ <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
141
+ <polyline points="17 8 12 3 7 8" />
142
+ <line x1="12" y1="3" x2="12" y2="15" />
143
+ </svg>
144
+ <p style={{ marginTop: '16px', fontSize: '18px', fontWeight: '500' }}>
145
+ {disabled ? 'Processing...' : 'Drop image here or click to browse'}
146
+ </p>
147
+ <p style={{ marginTop: '8px', fontSize: '14px', color: '#666' }}>
148
+ Supports PNG, JPG, WebP (max 50MB)
149
+ </p>
150
+ </div>
151
+ </div>
152
+ );
153
+ };
154
+
155
+ /**
156
+ * Image Preview Component
157
+ */
158
+ const ImagePreview = ({ originalUrl, processedUrl, maskUrl }) => {
159
+ const [viewMode, setViewMode] = useState('processed');
160
+
161
+ return (
162
+ <div className="image-preview" style={{ marginTop: '20px' }}>
163
+ <div className="preview-controls" style={{ marginBottom: '16px' }}>
164
+ <button
165
+ onClick={() => setViewMode('original')}
166
+ style={{
167
+ padding: '8px 16px',
168
+ marginRight: '8px',
169
+ backgroundColor: viewMode === 'original' ? '#007bff' : '#f0f0f0',
170
+ color: viewMode === 'original' ? 'white' : 'black',
171
+ border: 'none',
172
+ borderRadius: '4px',
173
+ cursor: 'pointer',
174
+ }}
175
+ >
176
+ Original
177
+ </button>
178
+ <button
179
+ onClick={() => setViewMode('processed')}
180
+ style={{
181
+ padding: '8px 16px',
182
+ marginRight: '8px',
183
+ backgroundColor: viewMode === 'processed' ? '#007bff' : '#f0f0f0',
184
+ color: viewMode === 'processed' ? 'white' : 'black',
185
+ border: 'none',
186
+ borderRadius: '4px',
187
+ cursor: 'pointer',
188
+ }}
189
+ >
190
+ Processed
191
+ </button>
192
+ {maskUrl && (
193
+ <button
194
+ onClick={() => setViewMode('mask')}
195
+ style={{
196
+ padding: '8px 16px',
197
+ backgroundColor: viewMode === 'mask' ? '#007bff' : '#f0f0f0',
198
+ color: viewMode === 'mask' ? 'white' : 'black',
199
+ border: 'none',
200
+ borderRadius: '4px',
201
+ cursor: 'pointer',
202
+ }}
203
+ >
204
+ Mask
205
+ </button>
206
+ )}
207
+ </div>
208
+
209
+ <div className="preview-image" style={{ position: 'relative', display: 'inline-block' }}>
210
+ {viewMode === 'original' && originalUrl && (
211
+ <img src={originalUrl} alt="Original" style={{ maxWidth: '100%', height: 'auto' }} />
212
+ )}
213
+ {viewMode === 'processed' && processedUrl && (
214
+ <img src={processedUrl} alt="Processed" style={{ maxWidth: '100%', height: 'auto' }} />
215
+ )}
216
+ {viewMode === 'mask' && maskUrl && (
217
+ <img src={maskUrl} alt="Mask" style={{ maxWidth: '100%', height: 'auto' }} />
218
+ )}
219
+ </div>
220
+ </div>
221
+ );
222
+ };
223
+
224
+ /**
225
+ * Progress Bar Component
226
+ */
227
+ const ProgressBar = ({ progress }) => {
228
+ return (
229
+ <div className="progress-bar" style={{ marginTop: '20px' }}>
230
+ <div
231
+ style={{
232
+ width: '100%',
233
+ height: '8px',
234
+ backgroundColor: '#f0f0f0',
235
+ borderRadius: '4px',
236
+ overflow: 'hidden',
237
+ }}
238
+ >
239
+ <div
240
+ style={{
241
+ width: `${progress}%`,
242
+ height: '100%',
243
+ backgroundColor: '#007bff',
244
+ transition: 'width 0.3s ease',
245
+ }}
246
+ />
247
+ </div>
248
+ <p style={{ marginTop: '8px', textAlign: 'center', fontSize: '14px' }}>
249
+ {progress}% Complete
250
+ </p>
251
+ </div>
252
+ );
253
+ };
254
+
255
+ /**
256
+ * Main BackgroundRemover Component
257
+ */
258
+ const BackgroundRemover = ({ apiKey = API_KEY }) => {
259
+ const [originalImage, setOriginalImage] = useState(null);
260
+ const [processedImage, setProcessedImage] = useState(null);
261
+ const [maskImage, setMaskImage] = useState(null);
262
+ const [selectedQuality, setSelectedQuality] = useState('high');
263
+ const [selectedModel, setSelectedModel] = useState('auto');
264
+ const [returnMask, setReturnMask] = useState(false);
265
+
266
+ const { removeBackground, isProcessing, progress, error } = useBackgroundFX(apiKey);
267
+
268
+ const handleFileDrop = useCallback(async (file) => {
269
+ // Validate file
270
+ if (!file.type.startsWith('image/')) {
271
+ alert('Please upload an image file');
272
+ return;
273
+ }
274
+
275
+ if (file.size > 50 * 1024 * 1024) {
276
+ alert('File size must be less than 50MB');
277
+ return;
278
+ }
279
+
280
+ // Create preview
281
+ const reader = new FileReader();
282
+ reader.onload = (e) => {
283
+ setOriginalImage(e.target.result);
284
+ };
285
+ reader.readAsDataURL(file);
286
+
287
+ // Process image
288
+ try {
289
+ const result = await removeBackground(file, {
290
+ quality: selectedQuality,
291
+ model: selectedModel,
292
+ return_mask: returnMask,
293
+ });
294
+
295
+ setProcessedImage(result.image);
296
+ if (result.mask) {
297
+ setMaskImage(result.mask);
298
+ }
299
+ } catch (err) {
300
+ console.error('Processing failed:', err);
301
+ }
302
+ }, [removeBackground, selectedQuality, selectedModel, returnMask]);
303
+
304
+ const handleDownload = useCallback((url, filename) => {
305
+ const link = document.createElement('a');
306
+ link.href = url;
307
+ link.download = filename;
308
+ document.body.appendChild(link);
309
+ link.click();
310
+ document.body.removeChild(link);
311
+ }, []);
312
+
313
+ const handleReset = useCallback(() => {
314
+ setOriginalImage(null);
315
+ setProcessedImage(null);
316
+ setMaskImage(null);
317
+ }, []);
318
+
319
+ return (
320
+ <div className="background-remover" style={{ maxWidth: '800px', margin: '0 auto', padding: '20px' }}>
321
+ <h1 style={{ textAlign: 'center', marginBottom: '32px' }}>
322
+ BackgroundFX Pro - React Example
323
+ </h1>
324
+
325
+ {/* Settings */}
326
+ <div className="settings" style={{ marginBottom: '24px' }}>
327
+ <div style={{ marginBottom: '16px' }}>
328
+ <label style={{ marginRight: '16px' }}>
329
+ Quality:
330
+ <select
331
+ value={selectedQuality}
332
+ onChange={(e) => setSelectedQuality(e.target.value)}
333
+ style={{ marginLeft: '8px', padding: '4px 8px' }}
334
+ >
335
+ <option value="low">Low</option>
336
+ <option value="medium">Medium</option>
337
+ <option value="high">High</option>
338
+ <option value="ultra">Ultra</option>
339
+ </select>
340
+ </label>
341
+
342
+ <label style={{ marginRight: '16px' }}>
343
+ Model:
344
+ <select
345
+ value={selectedModel}
346
+ onChange={(e) => setSelectedModel(e.target.value)}
347
+ style={{ marginLeft: '8px', padding: '4px 8px' }}
348
+ >
349
+ <option value="auto">Auto</option>
350
+ <option value="rembg">RemBG</option>
351
+ <option value="u2net">U2Net</option>
352
+ <option value="sam2">SAM2</option>
353
+ </select>
354
+ </label>
355
+
356
+ <label>
357
+ <input
358
+ type="checkbox"
359
+ checked={returnMask}
360
+ onChange={(e) => setReturnMask(e.target.checked)}
361
+ style={{ marginRight: '8px' }}
362
+ />
363
+ Return Mask
364
+ </label>
365
+ </div>
366
+ </div>
367
+
368
+ {/* Drop Zone */}
369
+ {!originalImage && (
370
+ <DropZone onDrop={handleFileDrop} disabled={isProcessing} />
371
+ )}
372
+
373
+ {/* Progress Bar */}
374
+ {isProcessing && <ProgressBar progress={progress} />}
375
+
376
+ {/* Error Message */}
377
+ {error && (
378
+ <div
379
+ style={{
380
+ marginTop: '16px',
381
+ padding: '12px',
382
+ backgroundColor: '#fee',
383
+ border: '1px solid #fcc',
384
+ borderRadius: '4px',
385
+ color: '#c00',
386
+ }}
387
+ >
388
+ Error: {error}
389
+ </div>
390
+ )}
391
+
392
+ {/* Image Preview */}
393
+ {(originalImage || processedImage) && (
394
+ <ImagePreview
395
+ originalUrl={originalImage}
396
+ processedUrl={processedImage}
397
+ maskUrl={maskImage}
398
+ />
399
+ )}
400
+
401
+ {/* Action Buttons */}
402
+ {processedImage && (
403
+ <div className="actions" style={{ marginTop: '24px', textAlign: 'center' }}>
404
+ <button
405
+ onClick={() => handleDownload(processedImage, 'processed.png')}
406
+ style={{
407
+ padding: '10px 20px',
408
+ marginRight: '8px',
409
+ backgroundColor: '#28a745',
410
+ color: 'white',
411
+ border: 'none',
412
+ borderRadius: '4px',
413
+ cursor: 'pointer',
414
+ }}
415
+ >
416
+ Download Result
417
+ </button>
418
+
419
+ {maskImage && (
420
+ <button
421
+ onClick={() => handleDownload(maskImage, 'mask.png')}
422
+ style={{
423
+ padding: '10px 20px',
424
+ marginRight: '8px',
425
+ backgroundColor: '#17a2b8',
426
+ color: 'white',
427
+ border: 'none',
428
+ borderRadius: '4px',
429
+ cursor: 'pointer',
430
+ }}
431
+ >
432
+ Download Mask
433
+ </button>
434
+ )}
435
+
436
+ <button
437
+ onClick={handleReset}
438
+ style={{
439
+ padding: '10px 20px',
440
+ backgroundColor: '#dc3545',
441
+ color: 'white',
442
+ border: 'none',
443
+ borderRadius: '4px',
444
+ cursor: 'pointer',
445
+ }}
446
+ >
447
+ Process Another
448
+ </button>
449
+ </div>
450
+ )}
451
+
452
+ {/* Instructions */}
453
+ <div className="instructions" style={{ marginTop: '40px', padding: '20px', backgroundColor: '#f8f9fa', borderRadius: '8px' }}>
454
+ <h3>How to use:</h3>
455
+ <ol>
456
+ <li>Select quality and model settings</li>
457
+ <li>Drag and drop an image or click to browse</li>
458
+ <li>Wait for processing to complete</li>
459
+ <li>Preview and download your results</li>
460
+ </ol>
461
+
462
+ <h3>Integration:</h3>
463
+ <pre style={{ backgroundColor: '#fff', padding: '12px', borderRadius: '4px', overflow: 'auto' }}>
464
+ {`npm install axios
465
+ import BackgroundRemover from './BackgroundRemover';
466
+
467
+ <BackgroundRemover apiKey="your-api-key" />`}
468
+ </pre>
469
+ </div>
470
+ </div>
471
+ );
472
+ };
473
+
474
+ export default BackgroundRemover;
475
+ export { useBackgroundFX, DropZone, ImagePreview, ProgressBar };