@gallop.software/studio 0.1.85 → 0.1.87
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/dist/{StudioUI-PPX6VKNU.mjs → StudioUI-6HTM3QHM.mjs} +202 -66
- package/dist/StudioUI-6HTM3QHM.mjs.map +1 -0
- package/dist/{StudioUI-P4TLZNF7.js → StudioUI-ZBSTYTUV.js} +206 -70
- package/dist/StudioUI-ZBSTYTUV.js.map +1 -0
- package/dist/index.js +1 -1
- package/dist/index.mjs +1 -1
- package/package.json +1 -1
- package/dist/StudioUI-P4TLZNF7.js.map +0 -1
- package/dist/StudioUI-PPX6VKNU.mjs.map +0 -1
|
@@ -1266,6 +1266,8 @@ function StudioToolbar() {
|
|
|
1266
1266
|
const [processing, setProcessing] = useState3(false);
|
|
1267
1267
|
const [showDeleteConfirm, setShowDeleteConfirm] = useState3(false);
|
|
1268
1268
|
const [showProcessConfirm, setShowProcessConfirm] = useState3(false);
|
|
1269
|
+
const [showSyncConfirm, setShowSyncConfirm] = useState3(false);
|
|
1270
|
+
const [syncImageCount, setSyncImageCount] = useState3(0);
|
|
1269
1271
|
const [showProgress, setShowProgress] = useState3(false);
|
|
1270
1272
|
const [progressState, setProgressState] = useState3({
|
|
1271
1273
|
current: 0,
|
|
@@ -1294,39 +1296,92 @@ function StudioToolbar() {
|
|
|
1294
1296
|
const handleFileChange = useCallback(async (e) => {
|
|
1295
1297
|
const files = e.target.files;
|
|
1296
1298
|
if (!files || files.length === 0) return;
|
|
1297
|
-
|
|
1299
|
+
const fileList = Array.from(files);
|
|
1300
|
+
if (fileList.length > 1) {
|
|
1301
|
+
setProgressState({
|
|
1302
|
+
current: 0,
|
|
1303
|
+
total: fileList.length,
|
|
1304
|
+
percent: 0,
|
|
1305
|
+
status: "processing",
|
|
1306
|
+
message: "Uploading files..."
|
|
1307
|
+
});
|
|
1308
|
+
setShowProgress(true);
|
|
1309
|
+
} else {
|
|
1310
|
+
setUploading(true);
|
|
1311
|
+
}
|
|
1312
|
+
let uploaded = 0;
|
|
1313
|
+
let errors = 0;
|
|
1298
1314
|
try {
|
|
1299
|
-
for (
|
|
1315
|
+
for (let i = 0; i < fileList.length; i++) {
|
|
1316
|
+
const file = fileList[i];
|
|
1317
|
+
if (fileList.length > 1) {
|
|
1318
|
+
setProgressState({
|
|
1319
|
+
current: i + 1,
|
|
1320
|
+
total: fileList.length,
|
|
1321
|
+
percent: Math.round((i + 1) / fileList.length * 100),
|
|
1322
|
+
status: "processing",
|
|
1323
|
+
currentFile: file.name
|
|
1324
|
+
});
|
|
1325
|
+
}
|
|
1300
1326
|
const formData = new FormData();
|
|
1301
1327
|
formData.append("file", file);
|
|
1302
1328
|
formData.append("path", currentPath);
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1329
|
+
try {
|
|
1330
|
+
const response = await fetch("/api/studio/upload", {
|
|
1331
|
+
method: "POST",
|
|
1332
|
+
body: formData
|
|
1333
|
+
});
|
|
1334
|
+
if (!response.ok) {
|
|
1335
|
+
const error = await response.json();
|
|
1336
|
+
errors++;
|
|
1337
|
+
if (fileList.length === 1) {
|
|
1338
|
+
if (response.status >= 500) {
|
|
1339
|
+
console.error("Upload error:", error);
|
|
1340
|
+
setAlertMessage({
|
|
1341
|
+
title: "Upload Failed",
|
|
1342
|
+
message: `Failed to upload ${file.name}: ${error.error || "Unknown error"}`
|
|
1343
|
+
});
|
|
1344
|
+
} else {
|
|
1345
|
+
setAlertMessage({
|
|
1346
|
+
title: "Cannot Upload Here",
|
|
1347
|
+
message: error.error || "Upload not allowed in this location."
|
|
1348
|
+
});
|
|
1349
|
+
}
|
|
1350
|
+
}
|
|
1315
1351
|
} else {
|
|
1316
|
-
|
|
1317
|
-
title: "Cannot Upload Here",
|
|
1318
|
-
message: error.error || "Upload not allowed in this location."
|
|
1319
|
-
});
|
|
1352
|
+
uploaded++;
|
|
1320
1353
|
}
|
|
1354
|
+
} catch {
|
|
1355
|
+
errors++;
|
|
1321
1356
|
}
|
|
1322
1357
|
}
|
|
1358
|
+
if (fileList.length > 1) {
|
|
1359
|
+
setProgressState({
|
|
1360
|
+
current: fileList.length,
|
|
1361
|
+
total: fileList.length,
|
|
1362
|
+
percent: 100,
|
|
1363
|
+
status: "complete",
|
|
1364
|
+
processed: uploaded,
|
|
1365
|
+
errors
|
|
1366
|
+
});
|
|
1367
|
+
}
|
|
1323
1368
|
triggerRefresh();
|
|
1324
1369
|
} catch (error) {
|
|
1325
1370
|
console.error("Upload error:", error);
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1371
|
+
if (fileList.length > 1) {
|
|
1372
|
+
setProgressState({
|
|
1373
|
+
current: 0,
|
|
1374
|
+
total: 0,
|
|
1375
|
+
percent: 0,
|
|
1376
|
+
status: "error",
|
|
1377
|
+
message: "Upload failed."
|
|
1378
|
+
});
|
|
1379
|
+
} else {
|
|
1380
|
+
setAlertMessage({
|
|
1381
|
+
title: "Upload Failed",
|
|
1382
|
+
message: "Upload failed. Check console for details."
|
|
1383
|
+
});
|
|
1384
|
+
}
|
|
1330
1385
|
} finally {
|
|
1331
1386
|
setUploading(false);
|
|
1332
1387
|
if (fileInputRef.current) {
|
|
@@ -1578,58 +1633,129 @@ function StudioToolbar() {
|
|
|
1578
1633
|
});
|
|
1579
1634
|
}
|
|
1580
1635
|
}, [selectedItems, clearSelection, triggerRefresh]);
|
|
1581
|
-
const
|
|
1636
|
+
const handleSyncClick = useCallback(async () => {
|
|
1582
1637
|
if (selectedItems.size === 0) return;
|
|
1583
|
-
const
|
|
1584
|
-
|
|
1638
|
+
const selectedPaths2 = Array.from(selectedItems);
|
|
1639
|
+
const imageExtensions = ["jpg", "jpeg", "png", "gif", "webp", "svg", "ico", "bmp", "tiff", "tif"];
|
|
1640
|
+
const selectedImagePaths = selectedPaths2.filter((p) => {
|
|
1641
|
+
const ext = p.split(".").pop()?.toLowerCase() || "";
|
|
1642
|
+
return imageExtensions.includes(ext);
|
|
1643
|
+
});
|
|
1644
|
+
const selectedFolders = selectedPaths2.filter((p) => !p.includes(".") || p.endsWith("/"));
|
|
1645
|
+
if (selectedFolders.length > 0) {
|
|
1646
|
+
try {
|
|
1647
|
+
const response = await fetch(`/api/studio/folder-images?folders=${encodeURIComponent(selectedFolders.join(","))}`);
|
|
1648
|
+
const data = await response.json();
|
|
1649
|
+
if (data.images) {
|
|
1650
|
+
for (const img of data.images) {
|
|
1651
|
+
const fullPath = `public/${img}`;
|
|
1652
|
+
if (!selectedImagePaths.includes(fullPath)) {
|
|
1653
|
+
selectedImagePaths.push(fullPath);
|
|
1654
|
+
}
|
|
1655
|
+
}
|
|
1656
|
+
}
|
|
1657
|
+
} catch (error) {
|
|
1658
|
+
console.error("Failed to get folder images:", error);
|
|
1659
|
+
}
|
|
1660
|
+
}
|
|
1661
|
+
if (selectedImagePaths.length === 0) {
|
|
1585
1662
|
setAlertMessage({
|
|
1586
|
-
title: "No Images
|
|
1587
|
-
message: "
|
|
1663
|
+
title: "No Images Found",
|
|
1664
|
+
message: "No images found in the selected items."
|
|
1588
1665
|
});
|
|
1589
1666
|
return;
|
|
1590
1667
|
}
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1668
|
+
setSyncImageCount(selectedImagePaths.length);
|
|
1669
|
+
setShowSyncConfirm(true);
|
|
1670
|
+
}, [selectedItems]);
|
|
1671
|
+
const handleSyncConfirm = useCallback(async () => {
|
|
1672
|
+
setShowSyncConfirm(false);
|
|
1673
|
+
const selectedPaths2 = Array.from(selectedItems);
|
|
1674
|
+
const imageExtensions = ["jpg", "jpeg", "png", "gif", "webp", "svg", "ico", "bmp", "tiff", "tif"];
|
|
1675
|
+
const selectedImagePaths = selectedPaths2.filter((p) => {
|
|
1676
|
+
const ext = p.split(".").pop()?.toLowerCase() || "";
|
|
1677
|
+
return imageExtensions.includes(ext);
|
|
1678
|
+
});
|
|
1679
|
+
const selectedFolders = selectedPaths2.filter((p) => !p.includes(".") || p.endsWith("/"));
|
|
1680
|
+
if (selectedFolders.length > 0) {
|
|
1681
|
+
try {
|
|
1682
|
+
const response = await fetch(`/api/studio/folder-images?folders=${encodeURIComponent(selectedFolders.join(","))}`);
|
|
1683
|
+
const data = await response.json();
|
|
1684
|
+
if (data.images) {
|
|
1685
|
+
for (const img of data.images) {
|
|
1686
|
+
const fullPath = `public/${img}`;
|
|
1687
|
+
if (!selectedImagePaths.includes(fullPath)) {
|
|
1688
|
+
selectedImagePaths.push(fullPath);
|
|
1689
|
+
}
|
|
1690
|
+
}
|
|
1612
1691
|
}
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
}
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1692
|
+
} catch (error) {
|
|
1693
|
+
console.error("Failed to get folder images:", error);
|
|
1694
|
+
}
|
|
1695
|
+
}
|
|
1696
|
+
const imageKeys = selectedImagePaths.map((p) => "/" + p.replace(/^public\//, ""));
|
|
1697
|
+
setProgressState({
|
|
1698
|
+
current: 0,
|
|
1699
|
+
total: imageKeys.length,
|
|
1700
|
+
percent: 0,
|
|
1701
|
+
status: "processing",
|
|
1702
|
+
message: "Syncing to CDN..."
|
|
1703
|
+
});
|
|
1704
|
+
setShowProgress(true);
|
|
1705
|
+
let synced = 0;
|
|
1706
|
+
let errors = 0;
|
|
1707
|
+
try {
|
|
1708
|
+
for (let i = 0; i < imageKeys.length; i++) {
|
|
1709
|
+
const imageKey = imageKeys[i];
|
|
1710
|
+
setProgressState({
|
|
1711
|
+
current: i + 1,
|
|
1712
|
+
total: imageKeys.length,
|
|
1713
|
+
percent: Math.round((i + 1) / imageKeys.length * 100),
|
|
1714
|
+
status: "processing",
|
|
1715
|
+
currentFile: imageKey.replace(/^\//, "")
|
|
1716
|
+
});
|
|
1717
|
+
try {
|
|
1718
|
+
const response = await fetch("/api/studio/sync", {
|
|
1719
|
+
method: "POST",
|
|
1720
|
+
headers: { "Content-Type": "application/json" },
|
|
1721
|
+
body: JSON.stringify({ imageKeys: [imageKey] })
|
|
1622
1722
|
});
|
|
1723
|
+
const data = await response.json();
|
|
1724
|
+
if (!response.ok) {
|
|
1725
|
+
if (data.error?.includes("R2 not configured") || data.error?.includes("CLOUDFLARE_R2")) {
|
|
1726
|
+
setShowProgress(false);
|
|
1727
|
+
setShowR2SetupModal(true);
|
|
1728
|
+
return;
|
|
1729
|
+
}
|
|
1730
|
+
errors++;
|
|
1731
|
+
} else if (data.synced?.length > 0) {
|
|
1732
|
+
synced++;
|
|
1733
|
+
} else if (data.errors?.length > 0) {
|
|
1734
|
+
errors++;
|
|
1735
|
+
}
|
|
1736
|
+
} catch {
|
|
1737
|
+
errors++;
|
|
1623
1738
|
}
|
|
1624
1739
|
}
|
|
1740
|
+
setProgressState({
|
|
1741
|
+
current: imageKeys.length,
|
|
1742
|
+
total: imageKeys.length,
|
|
1743
|
+
percent: 100,
|
|
1744
|
+
status: "complete",
|
|
1745
|
+
processed: synced,
|
|
1746
|
+
errors
|
|
1747
|
+
});
|
|
1748
|
+
clearSelection();
|
|
1749
|
+
triggerRefresh();
|
|
1625
1750
|
} catch (error) {
|
|
1626
1751
|
console.error("Sync error:", error);
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1752
|
+
setProgressState({
|
|
1753
|
+
current: 0,
|
|
1754
|
+
total: 0,
|
|
1755
|
+
percent: 0,
|
|
1756
|
+
status: "error",
|
|
1757
|
+
message: "Failed to sync to CDN."
|
|
1630
1758
|
});
|
|
1631
|
-
} finally {
|
|
1632
|
-
setSyncing(false);
|
|
1633
1759
|
}
|
|
1634
1760
|
}, [selectedItems, clearSelection, triggerRefresh]);
|
|
1635
1761
|
const handleCreateFolder = useCallback(async (folderName) => {
|
|
@@ -1740,6 +1866,16 @@ function StudioToolbar() {
|
|
|
1740
1866
|
onCancel: () => setShowDeleteConfirm(false)
|
|
1741
1867
|
}
|
|
1742
1868
|
),
|
|
1869
|
+
showSyncConfirm && /* @__PURE__ */ jsx4(
|
|
1870
|
+
ConfirmModal,
|
|
1871
|
+
{
|
|
1872
|
+
title: "Sync to CDN",
|
|
1873
|
+
message: `Sync ${syncImageCount} image${syncImageCount !== 1 ? "s" : ""} to Cloudflare R2? Images must be processed first. After syncing, local thumbnails will be deleted.`,
|
|
1874
|
+
confirmLabel: "Sync",
|
|
1875
|
+
onConfirm: handleSyncConfirm,
|
|
1876
|
+
onCancel: () => setShowSyncConfirm(false)
|
|
1877
|
+
}
|
|
1878
|
+
),
|
|
1743
1879
|
showProcessConfirm && /* @__PURE__ */ jsx4(
|
|
1744
1880
|
ConfirmModal,
|
|
1745
1881
|
{
|
|
@@ -1897,11 +2033,11 @@ function StudioToolbar() {
|
|
|
1897
2033
|
"button",
|
|
1898
2034
|
{
|
|
1899
2035
|
css: styles4.btn,
|
|
1900
|
-
onClick:
|
|
1901
|
-
disabled: !hasSelection
|
|
2036
|
+
onClick: handleSyncClick,
|
|
2037
|
+
disabled: !hasSelection,
|
|
1902
2038
|
children: [
|
|
1903
2039
|
/* @__PURE__ */ jsx4(CloudIcon, {}),
|
|
1904
|
-
|
|
2040
|
+
"Sync CDN"
|
|
1905
2041
|
]
|
|
1906
2042
|
}
|
|
1907
2043
|
),
|
|
@@ -4525,4 +4661,4 @@ export {
|
|
|
4525
4661
|
StudioUI,
|
|
4526
4662
|
StudioUI_default as default
|
|
4527
4663
|
};
|
|
4528
|
-
//# sourceMappingURL=StudioUI-
|
|
4664
|
+
//# sourceMappingURL=StudioUI-6HTM3QHM.mjs.map
|