@gallop.software/studio 1.5.5 → 1.5.7

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, {
@@ -2181,32 +2183,43 @@ function StudioToolbar() {
2181
2183
  status: "processing",
2182
2184
  currentFile: imageKey.replace(/^\//, "")
2183
2185
  });
2184
- try {
2185
- const response = await fetch("/api/studio/sync", {
2186
- method: "POST",
2187
- headers: { "Content-Type": "application/json" },
2188
- body: JSON.stringify({ imageKeys: [imageKey] })
2189
- });
2190
- const data = await response.json();
2191
- if (!response.ok) {
2192
- if (_optionalChain([data, 'access', _32 => _32.error, 'optionalAccess', _33 => _33.includes, 'call', _34 => _34("R2 not configured")]) || _optionalChain([data, 'access', _35 => _35.error, 'optionalAccess', _36 => _36.includes, 'call', _37 => _37("CLOUDFLARE_R2")])) {
2193
- setShowProgress(false);
2194
- setShowR2SetupModal(true);
2195
- return;
2186
+ let success = false;
2187
+ let lastError;
2188
+ for (let attempt = 0; attempt < 3 && !success; attempt++) {
2189
+ try {
2190
+ const response = await fetch("/api/studio/sync", {
2191
+ method: "POST",
2192
+ headers: { "Content-Type": "application/json" },
2193
+ body: JSON.stringify({ imageKeys: [imageKey] })
2194
+ });
2195
+ const data = await response.json();
2196
+ if (!response.ok) {
2197
+ if (_optionalChain([data, 'access', _32 => _32.error, 'optionalAccess', _33 => _33.includes, 'call', _34 => _34("R2 not configured")]) || _optionalChain([data, 'access', _35 => _35.error, 'optionalAccess', _36 => _36.includes, 'call', _37 => _37("CLOUDFLARE_R2")])) {
2198
+ setShowProgress(false);
2199
+ setShowR2SetupModal(true);
2200
+ return;
2201
+ }
2202
+ lastError = data.error || `Failed: ${imageKey}`;
2203
+ } else if (_optionalChain([data, 'access', _38 => _38.pushed, 'optionalAccess', _39 => _39.length]) > 0) {
2204
+ pushed++;
2205
+ success = true;
2206
+ } else if (_optionalChain([data, 'access', _40 => _40.errors, 'optionalAccess', _41 => _41.length]) > 0) {
2207
+ for (const errMsg of data.errors) {
2208
+ lastError = errMsg;
2209
+ }
2210
+ } else {
2211
+ success = true;
2196
2212
  }
2197
- errors++;
2198
- errorMessages.push(data.error || `Failed: ${imageKey}`);
2199
- } else if (_optionalChain([data, 'access', _38 => _38.pushed, 'optionalAccess', _39 => _39.length]) > 0) {
2200
- pushed++;
2201
- } else if (_optionalChain([data, 'access', _40 => _40.errors, 'optionalAccess', _41 => _41.length]) > 0) {
2202
- errors++;
2203
- for (const errMsg of data.errors) {
2204
- errorMessages.push(errMsg);
2213
+ } catch (err) {
2214
+ lastError = `Network error: ${imageKey}`;
2215
+ if (attempt < 2) {
2216
+ await new Promise((resolve) => setTimeout(resolve, 500 * (attempt + 1)));
2205
2217
  }
2206
2218
  }
2207
- } catch (err) {
2219
+ }
2220
+ if (!success && lastError) {
2208
2221
  errors++;
2209
- errorMessages.push(`Network error: ${imageKey}`);
2222
+ errorMessages.push(lastError);
2210
2223
  }
2211
2224
  }
2212
2225
  setProgressState({
@@ -2231,6 +2244,106 @@ function StudioToolbar() {
2231
2244
  });
2232
2245
  }
2233
2246
  }, [selectedItems, clearSelection, triggerRefresh]);
2247
+ const handleDownloadClick = _react.useCallback.call(void 0, async () => {
2248
+ if (selectedItems.size === 0) return;
2249
+ const selectedPaths2 = Array.from(selectedItems);
2250
+ const imageExtensions = ["jpg", "jpeg", "png", "gif", "webp", "svg", "ico", "bmp", "tiff", "tif"];
2251
+ const selectedImagePaths = selectedPaths2.filter((p) => {
2252
+ 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()]) || "";
2253
+ return imageExtensions.includes(ext);
2254
+ });
2255
+ if (selectedImagePaths.length === 0) {
2256
+ setAlertMessage({
2257
+ title: "No Images Found",
2258
+ message: "No images found in the selected items."
2259
+ });
2260
+ return;
2261
+ }
2262
+ setDownloadImageCount(selectedImagePaths.length);
2263
+ setShowDownloadConfirm(true);
2264
+ }, [selectedItems]);
2265
+ const handleDownloadConfirm = _react.useCallback.call(void 0, async () => {
2266
+ setShowDownloadConfirm(false);
2267
+ const selectedPaths2 = Array.from(selectedItems);
2268
+ const imageExtensions = ["jpg", "jpeg", "png", "gif", "webp", "svg", "ico", "bmp", "tiff", "tif"];
2269
+ const selectedImagePaths = selectedPaths2.filter((p) => {
2270
+ 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()]) || "";
2271
+ return imageExtensions.includes(ext);
2272
+ });
2273
+ const imageKeys = selectedImagePaths.map((p) => "/" + p.replace(/^public\//, ""));
2274
+ setProgressTitle("Downloading from CDN");
2275
+ setShowProgress(true);
2276
+ setProgressState({
2277
+ current: 0,
2278
+ total: imageKeys.length,
2279
+ percent: 0,
2280
+ status: "processing"
2281
+ });
2282
+ try {
2283
+ const response = await fetch("/api/studio/download-stream", {
2284
+ method: "POST",
2285
+ headers: { "Content-Type": "application/json" },
2286
+ body: JSON.stringify({ imageKeys })
2287
+ });
2288
+ if (!response.ok || !response.body) {
2289
+ throw new Error("Download request failed");
2290
+ }
2291
+ const reader = response.body.getReader();
2292
+ const decoder = new TextDecoder();
2293
+ let buffer = "";
2294
+ while (true) {
2295
+ const { done, value } = await reader.read();
2296
+ if (done) break;
2297
+ buffer += decoder.decode(value, { stream: true });
2298
+ const lines = buffer.split("\n");
2299
+ buffer = lines.pop() || "";
2300
+ for (const line of lines) {
2301
+ if (line.startsWith("data: ")) {
2302
+ try {
2303
+ const data = JSON.parse(line.slice(6));
2304
+ if (data.type === "progress") {
2305
+ setProgressState({
2306
+ current: data.current,
2307
+ total: data.total,
2308
+ percent: Math.round(data.current / data.total * 100),
2309
+ status: "processing",
2310
+ message: data.message
2311
+ });
2312
+ } else if (data.type === "complete") {
2313
+ setProgressState({
2314
+ current: data.total || imageKeys.length,
2315
+ total: data.total || imageKeys.length,
2316
+ percent: 100,
2317
+ status: "complete",
2318
+ message: data.message
2319
+ });
2320
+ } else if (data.type === "error") {
2321
+ setProgressState({
2322
+ current: 0,
2323
+ total: 0,
2324
+ percent: 0,
2325
+ status: "error",
2326
+ message: data.message
2327
+ });
2328
+ }
2329
+ } catch (e3) {
2330
+ }
2331
+ }
2332
+ }
2333
+ }
2334
+ clearSelection();
2335
+ triggerRefresh();
2336
+ } catch (error) {
2337
+ console.error("Download error:", error);
2338
+ setProgressState({
2339
+ current: 0,
2340
+ total: 0,
2341
+ percent: 0,
2342
+ status: "error",
2343
+ message: "Failed to download from CDN."
2344
+ });
2345
+ }
2346
+ }, [selectedItems, clearSelection, triggerRefresh]);
2234
2347
  const handleCreateFolder = _react.useCallback.call(void 0, async (folderName) => {
2235
2348
  setShowNewFolderModal(false);
2236
2349
  try {
@@ -2320,7 +2433,7 @@ function StudioToolbar() {
2320
2433
  errorMessage: data.message
2321
2434
  }));
2322
2435
  }
2323
- } catch (e3) {
2436
+ } catch (e4) {
2324
2437
  }
2325
2438
  }
2326
2439
  }
@@ -2349,6 +2462,10 @@ function StudioToolbar() {
2349
2462
  const item = fileItems.find((f) => f.path === path);
2350
2463
  return item && item.cdnPushed && !item.isRemote;
2351
2464
  });
2465
+ const allR2Selection = hasSelection && Array.from(selectedItems).every((path) => {
2466
+ const item = fileItems.find((f) => f.path === path);
2467
+ return item && item.type === "file" && item.cdnPushed && !item.isRemote;
2468
+ });
2352
2469
  const selectedPaths = Array.from(selectedItems);
2353
2470
  const singleFolderSelected = selectedPaths.length === 1 && !selectedPaths[0].includes(".");
2354
2471
  const selectedFolderPath = singleFolderSelected ? selectedPaths[0] : null;
@@ -2395,6 +2512,16 @@ function StudioToolbar() {
2395
2512
  onCancel: () => setShowSyncConfirm(false)
2396
2513
  }
2397
2514
  ),
2515
+ showDownloadConfirm && /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
2516
+ ConfirmModal,
2517
+ {
2518
+ title: "Download from CDN",
2519
+ message: `Download ${downloadImageCount} image${downloadImageCount !== 1 ? "s" : ""} from Cloudflare R2 to local storage? Images will be removed from the CDN.`,
2520
+ confirmLabel: "Download",
2521
+ onConfirm: handleDownloadConfirm,
2522
+ onCancel: () => setShowDownloadConfirm(false)
2523
+ }
2524
+ ),
2398
2525
  showProgress && /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
2399
2526
  ProgressModal,
2400
2527
  {
@@ -2550,7 +2677,18 @@ function StudioToolbar() {
2550
2677
  ]
2551
2678
  }
2552
2679
  ),
2553
- /* @__PURE__ */ _jsxruntime.jsxs.call(void 0,
2680
+ allR2Selection ? /* @__PURE__ */ _jsxruntime.jsxs.call(void 0,
2681
+ "button",
2682
+ {
2683
+ css: styles5.btn,
2684
+ onClick: handleDownloadClick,
2685
+ disabled: !hasSelection,
2686
+ children: [
2687
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, CloudDownloadIcon, {}),
2688
+ "Download"
2689
+ ]
2690
+ }
2691
+ ) : /* @__PURE__ */ _jsxruntime.jsxs.call(void 0,
2554
2692
  "button",
2555
2693
  {
2556
2694
  css: styles5.btn,
@@ -2649,6 +2787,9 @@ function MoveIcon() {
2649
2787
  function CloudIcon() {
2650
2788
  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
2789
  }
2790
+ function CloudDownloadIcon() {
2791
+ 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" }) });
2792
+ }
2652
2793
  function GridIcon() {
2653
2794
  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
2795
  }
@@ -4498,7 +4639,7 @@ function StudioDetailView() {
4498
4639
  });
4499
4640
  return;
4500
4641
  }
4501
- const reader = _optionalChain([response, 'access', _42 => _42.body, 'optionalAccess', _43 => _43.getReader, 'call', _44 => _44()]);
4642
+ const reader = _optionalChain([response, 'access', _54 => _54.body, 'optionalAccess', _55 => _55.getReader, 'call', _56 => _56()]);
4502
4643
  const decoder = new TextDecoder();
4503
4644
  if (reader) {
4504
4645
  let buffer = "";
@@ -4540,7 +4681,7 @@ function StudioDetailView() {
4540
4681
  message: data.message
4541
4682
  }));
4542
4683
  }
4543
- } catch (e4) {
4684
+ } catch (e5) {
4544
4685
  }
4545
4686
  }
4546
4687
  }
@@ -5462,7 +5603,7 @@ function useStudioActions({
5462
5603
  });
5463
5604
  return;
5464
5605
  }
5465
- const reader = _optionalChain([response, 'access', _45 => _45.body, 'optionalAccess', _46 => _46.getReader, 'call', _47 => _47()]);
5606
+ const reader = _optionalChain([response, 'access', _57 => _57.body, 'optionalAccess', _58 => _58.getReader, 'call', _59 => _59()]);
5466
5607
  const decoder = new TextDecoder();
5467
5608
  if (reader) {
5468
5609
  let buffer = "";
@@ -5492,7 +5633,7 @@ function useStudioActions({
5492
5633
  status: "complete",
5493
5634
  message: `Moved ${data.moved} file${data.moved !== 1 ? "s" : ""}${data.errors > 0 ? `, ${data.errors} error${data.errors !== 1 ? "s" : ""}` : ""}`
5494
5635
  }));
5495
- if (data.errors > 0 && _optionalChain([data, 'access', _48 => _48.errorMessages, 'optionalAccess', _49 => _49.length]) > 0) {
5636
+ if (data.errors > 0 && _optionalChain([data, 'access', _60 => _60.errorMessages, 'optionalAccess', _61 => _61.length]) > 0) {
5496
5637
  showError("Move Failed", data.errorMessages.join("\n"));
5497
5638
  }
5498
5639
  clearSelection();
@@ -5505,7 +5646,7 @@ function useStudioActions({
5505
5646
  message: data.message || "Unknown error"
5506
5647
  }));
5507
5648
  }
5508
- } catch (e5) {
5649
+ } catch (e6) {
5509
5650
  }
5510
5651
  }
5511
5652
  }
@@ -5620,7 +5761,7 @@ function useStudioActions({
5620
5761
  });
5621
5762
  return;
5622
5763
  }
5623
- const reader = _optionalChain([response, 'access', _50 => _50.body, 'optionalAccess', _51 => _51.getReader, 'call', _52 => _52()]);
5764
+ const reader = _optionalChain([response, 'access', _62 => _62.body, 'optionalAccess', _63 => _63.getReader, 'call', _64 => _64()]);
5624
5765
  const decoder = new TextDecoder();
5625
5766
  if (reader) {
5626
5767
  let buffer = "";
@@ -5669,7 +5810,7 @@ function useStudioActions({
5669
5810
  message: data.message
5670
5811
  }));
5671
5812
  }
5672
- } catch (e6) {
5813
+ } catch (e7) {
5673
5814
  }
5674
5815
  }
5675
5816
  }
@@ -5709,7 +5850,7 @@ function useStudioActions({
5709
5850
  setProgressState((prev) => ({
5710
5851
  ...prev,
5711
5852
  orphanedFiles: void 0,
5712
- message: _optionalChain([prev, 'access', _53 => _53.message, 'optionalAccess', _54 => _54.replace, 'call', _55 => _55(/Found \d+ orphaned thumbnail\(s\).*/, "Orphaned thumbnails deleted.")])
5853
+ message: _optionalChain([prev, 'access', _65 => _65.message, 'optionalAccess', _66 => _66.replace, 'call', _67 => _67(/Found \d+ orphaned thumbnail\(s\).*/, "Orphaned thumbnails deleted.")])
5713
5854
  }));
5714
5855
  triggerRefresh();
5715
5856
  } else {
@@ -6352,4 +6493,4 @@ var StudioUI_default = StudioUI;
6352
6493
 
6353
6494
 
6354
6495
  exports.StudioUI = StudioUI; exports.default = StudioUI_default;
6355
- //# sourceMappingURL=StudioUI-RRWDEHCI.js.map
6496
+ //# sourceMappingURL=StudioUI-O53YFD6Q.js.map