@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 = useCallback(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 {
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) {
@@ -1580,56 +1633,99 @@ function StudioToolbar() {
1580
1633
  }, [selectedItems, clearSelection, triggerRefresh]);
1581
1634
  const handleSyncCdn = useCallback(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 = p.split(".").pop()?.toLowerCase() || "";
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 = data.synced?.length || 0;
1601
- const errorCount = data.errors?.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 (data.error?.includes("R2 not configured") || data.error?.includes("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 (data.error?.includes("R2 not configured") || data.error?.includes("CLOUDFLARE_R2")) {
1696
+ setShowProgress(false);
1697
+ setShowR2SetupModal(true);
1698
+ return;
1699
+ }
1700
+ errors++;
1701
+ } else if (data.synced?.length > 0) {
1702
+ synced++;
1703
+ } else if (data.errors?.length > 0) {
1704
+ errors++;
1705
+ }
1706
+ } catch {
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 = useCallback(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__ */ jsx4(CloudIcon, {}),
1904
- syncing ? "Syncing..." : "Sync CDN"
2000
+ "Sync CDN"
1905
2001
  ]
1906
2002
  }
1907
2003
  ),
@@ -4525,4 +4621,4 @@ export {
4525
4621
  StudioUI,
4526
4622
  StudioUI_default as default
4527
4623
  };
4528
- //# sourceMappingURL=StudioUI-PPX6VKNU.mjs.map
4624
+ //# sourceMappingURL=StudioUI-QTKNMFLF.mjs.map