funuicss 3.8.2 → 3.8.4
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/css/fun.css +178 -194
- package/package.json +1 -1
- package/ui/accordion/Accordion.d.ts +56 -13
- package/ui/accordion/Accordion.js +449 -25
- package/ui/appbar/AppBar.d.ts +1 -0
- package/ui/appbar/AppBar.js +2 -2
- package/ui/flex/Flex.d.ts +1 -1
- package/ui/notification/Notification.d.ts +69 -12
- package/ui/notification/Notification.js +408 -19
- package/ui/theme/theme.js +1177 -85
package/ui/theme/theme.js
CHANGED
|
@@ -225,8 +225,27 @@ var loadThemeFromCDN = function (projectId) { return __awaiter(void 0, void 0, v
|
|
|
225
225
|
/* -------------------------------------------------------------------------- */
|
|
226
226
|
/* BUCKET JSON LOADER */
|
|
227
227
|
/* -------------------------------------------------------------------------- */
|
|
228
|
+
// Cache for JSON responses
|
|
229
|
+
var jsonFileCache = new Map();
|
|
230
|
+
var CACHE_DURATION = 5 * 60 * 1000; // 5 minutes
|
|
231
|
+
// Helper function to get cache key
|
|
232
|
+
var getCacheKey = function (bucketSanitizedName, projectId, page) {
|
|
233
|
+
return "".concat(projectId, ":").concat(bucketSanitizedName, ":").concat(page);
|
|
234
|
+
};
|
|
235
|
+
// Helper function to clean expired cache entries
|
|
236
|
+
var cleanExpiredCache = function () {
|
|
237
|
+
var now = Date.now();
|
|
238
|
+
for (var _i = 0, _a = Array.from(jsonFileCache.keys()); _i < _a.length; _i++) {
|
|
239
|
+
var key = _a[_i];
|
|
240
|
+
var entry = jsonFileCache.get(key);
|
|
241
|
+
if (entry && now - entry.timestamp > CACHE_DURATION) {
|
|
242
|
+
jsonFileCache.delete(key);
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
};
|
|
246
|
+
// Load JSON file with caching and performance optimizations
|
|
228
247
|
var loadBucketJsonFromCDN = function (bucketSanitizedName, projectId, page) { return __awaiter(void 0, void 0, void 0, function () {
|
|
229
|
-
var pageNumber, publicUrl, response, data, error_4;
|
|
248
|
+
var cacheKey, cached, pageNumber, publicUrl, controller_1, timeoutId, response, data, error_4;
|
|
230
249
|
return __generator(this, function (_a) {
|
|
231
250
|
switch (_a.label) {
|
|
232
251
|
case 0:
|
|
@@ -234,21 +253,42 @@ var loadBucketJsonFromCDN = function (bucketSanitizedName, projectId, page) { re
|
|
|
234
253
|
console.error('❌ Missing parameters for JSON loading');
|
|
235
254
|
return [2 /*return*/, null];
|
|
236
255
|
}
|
|
256
|
+
// Clean expired cache entries periodically
|
|
257
|
+
if (jsonFileCache.size > 100) { // Only clean when cache gets large
|
|
258
|
+
cleanExpiredCache();
|
|
259
|
+
}
|
|
260
|
+
cacheKey = getCacheKey(bucketSanitizedName, projectId, page);
|
|
261
|
+
cached = jsonFileCache.get(cacheKey);
|
|
262
|
+
if (cached && (Date.now() - cached.timestamp < CACHE_DURATION)) {
|
|
263
|
+
console.log("\uD83D\uDCE6 Returning cached data for page ".concat(page));
|
|
264
|
+
return [2 /*return*/, cached.data];
|
|
265
|
+
}
|
|
237
266
|
_a.label = 1;
|
|
238
267
|
case 1:
|
|
239
268
|
_a.trys.push([1, 6, , 7]);
|
|
240
269
|
pageNumber = page.toString();
|
|
241
270
|
publicUrl = "https://firebasestorage.googleapis.com/v0/b/funui-4bcd1.firebasestorage.app/o/projects%2F".concat(projectId, "%2Fbuckets%2F").concat(bucketSanitizedName, "%2F").concat(pageNumber, ".json?alt=media");
|
|
271
|
+
controller_1 = new AbortController();
|
|
272
|
+
timeoutId = setTimeout(function () { return controller_1.abort(); }, 10000) // 10 second timeout
|
|
273
|
+
;
|
|
242
274
|
return [4 /*yield*/, fetch(publicUrl, {
|
|
243
275
|
cache: 'no-cache',
|
|
276
|
+
signal: controller_1.signal
|
|
244
277
|
})];
|
|
245
278
|
case 2:
|
|
246
279
|
response = _a.sent();
|
|
280
|
+
clearTimeout(timeoutId);
|
|
247
281
|
if (!response.ok) return [3 /*break*/, 4];
|
|
248
|
-
return [4 /*yield*/, response.json()
|
|
282
|
+
return [4 /*yield*/, response.json()
|
|
283
|
+
// Cache the response
|
|
284
|
+
];
|
|
249
285
|
case 3:
|
|
250
286
|
data = _a.sent();
|
|
251
|
-
|
|
287
|
+
// Cache the response
|
|
288
|
+
jsonFileCache.set(cacheKey, {
|
|
289
|
+
data: data,
|
|
290
|
+
timestamp: Date.now()
|
|
291
|
+
});
|
|
252
292
|
return [2 /*return*/, data];
|
|
253
293
|
case 4:
|
|
254
294
|
// File might not exist (e.g., page out of range)
|
|
@@ -256,14 +296,41 @@ var loadBucketJsonFromCDN = function (bucketSanitizedName, projectId, page) { re
|
|
|
256
296
|
case 5: return [3 /*break*/, 7];
|
|
257
297
|
case 6:
|
|
258
298
|
error_4 = _a.sent();
|
|
259
|
-
|
|
299
|
+
if (error_4 instanceof DOMException && error_4.name === 'AbortError') {
|
|
300
|
+
console.error("\u23F0 Timeout loading JSON file for page ".concat(page));
|
|
301
|
+
}
|
|
302
|
+
else {
|
|
303
|
+
console.error("\u274C Error loading JSON file for page ".concat(page, ":"), error_4);
|
|
304
|
+
}
|
|
260
305
|
return [2 /*return*/, null];
|
|
261
306
|
case 7: return [2 /*return*/];
|
|
262
307
|
}
|
|
263
308
|
});
|
|
264
309
|
}); };
|
|
310
|
+
// Parallel loading for multiple pages
|
|
311
|
+
var loadMultipleJsonPages = function (bucketSanitizedName, projectId, pages) { return __awaiter(void 0, void 0, void 0, function () {
|
|
312
|
+
var promises, results, error_5;
|
|
313
|
+
return __generator(this, function (_a) {
|
|
314
|
+
switch (_a.label) {
|
|
315
|
+
case 0:
|
|
316
|
+
_a.trys.push([0, 2, , 3]);
|
|
317
|
+
promises = pages.map(function (page) {
|
|
318
|
+
return loadBucketJsonFromCDN(bucketSanitizedName, projectId, page);
|
|
319
|
+
});
|
|
320
|
+
return [4 /*yield*/, Promise.all(promises)];
|
|
321
|
+
case 1:
|
|
322
|
+
results = _a.sent();
|
|
323
|
+
return [2 /*return*/, results.filter(Boolean)];
|
|
324
|
+
case 2:
|
|
325
|
+
error_5 = _a.sent();
|
|
326
|
+
console.error('❌ Error loading multiple JSON pages:', error_5);
|
|
327
|
+
return [2 /*return*/, []];
|
|
328
|
+
case 3: return [2 /*return*/];
|
|
329
|
+
}
|
|
330
|
+
});
|
|
331
|
+
}); };
|
|
265
332
|
var listBucketJsonFiles = function (bucketSanitizedName, projectId) { return __awaiter(void 0, void 0, void 0, function () {
|
|
266
|
-
var files, page, hasMoreFiles, paddedPage, publicUrl,
|
|
333
|
+
var files, page, hasMoreFiles, batchSize, pageChecks, batchPromises, i, paddedPage, publicUrl, responses, i, currentPage, paddedPage, publicUrl, error_6;
|
|
267
334
|
return __generator(this, function (_a) {
|
|
268
335
|
switch (_a.label) {
|
|
269
336
|
case 0:
|
|
@@ -277,35 +344,47 @@ var listBucketJsonFiles = function (bucketSanitizedName, projectId) { return __a
|
|
|
277
344
|
files = [];
|
|
278
345
|
page = 1;
|
|
279
346
|
hasMoreFiles = true;
|
|
347
|
+
batchSize = 5;
|
|
348
|
+
pageChecks = [];
|
|
280
349
|
_a.label = 2;
|
|
281
350
|
case 2:
|
|
282
351
|
if (!(hasMoreFiles && page <= 100)) return [3 /*break*/, 4];
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
352
|
+
batchPromises = [];
|
|
353
|
+
for (i = 0; i < batchSize && page <= 100; i++) {
|
|
354
|
+
paddedPage = page.toString().padStart(3, '0');
|
|
355
|
+
publicUrl = "https://firebasestorage.googleapis.com/v0/b/funui-4bcd1.firebasestorage.app/o/projects%2F".concat(projectId, "%2Fbuckets%2F").concat(bucketSanitizedName, "%2Fpage_").concat(paddedPage, ".json?alt=media");
|
|
356
|
+
batchPromises.push(fetch(publicUrl, {
|
|
286
357
|
method: 'HEAD',
|
|
287
358
|
cache: 'no-cache',
|
|
288
|
-
})
|
|
289
|
-
case 3:
|
|
290
|
-
response = _a.sent();
|
|
291
|
-
if (response.ok) {
|
|
292
|
-
files.push({
|
|
293
|
-
name: "page_".concat(paddedPage, ".json"),
|
|
294
|
-
fullPath: "projects/".concat(projectId, "/buckets/").concat(bucketSanitizedName, "/page_").concat(paddedPage, ".json"),
|
|
295
|
-
url: publicUrl,
|
|
296
|
-
size: parseInt(response.headers.get('content-length') || '0', 10),
|
|
297
|
-
page: page
|
|
298
|
-
});
|
|
359
|
+
}));
|
|
299
360
|
page++;
|
|
300
361
|
}
|
|
301
|
-
|
|
302
|
-
|
|
362
|
+
return [4 /*yield*/, Promise.all(batchPromises)];
|
|
363
|
+
case 3:
|
|
364
|
+
responses = _a.sent();
|
|
365
|
+
for (i = 0; i < responses.length; i++) {
|
|
366
|
+
if (responses[i].ok) {
|
|
367
|
+
currentPage = page - batchSize + i;
|
|
368
|
+
paddedPage = currentPage.toString().padStart(3, '0');
|
|
369
|
+
publicUrl = "https://firebasestorage.googleapis.com/v0/b/funui-4bcd1.firebasestorage.app/o/projects%2F".concat(projectId, "%2Fbuckets%2F").concat(bucketSanitizedName, "%2Fpage_").concat(paddedPage, ".json?alt=media");
|
|
370
|
+
files.push({
|
|
371
|
+
name: "page_".concat(paddedPage, ".json"),
|
|
372
|
+
fullPath: "projects/".concat(projectId, "/buckets/").concat(bucketSanitizedName, "/page_").concat(paddedPage, ".json"),
|
|
373
|
+
url: publicUrl,
|
|
374
|
+
size: parseInt(responses[i].headers.get('content-length') || '0', 10),
|
|
375
|
+
page: currentPage
|
|
376
|
+
});
|
|
377
|
+
}
|
|
378
|
+
else {
|
|
379
|
+
hasMoreFiles = false;
|
|
380
|
+
break;
|
|
381
|
+
}
|
|
303
382
|
}
|
|
304
383
|
return [3 /*break*/, 2];
|
|
305
384
|
case 4: return [2 /*return*/, files];
|
|
306
385
|
case 5:
|
|
307
|
-
|
|
308
|
-
console.error('❌ Error listing JSON files:',
|
|
386
|
+
error_6 = _a.sent();
|
|
387
|
+
console.error('❌ Error listing JSON files:', error_6);
|
|
309
388
|
return [2 /*return*/, []];
|
|
310
389
|
case 6: return [2 /*return*/];
|
|
311
390
|
}
|
|
@@ -383,7 +462,6 @@ var sanitizeBucketName = function (name) {
|
|
|
383
462
|
var transformProjectBucket = function (bucket, projectId) {
|
|
384
463
|
return {
|
|
385
464
|
id: bucket.id || bucket._id || '',
|
|
386
|
-
// projectId: bucket.projectId || projectId || '',
|
|
387
465
|
name: bucket.name || '',
|
|
388
466
|
displayName: bucket.displayName || bucket.name || '',
|
|
389
467
|
category: bucket.category || 'uncategorized',
|
|
@@ -448,25 +526,33 @@ var ThemeProvider = function (_a) {
|
|
|
448
526
|
return __generator(this, function (_a) {
|
|
449
527
|
switch (_a.label) {
|
|
450
528
|
case 0:
|
|
451
|
-
_a.trys.push([0, 9, 10
|
|
529
|
+
_a.trys.push([0, 8, 9, 10]);
|
|
452
530
|
finalTheme = null;
|
|
453
531
|
finalVersion = null;
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
532
|
+
// If project data is provided directly, use it and skip all loading
|
|
533
|
+
if (providedProject) {
|
|
534
|
+
console.log('✅ Using provided project data directly');
|
|
535
|
+
finalTheme = providedProject;
|
|
536
|
+
finalVersion = providedProject.version || 0;
|
|
537
|
+
// Apply theme immediately
|
|
538
|
+
applyThemeData(finalTheme, root);
|
|
539
|
+
setCurrentVersion(finalVersion);
|
|
540
|
+
setError(null);
|
|
541
|
+
setIsLoading(false);
|
|
542
|
+
setIsInitialLoad(false);
|
|
543
|
+
return [2 /*return*/]; // Skip all other loading logic
|
|
544
|
+
}
|
|
545
|
+
return [4 /*yield*/, loadLocalTheme()];
|
|
546
|
+
case 1:
|
|
461
547
|
localTheme = _a.sent();
|
|
462
548
|
localVersion = (localTheme === null || localTheme === void 0 ? void 0 : localTheme.version) || 0;
|
|
463
|
-
if (!projectId) return [3 /*break*/,
|
|
549
|
+
if (!projectId) return [3 /*break*/, 6];
|
|
464
550
|
return [4 /*yield*/, validateOriginAccess(projectId)];
|
|
465
|
-
case
|
|
551
|
+
case 2:
|
|
466
552
|
hasAccess = _a.sent();
|
|
467
|
-
if (!hasAccess) return [3 /*break*/,
|
|
553
|
+
if (!hasAccess) return [3 /*break*/, 4];
|
|
468
554
|
return [4 /*yield*/, loadThemeFromCDN(projectId)];
|
|
469
|
-
case
|
|
555
|
+
case 3:
|
|
470
556
|
cdnTheme = _a.sent();
|
|
471
557
|
cdnVersion = (cdnTheme === null || cdnTheme === void 0 ? void 0 : cdnTheme.version) || 0;
|
|
472
558
|
if (cdnTheme) {
|
|
@@ -489,8 +575,8 @@ var ThemeProvider = function (_a) {
|
|
|
489
575
|
console.warn('⚠️ No theme found (CDN unavailable and no local theme)');
|
|
490
576
|
setError('Theme not found');
|
|
491
577
|
}
|
|
492
|
-
return [3 /*break*/,
|
|
493
|
-
case
|
|
578
|
+
return [3 /*break*/, 5];
|
|
579
|
+
case 4:
|
|
494
580
|
// Origin validation failed
|
|
495
581
|
if (localTheme) {
|
|
496
582
|
console.log('⚠️ Origin validation failed, using local theme');
|
|
@@ -501,9 +587,9 @@ var ThemeProvider = function (_a) {
|
|
|
501
587
|
console.error('❌ Origin validation failed and no local theme available');
|
|
502
588
|
setError('Access denied and no local theme available');
|
|
503
589
|
}
|
|
504
|
-
_a.label =
|
|
505
|
-
case
|
|
506
|
-
case
|
|
590
|
+
_a.label = 5;
|
|
591
|
+
case 5: return [3 /*break*/, 7];
|
|
592
|
+
case 6:
|
|
507
593
|
// No project ID provided - only use local theme
|
|
508
594
|
console.log('ℹ️ No project ID provided, using local theme only');
|
|
509
595
|
if (localTheme) {
|
|
@@ -515,8 +601,8 @@ var ThemeProvider = function (_a) {
|
|
|
515
601
|
console.log('ℹ️ No local theme file found - using base theme only');
|
|
516
602
|
// No error here - it's valid to use only base theme
|
|
517
603
|
}
|
|
518
|
-
_a.label =
|
|
519
|
-
case
|
|
604
|
+
_a.label = 7;
|
|
605
|
+
case 7:
|
|
520
606
|
// Apply the theme if we have one
|
|
521
607
|
if (finalTheme && (!currentVersion || finalVersion !== currentVersion)) {
|
|
522
608
|
applyThemeData(finalTheme, root);
|
|
@@ -526,33 +612,44 @@ var ThemeProvider = function (_a) {
|
|
|
526
612
|
else if (finalTheme) {
|
|
527
613
|
console.log('✓ Theme up to date');
|
|
528
614
|
}
|
|
529
|
-
return [3 /*break*/,
|
|
530
|
-
case
|
|
615
|
+
return [3 /*break*/, 10];
|
|
616
|
+
case 8:
|
|
531
617
|
err_1 = _a.sent();
|
|
532
618
|
console.error('❌ Error loading theme:', err_1);
|
|
533
619
|
setError('Failed to load theme');
|
|
534
|
-
return [3 /*break*/,
|
|
535
|
-
case
|
|
620
|
+
return [3 /*break*/, 10];
|
|
621
|
+
case 9:
|
|
536
622
|
setIsLoading(false);
|
|
537
623
|
setIsInitialLoad(false);
|
|
538
624
|
return [7 /*endfinally*/];
|
|
539
|
-
case
|
|
625
|
+
case 10: return [2 /*return*/];
|
|
540
626
|
}
|
|
541
627
|
});
|
|
542
628
|
}); };
|
|
543
|
-
//
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
if (pollTimer) {
|
|
553
|
-
clearInterval(pollTimer);
|
|
629
|
+
// Only load theme if no project is provided
|
|
630
|
+
if (!providedProject) {
|
|
631
|
+
// Initial load
|
|
632
|
+
loadTheme();
|
|
633
|
+
// Only poll for updates if we have a project ID
|
|
634
|
+
if (projectId) {
|
|
635
|
+
pollTimer = setInterval(function () {
|
|
636
|
+
loadTheme();
|
|
637
|
+
}, 5 * 60 * 1000);
|
|
554
638
|
}
|
|
555
|
-
|
|
639
|
+
return function () {
|
|
640
|
+
if (pollTimer) {
|
|
641
|
+
clearInterval(pollTimer);
|
|
642
|
+
}
|
|
643
|
+
};
|
|
644
|
+
}
|
|
645
|
+
else {
|
|
646
|
+
// If project is provided, skip loading and set state directly
|
|
647
|
+
console.log('✅ Using provided project data, skipping theme loading');
|
|
648
|
+
applyThemeData(providedProject, document.documentElement);
|
|
649
|
+
setCurrentVersion(providedProject.version || 0);
|
|
650
|
+
setIsLoading(false);
|
|
651
|
+
setIsInitialLoad(false);
|
|
652
|
+
}
|
|
556
653
|
}, [projectId, currentVersion, theme, providedProject]); // Added providedProject to dependencies
|
|
557
654
|
var applyThemeData = function (data, root) {
|
|
558
655
|
var _a;
|
|
@@ -660,7 +757,7 @@ exports.useVariable = useVariable;
|
|
|
660
757
|
var cachedAssets = [];
|
|
661
758
|
var getAsset = function (name) {
|
|
662
759
|
if (!cachedAssets.length) {
|
|
663
|
-
console.warn('No assets available. Make sure ThemeProvider is mounted
|
|
760
|
+
console.warn('No assets available. Make sure ThemeProvider is mounted.');
|
|
664
761
|
return undefined;
|
|
665
762
|
}
|
|
666
763
|
var asset = cachedAssets.find(function (a) { return a.name === name; });
|
|
@@ -943,7 +1040,7 @@ var useAllJsonRecords = function (bucketIdOrName) {
|
|
|
943
1040
|
var projectId = (0, exports.useTheme)().projectId;
|
|
944
1041
|
var _d = (0, exports.useBucketJsonFiles)(bucketIdOrName), files = _d.files, filesLoading = _d.loading;
|
|
945
1042
|
var loadAllRecords = (0, react_1.useCallback)(function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
946
|
-
var
|
|
1043
|
+
var pageNumbers, jsonDataArray, allRecords_1, err_4;
|
|
947
1044
|
return __generator(this, function (_a) {
|
|
948
1045
|
switch (_a.label) {
|
|
949
1046
|
case 0:
|
|
@@ -952,38 +1049,30 @@ var useAllJsonRecords = function (bucketIdOrName) {
|
|
|
952
1049
|
}
|
|
953
1050
|
_a.label = 1;
|
|
954
1051
|
case 1:
|
|
955
|
-
_a.trys.push([1,
|
|
1052
|
+
_a.trys.push([1, 3, 4, 5]);
|
|
956
1053
|
setLoading(true);
|
|
957
1054
|
setError(null);
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
_a.label = 2;
|
|
1055
|
+
pageNumbers = files.map(function (file) { return file.page; });
|
|
1056
|
+
return [4 /*yield*/, loadMultipleJsonPages(sanitizeBucketName(bucket.displayName || bucket.name), projectId, pageNumbers)];
|
|
961
1057
|
case 2:
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
1058
|
+
jsonDataArray = _a.sent();
|
|
1059
|
+
allRecords_1 = [];
|
|
1060
|
+
jsonDataArray.forEach(function (jsonData) {
|
|
1061
|
+
if (jsonData && jsonData.records) {
|
|
1062
|
+
allRecords_1.push.apply(allRecords_1, jsonData.records);
|
|
1063
|
+
}
|
|
1064
|
+
});
|
|
1065
|
+
setRecords(allRecords_1);
|
|
1066
|
+
return [3 /*break*/, 5];
|
|
966
1067
|
case 3:
|
|
967
|
-
jsonData = _a.sent();
|
|
968
|
-
if (jsonData && jsonData.records) {
|
|
969
|
-
allRecords.push.apply(allRecords, jsonData.records);
|
|
970
|
-
}
|
|
971
|
-
_a.label = 4;
|
|
972
|
-
case 4:
|
|
973
|
-
_i++;
|
|
974
|
-
return [3 /*break*/, 2];
|
|
975
|
-
case 5:
|
|
976
|
-
setRecords(allRecords);
|
|
977
|
-
return [3 /*break*/, 8];
|
|
978
|
-
case 6:
|
|
979
1068
|
err_4 = _a.sent();
|
|
980
1069
|
console.error('Error loading all records:', err_4);
|
|
981
1070
|
setError(err_4 instanceof Error ? err_4.message : 'Failed to load records');
|
|
982
|
-
return [3 /*break*/,
|
|
983
|
-
case
|
|
1071
|
+
return [3 /*break*/, 5];
|
|
1072
|
+
case 4:
|
|
984
1073
|
setLoading(false);
|
|
985
1074
|
return [7 /*endfinally*/];
|
|
986
|
-
case
|
|
1075
|
+
case 5: return [2 /*return*/];
|
|
987
1076
|
}
|
|
988
1077
|
});
|
|
989
1078
|
}); }, [bucket, projectId, files, filesLoading]);
|
|
@@ -1011,12 +1100,21 @@ var useBucketCache = function () {
|
|
|
1011
1100
|
var cacheKey = "".concat(bucket.id, "_").concat(projectId);
|
|
1012
1101
|
delete cachedJsonFiles[cacheKey];
|
|
1013
1102
|
delete cachedJsonData[cacheKey];
|
|
1103
|
+
// Also clear JSON file cache
|
|
1104
|
+
var bucketSanitizedName = sanitizeBucketName(bucket.displayName || bucket.name);
|
|
1105
|
+
for (var _i = 0, _a = Array.from(jsonFileCache.keys()); _i < _a.length; _i++) {
|
|
1106
|
+
var key = _a[_i];
|
|
1107
|
+
if (key.startsWith("".concat(projectId, ":").concat(bucketSanitizedName))) {
|
|
1108
|
+
jsonFileCache.delete(key);
|
|
1109
|
+
}
|
|
1110
|
+
}
|
|
1014
1111
|
}
|
|
1015
1112
|
}
|
|
1016
1113
|
}
|
|
1017
1114
|
else {
|
|
1018
1115
|
cachedJsonFiles = {};
|
|
1019
1116
|
cachedJsonData = {};
|
|
1117
|
+
jsonFileCache.clear();
|
|
1020
1118
|
}
|
|
1021
1119
|
}, []);
|
|
1022
1120
|
var refresh = (0, react_1.useCallback)(function () {
|
|
@@ -1062,3 +1160,997 @@ var getPaginatedRecords = function (bucketIdOrName, page, pageSize) { return __a
|
|
|
1062
1160
|
});
|
|
1063
1161
|
}); };
|
|
1064
1162
|
exports.getPaginatedRecords = getPaginatedRecords;
|
|
1163
|
+
// 'use client'
|
|
1164
|
+
// import React, {
|
|
1165
|
+
// useEffect,
|
|
1166
|
+
// createContext,
|
|
1167
|
+
// useContext,
|
|
1168
|
+
// useState,
|
|
1169
|
+
// ReactNode,
|
|
1170
|
+
// useMemo,
|
|
1171
|
+
// useCallback,
|
|
1172
|
+
// } from 'react'
|
|
1173
|
+
// import { colorVarsToDarken, themes } from './themes'
|
|
1174
|
+
// import { getDarkenAmount, darkenToRgba } from './darkenUtils'
|
|
1175
|
+
// /* -------------------------------------------------------------------------- */
|
|
1176
|
+
// /* TYPES */
|
|
1177
|
+
// /* -------------------------------------------------------------------------- */
|
|
1178
|
+
// export type ThemeVariant = 'standard' | 'minimal'
|
|
1179
|
+
// export type ThemeName =
|
|
1180
|
+
// | 'light'
|
|
1181
|
+
// | 'dark'
|
|
1182
|
+
// | 'dark-blue'
|
|
1183
|
+
// | 'light-gray'
|
|
1184
|
+
// | 'pastel-green'
|
|
1185
|
+
// | 'warm-orange'
|
|
1186
|
+
// | 'frosted-glass'
|
|
1187
|
+
// | 'midnight-purple'
|
|
1188
|
+
// | 'cyber-metal'
|
|
1189
|
+
// interface ThemeConfig {
|
|
1190
|
+
// [key: string]: any
|
|
1191
|
+
// }
|
|
1192
|
+
// interface Variable {
|
|
1193
|
+
// name: string
|
|
1194
|
+
// value: any
|
|
1195
|
+
// }
|
|
1196
|
+
// // Bucket Types
|
|
1197
|
+
// export interface BucketField {
|
|
1198
|
+
// name: string
|
|
1199
|
+
// label: string
|
|
1200
|
+
// type: string
|
|
1201
|
+
// required?: boolean
|
|
1202
|
+
// options?: string[]
|
|
1203
|
+
// min?: number
|
|
1204
|
+
// max?: number
|
|
1205
|
+
// multiple?: boolean
|
|
1206
|
+
// }
|
|
1207
|
+
// export interface Bucket {
|
|
1208
|
+
// id: string
|
|
1209
|
+
// name: string
|
|
1210
|
+
// displayName: string
|
|
1211
|
+
// category?: string
|
|
1212
|
+
// fields: BucketField[]
|
|
1213
|
+
// createdBy: string
|
|
1214
|
+
// createdAt: number
|
|
1215
|
+
// updatedAt: number
|
|
1216
|
+
// updatedBy: string
|
|
1217
|
+
// recordCount: number
|
|
1218
|
+
// jsonFilesCount: number
|
|
1219
|
+
// totalRecordsInJson: number
|
|
1220
|
+
// lastJsonExport?: number
|
|
1221
|
+
// userId?: string
|
|
1222
|
+
// createdAtString?: string
|
|
1223
|
+
// updatedAtString?: string
|
|
1224
|
+
// }
|
|
1225
|
+
// export interface BucketRecord {
|
|
1226
|
+
// id: string
|
|
1227
|
+
// values: Record<string, any>
|
|
1228
|
+
// createdBy: string
|
|
1229
|
+
// createdAt: number
|
|
1230
|
+
// updatedAt: number
|
|
1231
|
+
// updatedBy: string
|
|
1232
|
+
// }
|
|
1233
|
+
// export interface JsonFileMetadata {
|
|
1234
|
+
// page: number
|
|
1235
|
+
// totalPages: number
|
|
1236
|
+
// recordsInPage: number
|
|
1237
|
+
// totalRecords: number
|
|
1238
|
+
// exportedAt: string
|
|
1239
|
+
// recordsPerFile: number
|
|
1240
|
+
// projectId: string
|
|
1241
|
+
// bucketId: string
|
|
1242
|
+
// bucketName: string
|
|
1243
|
+
// }
|
|
1244
|
+
// export interface JsonFileData {
|
|
1245
|
+
// metadata: JsonFileMetadata
|
|
1246
|
+
// records: BucketRecord[]
|
|
1247
|
+
// }
|
|
1248
|
+
// export interface JsonFileInfo {
|
|
1249
|
+
// name: string
|
|
1250
|
+
// fullPath: string
|
|
1251
|
+
// url: string
|
|
1252
|
+
// size: number
|
|
1253
|
+
// page: number
|
|
1254
|
+
// }
|
|
1255
|
+
// interface ProjectData {
|
|
1256
|
+
// theme_config?: {
|
|
1257
|
+
// colors?: Record<string, string>
|
|
1258
|
+
// typography?: Record<string, string>
|
|
1259
|
+
// [key: string]: any
|
|
1260
|
+
// }
|
|
1261
|
+
// components?: Record<string, any>
|
|
1262
|
+
// default_variation?: ThemeVariant
|
|
1263
|
+
// variables?: Variable[]
|
|
1264
|
+
// assets?: Asset[]
|
|
1265
|
+
// buckets?: any[]
|
|
1266
|
+
// name?: string
|
|
1267
|
+
// project_id?: string
|
|
1268
|
+
// version?: number
|
|
1269
|
+
// updated_at?: string
|
|
1270
|
+
// trustedDomains?: Array<{
|
|
1271
|
+
// domain: string
|
|
1272
|
+
// status: string
|
|
1273
|
+
// isDefault?: boolean
|
|
1274
|
+
// }>
|
|
1275
|
+
// }
|
|
1276
|
+
// interface ThemeProviderProps {
|
|
1277
|
+
// theme: ThemeName
|
|
1278
|
+
// projectId?: string
|
|
1279
|
+
// funcss?: string
|
|
1280
|
+
// minHeight?: string
|
|
1281
|
+
// children: ReactNode
|
|
1282
|
+
// project?: ProjectData | null // New prop: directly provide project data
|
|
1283
|
+
// }
|
|
1284
|
+
// /* -------------------------------------------------------------------------- */
|
|
1285
|
+
// /* THEME CONTEXT */
|
|
1286
|
+
// /* -------------------------------------------------------------------------- */
|
|
1287
|
+
// interface ThemeContextType {
|
|
1288
|
+
// variant: ThemeVariant
|
|
1289
|
+
// setVariant: React.Dispatch<React.SetStateAction<ThemeVariant>>
|
|
1290
|
+
// themeConfig: ThemeConfig
|
|
1291
|
+
// projectData: ProjectData | null
|
|
1292
|
+
// isLoading: boolean
|
|
1293
|
+
// isInitialLoad: boolean
|
|
1294
|
+
// error: string | null
|
|
1295
|
+
// projectId?: string
|
|
1296
|
+
// }
|
|
1297
|
+
// const ThemeContext = createContext<ThemeContextType>({
|
|
1298
|
+
// variant: 'standard',
|
|
1299
|
+
// setVariant: () => {},
|
|
1300
|
+
// themeConfig: {},
|
|
1301
|
+
// projectData: null,
|
|
1302
|
+
// isLoading: true,
|
|
1303
|
+
// isInitialLoad: true,
|
|
1304
|
+
// error: null,
|
|
1305
|
+
// projectId: undefined,
|
|
1306
|
+
// })
|
|
1307
|
+
// export const useTheme = (): ThemeContextType => {
|
|
1308
|
+
// const context = useContext(ThemeContext)
|
|
1309
|
+
// if (!context) {
|
|
1310
|
+
// throw new Error('useTheme must be used within ThemeProvider')
|
|
1311
|
+
// }
|
|
1312
|
+
// return context
|
|
1313
|
+
// }
|
|
1314
|
+
// export const useVariant = () => {
|
|
1315
|
+
// const { variant, setVariant } = useTheme()
|
|
1316
|
+
// return { variant, setVariant }
|
|
1317
|
+
// }
|
|
1318
|
+
// /* -------------------------------------------------------------------------- */
|
|
1319
|
+
// /* ORIGIN VALIDATION */
|
|
1320
|
+
// /* -------------------------------------------------------------------------- */
|
|
1321
|
+
// const getCurrentOrigin = (): string => {
|
|
1322
|
+
// if (typeof window === 'undefined') return ''
|
|
1323
|
+
// // For local development, return localhost
|
|
1324
|
+
// if (window.location.hostname === 'localhost' || window.location.hostname === '127.0.0.1') {
|
|
1325
|
+
// return 'localhost'
|
|
1326
|
+
// }
|
|
1327
|
+
// // For production, return the domain without protocol and www
|
|
1328
|
+
// let domain = window.location.hostname
|
|
1329
|
+
// domain = domain.replace(/^www\./, '')
|
|
1330
|
+
// return domain
|
|
1331
|
+
// }
|
|
1332
|
+
// const validateOriginAccess = async (projectId: string): Promise<boolean> => {
|
|
1333
|
+
// if (!projectId) {
|
|
1334
|
+
// console.error('❌ No project ID provided for origin validation')
|
|
1335
|
+
// return false
|
|
1336
|
+
// }
|
|
1337
|
+
// const currentOrigin = getCurrentOrigin()
|
|
1338
|
+
// console.log(`🔍 Validating origin access for: ${currentOrigin}`)
|
|
1339
|
+
// try {
|
|
1340
|
+
// // Load project data from CDN to check trusted domains
|
|
1341
|
+
// const projectData = await loadThemeFromCDN(projectId)
|
|
1342
|
+
// if (!projectData) {
|
|
1343
|
+
// console.error('❌ Project not found or inaccessible')
|
|
1344
|
+
// return false
|
|
1345
|
+
// }
|
|
1346
|
+
// const trustedDomains = projectData.trustedDomains || []
|
|
1347
|
+
// // Check if current origin is in trusted domains
|
|
1348
|
+
// const hasAccess = trustedDomains.some(domain => {
|
|
1349
|
+
// const trustedDomain = domain.domain.toLowerCase()
|
|
1350
|
+
// const currentDomain = currentOrigin.toLowerCase()
|
|
1351
|
+
// // Exact match or subdomain match
|
|
1352
|
+
// return currentDomain === trustedDomain ||
|
|
1353
|
+
// currentDomain.endsWith('.' + trustedDomain) ||
|
|
1354
|
+
// (trustedDomain === 'localhost' && currentOrigin === 'localhost')
|
|
1355
|
+
// })
|
|
1356
|
+
// if (!hasAccess) {
|
|
1357
|
+
// console.error(`❌ Access denied: Origin "${currentOrigin}" is not in trusted domains`)
|
|
1358
|
+
// console.log('📋 Trusted domains:', trustedDomains.map(d => d.domain))
|
|
1359
|
+
// return false
|
|
1360
|
+
// }
|
|
1361
|
+
// return true
|
|
1362
|
+
// } catch (error) {
|
|
1363
|
+
// console.error('❌ Error during origin validation:', error)
|
|
1364
|
+
// return false
|
|
1365
|
+
// }
|
|
1366
|
+
// }
|
|
1367
|
+
// /* -------------------------------------------------------------------------- */
|
|
1368
|
+
// /* LOCAL FILE MANAGEMENT */
|
|
1369
|
+
// /* -------------------------------------------------------------------------- */
|
|
1370
|
+
// const loadLocalTheme = async (): Promise<ProjectData | null> => {
|
|
1371
|
+
// try {
|
|
1372
|
+
// const response = await fetch('/funui.json', {
|
|
1373
|
+
// cache: 'no-cache',
|
|
1374
|
+
// })
|
|
1375
|
+
// if (response.ok) {
|
|
1376
|
+
// const data = await response.json()
|
|
1377
|
+
// return data
|
|
1378
|
+
// }
|
|
1379
|
+
// } catch (error) {
|
|
1380
|
+
// console.log('ℹ️ No local theme file found')
|
|
1381
|
+
// }
|
|
1382
|
+
// return null
|
|
1383
|
+
// }
|
|
1384
|
+
// /* -------------------------------------------------------------------------- */
|
|
1385
|
+
// /* CDN THEME LOADER */
|
|
1386
|
+
// /* -------------------------------------------------------------------------- */
|
|
1387
|
+
// const loadThemeFromCDN = async (projectId: string): Promise<ProjectData | null> => {
|
|
1388
|
+
// if (!projectId) {
|
|
1389
|
+
// console.error('❌ No project ID provided for CDN loading')
|
|
1390
|
+
// return null
|
|
1391
|
+
// }
|
|
1392
|
+
// // Try Firebase Storage public URL
|
|
1393
|
+
// try {
|
|
1394
|
+
// const publicUrl = `https://firebasestorage.googleapis.com/v0/b/funui-4bcd1.firebasestorage.app/o/themes%2F${projectId}.json?alt=media`
|
|
1395
|
+
// const response = await fetch(publicUrl, {
|
|
1396
|
+
// cache: 'no-cache',
|
|
1397
|
+
// })
|
|
1398
|
+
// if (response.ok) {
|
|
1399
|
+
// const data = await response.json()
|
|
1400
|
+
// return data
|
|
1401
|
+
// } else {
|
|
1402
|
+
// console.error('❌ Firebase Storage fetch failed:', response.status, response.statusText)
|
|
1403
|
+
// }
|
|
1404
|
+
// } catch (error) {
|
|
1405
|
+
// console.error('❌ Error loading from Firebase Storage:', error)
|
|
1406
|
+
// }
|
|
1407
|
+
// return null
|
|
1408
|
+
// }
|
|
1409
|
+
// /* -------------------------------------------------------------------------- */
|
|
1410
|
+
// /* BUCKET JSON LOADER */
|
|
1411
|
+
// /* -------------------------------------------------------------------------- */
|
|
1412
|
+
// const loadBucketJsonFromCDN = async (
|
|
1413
|
+
// bucketSanitizedName: string,
|
|
1414
|
+
// projectId: string,
|
|
1415
|
+
// page: number
|
|
1416
|
+
// ): Promise<JsonFileData | null> => {
|
|
1417
|
+
// if (!bucketSanitizedName || !projectId) {
|
|
1418
|
+
// console.error('❌ Missing parameters for JSON loading')
|
|
1419
|
+
// return null
|
|
1420
|
+
// }
|
|
1421
|
+
// try {
|
|
1422
|
+
// // Construct the URL for JSON files
|
|
1423
|
+
// const pageNumber = page.toString()
|
|
1424
|
+
// const publicUrl = `https://firebasestorage.googleapis.com/v0/b/funui-4bcd1.firebasestorage.app/o/projects%2F${projectId}%2Fbuckets%2F${bucketSanitizedName}%2F${pageNumber}.json?alt=media`
|
|
1425
|
+
// const response = await fetch(publicUrl, {
|
|
1426
|
+
// cache: 'no-cache',
|
|
1427
|
+
// })
|
|
1428
|
+
// if (response.ok) {
|
|
1429
|
+
// const data = await response.json()
|
|
1430
|
+
// console.log(data)
|
|
1431
|
+
// return data
|
|
1432
|
+
// } else {
|
|
1433
|
+
// // File might not exist (e.g., page out of range)
|
|
1434
|
+
// return null
|
|
1435
|
+
// }
|
|
1436
|
+
// } catch (error) {
|
|
1437
|
+
// console.error(`❌ Error loading JSON file for page ${page}:`, error)
|
|
1438
|
+
// return null
|
|
1439
|
+
// }
|
|
1440
|
+
// }
|
|
1441
|
+
// const listBucketJsonFiles = async (
|
|
1442
|
+
// bucketSanitizedName: string,
|
|
1443
|
+
// projectId: string
|
|
1444
|
+
// ): Promise<JsonFileInfo[]> => {
|
|
1445
|
+
// if (!bucketSanitizedName || !projectId) {
|
|
1446
|
+
// console.error('❌ Missing parameters for listing JSON files')
|
|
1447
|
+
// return []
|
|
1448
|
+
// }
|
|
1449
|
+
// try {
|
|
1450
|
+
// const files: JsonFileInfo[] = []
|
|
1451
|
+
// let page = 1
|
|
1452
|
+
// let hasMoreFiles = true
|
|
1453
|
+
// while (hasMoreFiles && page <= 100) {
|
|
1454
|
+
// const paddedPage = page.toString().padStart(3, '0')
|
|
1455
|
+
// const publicUrl = `https://firebasestorage.googleapis.com/v0/b/funui-4bcd1.firebasestorage.app/o/projects%2F${projectId}%2Fbuckets%2F${bucketSanitizedName}%2Fpage_${paddedPage}.json?alt=media`
|
|
1456
|
+
// const response = await fetch(publicUrl, {
|
|
1457
|
+
// method: 'HEAD',
|
|
1458
|
+
// cache: 'no-cache',
|
|
1459
|
+
// })
|
|
1460
|
+
// if (response.ok) {
|
|
1461
|
+
// files.push({
|
|
1462
|
+
// name: `page_${paddedPage}.json`,
|
|
1463
|
+
// fullPath: `projects/${projectId}/buckets/${bucketSanitizedName}/page_${paddedPage}.json`,
|
|
1464
|
+
// url: publicUrl,
|
|
1465
|
+
// size: parseInt(response.headers.get('content-length') || '0', 10),
|
|
1466
|
+
// page: page
|
|
1467
|
+
// })
|
|
1468
|
+
// page++
|
|
1469
|
+
// } else {
|
|
1470
|
+
// hasMoreFiles = false
|
|
1471
|
+
// }
|
|
1472
|
+
// }
|
|
1473
|
+
// return files
|
|
1474
|
+
// } catch (error) {
|
|
1475
|
+
// console.error('❌ Error listing JSON files:', error)
|
|
1476
|
+
// return []
|
|
1477
|
+
// }
|
|
1478
|
+
// }
|
|
1479
|
+
// /* -------------------------------------------------------------------------- */
|
|
1480
|
+
// /* CSS VARIABLE APPLIER */
|
|
1481
|
+
// /* -------------------------------------------------------------------------- */
|
|
1482
|
+
// const applyTypographyVariables = (typography: Record<string, string>, root: HTMLElement) => {
|
|
1483
|
+
// if (!typography) return
|
|
1484
|
+
// Object.entries(typography).forEach(([key, value]) => {
|
|
1485
|
+
// const cssVarName = `--${key.replace(/_/g, '-')}`
|
|
1486
|
+
// root.style.setProperty(cssVarName, value)
|
|
1487
|
+
// })
|
|
1488
|
+
// }
|
|
1489
|
+
// const applyColorVariables = (colors: Record<string, string>, root: HTMLElement) => {
|
|
1490
|
+
// if (!colors) return
|
|
1491
|
+
// Object.entries(colors).forEach(([key, value]) => {
|
|
1492
|
+
// const cssVarName = `--${key.replace(/_/g, '-')}`
|
|
1493
|
+
// root.style.setProperty(cssVarName, value)
|
|
1494
|
+
// })
|
|
1495
|
+
// }
|
|
1496
|
+
// const applyThemeConfig = (themeConfig: Record<string, any>, root: HTMLElement) => {
|
|
1497
|
+
// if (!themeConfig) return
|
|
1498
|
+
// if (themeConfig.colors) {
|
|
1499
|
+
// applyColorVariables(themeConfig.colors, root)
|
|
1500
|
+
// }
|
|
1501
|
+
// if (themeConfig.typography) {
|
|
1502
|
+
// applyTypographyVariables(themeConfig.typography, root)
|
|
1503
|
+
// }
|
|
1504
|
+
// Object.entries(themeConfig).forEach(([key, value]) => {
|
|
1505
|
+
// if (key !== 'colors' && key !== 'typography' && typeof value === 'string') {
|
|
1506
|
+
// const cssVarName = `--${key.replace(/_/g, '-')}`
|
|
1507
|
+
// root.style.setProperty(cssVarName, value)
|
|
1508
|
+
// }
|
|
1509
|
+
// })
|
|
1510
|
+
// }
|
|
1511
|
+
// /* -------------------------------------------------------------------------- */
|
|
1512
|
+
// /* VARIABLES HELPER */
|
|
1513
|
+
// /* -------------------------------------------------------------------------- */
|
|
1514
|
+
// let cachedProjectData: ProjectData | null = null
|
|
1515
|
+
// export const getVariable = (name: string): { name: string; value: any } | undefined => {
|
|
1516
|
+
// if (!cachedProjectData?.variables) {
|
|
1517
|
+
// console.warn('No variables available. Make sure ThemeProvider is mounted.')
|
|
1518
|
+
// return undefined
|
|
1519
|
+
// }
|
|
1520
|
+
// const variable = cachedProjectData.variables.find(v => v.name === name)
|
|
1521
|
+
// return variable
|
|
1522
|
+
// }
|
|
1523
|
+
// export const getAllVariables = (): Variable[] => {
|
|
1524
|
+
// return cachedProjectData?.variables || []
|
|
1525
|
+
// }
|
|
1526
|
+
// /* -------------------------------------------------------------------------- */
|
|
1527
|
+
// /* BUCKET UTILITIES */
|
|
1528
|
+
// /* -------------------------------------------------------------------------- */
|
|
1529
|
+
// // Cache for buckets and JSON files
|
|
1530
|
+
// let cachedBuckets: Bucket[] = []
|
|
1531
|
+
// let cachedJsonFiles: Record<string, JsonFileInfo[]> = {}
|
|
1532
|
+
// let cachedJsonData: Record<string, Record<number, JsonFileData>> = {}
|
|
1533
|
+
// const sanitizeBucketName = (name: string): string => {
|
|
1534
|
+
// return name
|
|
1535
|
+
// .trim()
|
|
1536
|
+
// .replace(/\s+/g, '_')
|
|
1537
|
+
// .replace(/[^a-zA-Z0-9_-]/g, '')
|
|
1538
|
+
// .toLowerCase()
|
|
1539
|
+
// }
|
|
1540
|
+
// const transformProjectBucket = (bucket: any, projectId: string): Bucket => {
|
|
1541
|
+
// return {
|
|
1542
|
+
// id: bucket.id || bucket._id || '',
|
|
1543
|
+
// // projectId: bucket.projectId || projectId || '',
|
|
1544
|
+
// name: bucket.name || '',
|
|
1545
|
+
// displayName: bucket.displayName || bucket.name || '',
|
|
1546
|
+
// category: bucket.category || 'uncategorized',
|
|
1547
|
+
// fields: Array.isArray(bucket.fields) ? bucket.fields : [],
|
|
1548
|
+
// createdBy: bucket.createdBy || bucket.userId || '',
|
|
1549
|
+
// createdAt: typeof bucket.createdAt === 'number' ? bucket.createdAt : Date.now(),
|
|
1550
|
+
// updatedAt: typeof bucket.updatedAt === 'number' ? bucket.updatedAt : Date.now(),
|
|
1551
|
+
// updatedBy: bucket.updatedBy || bucket.createdBy || '',
|
|
1552
|
+
// recordCount: bucket.recordCount || 0,
|
|
1553
|
+
// jsonFilesCount: bucket.jsonFilesCount || 0,
|
|
1554
|
+
// totalRecordsInJson: bucket.totalRecordsInJson || 0,
|
|
1555
|
+
// lastJsonExport: bucket.lastJsonExport,
|
|
1556
|
+
// userId: bucket.userId,
|
|
1557
|
+
// createdAtString: bucket.createdAt,
|
|
1558
|
+
// updatedAtString: bucket.updatedAt
|
|
1559
|
+
// }
|
|
1560
|
+
// }
|
|
1561
|
+
// /* -------------------------------------------------------------------------- */
|
|
1562
|
+
// /* COMPONENT */
|
|
1563
|
+
// /* -------------------------------------------------------------------------- */
|
|
1564
|
+
// const ThemeProvider: React.FC<ThemeProviderProps> = ({
|
|
1565
|
+
// theme,
|
|
1566
|
+
// children,
|
|
1567
|
+
// funcss = '',
|
|
1568
|
+
// minHeight = '100vh',
|
|
1569
|
+
// projectId,
|
|
1570
|
+
// project: providedProject, // New prop
|
|
1571
|
+
// }) => {
|
|
1572
|
+
// const [variant, setVariant] = useState<ThemeVariant>('standard')
|
|
1573
|
+
// const [themeConfig, setThemeConfig] = useState<ThemeConfig>({})
|
|
1574
|
+
// const [projectData, setProjectData] = useState<ProjectData | null>(null)
|
|
1575
|
+
// const [isLoading, setIsLoading] = useState(true)
|
|
1576
|
+
// const [isInitialLoad, setIsInitialLoad] = useState(true)
|
|
1577
|
+
// const [error, setError] = useState<string | null>(null)
|
|
1578
|
+
// const [currentVersion, setCurrentVersion] = useState<number | null>(null)
|
|
1579
|
+
// /* -------------------------- Apply base theme --------------------------- */
|
|
1580
|
+
// useEffect(() => {
|
|
1581
|
+
// const root = document.documentElement
|
|
1582
|
+
// const selectedTheme = themes[theme] || themes.light
|
|
1583
|
+
// Object.entries(selectedTheme).forEach(([key, value]) => {
|
|
1584
|
+
// root.style.setProperty(key, value)
|
|
1585
|
+
// })
|
|
1586
|
+
// if (
|
|
1587
|
+
// ['dark', 'dark-blue', 'midnight-purple', 'cyber-metal'].includes(theme)
|
|
1588
|
+
// ) {
|
|
1589
|
+
// colorVarsToDarken.forEach((varName) => {
|
|
1590
|
+
// const original = getComputedStyle(root)
|
|
1591
|
+
// .getPropertyValue(varName)
|
|
1592
|
+
// .trim()
|
|
1593
|
+
// if (original) {
|
|
1594
|
+
// const darkAmount = getDarkenAmount(varName)
|
|
1595
|
+
// const rgba = darkenToRgba(original, darkAmount, 0.9)
|
|
1596
|
+
// root.style.setProperty(varName, rgba)
|
|
1597
|
+
// }
|
|
1598
|
+
// })
|
|
1599
|
+
// }
|
|
1600
|
+
// }, [theme])
|
|
1601
|
+
// /* ---------------------- Theme Loading Logic ----------------------- */
|
|
1602
|
+
// useEffect(() => {
|
|
1603
|
+
// if (typeof window === 'undefined') {
|
|
1604
|
+
// setIsLoading(false)
|
|
1605
|
+
// setIsInitialLoad(false)
|
|
1606
|
+
// return
|
|
1607
|
+
// }
|
|
1608
|
+
// const root = document.documentElement
|
|
1609
|
+
// let pollTimer: NodeJS.Timeout
|
|
1610
|
+
// const loadTheme = async () => {
|
|
1611
|
+
// try {
|
|
1612
|
+
// let finalTheme: ProjectData | null = null
|
|
1613
|
+
// let finalVersion: number | null = null
|
|
1614
|
+
// // If project data is provided directly, use it and skip all loading
|
|
1615
|
+
// if (providedProject) {
|
|
1616
|
+
// console.log('✅ Using provided project data directly')
|
|
1617
|
+
// finalTheme = providedProject
|
|
1618
|
+
// finalVersion = providedProject.version || 0
|
|
1619
|
+
// } else {
|
|
1620
|
+
// // Otherwise, follow the normal loading flow
|
|
1621
|
+
// // First, try to load local theme
|
|
1622
|
+
// const localTheme = await loadLocalTheme()
|
|
1623
|
+
// const localVersion = localTheme?.version || 0
|
|
1624
|
+
// if (projectId) {
|
|
1625
|
+
// // Validate origin access for CDN
|
|
1626
|
+
// const hasAccess = await validateOriginAccess(projectId)
|
|
1627
|
+
// if (hasAccess) {
|
|
1628
|
+
// // Try to load from CDN
|
|
1629
|
+
// const cdnTheme = await loadThemeFromCDN(projectId)
|
|
1630
|
+
// const cdnVersion = cdnTheme?.version || 0
|
|
1631
|
+
// if (cdnTheme) {
|
|
1632
|
+
// // CDN theme available - use it
|
|
1633
|
+
// finalTheme = cdnTheme
|
|
1634
|
+
// finalVersion = cdnVersion
|
|
1635
|
+
// if (cdnVersion !== localVersion) {
|
|
1636
|
+
// console.log(`🔄 Version mismatch: Local(${localVersion}) vs CDN(${cdnVersion})`)
|
|
1637
|
+
// console.log('ℹ️ Using CDN version. Please update your local funui.json file manually.')
|
|
1638
|
+
// }
|
|
1639
|
+
// } else if (localTheme) {
|
|
1640
|
+
// // CDN not available but we have local theme
|
|
1641
|
+
// console.log('⚠️ CDN unavailable, using local theme')
|
|
1642
|
+
// finalTheme = localTheme
|
|
1643
|
+
// finalVersion = localVersion
|
|
1644
|
+
// } else {
|
|
1645
|
+
// // No theme available anywhere
|
|
1646
|
+
// console.warn('⚠️ No theme found (CDN unavailable and no local theme)')
|
|
1647
|
+
// setError('Theme not found')
|
|
1648
|
+
// }
|
|
1649
|
+
// } else {
|
|
1650
|
+
// // Origin validation failed
|
|
1651
|
+
// if (localTheme) {
|
|
1652
|
+
// console.log('⚠️ Origin validation failed, using local theme')
|
|
1653
|
+
// finalTheme = localTheme
|
|
1654
|
+
// finalVersion = localVersion
|
|
1655
|
+
// } else {
|
|
1656
|
+
// console.error('❌ Origin validation failed and no local theme available')
|
|
1657
|
+
// setError('Access denied and no local theme available')
|
|
1658
|
+
// }
|
|
1659
|
+
// }
|
|
1660
|
+
// } else {
|
|
1661
|
+
// // No project ID provided - only use local theme
|
|
1662
|
+
// console.log('ℹ️ No project ID provided, using local theme only')
|
|
1663
|
+
// if (localTheme) {
|
|
1664
|
+
// finalTheme = localTheme
|
|
1665
|
+
// finalVersion = localVersion
|
|
1666
|
+
// console.log('✅ Theme loaded from local file')
|
|
1667
|
+
// } else {
|
|
1668
|
+
// console.log('ℹ️ No local theme file found - using base theme only')
|
|
1669
|
+
// // No error here - it's valid to use only base theme
|
|
1670
|
+
// }
|
|
1671
|
+
// }
|
|
1672
|
+
// }
|
|
1673
|
+
// // Apply the theme if we have one
|
|
1674
|
+
// if (finalTheme && (!currentVersion || finalVersion !== currentVersion)) {
|
|
1675
|
+
// applyThemeData(finalTheme, root)
|
|
1676
|
+
// setCurrentVersion(finalVersion)
|
|
1677
|
+
// setError(null)
|
|
1678
|
+
// } else if (finalTheme) {
|
|
1679
|
+
// console.log('✓ Theme up to date')
|
|
1680
|
+
// }
|
|
1681
|
+
// } catch (err) {
|
|
1682
|
+
// console.error('❌ Error loading theme:', err)
|
|
1683
|
+
// setError('Failed to load theme')
|
|
1684
|
+
// } finally {
|
|
1685
|
+
// setIsLoading(false)
|
|
1686
|
+
// setIsInitialLoad(false)
|
|
1687
|
+
// }
|
|
1688
|
+
// }
|
|
1689
|
+
// // Initial load
|
|
1690
|
+
// loadTheme()
|
|
1691
|
+
// // Only poll for updates if we have a project ID AND no provided project
|
|
1692
|
+
// if (projectId && !providedProject) {
|
|
1693
|
+
// pollTimer = setInterval(() => {
|
|
1694
|
+
// loadTheme()
|
|
1695
|
+
// }, 5 * 60 * 1000)
|
|
1696
|
+
// }
|
|
1697
|
+
// return () => {
|
|
1698
|
+
// if (pollTimer) {
|
|
1699
|
+
// clearInterval(pollTimer)
|
|
1700
|
+
// }
|
|
1701
|
+
// }
|
|
1702
|
+
// }, [projectId, currentVersion, theme, providedProject]) // Added providedProject to dependencies
|
|
1703
|
+
// const applyThemeData = (data: ProjectData, root: HTMLElement) => {
|
|
1704
|
+
// const themeConfig = data.theme_config ?? {}
|
|
1705
|
+
// const newVariant = data.default_variation || 'standard'
|
|
1706
|
+
// setVariant(newVariant)
|
|
1707
|
+
// setThemeConfig(themeConfig)
|
|
1708
|
+
// setProjectData(data)
|
|
1709
|
+
// // Cache for variable access
|
|
1710
|
+
// cachedProjectData = data
|
|
1711
|
+
// // Cache for asset access
|
|
1712
|
+
// cachedAssets = data.assets || []
|
|
1713
|
+
// // Cache for bucket access
|
|
1714
|
+
// const projectBuckets = data.buckets || []
|
|
1715
|
+
// cachedBuckets = projectBuckets.map(bucket =>
|
|
1716
|
+
// transformProjectBucket(bucket, data.project_id || '')
|
|
1717
|
+
// )
|
|
1718
|
+
// // Apply all theme config to CSS variables
|
|
1719
|
+
// applyThemeConfig(themeConfig, root)
|
|
1720
|
+
// }
|
|
1721
|
+
// const contextValue = useMemo(
|
|
1722
|
+
// () => ({
|
|
1723
|
+
// variant,
|
|
1724
|
+
// setVariant,
|
|
1725
|
+
// themeConfig,
|
|
1726
|
+
// projectData,
|
|
1727
|
+
// isLoading,
|
|
1728
|
+
// isInitialLoad,
|
|
1729
|
+
// error,
|
|
1730
|
+
// projectId, // Include projectId in context
|
|
1731
|
+
// }),
|
|
1732
|
+
// [variant, themeConfig, projectData, isLoading, isInitialLoad, error, projectId]
|
|
1733
|
+
// )
|
|
1734
|
+
// return (
|
|
1735
|
+
// <ThemeContext.Provider value={contextValue}>
|
|
1736
|
+
// <div
|
|
1737
|
+
// className={`theme-${theme} ${funcss}`}
|
|
1738
|
+
// style={{
|
|
1739
|
+
// backgroundColor: 'var(--page-bg)',
|
|
1740
|
+
// color: 'var(--text-color)',
|
|
1741
|
+
// minHeight: minHeight,
|
|
1742
|
+
// transition: isInitialLoad ? 'none' : 'background-color 0.3s ease, color 0.3s ease',
|
|
1743
|
+
// }}
|
|
1744
|
+
// >
|
|
1745
|
+
// {children}
|
|
1746
|
+
// </div>
|
|
1747
|
+
// </ThemeContext.Provider>
|
|
1748
|
+
// )
|
|
1749
|
+
// }
|
|
1750
|
+
// export default ThemeProvider
|
|
1751
|
+
// /* -------------------------------------------------------------------------- */
|
|
1752
|
+
// /* HELPER HOOKS */
|
|
1753
|
+
// /* -------------------------------------------------------------------------- */
|
|
1754
|
+
// export const useThemeValue = (key: string): string | undefined => {
|
|
1755
|
+
// const { themeConfig } = useTheme()
|
|
1756
|
+
// return themeConfig[key]
|
|
1757
|
+
// }
|
|
1758
|
+
// export const useComponentConfig = (componentName: string): any => {
|
|
1759
|
+
// const { projectData } = useTheme()
|
|
1760
|
+
// return projectData?.components?.[componentName] || {}
|
|
1761
|
+
// }
|
|
1762
|
+
// export const useColors = (): Record<string, string> => {
|
|
1763
|
+
// const { projectData } = useTheme()
|
|
1764
|
+
// return projectData?.theme_config?.colors || {}
|
|
1765
|
+
// }
|
|
1766
|
+
// export const useTypography = (): Record<string, string> => {
|
|
1767
|
+
// const { projectData } = useTheme()
|
|
1768
|
+
// return projectData?.theme_config?.typography || {}
|
|
1769
|
+
// }
|
|
1770
|
+
// export const useThemeConfig = (): Record<string, any> => {
|
|
1771
|
+
// const { projectData } = useTheme()
|
|
1772
|
+
// return projectData?.theme_config || {}
|
|
1773
|
+
// }
|
|
1774
|
+
// export const useProjectData = (): ProjectData | null => {
|
|
1775
|
+
// const { projectData } = useTheme()
|
|
1776
|
+
// return projectData
|
|
1777
|
+
// }
|
|
1778
|
+
// export const useColor = (colorName: string): string | undefined => {
|
|
1779
|
+
// const colors = useColors()
|
|
1780
|
+
// return colors[colorName]
|
|
1781
|
+
// }
|
|
1782
|
+
// export const useTypographyValue = (property: string): string | undefined => {
|
|
1783
|
+
// const typography = useTypography()
|
|
1784
|
+
// return typography[property]
|
|
1785
|
+
// }
|
|
1786
|
+
// export const useComponentVariant = (componentName: string, variantName: string = 'default'): any => {
|
|
1787
|
+
// const componentConfig = useComponentConfig(componentName)
|
|
1788
|
+
// return componentConfig[variantName] || {}
|
|
1789
|
+
// }
|
|
1790
|
+
// // Hook to access variables
|
|
1791
|
+
// export const useVariables = (): Variable[] => {
|
|
1792
|
+
// const { projectData } = useTheme()
|
|
1793
|
+
// return projectData?.variables || []
|
|
1794
|
+
// }
|
|
1795
|
+
// // Hook to get a specific variable
|
|
1796
|
+
// export const useVariable = (name: string): any => {
|
|
1797
|
+
// const variables = useVariables()
|
|
1798
|
+
// const variable = variables.find(v => v.name === name)
|
|
1799
|
+
// return variable?.value
|
|
1800
|
+
// }
|
|
1801
|
+
// /* -------------------------------------------------------------------------- */
|
|
1802
|
+
// /* ASSETS HELPER */
|
|
1803
|
+
// /* -------------------------------------------------------------------------- */
|
|
1804
|
+
// interface Asset {
|
|
1805
|
+
// name: string
|
|
1806
|
+
// url: string
|
|
1807
|
+
// file_type: string
|
|
1808
|
+
// }
|
|
1809
|
+
// let cachedAssets: Asset[] = []
|
|
1810
|
+
// export const getAsset = (name: string): Asset | undefined => {
|
|
1811
|
+
// if (!cachedAssets.length) {
|
|
1812
|
+
// console.warn('No assets available. Make sure ThemeProvider is mounted and assets are loaded.')
|
|
1813
|
+
// return undefined
|
|
1814
|
+
// }
|
|
1815
|
+
// const asset = cachedAssets.find(a => a.name === name)
|
|
1816
|
+
// return asset
|
|
1817
|
+
// }
|
|
1818
|
+
// export const getAllAssets = (): Asset[] => {
|
|
1819
|
+
// return cachedAssets
|
|
1820
|
+
// }
|
|
1821
|
+
// export const getAssetValue = (name: string): string | undefined => {
|
|
1822
|
+
// const asset = getAsset(name)
|
|
1823
|
+
// return asset?.url
|
|
1824
|
+
// }
|
|
1825
|
+
// export const getAssetType = (name: string): string | undefined => {
|
|
1826
|
+
// const asset = getAsset(name)
|
|
1827
|
+
// return asset?.file_type
|
|
1828
|
+
// }
|
|
1829
|
+
// export const getAssetInfo = (name: string): { value: string; type: string; name: string } | undefined => {
|
|
1830
|
+
// const asset = getAsset(name)
|
|
1831
|
+
// if (!asset) return undefined
|
|
1832
|
+
// return {
|
|
1833
|
+
// value: asset.url,
|
|
1834
|
+
// type: asset.file_type,
|
|
1835
|
+
// name: asset.name
|
|
1836
|
+
// }
|
|
1837
|
+
// }
|
|
1838
|
+
// // Hook to access all assets
|
|
1839
|
+
// export const useAssets = (): Asset[] => {
|
|
1840
|
+
// const { projectData } = useTheme()
|
|
1841
|
+
// return projectData?.assets || []
|
|
1842
|
+
// }
|
|
1843
|
+
// // Hook to get a specific asset
|
|
1844
|
+
// export const useAsset = (name: string): Asset | undefined => {
|
|
1845
|
+
// const assets = useAssets()
|
|
1846
|
+
// const asset = assets.find(a => a.name === name)
|
|
1847
|
+
// return asset
|
|
1848
|
+
// }
|
|
1849
|
+
// // Hook to get asset URL (most common use case)
|
|
1850
|
+
// export const useAssetValue = (name: string): string | undefined => {
|
|
1851
|
+
// const asset = useAsset(name)
|
|
1852
|
+
// return asset?.url
|
|
1853
|
+
// }
|
|
1854
|
+
// // Hook to get asset type
|
|
1855
|
+
// export const useAssetType = (name: string): string | undefined => {
|
|
1856
|
+
// const asset = useAsset(name)
|
|
1857
|
+
// return asset?.file_type
|
|
1858
|
+
// }
|
|
1859
|
+
// // Hook to get complete asset info
|
|
1860
|
+
// export const useAssetInfo = (name: string): { value: string; type: string; name: string } | undefined => {
|
|
1861
|
+
// const asset = useAsset(name)
|
|
1862
|
+
// if (!asset) return undefined
|
|
1863
|
+
// return {
|
|
1864
|
+
// value: asset.url,
|
|
1865
|
+
// type: asset.file_type,
|
|
1866
|
+
// name: asset.name
|
|
1867
|
+
// }
|
|
1868
|
+
// }
|
|
1869
|
+
// // Hook to filter assets by type
|
|
1870
|
+
// export const useAssetsByType = (type: string): Asset[] => {
|
|
1871
|
+
// const assets = useAssets()
|
|
1872
|
+
// return assets.filter(asset => asset.file_type === type)
|
|
1873
|
+
// }
|
|
1874
|
+
// // Hook to get image assets
|
|
1875
|
+
// export const useImageAssets = (): Asset[] => {
|
|
1876
|
+
// return useAssetsByType('image')
|
|
1877
|
+
// }
|
|
1878
|
+
// // Hook to get video assets
|
|
1879
|
+
// export const useVideoAssets = (): Asset[] => {
|
|
1880
|
+
// return useAssetsByType('video')
|
|
1881
|
+
// }
|
|
1882
|
+
// // Hook to get audio assets
|
|
1883
|
+
// export const useAudioAssets = (): Asset[] => {
|
|
1884
|
+
// return useAssetsByType('audio')
|
|
1885
|
+
// }
|
|
1886
|
+
// // Hook to get document assets
|
|
1887
|
+
// export const useDocumentAssets = (): Asset[] => {
|
|
1888
|
+
// return useAssetsByType('document')
|
|
1889
|
+
// }
|
|
1890
|
+
// /* -------------------------------------------------------------------------- */
|
|
1891
|
+
// /* BUCKET HOOKS */
|
|
1892
|
+
// /* -------------------------------------------------------------------------- */
|
|
1893
|
+
// // Helper function to get bucket
|
|
1894
|
+
// const getBucketFromCache = (bucketIdOrName: string): Bucket | undefined => {
|
|
1895
|
+
// if (!cachedBuckets.length) {
|
|
1896
|
+
// console.warn('No buckets available. Make sure ThemeProvider is mounted.')
|
|
1897
|
+
// return undefined
|
|
1898
|
+
// }
|
|
1899
|
+
// return cachedBuckets.find(b =>
|
|
1900
|
+
// b.id === bucketIdOrName ||
|
|
1901
|
+
// b.name.toLowerCase() === bucketIdOrName.toLowerCase() ||
|
|
1902
|
+
// b.displayName?.toLowerCase() === bucketIdOrName.toLowerCase()
|
|
1903
|
+
// )
|
|
1904
|
+
// }
|
|
1905
|
+
// // Hook to access all buckets
|
|
1906
|
+
// export const useBuckets = (): Bucket[] => {
|
|
1907
|
+
// const { projectData } = useTheme()
|
|
1908
|
+
// const [buckets, setBuckets] = useState<Bucket[]>([])
|
|
1909
|
+
// useEffect(() => {
|
|
1910
|
+
// if (projectData?.buckets) {
|
|
1911
|
+
// const transformedBuckets = projectData.buckets.map(bucket =>
|
|
1912
|
+
// transformProjectBucket(bucket, projectData.project_id || '')
|
|
1913
|
+
// )
|
|
1914
|
+
// setBuckets(transformedBuckets)
|
|
1915
|
+
// cachedBuckets = transformedBuckets
|
|
1916
|
+
// } else {
|
|
1917
|
+
// setBuckets([])
|
|
1918
|
+
// }
|
|
1919
|
+
// }, [projectData])
|
|
1920
|
+
// return buckets
|
|
1921
|
+
// }
|
|
1922
|
+
// // Hook to get a specific bucket
|
|
1923
|
+
// export const useBucket = (bucketIdOrName: string): Bucket | undefined => {
|
|
1924
|
+
// const buckets = useBuckets()
|
|
1925
|
+
// return buckets.find(b =>
|
|
1926
|
+
// b.id === bucketIdOrName ||
|
|
1927
|
+
// b.name.toLowerCase() === bucketIdOrName.toLowerCase() ||
|
|
1928
|
+
// b.displayName?.toLowerCase() === bucketIdOrName.toLowerCase()
|
|
1929
|
+
// )
|
|
1930
|
+
// }
|
|
1931
|
+
// // Hook to get buckets by category
|
|
1932
|
+
// export const useBucketsByCategory = (category: string): Bucket[] => {
|
|
1933
|
+
// const buckets = useBuckets()
|
|
1934
|
+
// if (category === 'all' || category === 'uncategorized') {
|
|
1935
|
+
// return buckets.filter(b => !b.category || b.category === 'uncategorized')
|
|
1936
|
+
// }
|
|
1937
|
+
// return buckets.filter(b => b.category === category)
|
|
1938
|
+
// }
|
|
1939
|
+
// // Hook to get bucket JSON files
|
|
1940
|
+
// export const useBucketJsonFiles = (bucketIdOrName: string): {
|
|
1941
|
+
// files: JsonFileInfo[]
|
|
1942
|
+
// loading: boolean
|
|
1943
|
+
// error: string | null
|
|
1944
|
+
// refresh: () => Promise<void>
|
|
1945
|
+
// } => {
|
|
1946
|
+
// const [files, setFiles] = useState<JsonFileInfo[]>([])
|
|
1947
|
+
// const [loading, setLoading] = useState(true)
|
|
1948
|
+
// const [error, setError] = useState<string | null>(null)
|
|
1949
|
+
// const bucket = useBucket(bucketIdOrName)
|
|
1950
|
+
// const { projectId } = useTheme()
|
|
1951
|
+
// const loadFiles = useCallback(async () => {
|
|
1952
|
+
// if (!bucket || !projectId) {
|
|
1953
|
+
// setFiles([])
|
|
1954
|
+
// setLoading(false)
|
|
1955
|
+
// return
|
|
1956
|
+
// }
|
|
1957
|
+
// try {
|
|
1958
|
+
// setLoading(true)
|
|
1959
|
+
// setError(null)
|
|
1960
|
+
// const bucketSanitizedName = sanitizeBucketName(bucket.displayName || bucket.name)
|
|
1961
|
+
// const jsonFiles = await listBucketJsonFiles(bucketSanitizedName, projectId)
|
|
1962
|
+
// setFiles(jsonFiles)
|
|
1963
|
+
// // Update cache
|
|
1964
|
+
// const cacheKey = `${bucket.id}_${projectId}`
|
|
1965
|
+
// cachedJsonFiles[cacheKey] = jsonFiles
|
|
1966
|
+
// } catch (err) {
|
|
1967
|
+
// console.error('Error loading JSON files:', err)
|
|
1968
|
+
// setError(err instanceof Error ? err.message : 'Failed to load JSON files')
|
|
1969
|
+
// } finally {
|
|
1970
|
+
// setLoading(false)
|
|
1971
|
+
// }
|
|
1972
|
+
// }, [bucket, projectId])
|
|
1973
|
+
// useEffect(() => {
|
|
1974
|
+
// loadFiles()
|
|
1975
|
+
// }, [loadFiles])
|
|
1976
|
+
// return {
|
|
1977
|
+
// files,
|
|
1978
|
+
// loading,
|
|
1979
|
+
// error,
|
|
1980
|
+
// refresh: loadFiles
|
|
1981
|
+
// }
|
|
1982
|
+
// }
|
|
1983
|
+
// // Hook to get paginated records (YOU ONLY NEED TO PASS PAGE NUMBER!)
|
|
1984
|
+
// export const usePaginatedRecords = (
|
|
1985
|
+
// bucketIdOrName: string,
|
|
1986
|
+
// page: number,
|
|
1987
|
+
// pageSize?: number
|
|
1988
|
+
// ): {
|
|
1989
|
+
// records: BucketRecord[]
|
|
1990
|
+
// metadata: JsonFileMetadata | null
|
|
1991
|
+
// loading: boolean
|
|
1992
|
+
// error: string | null
|
|
1993
|
+
// refresh: () => Promise<void>
|
|
1994
|
+
// } => {
|
|
1995
|
+
// const [records, setRecords] = useState<BucketRecord[]>([])
|
|
1996
|
+
// const [metadata, setMetadata] = useState<JsonFileMetadata | null>(null)
|
|
1997
|
+
// const [loading, setLoading] = useState(true)
|
|
1998
|
+
// const [error, setError] = useState<string | null>(null)
|
|
1999
|
+
// const bucket = useBucket(bucketIdOrName)
|
|
2000
|
+
// const { projectId } = useTheme()
|
|
2001
|
+
// const loadRecords = useCallback(async () => {
|
|
2002
|
+
// if (!bucket || !projectId) {
|
|
2003
|
+
// setRecords([])
|
|
2004
|
+
// setMetadata(null)
|
|
2005
|
+
// setLoading(false)
|
|
2006
|
+
// return
|
|
2007
|
+
// }
|
|
2008
|
+
// try {
|
|
2009
|
+
// setLoading(true)
|
|
2010
|
+
// setError(null)
|
|
2011
|
+
// const bucketSanitizedName = sanitizeBucketName(bucket.displayName || bucket.name)
|
|
2012
|
+
// const jsonData = await loadBucketJsonFromCDN(bucketSanitizedName, projectId, page)
|
|
2013
|
+
// if (jsonData) {
|
|
2014
|
+
// let recordsToSet = jsonData.records || []
|
|
2015
|
+
// // If pageSize is specified, limit the records
|
|
2016
|
+
// if (pageSize && recordsToSet.length > pageSize) {
|
|
2017
|
+
// recordsToSet = recordsToSet.slice(0, pageSize)
|
|
2018
|
+
// }
|
|
2019
|
+
// setRecords(recordsToSet)
|
|
2020
|
+
// setMetadata(jsonData.metadata)
|
|
2021
|
+
// // Update cache
|
|
2022
|
+
// const cacheKey = `${bucket.id}_${projectId}`
|
|
2023
|
+
// if (!cachedJsonData[cacheKey]) {
|
|
2024
|
+
// cachedJsonData[cacheKey] = {}
|
|
2025
|
+
// }
|
|
2026
|
+
// cachedJsonData[cacheKey][page] = jsonData
|
|
2027
|
+
// } else {
|
|
2028
|
+
// setRecords([])
|
|
2029
|
+
// setMetadata(null)
|
|
2030
|
+
// }
|
|
2031
|
+
// } catch (err) {
|
|
2032
|
+
// console.error(`Error loading records for page ${page}:`, err)
|
|
2033
|
+
// setError(err instanceof Error ? err.message : 'Failed to load records')
|
|
2034
|
+
// } finally {
|
|
2035
|
+
// setLoading(false)
|
|
2036
|
+
// }
|
|
2037
|
+
// }, [bucket, projectId, page, pageSize])
|
|
2038
|
+
// useEffect(() => {
|
|
2039
|
+
// loadRecords()
|
|
2040
|
+
// }, [loadRecords])
|
|
2041
|
+
// return {
|
|
2042
|
+
// records,
|
|
2043
|
+
// metadata,
|
|
2044
|
+
// loading,
|
|
2045
|
+
// error,
|
|
2046
|
+
// refresh: loadRecords
|
|
2047
|
+
// }
|
|
2048
|
+
// }
|
|
2049
|
+
// // Hook to get all records from JSON files
|
|
2050
|
+
// export const useAllJsonRecords = (
|
|
2051
|
+
// bucketIdOrName: string
|
|
2052
|
+
// ): {
|
|
2053
|
+
// records: BucketRecord[]
|
|
2054
|
+
// loading: boolean
|
|
2055
|
+
// error: string | null
|
|
2056
|
+
// refresh: () => Promise<void>
|
|
2057
|
+
// } => {
|
|
2058
|
+
// const [records, setRecords] = useState<BucketRecord[]>([])
|
|
2059
|
+
// const [loading, setLoading] = useState(true)
|
|
2060
|
+
// const [error, setError] = useState<string | null>(null)
|
|
2061
|
+
// const bucket = useBucket(bucketIdOrName)
|
|
2062
|
+
// const { projectId } = useTheme()
|
|
2063
|
+
// const { files, loading: filesLoading } = useBucketJsonFiles(bucketIdOrName)
|
|
2064
|
+
// const loadAllRecords = useCallback(async () => {
|
|
2065
|
+
// if (!bucket || !projectId || filesLoading) {
|
|
2066
|
+
// return
|
|
2067
|
+
// }
|
|
2068
|
+
// try {
|
|
2069
|
+
// setLoading(true)
|
|
2070
|
+
// setError(null)
|
|
2071
|
+
// const allRecords: BucketRecord[] = []
|
|
2072
|
+
// for (const file of files) {
|
|
2073
|
+
// const bucketSanitizedName = sanitizeBucketName(bucket.displayName || bucket.name)
|
|
2074
|
+
// const jsonData = await loadBucketJsonFromCDN(bucketSanitizedName, projectId, file.page)
|
|
2075
|
+
// if (jsonData && jsonData.records) {
|
|
2076
|
+
// allRecords.push(...jsonData.records)
|
|
2077
|
+
// }
|
|
2078
|
+
// }
|
|
2079
|
+
// setRecords(allRecords)
|
|
2080
|
+
// } catch (err) {
|
|
2081
|
+
// console.error('Error loading all records:', err)
|
|
2082
|
+
// setError(err instanceof Error ? err.message : 'Failed to load records')
|
|
2083
|
+
// } finally {
|
|
2084
|
+
// setLoading(false)
|
|
2085
|
+
// }
|
|
2086
|
+
// }, [bucket, projectId, files, filesLoading])
|
|
2087
|
+
// useEffect(() => {
|
|
2088
|
+
// if (!filesLoading) {
|
|
2089
|
+
// loadAllRecords()
|
|
2090
|
+
// }
|
|
2091
|
+
// }, [loadAllRecords, filesLoading])
|
|
2092
|
+
// return {
|
|
2093
|
+
// records,
|
|
2094
|
+
// loading: loading || filesLoading,
|
|
2095
|
+
// error,
|
|
2096
|
+
// refresh: loadAllRecords
|
|
2097
|
+
// }
|
|
2098
|
+
// }
|
|
2099
|
+
// // Hook for cache management
|
|
2100
|
+
// export const useBucketCache = () => {
|
|
2101
|
+
// const clearCache = useCallback((bucketIdOrName?: string) => {
|
|
2102
|
+
// if (bucketIdOrName) {
|
|
2103
|
+
// const bucket = getBucketFromCache(bucketIdOrName)
|
|
2104
|
+
// if (bucket) {
|
|
2105
|
+
// const { projectId } = useContext(ThemeContext)
|
|
2106
|
+
// if (projectId) {
|
|
2107
|
+
// const cacheKey = `${bucket.id}_${projectId}`
|
|
2108
|
+
// delete cachedJsonFiles[cacheKey]
|
|
2109
|
+
// delete cachedJsonData[cacheKey]
|
|
2110
|
+
// }
|
|
2111
|
+
// }
|
|
2112
|
+
// } else {
|
|
2113
|
+
// cachedJsonFiles = {}
|
|
2114
|
+
// cachedJsonData = {}
|
|
2115
|
+
// }
|
|
2116
|
+
// }, [])
|
|
2117
|
+
// const refresh = useCallback(() => {
|
|
2118
|
+
// // This will be handled by the ThemeProvider when project data changes
|
|
2119
|
+
// console.log('Buckets will refresh when project data updates')
|
|
2120
|
+
// }, [])
|
|
2121
|
+
// return {
|
|
2122
|
+
// clearCache,
|
|
2123
|
+
// refresh
|
|
2124
|
+
// }
|
|
2125
|
+
// }
|
|
2126
|
+
// // Helper function to get bucket records (for non-hook usage)
|
|
2127
|
+
// export const getPaginatedRecords = async (
|
|
2128
|
+
// bucketIdOrName: string,
|
|
2129
|
+
// page: number,
|
|
2130
|
+
// pageSize?: number
|
|
2131
|
+
// ): Promise<{
|
|
2132
|
+
// records: BucketRecord[]
|
|
2133
|
+
// metadata: JsonFileMetadata | null
|
|
2134
|
+
// }> => {
|
|
2135
|
+
// const bucket = getBucketFromCache(bucketIdOrName)
|
|
2136
|
+
// if (!bucket) {
|
|
2137
|
+
// throw new Error(`Bucket not found: ${bucketIdOrName}`)
|
|
2138
|
+
// }
|
|
2139
|
+
// const { projectId } = useContext(ThemeContext)
|
|
2140
|
+
// if (!projectId) {
|
|
2141
|
+
// throw new Error('Project ID not available')
|
|
2142
|
+
// }
|
|
2143
|
+
// const bucketSanitizedName = sanitizeBucketName(bucket.displayName || bucket.name)
|
|
2144
|
+
// const jsonData = await loadBucketJsonFromCDN(bucketSanitizedName, projectId, page)
|
|
2145
|
+
// if (!jsonData) {
|
|
2146
|
+
// return { records: [], metadata: null }
|
|
2147
|
+
// }
|
|
2148
|
+
// let records = jsonData.records || []
|
|
2149
|
+
// if (pageSize && records.length > pageSize) {
|
|
2150
|
+
// records = records.slice(0, pageSize)
|
|
2151
|
+
// }
|
|
2152
|
+
// return {
|
|
2153
|
+
// records,
|
|
2154
|
+
// metadata: jsonData.metadata
|
|
2155
|
+
// }
|
|
2156
|
+
// }
|