@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: _react3.css`
688
689
  position: fixed;
@@ -1023,6 +1024,11 @@ function R2SetupModal({ isOpen, onClose }) {
1023
1024
  /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "span", { css: styles3.envKey, children: "CLOUDFLARE_R2_PUBLIC_URL" }),
1024
1025
  "=",
1025
1026
  /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "span", { css: styles3.envValue, children: "https://pub-xxx.r2.dev" })
1027
+ ] }),
1028
+ /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "span", { css: styles3.envVar, children: [
1029
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "span", { css: styles3.envKey, children: "NEXT_PUBLIC_CLOUDFLARE_R2_PUBLIC_URL" }),
1030
+ "=",
1031
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "span", { css: styles3.envValue, children: "https://pub-xxx.r2.dev" })
1026
1032
  ] })
1027
1033
  ] }),
1028
1034
  /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "button", { css: styles3.copyBtn, onClick: handleCopy, title: "Copy to clipboard", children: [
@@ -1288,39 +1294,92 @@ function StudioToolbar() {
1288
1294
  const handleFileChange = _react.useCallback.call(void 0, 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 (e2) {
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) {
@@ -1461,7 +1520,7 @@ function StudioToolbar() {
1461
1520
  message: data.message
1462
1521
  }));
1463
1522
  }
1464
- } catch (e2) {
1523
+ } catch (e3) {
1465
1524
  }
1466
1525
  }
1467
1526
  }
@@ -1574,56 +1633,99 @@ function StudioToolbar() {
1574
1633
  }, [selectedItems, clearSelection, triggerRefresh]);
1575
1634
  const handleSyncCdn = _react.useCallback.call(void 0, 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 = _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\//, ""));
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 = _optionalChain([data, 'access', _19 => _19.synced, 'optionalAccess', _20 => _20.length]) || 0;
1595
- const errorCount = _optionalChain([data, 'access', _21 => _21.errors, 'optionalAccess', _22 => _22.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 (_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")])) {
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 (_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++;
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 = _react.useCallback.call(void 0, 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__ */ _jsxruntime.jsx.call(void 0, CloudIcon, {}),
1898
- syncing ? "Syncing..." : "Sync CDN"
2000
+ "Sync CDN"
1899
2001
  ]
1900
2002
  }
1901
2003
  ),
@@ -3533,7 +3635,7 @@ function StudioDetailView() {
3533
3635
  });
3534
3636
  triggerRefresh();
3535
3637
  } else {
3536
- 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")])) {
3537
3639
  setShowR2SetupModal(true);
3538
3640
  } else {
3539
3641
  setAlertMessage({
@@ -3572,7 +3674,7 @@ function StudioDetailView() {
3572
3674
  if (!response.ok) {
3573
3675
  throw new Error("Processing failed");
3574
3676
  }
3575
- 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()]);
3576
3678
  if (!reader) {
3577
3679
  throw new Error("No response body");
3578
3680
  }
@@ -3589,7 +3691,7 @@ function StudioDetailView() {
3589
3691
  try {
3590
3692
  const data = JSON.parse(line.slice(6));
3591
3693
  setProcessProgress(data);
3592
- } catch (e3) {
3694
+ } catch (e5) {
3593
3695
  }
3594
3696
  }
3595
3697
  }
@@ -4519,4 +4621,4 @@ var StudioUI_default = StudioUI;
4519
4621
 
4520
4622
 
4521
4623
  exports.StudioUI = StudioUI; exports.default = StudioUI_default;
4522
- //# sourceMappingURL=StudioUI-VNSW7JUI.js.map
4624
+ //# sourceMappingURL=StudioUI-PH265HCB.js.map