@gallop.software/studio 0.1.85 → 0.1.86

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.
@@ -1294,39 +1294,92 @@ function StudioToolbar() {
1294
1294
  const handleFileChange = _react.useCallback.call(void 0, async (e) => {
1295
1295
  const files = e.target.files;
1296
1296
  if (!files || files.length === 0) return;
1297
- setUploading(true);
1297
+ const fileList = Array.from(files);
1298
+ if (fileList.length > 1) {
1299
+ setProgressState({
1300
+ current: 0,
1301
+ total: fileList.length,
1302
+ percent: 0,
1303
+ status: "processing",
1304
+ message: "Uploading files..."
1305
+ });
1306
+ setShowProgress(true);
1307
+ } else {
1308
+ setUploading(true);
1309
+ }
1310
+ let uploaded = 0;
1311
+ let errors = 0;
1298
1312
  try {
1299
- for (const file of Array.from(files)) {
1313
+ for (let i = 0; i < fileList.length; i++) {
1314
+ const file = fileList[i];
1315
+ if (fileList.length > 1) {
1316
+ setProgressState({
1317
+ current: i + 1,
1318
+ total: fileList.length,
1319
+ percent: Math.round((i + 1) / fileList.length * 100),
1320
+ status: "processing",
1321
+ currentFile: file.name
1322
+ });
1323
+ }
1300
1324
  const formData = new FormData();
1301
1325
  formData.append("file", file);
1302
1326
  formData.append("path", currentPath);
1303
- const response = await fetch("/api/studio/upload", {
1304
- method: "POST",
1305
- body: formData
1306
- });
1307
- if (!response.ok) {
1308
- const error = await response.json();
1309
- if (response.status >= 500) {
1310
- console.error("Upload error:", error);
1311
- setAlertMessage({
1312
- title: "Upload Failed",
1313
- message: `Failed to upload ${file.name}: ${error.error || "Unknown error"}`
1314
- });
1327
+ try {
1328
+ const response = await fetch("/api/studio/upload", {
1329
+ method: "POST",
1330
+ body: formData
1331
+ });
1332
+ if (!response.ok) {
1333
+ const error = await response.json();
1334
+ errors++;
1335
+ if (fileList.length === 1) {
1336
+ if (response.status >= 500) {
1337
+ console.error("Upload error:", error);
1338
+ setAlertMessage({
1339
+ title: "Upload Failed",
1340
+ message: `Failed to upload ${file.name}: ${error.error || "Unknown error"}`
1341
+ });
1342
+ } else {
1343
+ setAlertMessage({
1344
+ title: "Cannot Upload Here",
1345
+ message: error.error || "Upload not allowed in this location."
1346
+ });
1347
+ }
1348
+ }
1315
1349
  } else {
1316
- setAlertMessage({
1317
- title: "Cannot Upload Here",
1318
- message: error.error || "Upload not allowed in this location."
1319
- });
1350
+ uploaded++;
1320
1351
  }
1352
+ } catch (e2) {
1353
+ errors++;
1321
1354
  }
1322
1355
  }
1356
+ if (fileList.length > 1) {
1357
+ setProgressState({
1358
+ current: fileList.length,
1359
+ total: fileList.length,
1360
+ percent: 100,
1361
+ status: "complete",
1362
+ processed: uploaded,
1363
+ errors
1364
+ });
1365
+ }
1323
1366
  triggerRefresh();
1324
1367
  } catch (error) {
1325
1368
  console.error("Upload error:", error);
1326
- setAlertMessage({
1327
- title: "Upload Failed",
1328
- message: "Upload failed. Check console for details."
1329
- });
1369
+ if (fileList.length > 1) {
1370
+ setProgressState({
1371
+ current: 0,
1372
+ total: 0,
1373
+ percent: 0,
1374
+ status: "error",
1375
+ message: "Upload failed."
1376
+ });
1377
+ } else {
1378
+ setAlertMessage({
1379
+ title: "Upload Failed",
1380
+ message: "Upload failed. Check console for details."
1381
+ });
1382
+ }
1330
1383
  } finally {
1331
1384
  setUploading(false);
1332
1385
  if (fileInputRef.current) {
@@ -1467,7 +1520,7 @@ function StudioToolbar() {
1467
1520
  message: data.message
1468
1521
  }));
1469
1522
  }
1470
- } catch (e2) {
1523
+ } catch (e3) {
1471
1524
  }
1472
1525
  }
1473
1526
  }
@@ -1580,56 +1633,99 @@ function StudioToolbar() {
1580
1633
  }, [selectedItems, clearSelection, triggerRefresh]);
1581
1634
  const handleSyncCdn = _react.useCallback.call(void 0, async () => {
1582
1635
  if (selectedItems.size === 0) return;
1583
- const imageKeys = Array.from(selectedItems).filter((p) => !p.endsWith("/")).map((p) => "/" + p.replace(/^public\//, ""));
1636
+ const selectedPaths2 = Array.from(selectedItems);
1637
+ const imageExtensions = ["jpg", "jpeg", "png", "gif", "webp", "svg", "ico", "bmp", "tiff", "tif"];
1638
+ const selectedImagePaths = selectedPaths2.filter((p) => {
1639
+ const ext = _optionalChain([p, 'access', _19 => _19.split, 'call', _20 => _20("."), 'access', _21 => _21.pop, 'call', _22 => _22(), 'optionalAccess', _23 => _23.toLowerCase, 'call', _24 => _24()]) || "";
1640
+ return imageExtensions.includes(ext);
1641
+ });
1642
+ const selectedFolders = selectedPaths2.filter((p) => !p.includes(".") || p.endsWith("/"));
1643
+ if (selectedFolders.length > 0) {
1644
+ try {
1645
+ const response = await fetch(`/api/studio/folder-images?folders=${encodeURIComponent(selectedFolders.join(","))}`);
1646
+ const data = await response.json();
1647
+ if (data.images) {
1648
+ for (const img of data.images) {
1649
+ const fullPath = `public/${img}`;
1650
+ if (!selectedImagePaths.includes(fullPath)) {
1651
+ selectedImagePaths.push(fullPath);
1652
+ }
1653
+ }
1654
+ }
1655
+ } catch (error) {
1656
+ console.error("Failed to get folder images:", error);
1657
+ }
1658
+ }
1659
+ const imageKeys = selectedImagePaths.map((p) => "/" + p.replace(/^public\//, ""));
1584
1660
  if (imageKeys.length === 0) {
1585
1661
  setAlertMessage({
1586
- title: "No Images Selected",
1587
- message: "Please select image files to sync to CDN."
1662
+ title: "No Images Found",
1663
+ message: "No images found in the selected items."
1588
1664
  });
1589
1665
  return;
1590
1666
  }
1591
- setSyncing(true);
1667
+ setProgressState({
1668
+ current: 0,
1669
+ total: imageKeys.length,
1670
+ percent: 0,
1671
+ status: "processing",
1672
+ message: "Syncing to CDN..."
1673
+ });
1674
+ setShowProgress(true);
1675
+ let synced = 0;
1676
+ let errors = 0;
1592
1677
  try {
1593
- const response = await fetch("/api/studio/sync", {
1594
- method: "POST",
1595
- headers: { "Content-Type": "application/json" },
1596
- body: JSON.stringify({ imageKeys })
1597
- });
1598
- const data = await response.json();
1599
- if (response.ok) {
1600
- const syncedCount = _optionalChain([data, 'access', _19 => _19.synced, 'optionalAccess', _20 => _20.length]) || 0;
1601
- const errorCount = _optionalChain([data, 'access', _21 => _21.errors, 'optionalAccess', _22 => _22.length]) || 0;
1602
- if (errorCount > 0) {
1603
- setAlertMessage({
1604
- title: "Sync Partially Complete",
1605
- message: `Synced ${syncedCount} images. ${errorCount} failed.`
1606
- });
1607
- } else {
1608
- setAlertMessage({
1609
- title: "Sync Complete",
1610
- message: `Successfully synced ${syncedCount} images to CDN.`
1611
- });
1612
- }
1613
- clearSelection();
1614
- triggerRefresh();
1615
- } else {
1616
- if (_optionalChain([data, 'access', _23 => _23.error, 'optionalAccess', _24 => _24.includes, 'call', _25 => _25("R2 not configured")]) || _optionalChain([data, 'access', _26 => _26.error, 'optionalAccess', _27 => _27.includes, 'call', _28 => _28("CLOUDFLARE_R2")])) {
1617
- setShowR2SetupModal(true);
1618
- } else {
1619
- setAlertMessage({
1620
- title: "Sync Failed",
1621
- message: data.error || "Failed to sync to CDN."
1678
+ for (let i = 0; i < imageKeys.length; i++) {
1679
+ const imageKey = imageKeys[i];
1680
+ setProgressState({
1681
+ current: i + 1,
1682
+ total: imageKeys.length,
1683
+ percent: Math.round((i + 1) / imageKeys.length * 100),
1684
+ status: "processing",
1685
+ currentFile: imageKey.replace(/^\//, "")
1686
+ });
1687
+ try {
1688
+ const response = await fetch("/api/studio/sync", {
1689
+ method: "POST",
1690
+ headers: { "Content-Type": "application/json" },
1691
+ body: JSON.stringify({ imageKeys: [imageKey] })
1622
1692
  });
1693
+ const data = await response.json();
1694
+ if (!response.ok) {
1695
+ if (_optionalChain([data, 'access', _25 => _25.error, 'optionalAccess', _26 => _26.includes, 'call', _27 => _27("R2 not configured")]) || _optionalChain([data, 'access', _28 => _28.error, 'optionalAccess', _29 => _29.includes, 'call', _30 => _30("CLOUDFLARE_R2")])) {
1696
+ setShowProgress(false);
1697
+ setShowR2SetupModal(true);
1698
+ return;
1699
+ }
1700
+ errors++;
1701
+ } else if (_optionalChain([data, 'access', _31 => _31.synced, 'optionalAccess', _32 => _32.length]) > 0) {
1702
+ synced++;
1703
+ } else if (_optionalChain([data, 'access', _33 => _33.errors, 'optionalAccess', _34 => _34.length]) > 0) {
1704
+ errors++;
1705
+ }
1706
+ } catch (e4) {
1707
+ errors++;
1623
1708
  }
1624
1709
  }
1710
+ setProgressState({
1711
+ current: imageKeys.length,
1712
+ total: imageKeys.length,
1713
+ percent: 100,
1714
+ status: "complete",
1715
+ processed: synced,
1716
+ errors
1717
+ });
1718
+ clearSelection();
1719
+ triggerRefresh();
1625
1720
  } catch (error) {
1626
1721
  console.error("Sync error:", error);
1627
- setAlertMessage({
1628
- title: "Sync Failed",
1629
- message: "Failed to sync to CDN. Check console for details."
1722
+ setProgressState({
1723
+ current: 0,
1724
+ total: 0,
1725
+ percent: 0,
1726
+ status: "error",
1727
+ message: "Failed to sync to CDN."
1630
1728
  });
1631
- } finally {
1632
- setSyncing(false);
1633
1729
  }
1634
1730
  }, [selectedItems, clearSelection, triggerRefresh]);
1635
1731
  const handleCreateFolder = _react.useCallback.call(void 0, async (folderName) => {
@@ -1898,10 +1994,10 @@ function StudioToolbar() {
1898
1994
  {
1899
1995
  css: styles4.btn,
1900
1996
  onClick: handleSyncCdn,
1901
- disabled: !hasSelection || syncing,
1997
+ disabled: !hasSelection,
1902
1998
  children: [
1903
1999
  /* @__PURE__ */ _jsxruntime.jsx.call(void 0, CloudIcon, {}),
1904
- syncing ? "Syncing..." : "Sync CDN"
2000
+ "Sync CDN"
1905
2001
  ]
1906
2002
  }
1907
2003
  ),
@@ -3539,7 +3635,7 @@ function StudioDetailView() {
3539
3635
  });
3540
3636
  triggerRefresh();
3541
3637
  } else {
3542
- if (_optionalChain([data, 'access', _29 => _29.error, 'optionalAccess', _30 => _30.includes, 'call', _31 => _31("R2 not configured")]) || _optionalChain([data, 'access', _32 => _32.error, 'optionalAccess', _33 => _33.includes, 'call', _34 => _34("CLOUDFLARE_R2")])) {
3638
+ if (_optionalChain([data, 'access', _35 => _35.error, 'optionalAccess', _36 => _36.includes, 'call', _37 => _37("R2 not configured")]) || _optionalChain([data, 'access', _38 => _38.error, 'optionalAccess', _39 => _39.includes, 'call', _40 => _40("CLOUDFLARE_R2")])) {
3543
3639
  setShowR2SetupModal(true);
3544
3640
  } else {
3545
3641
  setAlertMessage({
@@ -3578,7 +3674,7 @@ function StudioDetailView() {
3578
3674
  if (!response.ok) {
3579
3675
  throw new Error("Processing failed");
3580
3676
  }
3581
- const reader = _optionalChain([response, 'access', _35 => _35.body, 'optionalAccess', _36 => _36.getReader, 'call', _37 => _37()]);
3677
+ const reader = _optionalChain([response, 'access', _41 => _41.body, 'optionalAccess', _42 => _42.getReader, 'call', _43 => _43()]);
3582
3678
  if (!reader) {
3583
3679
  throw new Error("No response body");
3584
3680
  }
@@ -3595,7 +3691,7 @@ function StudioDetailView() {
3595
3691
  try {
3596
3692
  const data = JSON.parse(line.slice(6));
3597
3693
  setProcessProgress(data);
3598
- } catch (e3) {
3694
+ } catch (e5) {
3599
3695
  }
3600
3696
  }
3601
3697
  }
@@ -4525,4 +4621,4 @@ var StudioUI_default = StudioUI;
4525
4621
 
4526
4622
 
4527
4623
  exports.StudioUI = StudioUI; exports.default = StudioUI_default;
4528
- //# sourceMappingURL=StudioUI-P4TLZNF7.js.map
4624
+ //# sourceMappingURL=StudioUI-PH265HCB.js.map