@visns-studio/visns-components 5.15.9 → 5.15.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +40 -2
- package/package.json +1 -1
- package/src/components/Breadcrumb.jsx +19 -11
- package/src/components/CameraPlacement.jsx +292 -2
- package/src/components/Form.jsx +240 -95
- package/src/components/generic/GenericReport.jsx +379 -5
- package/src/components/oauth/DataPreviewModal.jsx +372 -0
- package/src/components/oauth/DataSyncWizard.jsx +850 -0
- package/src/components/oauth/OAuthConnectionStatus.jsx +85 -0
- package/src/components/oauth/OAuthIntegrationsPage.jsx +185 -0
- package/src/components/oauth/OAuthManager.jsx +260 -0
- package/src/components/oauth/OAuthProviderCard.jsx +291 -0
- package/src/components/styles/CameraPlacement.module.scss +13 -2
- package/src/components/styles/DataPreviewModal.module.scss +658 -0
- package/src/components/styles/DataSyncWizard.module.scss +1211 -0
- package/src/components/styles/GenericReport.module.scss +342 -0
- package/src/components/styles/OAuthConnectionStatus.module.scss +143 -0
- package/src/components/styles/OAuthManager.module.scss +119 -0
- package/src/components/styles/OAuthProviderCard.module.scss +760 -0
- package/src/index.js +12 -0
package/README.md
CHANGED
|
@@ -9,10 +9,11 @@ A comprehensive React component library used by the VISNS Studio team for CRM an
|
|
|
9
9
|
|
|
10
10
|
VISNS Components is a React-based UI component library that provides a set of reusable, consistent, and customizable components for building web applications. It includes components for authentication, data grids, forms, navigation, and more, designed to work seamlessly together.
|
|
11
11
|
|
|
12
|
-
## Recent Updates (v5.15.
|
|
12
|
+
## Recent Updates (v5.15.10)
|
|
13
13
|
|
|
14
14
|
### Latest Enhancements
|
|
15
15
|
|
|
16
|
+
- **CameraPlacement Legacy Import**: Added comprehensive legacy JSON file import functionality with support for multiple data formats, automatic image loading, and seamless database integration
|
|
16
17
|
- **CameraPlacement Module**: Enhanced interactive camera placement tool with improved UI consistency, better icon visibility, drag-and-drop image upload, professional export capabilities, and complete Verkada database integration
|
|
17
18
|
- **Complete StandardModal Migration**: All components now use StandardModal instead of reactjs-popup for consistent modal behavior across the entire library
|
|
18
19
|
- **HTML Content Support**: Terms and conditions now properly parse HTML content from TinyMCE editors with clean rendering
|
|
@@ -2461,6 +2462,15 @@ import { CameraPlacement } from 'visns-components';
|
|
|
2461
2462
|
<CameraPlacement />
|
|
2462
2463
|
```
|
|
2463
2464
|
|
|
2465
|
+
**Legacy Import Workflow:**
|
|
2466
|
+
1. **Click Import Button**: Orange folder icon (📁) in the header controls
|
|
2467
|
+
2. **Select JSON File**: Choose legacy camera placement JSON file (.json extension)
|
|
2468
|
+
3. **Automatic Processing**: Smart parser handles multiple formats automatically
|
|
2469
|
+
4. **Import Validation**: Real-time validation with detailed error messages if needed
|
|
2470
|
+
5. **Data Loading**: Cameras, background images, and project metadata loaded instantly
|
|
2471
|
+
6. **Review & Adjust**: Fine-tune camera positions if coordinate scaling differs
|
|
2472
|
+
7. **Save to Database**: Use existing save functionality to persist imported data
|
|
2473
|
+
|
|
2464
2474
|
**Key Features:**
|
|
2465
2475
|
- **Enhanced User Interface**: Modern icon-based controls with tooltips, consistent color theming using project CSS variables
|
|
2466
2476
|
- **Dual Mode Support**: Switch between Mapbox map integration and image-based placement with seamless transitions
|
|
@@ -2470,10 +2480,14 @@ import { CameraPlacement } from 'visns-components';
|
|
|
2470
2480
|
- **FOV Visualization**: Intelligent field-of-view triangles with inverse distance scaling for realistic coverage display
|
|
2471
2481
|
- **Interactive Placement**: Drag-and-drop camera positioning with rotation handles for directional adjustment
|
|
2472
2482
|
- **Save/Load Projects**: Export/import camera placements as JSON files with project metadata and timestamps
|
|
2483
|
+
- **Legacy JSON Import**: One-click import of legacy camera placement files with automatic format detection and base64 image support
|
|
2473
2484
|
- **Professional Exports**: Generate high-quality PNG reports with side-by-side camera specifications and installation details
|
|
2474
2485
|
- **SweetAlert Integration**: Professional modal dialogs for confirmations, error handling, and success notifications
|
|
2475
2486
|
|
|
2476
|
-
**Recent Improvements (v5.15.
|
|
2487
|
+
**Recent Improvements (v5.15.10):**
|
|
2488
|
+
- **Legacy JSON Import**: Comprehensive import functionality supporting multiple legacy data formats with automatic image loading and base64 processing
|
|
2489
|
+
- **Enhanced Controls Layout**: Expanded controls container to accommodate new import button with proper spacing and responsive design
|
|
2490
|
+
- **Smart Data Parser**: Intelligent JSON parsing that handles 4+ different legacy format structures with fallback handling
|
|
2477
2491
|
- **Icon Visibility**: Fixed save button icon visibility with improved color contrast and explicit styling
|
|
2478
2492
|
- **UI Consistency**: All buttons now use project-standard color variables for consistent theming
|
|
2479
2493
|
- **Tooltips**: CSS-based tooltips for all control buttons with descriptive text
|
|
@@ -2488,6 +2502,30 @@ import { CameraPlacement } from 'visns-components';
|
|
|
2488
2502
|
- **Advanced Models**: Support for optical zoom, telephoto lenses, 4K resolution, and specialized security features
|
|
2489
2503
|
- **Real-time Updates**: Live camera specification display in sidebar with direction controls and coordinate tracking
|
|
2490
2504
|
|
|
2505
|
+
**Legacy Import System (v5.15.10+):**
|
|
2506
|
+
- **Multi-Format Support**: Handles 4+ different legacy JSON structures automatically with intelligent format detection
|
|
2507
|
+
- **Base64 Image Processing**: Seamless import of large images (4MB+) with base64 data URL support
|
|
2508
|
+
- **Smart Data Validation**: Comprehensive validation with user-friendly error messages and detailed debugging
|
|
2509
|
+
- **Format Flexibility**: Supports various naming conventions (`image.src`, `uploadedImage`, `backgroundImage`, etc.)
|
|
2510
|
+
- **One-Click Migration**: Import legacy projects with cameras, images, and metadata in a single operation
|
|
2511
|
+
- **Database Integration**: Imported data immediately compatible with save/load database functionality
|
|
2512
|
+
- **Visual Import Feedback**: Loading indicators, success dialogs with import summaries, and error handling
|
|
2513
|
+
|
|
2514
|
+
**Supported Legacy Formats:**
|
|
2515
|
+
```javascript
|
|
2516
|
+
// Format 1: Standard export with image object
|
|
2517
|
+
{ "image": { "src": "data:image/...", "name": "..." }, "cameras": [...] }
|
|
2518
|
+
|
|
2519
|
+
// Format 2: Nested placement structure
|
|
2520
|
+
{ "placement": { "cameras": [...], "image": "...", "projectName": "..." } }
|
|
2521
|
+
|
|
2522
|
+
// Format 3: Simple camera array
|
|
2523
|
+
[{ "id": 1, "name": "Camera 1", "x": 100, "y": 200, ... }]
|
|
2524
|
+
|
|
2525
|
+
// Format 4: Auto-detected custom structures
|
|
2526
|
+
{ "customData": { "cameraArray": [...] }, "backgroundImage": "..." }
|
|
2527
|
+
```
|
|
2528
|
+
|
|
2491
2529
|
**Technical Capabilities:**
|
|
2492
2530
|
- **Map Integration**: Mapbox GL JS with geocoding search, satellite imagery, and custom marker styling
|
|
2493
2531
|
- **Image Processing**: Support for floor plans and site diagrams with HTML5 file API and FileReader integration
|
package/package.json
CHANGED
|
@@ -94,7 +94,7 @@
|
|
|
94
94
|
"react-dom": "^17.0.0 || ^18.0.0"
|
|
95
95
|
},
|
|
96
96
|
"name": "@visns-studio/visns-components",
|
|
97
|
-
"version": "5.15.
|
|
97
|
+
"version": "5.15.11",
|
|
98
98
|
"description": "Various packages to assist in the development of our Custom Applications.",
|
|
99
99
|
"main": "src/index.js",
|
|
100
100
|
"files": [
|
|
@@ -70,9 +70,13 @@ function Breadcrumb({ data, page }) {
|
|
|
70
70
|
|
|
71
71
|
return (
|
|
72
72
|
<h1>
|
|
73
|
-
{page?.parentTitle &&
|
|
73
|
+
{page?.parentTitle && (
|
|
74
74
|
<>
|
|
75
|
-
|
|
75
|
+
{page?.parentUrl ? (
|
|
76
|
+
<Link to={page.parentUrl}>{parse(page.parentTitle)}</Link>
|
|
77
|
+
) : (
|
|
78
|
+
<span>{parse(page.parentTitle)}</span>
|
|
79
|
+
)}
|
|
76
80
|
<ChevronRight strokeWidth={2} size={18} />
|
|
77
81
|
</>
|
|
78
82
|
)}
|
|
@@ -92,15 +96,19 @@ function Breadcrumb({ data, page }) {
|
|
|
92
96
|
page &&
|
|
93
97
|
page.title && (
|
|
94
98
|
<>
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
page.
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
99
|
+
{page.previousUrl ? (
|
|
100
|
+
<Link
|
|
101
|
+
to={`${page.previousUrl}${
|
|
102
|
+
page.previousUrlKey && data[page.previousUrlKey]
|
|
103
|
+
? '/' + data[page.previousUrlKey]
|
|
104
|
+
: ''
|
|
105
|
+
}`}
|
|
106
|
+
>
|
|
107
|
+
{parse(page.title)}
|
|
108
|
+
</Link>
|
|
109
|
+
) : (
|
|
110
|
+
<span>{parse(page.title)}</span>
|
|
111
|
+
)}
|
|
104
112
|
</>
|
|
105
113
|
)
|
|
106
114
|
)}
|
|
@@ -48,6 +48,7 @@ const CameraPlacement = ({ userProfile }) => {
|
|
|
48
48
|
const geocoderRef = useRef(null);
|
|
49
49
|
const imageContainer = useRef(null);
|
|
50
50
|
const fileInputRef = useRef(null);
|
|
51
|
+
const jsonFileInputRef = useRef(null);
|
|
51
52
|
const [isDragOver, setIsDragOver] = useState(false);
|
|
52
53
|
const [isRotating, setIsRotating] = useState(false);
|
|
53
54
|
|
|
@@ -186,6 +187,282 @@ const CameraPlacement = ({ userProfile }) => {
|
|
|
186
187
|
[map]
|
|
187
188
|
);
|
|
188
189
|
|
|
190
|
+
// Handle legacy JSON file upload
|
|
191
|
+
const handleJsonFileUpload = useCallback(async (event) => {
|
|
192
|
+
const file = event.target.files[0];
|
|
193
|
+
if (!file) return;
|
|
194
|
+
|
|
195
|
+
if (!file.name.toLowerCase().endsWith('.json')) {
|
|
196
|
+
Swal.fire({
|
|
197
|
+
icon: 'error',
|
|
198
|
+
title: 'Invalid File Type',
|
|
199
|
+
text: 'Please select a JSON file (.json extension)',
|
|
200
|
+
confirmButtonColor: '#ef4444',
|
|
201
|
+
});
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
try {
|
|
206
|
+
const text = await file.text();
|
|
207
|
+
const jsonData = JSON.parse(text);
|
|
208
|
+
|
|
209
|
+
// Show loading indicator
|
|
210
|
+
Swal.fire({
|
|
211
|
+
title: 'Processing Legacy Data...',
|
|
212
|
+
text: 'Converting and validating JSON data',
|
|
213
|
+
allowOutsideClick: false,
|
|
214
|
+
showConfirmButton: false,
|
|
215
|
+
didOpen: () => {
|
|
216
|
+
Swal.showLoading();
|
|
217
|
+
}
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
// Process and validate the JSON data
|
|
221
|
+
const result = await processLegacyJsonData(jsonData);
|
|
222
|
+
|
|
223
|
+
Swal.close();
|
|
224
|
+
|
|
225
|
+
if (result.success) {
|
|
226
|
+
// Load the processed data
|
|
227
|
+
setProjectName(result.data.projectName);
|
|
228
|
+
setMode(result.data.mode);
|
|
229
|
+
setCameras(result.data.cameras);
|
|
230
|
+
|
|
231
|
+
if (result.data.uploadedImage) {
|
|
232
|
+
setUploadedImage(result.data.uploadedImage);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// Clear the selected project since this is new legacy data
|
|
236
|
+
setSelectedProject(null);
|
|
237
|
+
setSidebarTab('cameras');
|
|
238
|
+
|
|
239
|
+
Swal.fire({
|
|
240
|
+
icon: 'success',
|
|
241
|
+
title: 'Legacy Data Loaded!',
|
|
242
|
+
html: `
|
|
243
|
+
<div style="text-align: left;">
|
|
244
|
+
<p><strong>Successfully imported:</strong></p>
|
|
245
|
+
<ul style="margin: 10px 0; padding-left: 20px;">
|
|
246
|
+
<li>Project: "${result.data.projectName}"</li>
|
|
247
|
+
<li>Cameras: ${result.data.cameras.length}</li>
|
|
248
|
+
<li>Mode: ${result.data.mode}</li>
|
|
249
|
+
${result.data.uploadedImage ? '<li>Image: Yes</li>' : '<li>Image: No</li>'}
|
|
250
|
+
</ul>
|
|
251
|
+
<p style="color: #6b7280; font-size: 14px; margin-top: 15px;">
|
|
252
|
+
<strong>Next steps:</strong> Review the imported data and save it to the database using the Save button.
|
|
253
|
+
</p>
|
|
254
|
+
</div>
|
|
255
|
+
`,
|
|
256
|
+
confirmButtonColor: '#3b82f6',
|
|
257
|
+
confirmButtonText: 'Continue',
|
|
258
|
+
});
|
|
259
|
+
} else {
|
|
260
|
+
throw new Error(result.error);
|
|
261
|
+
}
|
|
262
|
+
} catch (error) {
|
|
263
|
+
Swal.fire({
|
|
264
|
+
icon: 'error',
|
|
265
|
+
title: 'Import Failed',
|
|
266
|
+
html: `
|
|
267
|
+
<div style="text-align: left;">
|
|
268
|
+
<p>Failed to import legacy JSON file:</p>
|
|
269
|
+
<div style="background: #f3f4f6; padding: 10px; border-radius: 4px; margin: 10px 0; font-family: monospace; font-size: 12px; max-height: 150px; overflow-y: auto;">
|
|
270
|
+
${error.message}
|
|
271
|
+
</div>
|
|
272
|
+
<p style="color: #6b7280; font-size: 14px;">
|
|
273
|
+
Please check that the JSON file contains valid camera placement data.
|
|
274
|
+
</p>
|
|
275
|
+
</div>
|
|
276
|
+
`,
|
|
277
|
+
confirmButtonColor: '#ef4444',
|
|
278
|
+
});
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
// Clear the file input
|
|
282
|
+
event.target.value = '';
|
|
283
|
+
}, []);
|
|
284
|
+
|
|
285
|
+
// Process legacy JSON data and convert to current format
|
|
286
|
+
const processLegacyJsonData = useCallback(async (jsonData) => {
|
|
287
|
+
try {
|
|
288
|
+
// Validate basic structure
|
|
289
|
+
if (!jsonData || typeof jsonData !== 'object') {
|
|
290
|
+
throw new Error('Invalid JSON structure - expected an object');
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
let cameras = [];
|
|
294
|
+
let projectName = '';
|
|
295
|
+
let mode = 'image';
|
|
296
|
+
let uploadedImage = null;
|
|
297
|
+
|
|
298
|
+
// Handle different legacy JSON formats
|
|
299
|
+
|
|
300
|
+
// Format 1: Direct camera placement export with cameras array
|
|
301
|
+
if (jsonData.cameras && Array.isArray(jsonData.cameras)) {
|
|
302
|
+
cameras = jsonData.cameras.map((camera, index) => {
|
|
303
|
+
// Ensure required fields exist with fallbacks
|
|
304
|
+
const id = camera.id || Date.now() + index;
|
|
305
|
+
const name = camera.name || camera.customName || `Camera ${index + 1}`;
|
|
306
|
+
const x = typeof camera.x === 'number' ? camera.x : (camera.position?.x || 0);
|
|
307
|
+
const y = typeof camera.y === 'number' ? camera.y : (camera.position?.y || 0);
|
|
308
|
+
const direction = typeof camera.direction === 'number' ? camera.direction : (camera.rotation || 0);
|
|
309
|
+
|
|
310
|
+
return {
|
|
311
|
+
id: id,
|
|
312
|
+
name: name,
|
|
313
|
+
customName: camera.customName || '',
|
|
314
|
+
x: x,
|
|
315
|
+
y: y,
|
|
316
|
+
direction: direction,
|
|
317
|
+
verkadaModel: camera.verkadaModel || camera.model || null,
|
|
318
|
+
timestamp: camera.timestamp || new Date().toISOString(),
|
|
319
|
+
};
|
|
320
|
+
});
|
|
321
|
+
|
|
322
|
+
projectName = jsonData.projectName || jsonData.name || jsonData.title || 'Imported Legacy Project';
|
|
323
|
+
mode = jsonData.mode || 'image';
|
|
324
|
+
|
|
325
|
+
// Handle different image data formats - extract src string directly
|
|
326
|
+
if (jsonData.image && jsonData.image.src) {
|
|
327
|
+
// Format: { "image": { "src": "data:image/png;base64,..." } }
|
|
328
|
+
uploadedImage = jsonData.image.src;
|
|
329
|
+
} else if (jsonData.uploadedImage && jsonData.uploadedImage.src) {
|
|
330
|
+
// Format: { "uploadedImage": { "src": "...", "name": "..." } }
|
|
331
|
+
uploadedImage = jsonData.uploadedImage.src;
|
|
332
|
+
} else if (jsonData.uploadedImage && typeof jsonData.uploadedImage === 'string') {
|
|
333
|
+
// Format: { "uploadedImage": "data:image/..." }
|
|
334
|
+
uploadedImage = jsonData.uploadedImage;
|
|
335
|
+
} else if (jsonData.backgroundImage) {
|
|
336
|
+
// Format: { "backgroundImage": "data:image/..." }
|
|
337
|
+
uploadedImage = jsonData.backgroundImage;
|
|
338
|
+
} else {
|
|
339
|
+
uploadedImage = null;
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
// Format 2: Legacy format with different structure
|
|
344
|
+
else if (jsonData.placement || jsonData.cameraPlacement) {
|
|
345
|
+
const placement = jsonData.placement || jsonData.cameraPlacement;
|
|
346
|
+
|
|
347
|
+
if (placement.cameras && Array.isArray(placement.cameras)) {
|
|
348
|
+
cameras = placement.cameras.map((camera, index) => ({
|
|
349
|
+
id: camera.id || Date.now() + index,
|
|
350
|
+
name: camera.name || `Camera ${index + 1}`,
|
|
351
|
+
customName: camera.customName || '',
|
|
352
|
+
x: camera.x || 0,
|
|
353
|
+
y: camera.y || 0,
|
|
354
|
+
direction: camera.direction || 0,
|
|
355
|
+
verkadaModel: camera.verkadaModel || null,
|
|
356
|
+
timestamp: new Date().toISOString(),
|
|
357
|
+
}));
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
projectName = placement.projectName || placement.name || 'Imported Legacy Project';
|
|
361
|
+
mode = placement.mode || 'image';
|
|
362
|
+
|
|
363
|
+
// Handle different image data formats - extract src string directly
|
|
364
|
+
if (placement.image && placement.image.src) {
|
|
365
|
+
uploadedImage = placement.image.src;
|
|
366
|
+
} else if (placement.image && typeof placement.image === 'string') {
|
|
367
|
+
uploadedImage = placement.image;
|
|
368
|
+
} else if (placement.backgroundImage) {
|
|
369
|
+
uploadedImage = placement.backgroundImage;
|
|
370
|
+
} else {
|
|
371
|
+
uploadedImage = null;
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
// Format 3: Simple array of cameras
|
|
376
|
+
else if (Array.isArray(jsonData)) {
|
|
377
|
+
cameras = jsonData.map((camera, index) => ({
|
|
378
|
+
id: camera.id || Date.now() + index,
|
|
379
|
+
name: camera.name || `Camera ${index + 1}`,
|
|
380
|
+
customName: camera.customName || '',
|
|
381
|
+
x: camera.x || 0,
|
|
382
|
+
y: camera.y || 0,
|
|
383
|
+
direction: camera.direction || 0,
|
|
384
|
+
verkadaModel: camera.verkadaModel || null,
|
|
385
|
+
timestamp: new Date().toISOString(),
|
|
386
|
+
}));
|
|
387
|
+
|
|
388
|
+
projectName = 'Imported Legacy Project';
|
|
389
|
+
mode = 'image';
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
// Format 4: Check for other common legacy formats
|
|
393
|
+
else {
|
|
394
|
+
// Look for any array that might contain camera data
|
|
395
|
+
const possibleCameraArrays = Object.values(jsonData).filter(value =>
|
|
396
|
+
Array.isArray(value) && value.length > 0 &&
|
|
397
|
+
value[0] && typeof value[0] === 'object'
|
|
398
|
+
);
|
|
399
|
+
|
|
400
|
+
if (possibleCameraArrays.length > 0) {
|
|
401
|
+
const cameraArray = possibleCameraArrays[0];
|
|
402
|
+
cameras = cameraArray.map((camera, index) => ({
|
|
403
|
+
id: camera.id || Date.now() + index,
|
|
404
|
+
name: camera.name || `Camera ${index + 1}`,
|
|
405
|
+
customName: camera.customName || '',
|
|
406
|
+
x: camera.x || 0,
|
|
407
|
+
y: camera.y || 0,
|
|
408
|
+
direction: camera.direction || 0,
|
|
409
|
+
verkadaModel: camera.verkadaModel || null,
|
|
410
|
+
timestamp: new Date().toISOString(),
|
|
411
|
+
}));
|
|
412
|
+
|
|
413
|
+
projectName = jsonData.name || jsonData.title || 'Imported Legacy Project';
|
|
414
|
+
mode = jsonData.mode || 'image';
|
|
415
|
+
|
|
416
|
+
// Handle different image data formats - extract src string directly
|
|
417
|
+
if (jsonData.image && jsonData.image.src) {
|
|
418
|
+
uploadedImage = jsonData.image.src;
|
|
419
|
+
} else if (jsonData.image && typeof jsonData.image === 'string') {
|
|
420
|
+
uploadedImage = jsonData.image;
|
|
421
|
+
} else if (jsonData.backgroundImage) {
|
|
422
|
+
uploadedImage = jsonData.backgroundImage;
|
|
423
|
+
} else {
|
|
424
|
+
uploadedImage = null;
|
|
425
|
+
}
|
|
426
|
+
} else {
|
|
427
|
+
throw new Error('No valid camera data found in JSON file. Expected arrays of camera objects.');
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
// Validate that we have at least some cameras
|
|
432
|
+
if (!cameras || cameras.length === 0) {
|
|
433
|
+
throw new Error('No cameras found in the JSON file');
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
// Validate camera data
|
|
437
|
+
const invalidCameras = cameras.filter(camera =>
|
|
438
|
+
typeof camera.x !== 'number' ||
|
|
439
|
+
typeof camera.y !== 'number' ||
|
|
440
|
+
isNaN(camera.x) ||
|
|
441
|
+
isNaN(camera.y)
|
|
442
|
+
);
|
|
443
|
+
|
|
444
|
+
if (invalidCameras.length > 0) {
|
|
445
|
+
throw new Error(`${invalidCameras.length} cameras have invalid position data (x, y coordinates must be numbers)`);
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
return {
|
|
449
|
+
success: true,
|
|
450
|
+
data: {
|
|
451
|
+
projectName: projectName,
|
|
452
|
+
mode: mode,
|
|
453
|
+
cameras: cameras,
|
|
454
|
+
uploadedImage: uploadedImage
|
|
455
|
+
}
|
|
456
|
+
};
|
|
457
|
+
|
|
458
|
+
} catch (error) {
|
|
459
|
+
return {
|
|
460
|
+
success: false,
|
|
461
|
+
error: error.message
|
|
462
|
+
};
|
|
463
|
+
}
|
|
464
|
+
}, []);
|
|
465
|
+
|
|
189
466
|
// Initialize Mapbox GL map
|
|
190
467
|
useEffect(() => {
|
|
191
468
|
if (!mapContainer.current || mode !== 'map') return;
|
|
@@ -575,7 +852,7 @@ const CameraPlacement = ({ userProfile }) => {
|
|
|
575
852
|
const loadProject = useCallback(async (lightweightProject) => {
|
|
576
853
|
try {
|
|
577
854
|
// Show loading indicator
|
|
578
|
-
|
|
855
|
+
Swal.fire({
|
|
579
856
|
title: 'Loading Project...',
|
|
580
857
|
text: 'Fetching project data',
|
|
581
858
|
allowOutsideClick: false,
|
|
@@ -1194,6 +1471,12 @@ const CameraPlacement = ({ userProfile }) => {
|
|
|
1194
1471
|
tooltip="Upload Image"
|
|
1195
1472
|
className={styles.uploadBtn}
|
|
1196
1473
|
/>
|
|
1474
|
+
<IconButton
|
|
1475
|
+
icon={FolderOpen}
|
|
1476
|
+
onClick={() => jsonFileInputRef.current?.click()}
|
|
1477
|
+
tooltip="Import Legacy JSON File"
|
|
1478
|
+
className={styles.importBtn}
|
|
1479
|
+
/>
|
|
1197
1480
|
<IconButton
|
|
1198
1481
|
icon={Save}
|
|
1199
1482
|
onClick={saveToDatabase}
|
|
@@ -2289,7 +2572,7 @@ const CameraPlacement = ({ userProfile }) => {
|
|
|
2289
2572
|
</div>
|
|
2290
2573
|
</div>
|
|
2291
2574
|
|
|
2292
|
-
{/* Hidden file
|
|
2575
|
+
{/* Hidden file inputs */}
|
|
2293
2576
|
<input
|
|
2294
2577
|
type="file"
|
|
2295
2578
|
ref={fileInputRef}
|
|
@@ -2297,6 +2580,13 @@ const CameraPlacement = ({ userProfile }) => {
|
|
|
2297
2580
|
accept="image/*"
|
|
2298
2581
|
style={{ display: 'none' }}
|
|
2299
2582
|
/>
|
|
2583
|
+
<input
|
|
2584
|
+
type="file"
|
|
2585
|
+
ref={jsonFileInputRef}
|
|
2586
|
+
onChange={handleJsonFileUpload}
|
|
2587
|
+
accept=".json,application/json"
|
|
2588
|
+
style={{ display: 'none' }}
|
|
2589
|
+
/>
|
|
2300
2590
|
|
|
2301
2591
|
{showModal && selectedCamera && (
|
|
2302
2592
|
<CameraSelectionModal
|