@gallop.software/studio 0.1.84 → 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.
@@ -682,7 +682,8 @@ var ENV_TEMPLATE = `CLOUDFLARE_R2_ACCOUNT_ID=your_account_id
682
682
  CLOUDFLARE_R2_ACCESS_KEY_ID=your_access_key
683
683
  CLOUDFLARE_R2_SECRET_ACCESS_KEY=your_secret_key
684
684
  CLOUDFLARE_R2_BUCKET_NAME=your_bucket_name
685
- CLOUDFLARE_R2_PUBLIC_URL=https://pub-xxx.r2.dev`;
685
+ CLOUDFLARE_R2_PUBLIC_URL=https://pub-xxx.r2.dev
686
+ NEXT_PUBLIC_CLOUDFLARE_R2_PUBLIC_URL=https://pub-xxx.r2.dev`;
686
687
  var styles3 = {
687
688
  overlay: css3`
688
689
  position: fixed;
@@ -1023,6 +1024,11 @@ function R2SetupModal({ isOpen, onClose }) {
1023
1024
  /* @__PURE__ */ jsx3("span", { css: styles3.envKey, children: "CLOUDFLARE_R2_PUBLIC_URL" }),
1024
1025
  "=",
1025
1026
  /* @__PURE__ */ jsx3("span", { css: styles3.envValue, children: "https://pub-xxx.r2.dev" })
1027
+ ] }),
1028
+ /* @__PURE__ */ jsxs3("span", { css: styles3.envVar, children: [
1029
+ /* @__PURE__ */ jsx3("span", { css: styles3.envKey, children: "NEXT_PUBLIC_CLOUDFLARE_R2_PUBLIC_URL" }),
1030
+ "=",
1031
+ /* @__PURE__ */ jsx3("span", { css: styles3.envValue, children: "https://pub-xxx.r2.dev" })
1026
1032
  ] })
1027
1033
  ] }),
1028
1034
  /* @__PURE__ */ jsxs3("button", { css: styles3.copyBtn, onClick: handleCopy, title: "Copy to clipboard", children: [
@@ -1288,39 +1294,92 @@ function StudioToolbar() {
1288
1294
  const handleFileChange = useCallback(async (e) => {
1289
1295
  const files = e.target.files;
1290
1296
  if (!files || files.length === 0) return;
1291
- 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;
1292
1312
  try {
1293
- 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
+ }
1294
1324
  const formData = new FormData();
1295
1325
  formData.append("file", file);
1296
1326
  formData.append("path", currentPath);
1297
- const response = await fetch("/api/studio/upload", {
1298
- method: "POST",
1299
- body: formData
1300
- });
1301
- if (!response.ok) {
1302
- const error = await response.json();
1303
- if (response.status >= 500) {
1304
- console.error("Upload error:", error);
1305
- setAlertMessage({
1306
- title: "Upload Failed",
1307
- message: `Failed to upload ${file.name}: ${error.error || "Unknown error"}`
1308
- });
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
+ }
1309
1349
  } else {
1310
- setAlertMessage({
1311
- title: "Cannot Upload Here",
1312
- message: error.error || "Upload not allowed in this location."
1313
- });
1350
+ uploaded++;
1314
1351
  }
1352
+ } catch {
1353
+ errors++;
1315
1354
  }
1316
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
+ }
1317
1366
  triggerRefresh();
1318
1367
  } catch (error) {
1319
1368
  console.error("Upload error:", error);
1320
- setAlertMessage({
1321
- title: "Upload Failed",
1322
- message: "Upload failed. Check console for details."
1323
- });
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
+ }
1324
1383
  } finally {
1325
1384
  setUploading(false);
1326
1385
  if (fileInputRef.current) {
@@ -1574,56 +1633,99 @@ function StudioToolbar() {
1574
1633
  }, [selectedItems, clearSelection, triggerRefresh]);
1575
1634
  const handleSyncCdn = useCallback(async () => {
1576
1635
  if (selectedItems.size === 0) return;
1577
- 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\//, ""));
1578
1660
  if (imageKeys.length === 0) {
1579
1661
  setAlertMessage({
1580
- title: "No Images Selected",
1581
- message: "Please select image files to sync to CDN."
1662
+ title: "No Images Found",
1663
+ message: "No images found in the selected items."
1582
1664
  });
1583
1665
  return;
1584
1666
  }
1585
- 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;
1586
1677
  try {
1587
- const response = await fetch("/api/studio/sync", {
1588
- method: "POST",
1589
- headers: { "Content-Type": "application/json" },
1590
- body: JSON.stringify({ imageKeys })
1591
- });
1592
- const data = await response.json();
1593
- if (response.ok) {
1594
- const syncedCount = data.synced?.length || 0;
1595
- const errorCount = data.errors?.length || 0;
1596
- if (errorCount > 0) {
1597
- setAlertMessage({
1598
- title: "Sync Partially Complete",
1599
- message: `Synced ${syncedCount} images. ${errorCount} failed.`
1600
- });
1601
- } else {
1602
- setAlertMessage({
1603
- title: "Sync Complete",
1604
- message: `Successfully synced ${syncedCount} images to CDN.`
1605
- });
1606
- }
1607
- clearSelection();
1608
- triggerRefresh();
1609
- } else {
1610
- if (data.error?.includes("R2 not configured") || data.error?.includes("CLOUDFLARE_R2")) {
1611
- setShowR2SetupModal(true);
1612
- } else {
1613
- setAlertMessage({
1614
- title: "Sync Failed",
1615
- 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] })
1616
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++;
1617
1708
  }
1618
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();
1619
1720
  } catch (error) {
1620
1721
  console.error("Sync error:", error);
1621
- setAlertMessage({
1622
- title: "Sync Failed",
1623
- 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."
1624
1728
  });
1625
- } finally {
1626
- setSyncing(false);
1627
1729
  }
1628
1730
  }, [selectedItems, clearSelection, triggerRefresh]);
1629
1731
  const handleCreateFolder = useCallback(async (folderName) => {
@@ -1892,10 +1994,10 @@ function StudioToolbar() {
1892
1994
  {
1893
1995
  css: styles4.btn,
1894
1996
  onClick: handleSyncCdn,
1895
- disabled: !hasSelection || syncing,
1997
+ disabled: !hasSelection,
1896
1998
  children: [
1897
1999
  /* @__PURE__ */ jsx4(CloudIcon, {}),
1898
- syncing ? "Syncing..." : "Sync CDN"
2000
+ "Sync CDN"
1899
2001
  ]
1900
2002
  }
1901
2003
  ),
@@ -4519,4 +4621,4 @@ export {
4519
4621
  StudioUI,
4520
4622
  StudioUI_default as default
4521
4623
  };
4522
- //# sourceMappingURL=StudioUI-EER6PAXH.mjs.map
4624
+ //# sourceMappingURL=StudioUI-QTKNMFLF.mjs.map