@gallop.software/studio 1.5.4 → 1.5.6

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.
@@ -1752,6 +1752,8 @@ function StudioToolbar() {
1752
1752
  const [syncImageCount, setSyncImageCount] = _react.useState.call(void 0, 0);
1753
1753
  const [syncHasRemote, setSyncHasRemote] = _react.useState.call(void 0, false);
1754
1754
  const [syncHasLocal, setSyncHasLocal] = _react.useState.call(void 0, false);
1755
+ const [showDownloadConfirm, setShowDownloadConfirm] = _react.useState.call(void 0, false);
1756
+ const [downloadImageCount, setDownloadImageCount] = _react.useState.call(void 0, 0);
1755
1757
  const [showProgress, setShowProgress] = _react.useState.call(void 0, false);
1756
1758
  const [progressTitle, setProgressTitle] = _react.useState.call(void 0, "Processing Images");
1757
1759
  const [progressState, setProgressState] = _react.useState.call(void 0, {
@@ -2231,6 +2233,106 @@ function StudioToolbar() {
2231
2233
  });
2232
2234
  }
2233
2235
  }, [selectedItems, clearSelection, triggerRefresh]);
2236
+ const handleDownloadClick = _react.useCallback.call(void 0, async () => {
2237
+ if (selectedItems.size === 0) return;
2238
+ const selectedPaths2 = Array.from(selectedItems);
2239
+ const imageExtensions = ["jpg", "jpeg", "png", "gif", "webp", "svg", "ico", "bmp", "tiff", "tif"];
2240
+ const selectedImagePaths = selectedPaths2.filter((p) => {
2241
+ const ext = _optionalChain([p, 'access', _42 => _42.split, 'call', _43 => _43("."), 'access', _44 => _44.pop, 'call', _45 => _45(), 'optionalAccess', _46 => _46.toLowerCase, 'call', _47 => _47()]) || "";
2242
+ return imageExtensions.includes(ext);
2243
+ });
2244
+ if (selectedImagePaths.length === 0) {
2245
+ setAlertMessage({
2246
+ title: "No Images Found",
2247
+ message: "No images found in the selected items."
2248
+ });
2249
+ return;
2250
+ }
2251
+ setDownloadImageCount(selectedImagePaths.length);
2252
+ setShowDownloadConfirm(true);
2253
+ }, [selectedItems]);
2254
+ const handleDownloadConfirm = _react.useCallback.call(void 0, async () => {
2255
+ setShowDownloadConfirm(false);
2256
+ const selectedPaths2 = Array.from(selectedItems);
2257
+ const imageExtensions = ["jpg", "jpeg", "png", "gif", "webp", "svg", "ico", "bmp", "tiff", "tif"];
2258
+ const selectedImagePaths = selectedPaths2.filter((p) => {
2259
+ const ext = _optionalChain([p, 'access', _48 => _48.split, 'call', _49 => _49("."), 'access', _50 => _50.pop, 'call', _51 => _51(), 'optionalAccess', _52 => _52.toLowerCase, 'call', _53 => _53()]) || "";
2260
+ return imageExtensions.includes(ext);
2261
+ });
2262
+ const imageKeys = selectedImagePaths.map((p) => "/" + p.replace(/^public\//, ""));
2263
+ setProgressTitle("Downloading from CDN");
2264
+ setShowProgress(true);
2265
+ setProgressState({
2266
+ current: 0,
2267
+ total: imageKeys.length,
2268
+ percent: 0,
2269
+ status: "processing"
2270
+ });
2271
+ try {
2272
+ const response = await fetch("/api/studio/download-stream", {
2273
+ method: "POST",
2274
+ headers: { "Content-Type": "application/json" },
2275
+ body: JSON.stringify({ imageKeys })
2276
+ });
2277
+ if (!response.ok || !response.body) {
2278
+ throw new Error("Download request failed");
2279
+ }
2280
+ const reader = response.body.getReader();
2281
+ const decoder = new TextDecoder();
2282
+ let buffer = "";
2283
+ while (true) {
2284
+ const { done, value } = await reader.read();
2285
+ if (done) break;
2286
+ buffer += decoder.decode(value, { stream: true });
2287
+ const lines = buffer.split("\n");
2288
+ buffer = lines.pop() || "";
2289
+ for (const line of lines) {
2290
+ if (line.startsWith("data: ")) {
2291
+ try {
2292
+ const data = JSON.parse(line.slice(6));
2293
+ if (data.type === "progress") {
2294
+ setProgressState({
2295
+ current: data.current,
2296
+ total: data.total,
2297
+ percent: Math.round(data.current / data.total * 100),
2298
+ status: "processing",
2299
+ message: data.message
2300
+ });
2301
+ } else if (data.type === "complete") {
2302
+ setProgressState({
2303
+ current: data.total || imageKeys.length,
2304
+ total: data.total || imageKeys.length,
2305
+ percent: 100,
2306
+ status: "complete",
2307
+ message: data.message
2308
+ });
2309
+ } else if (data.type === "error") {
2310
+ setProgressState({
2311
+ current: 0,
2312
+ total: 0,
2313
+ percent: 0,
2314
+ status: "error",
2315
+ message: data.message
2316
+ });
2317
+ }
2318
+ } catch (e3) {
2319
+ }
2320
+ }
2321
+ }
2322
+ }
2323
+ clearSelection();
2324
+ triggerRefresh();
2325
+ } catch (error) {
2326
+ console.error("Download error:", error);
2327
+ setProgressState({
2328
+ current: 0,
2329
+ total: 0,
2330
+ percent: 0,
2331
+ status: "error",
2332
+ message: "Failed to download from CDN."
2333
+ });
2334
+ }
2335
+ }, [selectedItems, clearSelection, triggerRefresh]);
2234
2336
  const handleCreateFolder = _react.useCallback.call(void 0, async (folderName) => {
2235
2337
  setShowNewFolderModal(false);
2236
2338
  try {
@@ -2320,7 +2422,7 @@ function StudioToolbar() {
2320
2422
  errorMessage: data.message
2321
2423
  }));
2322
2424
  }
2323
- } catch (e3) {
2425
+ } catch (e4) {
2324
2426
  }
2325
2427
  }
2326
2428
  }
@@ -2349,6 +2451,10 @@ function StudioToolbar() {
2349
2451
  const item = fileItems.find((f) => f.path === path);
2350
2452
  return item && item.cdnPushed && !item.isRemote;
2351
2453
  });
2454
+ const allR2Selection = hasSelection && Array.from(selectedItems).every((path) => {
2455
+ const item = fileItems.find((f) => f.path === path);
2456
+ return item && item.type === "file" && item.cdnPushed && !item.isRemote;
2457
+ });
2352
2458
  const selectedPaths = Array.from(selectedItems);
2353
2459
  const singleFolderSelected = selectedPaths.length === 1 && !selectedPaths[0].includes(".");
2354
2460
  const selectedFolderPath = singleFolderSelected ? selectedPaths[0] : null;
@@ -2395,6 +2501,16 @@ function StudioToolbar() {
2395
2501
  onCancel: () => setShowSyncConfirm(false)
2396
2502
  }
2397
2503
  ),
2504
+ showDownloadConfirm && /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
2505
+ ConfirmModal,
2506
+ {
2507
+ title: "Download from CDN",
2508
+ message: `Download ${downloadImageCount} image${downloadImageCount !== 1 ? "s" : ""} from Cloudflare R2 to local storage? Images will be removed from the CDN.`,
2509
+ confirmLabel: "Download",
2510
+ onConfirm: handleDownloadConfirm,
2511
+ onCancel: () => setShowDownloadConfirm(false)
2512
+ }
2513
+ ),
2398
2514
  showProgress && /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
2399
2515
  ProgressModal,
2400
2516
  {
@@ -2550,7 +2666,18 @@ function StudioToolbar() {
2550
2666
  ]
2551
2667
  }
2552
2668
  ),
2553
- /* @__PURE__ */ _jsxruntime.jsxs.call(void 0,
2669
+ allR2Selection ? /* @__PURE__ */ _jsxruntime.jsxs.call(void 0,
2670
+ "button",
2671
+ {
2672
+ css: styles5.btn,
2673
+ onClick: handleDownloadClick,
2674
+ disabled: !hasSelection,
2675
+ children: [
2676
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, CloudDownloadIcon, {}),
2677
+ "Download"
2678
+ ]
2679
+ }
2680
+ ) : /* @__PURE__ */ _jsxruntime.jsxs.call(void 0,
2554
2681
  "button",
2555
2682
  {
2556
2683
  css: styles5.btn,
@@ -2649,6 +2776,9 @@ function MoveIcon() {
2649
2776
  function CloudIcon() {
2650
2777
  return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "svg", { css: styles5.icon, fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M7 16a4 4 0 01-.88-7.903A5 5 0 1115.9 6L16 6a5 5 0 011 9.9M15 13l-3-3m0 0l-3 3m3-3v12" }) });
2651
2778
  }
2779
+ function CloudDownloadIcon() {
2780
+ return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "svg", { css: styles5.icon, fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M7 16a4 4 0 01-.88-7.903A5 5 0 1115.9 6L16 6a5 5 0 011 9.9M9 19l3 3m0 0l3-3m-3 3V10" }) });
2781
+ }
2652
2782
  function GridIcon() {
2653
2783
  return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "svg", { css: styles5.icon, fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M4 6a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2H6a2 2 0 01-2-2V6zM14 6a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2h-2a2 2 0 01-2-2V6zM4 16a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2H6a2 2 0 01-2-2v-2zM14 16a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2h-2a2 2 0 01-2-2v-2z" }) });
2654
2784
  }
@@ -3163,11 +3293,18 @@ var styles6 = {
3163
3293
  storedLabel: _react3.css`
3164
3294
  display: flex;
3165
3295
  align-items: center;
3166
- gap: 4px;
3167
- font-size: ${_chunkN6JYTJCBjs.fontSize.xs};
3168
- color: #f59e0b;
3169
3296
  margin: 2px 0 0 0;
3170
3297
  `,
3298
+ storedIconCloud: _react3.css`
3299
+ width: 16px;
3300
+ height: 16px;
3301
+ color: #f59e0b;
3302
+ `,
3303
+ storedIconRemote: _react3.css`
3304
+ width: 16px;
3305
+ height: 16px;
3306
+ color: #ef4444;
3307
+ `,
3171
3308
  globeIcon: _react3.css`
3172
3309
  width: 18px;
3173
3310
  height: 18px;
@@ -3454,10 +3591,7 @@ function GridItem({ item, isSelected, onClick, onOpen, onGenerateThumbnail }) {
3454
3591
  item.fileCount,
3455
3592
  " files"
3456
3593
  ] })
3457
- ] }) : item.cdnPushed ? /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "p", { css: styles6.storedLabel, children: [
3458
- /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "svg", { css: item.isRemote ? styles6.folderStatIconRemote : styles6.folderStatIconCloud, fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: item.isRemote ? /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M21 12a9 9 0 01-9 9m9-9a9 9 0 00-9-9m9 9H3m9 9a9 9 0 01-9-9m9 9c1.657 0 3-4.03 3-9s-1.343-9-3-9m0 18c-1.657 0-3-4.03-3-9s1.343-9 3-9m-9 9a9 9 0 019-9" }) : /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M3 15a4 4 0 004 4h9a5 5 0 10-.1-9.999 5.002 5.002 0 10-9.78 2.096A4.001 4.001 0 003 15z" }) }),
3459
- "stored"
3460
- ] }) : item.size !== void 0 && /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "p", { css: styles6.size, children: formatFileSize(item.size) })
3594
+ ] }) : item.cdnPushed ? /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "p", { css: styles6.storedLabel, children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "svg", { css: item.isRemote ? styles6.storedIconRemote : styles6.storedIconCloud, fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: item.isRemote ? /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M21 12a9 9 0 01-9 9m9-9a9 9 0 00-9-9m9 9H3m9 9a9 9 0 01-9-9m9 9c1.657 0 3-4.03 3-9s-1.343-9-3-9m0 18c-1.657 0-3-4.03-3-9s1.343-9 3-9m-9 9a9 9 0 019-9" }) : /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M3 15a4 4 0 004 4h9a5 5 0 10-.1-9.999 5.002 5.002 0 10-9.78 2.096A4.001 4.001 0 003 15z" }) }) }) : item.size !== void 0 && /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "p", { css: styles6.size, children: formatFileSize(item.size) })
3461
3595
  ] }) }) })
3462
3596
  ]
3463
3597
  }
@@ -3697,10 +3831,17 @@ var styles7 = {
3697
3831
  storedLabel: _react3.css`
3698
3832
  display: flex;
3699
3833
  align-items: center;
3700
- gap: 4px;
3701
- font-size: ${_chunkN6JYTJCBjs.fontSize.xs};
3834
+ `,
3835
+ storedIconCloud: _react3.css`
3836
+ width: 16px;
3837
+ height: 16px;
3702
3838
  color: #f59e0b;
3703
3839
  `,
3840
+ storedIconRemote: _react3.css`
3841
+ width: 16px;
3842
+ height: 16px;
3843
+ color: #ef4444;
3844
+ `,
3704
3845
  globeIcon: _react3.css`
3705
3846
  width: 16px;
3706
3847
  height: 16px;
@@ -4057,10 +4198,7 @@ function ListRow({ item, isSelected, onClick, onOpen, onGenerateThumbnail }) {
4057
4198
  " files"
4058
4199
  ] }),
4059
4200
  !item.localCount && !item.cloudCount && !item.remoteCount && item.fileCount === void 0 && "--"
4060
- ] }) : item.cdnPushed ? /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "span", { css: styles7.storedLabel, children: [
4061
- /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "svg", { css: item.isRemote ? styles7.folderStatIconRemote : styles7.folderStatIconCloud, fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: item.isRemote ? /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M21 12a9 9 0 01-9 9m9-9a9 9 0 00-9-9m9 9H3m9 9a9 9 0 01-9-9m9 9c1.657 0 3-4.03 3-9s-1.343-9-3-9m0 18c-1.657 0-3-4.03-3-9s1.343-9 3-9m-9 9a9 9 0 019-9" }) : /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M3 15a4 4 0 004 4h9a5 5 0 10-.1-9.999 5.002 5.002 0 10-9.78 2.096A4.001 4.001 0 003 15z" }) }),
4062
- "stored"
4063
- ] }) : item.size !== void 0 ? formatFileSize2(item.size) : "--" }),
4201
+ ] }) : item.cdnPushed ? /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "span", { css: styles7.storedLabel, children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "svg", { css: item.isRemote ? styles7.storedIconRemote : styles7.storedIconCloud, fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: item.isRemote ? /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M21 12a9 9 0 01-9 9m9-9a9 9 0 00-9-9m9 9H3m9 9a9 9 0 01-9-9m9 9c1.657 0 3-4.03 3-9s-1.343-9-3-9m0 18c-1.657 0-3-4.03-3-9s1.343-9 3-9m-9 9a9 9 0 019-9" }) : /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M3 15a4 4 0 004 4h9a5 5 0 10-.1-9.999 5.002 5.002 0 10-9.78 2.096A4.001 4.001 0 003 15z" }) }) }) : item.size !== void 0 ? formatFileSize2(item.size) : "--" }),
4064
4202
  /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "td", { css: [styles7.td, styles7.meta], children: isFolder ? item.totalSize !== void 0 ? formatFileSize2(item.totalSize) : "--" : item.dimensions ? `${item.dimensions.width}x${item.dimensions.height}` : "--" }),
4065
4203
  /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "td", { css: styles7.td, children: item.cdnPushed ? item.isRemote ? /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "span", { css: styles7.cdnBadgeRemote, children: "Remote" }) : /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "span", { css: styles7.cdnBadge, children: [
4066
4204
  /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "svg", { css: styles7.cdnIcon, fill: "currentColor", viewBox: "0 0 20 20", children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "path", { fillRule: "evenodd", d: "M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z", clipRule: "evenodd" }) }),
@@ -4490,7 +4628,7 @@ function StudioDetailView() {
4490
4628
  });
4491
4629
  return;
4492
4630
  }
4493
- const reader = _optionalChain([response, 'access', _42 => _42.body, 'optionalAccess', _43 => _43.getReader, 'call', _44 => _44()]);
4631
+ const reader = _optionalChain([response, 'access', _54 => _54.body, 'optionalAccess', _55 => _55.getReader, 'call', _56 => _56()]);
4494
4632
  const decoder = new TextDecoder();
4495
4633
  if (reader) {
4496
4634
  let buffer = "";
@@ -4532,7 +4670,7 @@ function StudioDetailView() {
4532
4670
  message: data.message
4533
4671
  }));
4534
4672
  }
4535
- } catch (e4) {
4673
+ } catch (e5) {
4536
4674
  }
4537
4675
  }
4538
4676
  }
@@ -5454,7 +5592,7 @@ function useStudioActions({
5454
5592
  });
5455
5593
  return;
5456
5594
  }
5457
- const reader = _optionalChain([response, 'access', _45 => _45.body, 'optionalAccess', _46 => _46.getReader, 'call', _47 => _47()]);
5595
+ const reader = _optionalChain([response, 'access', _57 => _57.body, 'optionalAccess', _58 => _58.getReader, 'call', _59 => _59()]);
5458
5596
  const decoder = new TextDecoder();
5459
5597
  if (reader) {
5460
5598
  let buffer = "";
@@ -5484,7 +5622,7 @@ function useStudioActions({
5484
5622
  status: "complete",
5485
5623
  message: `Moved ${data.moved} file${data.moved !== 1 ? "s" : ""}${data.errors > 0 ? `, ${data.errors} error${data.errors !== 1 ? "s" : ""}` : ""}`
5486
5624
  }));
5487
- if (data.errors > 0 && _optionalChain([data, 'access', _48 => _48.errorMessages, 'optionalAccess', _49 => _49.length]) > 0) {
5625
+ if (data.errors > 0 && _optionalChain([data, 'access', _60 => _60.errorMessages, 'optionalAccess', _61 => _61.length]) > 0) {
5488
5626
  showError("Move Failed", data.errorMessages.join("\n"));
5489
5627
  }
5490
5628
  clearSelection();
@@ -5497,7 +5635,7 @@ function useStudioActions({
5497
5635
  message: data.message || "Unknown error"
5498
5636
  }));
5499
5637
  }
5500
- } catch (e5) {
5638
+ } catch (e6) {
5501
5639
  }
5502
5640
  }
5503
5641
  }
@@ -5612,7 +5750,7 @@ function useStudioActions({
5612
5750
  });
5613
5751
  return;
5614
5752
  }
5615
- const reader = _optionalChain([response, 'access', _50 => _50.body, 'optionalAccess', _51 => _51.getReader, 'call', _52 => _52()]);
5753
+ const reader = _optionalChain([response, 'access', _62 => _62.body, 'optionalAccess', _63 => _63.getReader, 'call', _64 => _64()]);
5616
5754
  const decoder = new TextDecoder();
5617
5755
  if (reader) {
5618
5756
  let buffer = "";
@@ -5661,7 +5799,7 @@ function useStudioActions({
5661
5799
  message: data.message
5662
5800
  }));
5663
5801
  }
5664
- } catch (e6) {
5802
+ } catch (e7) {
5665
5803
  }
5666
5804
  }
5667
5805
  }
@@ -5701,7 +5839,7 @@ function useStudioActions({
5701
5839
  setProgressState((prev) => ({
5702
5840
  ...prev,
5703
5841
  orphanedFiles: void 0,
5704
- message: _optionalChain([prev, 'access', _53 => _53.message, 'optionalAccess', _54 => _54.replace, 'call', _55 => _55(/Found \d+ orphaned thumbnail\(s\).*/, "Orphaned thumbnails deleted.")])
5842
+ message: _optionalChain([prev, 'access', _65 => _65.message, 'optionalAccess', _66 => _66.replace, 'call', _67 => _67(/Found \d+ orphaned thumbnail\(s\).*/, "Orphaned thumbnails deleted.")])
5705
5843
  }));
5706
5844
  triggerRefresh();
5707
5845
  } else {
@@ -6344,4 +6482,4 @@ var StudioUI_default = StudioUI;
6344
6482
 
6345
6483
 
6346
6484
  exports.StudioUI = StudioUI; exports.default = StudioUI_default;
6347
- //# sourceMappingURL=StudioUI-JNACMWLN.js.map
6485
+ //# sourceMappingURL=StudioUI-2PPNO4QC.js.map