@gallop.software/studio 0.1.86 → 0.1.88

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.
@@ -310,6 +310,23 @@ var progressStyles = {
310
310
  white-space: nowrap;
311
311
  overflow: hidden;
312
312
  text-overflow: ellipsis;
313
+ `,
314
+ errorList: css`
315
+ margin-top: 12px;
316
+ padding: 12px;
317
+ background: #fef2f2;
318
+ border: 1px solid #fecaca;
319
+ border-radius: 6px;
320
+ max-height: 200px;
321
+ overflow-y: auto;
322
+ `,
323
+ errorItem: css`
324
+ font-size: ${fontSize.xs};
325
+ color: #991b1b;
326
+ margin: 0 0 4px;
327
+ &:last-child {
328
+ margin-bottom: 0;
329
+ }
313
330
  `
314
331
  };
315
332
  function ProgressModal({
@@ -331,26 +348,36 @@ function ProgressModal({
331
348
  " image",
332
349
  (progress.processed ?? progress.current) !== 1 ? "s" : "",
333
350
  " before stopping."
334
- ] }) : isComplete ? /* @__PURE__ */ jsxs("p", { css: styles.message, children: [
335
- "Processed ",
336
- progress.processed,
337
- " image",
338
- progress.processed !== 1 ? "s" : "",
339
- ".",
340
- progress.orphansRemoved !== void 0 && progress.orphansRemoved > 0 ? /* @__PURE__ */ jsxs(Fragment, { children: [
341
- " Removed ",
342
- progress.orphansRemoved,
343
- " orphaned thumbnail",
344
- progress.orphansRemoved !== 1 ? "s" : "",
345
- "."
346
- ] }) : null,
347
- progress.errors !== void 0 && progress.errors > 0 ? /* @__PURE__ */ jsxs(Fragment, { children: [
348
- " ",
349
- progress.errors,
350
- " error",
351
- progress.errors !== 1 ? "s" : "",
352
- " occurred."
353
- ] }) : null
351
+ ] }) : isComplete ? /* @__PURE__ */ jsxs(Fragment, { children: [
352
+ /* @__PURE__ */ jsxs("p", { css: styles.message, children: [
353
+ "Processed ",
354
+ progress.processed,
355
+ " image",
356
+ progress.processed !== 1 ? "s" : "",
357
+ ".",
358
+ progress.orphansRemoved !== void 0 && progress.orphansRemoved > 0 ? /* @__PURE__ */ jsxs(Fragment, { children: [
359
+ " Removed ",
360
+ progress.orphansRemoved,
361
+ " orphaned thumbnail",
362
+ progress.orphansRemoved !== 1 ? "s" : "",
363
+ "."
364
+ ] }) : null,
365
+ progress.errors !== void 0 && progress.errors > 0 ? /* @__PURE__ */ jsxs(Fragment, { children: [
366
+ " ",
367
+ progress.errors,
368
+ " error",
369
+ progress.errors !== 1 ? "s" : "",
370
+ " occurred."
371
+ ] }) : null
372
+ ] }),
373
+ progress.errorMessages && progress.errorMessages.length > 0 && /* @__PURE__ */ jsxs("div", { css: progressStyles.errorList, children: [
374
+ progress.errorMessages.slice(0, 10).map((msg, i) => /* @__PURE__ */ jsx("p", { css: progressStyles.errorItem, children: msg }, i)),
375
+ progress.errorMessages.length > 10 && /* @__PURE__ */ jsxs("p", { css: progressStyles.errorItem, children: [
376
+ "...and ",
377
+ progress.errorMessages.length - 10,
378
+ " more"
379
+ ] })
380
+ ] })
354
381
  ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
355
382
  /* @__PURE__ */ jsx("p", { css: styles.message, children: progress.status === "cleanup" ? "Cleaning up orphaned files..." : `Processing images...` }),
356
383
  /* @__PURE__ */ jsxs("div", { css: progressStyles.progressContainer, children: [
@@ -1266,6 +1293,8 @@ function StudioToolbar() {
1266
1293
  const [processing, setProcessing] = useState3(false);
1267
1294
  const [showDeleteConfirm, setShowDeleteConfirm] = useState3(false);
1268
1295
  const [showProcessConfirm, setShowProcessConfirm] = useState3(false);
1296
+ const [showSyncConfirm, setShowSyncConfirm] = useState3(false);
1297
+ const [syncImageCount, setSyncImageCount] = useState3(0);
1269
1298
  const [showProgress, setShowProgress] = useState3(false);
1270
1299
  const [progressState, setProgressState] = useState3({
1271
1300
  current: 0,
@@ -1631,7 +1660,7 @@ function StudioToolbar() {
1631
1660
  });
1632
1661
  }
1633
1662
  }, [selectedItems, clearSelection, triggerRefresh]);
1634
- const handleSyncCdn = useCallback(async () => {
1663
+ const handleSyncClick = useCallback(async () => {
1635
1664
  if (selectedItems.size === 0) return;
1636
1665
  const selectedPaths2 = Array.from(selectedItems);
1637
1666
  const imageExtensions = ["jpg", "jpeg", "png", "gif", "webp", "svg", "ico", "bmp", "tiff", "tif"];
@@ -1656,14 +1685,42 @@ function StudioToolbar() {
1656
1685
  console.error("Failed to get folder images:", error);
1657
1686
  }
1658
1687
  }
1659
- const imageKeys = selectedImagePaths.map((p) => "/" + p.replace(/^public\//, ""));
1660
- if (imageKeys.length === 0) {
1688
+ if (selectedImagePaths.length === 0) {
1661
1689
  setAlertMessage({
1662
1690
  title: "No Images Found",
1663
1691
  message: "No images found in the selected items."
1664
1692
  });
1665
1693
  return;
1666
1694
  }
1695
+ setSyncImageCount(selectedImagePaths.length);
1696
+ setShowSyncConfirm(true);
1697
+ }, [selectedItems]);
1698
+ const handleSyncConfirm = useCallback(async () => {
1699
+ setShowSyncConfirm(false);
1700
+ const selectedPaths2 = Array.from(selectedItems);
1701
+ const imageExtensions = ["jpg", "jpeg", "png", "gif", "webp", "svg", "ico", "bmp", "tiff", "tif"];
1702
+ const selectedImagePaths = selectedPaths2.filter((p) => {
1703
+ const ext = p.split(".").pop()?.toLowerCase() || "";
1704
+ return imageExtensions.includes(ext);
1705
+ });
1706
+ const selectedFolders = selectedPaths2.filter((p) => !p.includes(".") || p.endsWith("/"));
1707
+ if (selectedFolders.length > 0) {
1708
+ try {
1709
+ const response = await fetch(`/api/studio/folder-images?folders=${encodeURIComponent(selectedFolders.join(","))}`);
1710
+ const data = await response.json();
1711
+ if (data.images) {
1712
+ for (const img of data.images) {
1713
+ const fullPath = `public/${img}`;
1714
+ if (!selectedImagePaths.includes(fullPath)) {
1715
+ selectedImagePaths.push(fullPath);
1716
+ }
1717
+ }
1718
+ }
1719
+ } catch (error) {
1720
+ console.error("Failed to get folder images:", error);
1721
+ }
1722
+ }
1723
+ const imageKeys = selectedImagePaths.map((p) => "/" + p.replace(/^public\//, ""));
1667
1724
  setProgressState({
1668
1725
  current: 0,
1669
1726
  total: imageKeys.length,
@@ -1674,6 +1731,7 @@ function StudioToolbar() {
1674
1731
  setShowProgress(true);
1675
1732
  let synced = 0;
1676
1733
  let errors = 0;
1734
+ const errorMessages = [];
1677
1735
  try {
1678
1736
  for (let i = 0; i < imageKeys.length; i++) {
1679
1737
  const imageKey = imageKeys[i];
@@ -1698,13 +1756,18 @@ function StudioToolbar() {
1698
1756
  return;
1699
1757
  }
1700
1758
  errors++;
1759
+ errorMessages.push(data.error || `Failed: ${imageKey}`);
1701
1760
  } else if (data.synced?.length > 0) {
1702
1761
  synced++;
1703
1762
  } else if (data.errors?.length > 0) {
1704
1763
  errors++;
1764
+ for (const errMsg of data.errors) {
1765
+ errorMessages.push(errMsg);
1766
+ }
1705
1767
  }
1706
- } catch {
1768
+ } catch (err) {
1707
1769
  errors++;
1770
+ errorMessages.push(`Network error: ${imageKey}`);
1708
1771
  }
1709
1772
  }
1710
1773
  setProgressState({
@@ -1713,7 +1776,8 @@ function StudioToolbar() {
1713
1776
  percent: 100,
1714
1777
  status: "complete",
1715
1778
  processed: synced,
1716
- errors
1779
+ errors,
1780
+ errorMessages: errorMessages.length > 0 ? errorMessages : void 0
1717
1781
  });
1718
1782
  clearSelection();
1719
1783
  triggerRefresh();
@@ -1836,6 +1900,16 @@ function StudioToolbar() {
1836
1900
  onCancel: () => setShowDeleteConfirm(false)
1837
1901
  }
1838
1902
  ),
1903
+ showSyncConfirm && /* @__PURE__ */ jsx4(
1904
+ ConfirmModal,
1905
+ {
1906
+ title: "Sync to CDN",
1907
+ message: `Sync ${syncImageCount} image${syncImageCount !== 1 ? "s" : ""} to Cloudflare R2? Images must be processed first. After syncing, local thumbnails will be deleted.`,
1908
+ confirmLabel: "Sync",
1909
+ onConfirm: handleSyncConfirm,
1910
+ onCancel: () => setShowSyncConfirm(false)
1911
+ }
1912
+ ),
1839
1913
  showProcessConfirm && /* @__PURE__ */ jsx4(
1840
1914
  ConfirmModal,
1841
1915
  {
@@ -1993,7 +2067,7 @@ function StudioToolbar() {
1993
2067
  "button",
1994
2068
  {
1995
2069
  css: styles4.btn,
1996
- onClick: handleSyncCdn,
2070
+ onClick: handleSyncClick,
1997
2071
  disabled: !hasSelection,
1998
2072
  children: [
1999
2073
  /* @__PURE__ */ jsx4(CloudIcon, {}),
@@ -4621,4 +4695,4 @@ export {
4621
4695
  StudioUI,
4622
4696
  StudioUI_default as default
4623
4697
  };
4624
- //# sourceMappingURL=StudioUI-QTKNMFLF.mjs.map
4698
+ //# sourceMappingURL=StudioUI-6CQ7MX7R.mjs.map