@gallop.software/studio 1.4.0 → 1.4.2

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.
@@ -20,11 +20,11 @@ var defaultActionState = {
20
20
  showMoveModal: false,
21
21
  showSyncConfirm: false,
22
22
  showProcessConfirm: false,
23
- showUnprocessConfirm: false,
24
23
  actionPaths: [],
25
24
  syncImageCount: 0,
26
25
  syncHasRemote: false,
27
- syncHasLocal: false
26
+ syncHasLocal: false,
27
+ processMode: "generate"
28
28
  };
29
29
  var defaultState = {
30
30
  isOpen: false,
@@ -91,7 +91,7 @@ var defaultState = {
91
91
  },
92
92
  requestProcess: () => {
93
93
  },
94
- requestUnprocess: () => {
94
+ setProcessMode: () => {
95
95
  },
96
96
  confirmDelete: async () => {
97
97
  },
@@ -101,8 +101,6 @@ var defaultState = {
101
101
  },
102
102
  confirmProcess: async () => {
103
103
  },
104
- confirmUnprocess: async () => {
105
- },
106
104
  cancelAction: () => {
107
105
  },
108
106
  closeProgress: () => {
@@ -1743,15 +1741,13 @@ var styles5 = {
1743
1741
  `
1744
1742
  };
1745
1743
  function StudioToolbar() {
1746
- const { selectedItems, viewMode, setViewMode, clearSelection, currentPath, triggerRefresh, focusedItem, scanRequested, clearScanRequest, fileItems } = useStudio();
1744
+ const { selectedItems, viewMode, setViewMode, clearSelection, currentPath, triggerRefresh, focusedItem, scanRequested, clearScanRequest, fileItems, requestProcess, actionState } = useStudio();
1747
1745
  const fileInputRef = _react.useRef.call(void 0, null);
1748
1746
  const abortControllerRef = _react.useRef.call(void 0, null);
1749
1747
  const [showAddNewModal, setShowAddNewModal] = _react.useState.call(void 0, false);
1750
1748
  const [uploading, setUploading] = _react.useState.call(void 0, false);
1751
1749
  const [scanning, setScanning] = _react.useState.call(void 0, false);
1752
- const [processing, setProcessing] = _react.useState.call(void 0, false);
1753
1750
  const [showDeleteConfirm, setShowDeleteConfirm] = _react.useState.call(void 0, false);
1754
- const [showProcessConfirm, setShowProcessConfirm] = _react.useState.call(void 0, false);
1755
1751
  const [showSyncConfirm, setShowSyncConfirm] = _react.useState.call(void 0, false);
1756
1752
  const [syncImageCount, setSyncImageCount] = _react.useState.call(void 0, 0);
1757
1753
  const [syncHasRemote, setSyncHasRemote] = _react.useState.call(void 0, false);
@@ -1764,9 +1760,6 @@ function StudioToolbar() {
1764
1760
  percent: 0,
1765
1761
  status: "processing"
1766
1762
  });
1767
- const [processCount, setProcessCount] = _react.useState.call(void 0, 0);
1768
- const [processMode, setProcessMode] = _react.useState.call(void 0, "all");
1769
- const [imagesToProcess, setImagesToProcess] = _react.useState.call(void 0, []);
1770
1763
  const [alertMessage, setAlertMessage] = _react.useState.call(void 0, null);
1771
1764
  const [showNewFolderModal, setShowNewFolderModal] = _react.useState.call(void 0, false);
1772
1765
  const [showRenameFolderModal, setShowRenameFolderModal] = _react.useState.call(void 0, false);
@@ -2001,234 +1994,29 @@ function StudioToolbar() {
2001
1994
  });
2002
1995
  return;
2003
1996
  }
2004
- setProcessCount(selectedImagePaths.length);
2005
- setImagesToProcess(selectedImagePaths);
2006
- setProcessMode("selected");
2007
- setShowProcessConfirm(true);
1997
+ requestProcess(selectedImagePaths);
2008
1998
  } else {
2009
1999
  try {
2010
2000
  const response = await fetch("/api/studio/count-images");
2011
2001
  const data = await response.json();
2012
- if (data.count === 0) {
2002
+ if (!data.images || data.images.length === 0) {
2013
2003
  setAlertMessage({
2014
2004
  title: "No Images Found",
2015
2005
  message: "No images found in the public folder to process."
2016
2006
  });
2017
2007
  return;
2018
2008
  }
2019
- setProcessCount(data.count);
2020
- setProcessMode("all");
2021
- setShowProcessConfirm(true);
2009
+ const allImagePaths = data.images.map((img) => `public/${img}`);
2010
+ requestProcess(allImagePaths);
2022
2011
  } catch (error) {
2023
- console.error("Failed to count images:", error);
2012
+ console.error("Failed to get images:", error);
2024
2013
  setAlertMessage({
2025
2014
  title: "Error",
2026
- message: "Failed to count images."
2027
- });
2028
- }
2029
- }
2030
- }, [selectedItems]);
2031
- const handleProcessConfirm = _react.useCallback.call(void 0, async () => {
2032
- setShowProcessConfirm(false);
2033
- setProcessing(true);
2034
- abortControllerRef.current = new AbortController();
2035
- const signal = abortControllerRef.current.signal;
2036
- try {
2037
- if (processMode === "all") {
2038
- setProgressTitle("Processing Images");
2039
- setShowProgress(true);
2040
- setProgressState({
2041
- current: 0,
2042
- total: processCount,
2043
- percent: 0,
2044
- status: "processing"
2045
- });
2046
- const response = await fetch("/api/studio/process-all", {
2047
- method: "POST",
2048
- signal
2049
- });
2050
- if (!response.body) {
2051
- throw new Error("No response body");
2052
- }
2053
- const reader = response.body.getReader();
2054
- const decoder = new TextDecoder();
2055
- try {
2056
- while (true) {
2057
- const { done, value } = await reader.read();
2058
- if (done) break;
2059
- if (signal.aborted) {
2060
- reader.cancel();
2061
- break;
2062
- }
2063
- const text = decoder.decode(value);
2064
- const lines = text.split("\n\n").filter((line) => line.startsWith("data: "));
2065
- for (const line of lines) {
2066
- try {
2067
- const data = JSON.parse(line.replace("data: ", ""));
2068
- if (data.type === "start") {
2069
- setProgressState((prev) => ({
2070
- ...prev,
2071
- total: data.total
2072
- }));
2073
- } else if (data.type === "progress") {
2074
- setProgressState({
2075
- current: data.current,
2076
- total: data.total,
2077
- percent: data.percent,
2078
- currentFile: data.currentFile,
2079
- status: "processing"
2080
- });
2081
- } else if (data.type === "cleanup") {
2082
- setProgressState((prev) => ({
2083
- ...prev,
2084
- status: "cleanup",
2085
- currentFile: void 0
2086
- }));
2087
- } else if (data.type === "complete") {
2088
- setProgressState({
2089
- current: data.processed,
2090
- total: data.processed,
2091
- percent: 100,
2092
- status: "complete",
2093
- processed: data.processed,
2094
- alreadyProcessed: data.alreadyProcessed,
2095
- orphansRemoved: data.orphansRemoved,
2096
- errors: data.errors
2097
- });
2098
- triggerRefresh();
2099
- } else if (data.type === "error") {
2100
- setProgressState((prev) => ({
2101
- ...prev,
2102
- status: "error",
2103
- message: data.message
2104
- }));
2105
- }
2106
- } catch (e3) {
2107
- }
2108
- }
2109
- }
2110
- } catch (err) {
2111
- if (signal.aborted) {
2112
- setProgressState((prev) => ({
2113
- ...prev,
2114
- status: "stopped",
2115
- processed: prev.current
2116
- }));
2117
- triggerRefresh();
2118
- } else {
2119
- throw err;
2120
- }
2121
- }
2122
- } else {
2123
- setShowProgress(true);
2124
- setProgressState({
2125
- current: 0,
2126
- total: processCount,
2127
- percent: 0,
2128
- status: "processing"
2129
- });
2130
- const selectedImageKeys = imagesToProcess.map((p) => {
2131
- const key = p.replace(/^public\//, "");
2132
- return key.startsWith("/") ? key : `/${key}`;
2015
+ message: "Failed to get images."
2133
2016
  });
2134
- const response = await fetch("/api/studio/reprocess-stream", {
2135
- method: "POST",
2136
- headers: { "Content-Type": "application/json" },
2137
- body: JSON.stringify({ imageKeys: selectedImageKeys }),
2138
- signal
2139
- });
2140
- if (!response.ok) {
2141
- const error = await response.json();
2142
- setProgressState({
2143
- current: 0,
2144
- total: 0,
2145
- percent: 0,
2146
- status: "error",
2147
- message: error.error || "Unknown error"
2148
- });
2149
- } else {
2150
- const reader = _optionalChain([response, 'access', _20 => _20.body, 'optionalAccess', _21 => _21.getReader, 'call', _22 => _22()]);
2151
- const decoder = new TextDecoder();
2152
- if (reader) {
2153
- let buffer = "";
2154
- while (true) {
2155
- const { done, value } = await reader.read();
2156
- if (done) break;
2157
- buffer += decoder.decode(value, { stream: true });
2158
- const lines = buffer.split("\n");
2159
- buffer = lines.pop() || "";
2160
- for (const line of lines) {
2161
- if (line.startsWith("data: ")) {
2162
- try {
2163
- const data = JSON.parse(line.slice(6));
2164
- if (data.type === "start") {
2165
- setProgressState((prev) => ({
2166
- ...prev,
2167
- total: data.total
2168
- }));
2169
- } else if (data.type === "progress") {
2170
- setProgressState({
2171
- current: data.current,
2172
- total: data.total,
2173
- percent: data.percent,
2174
- status: "processing",
2175
- message: data.message
2176
- });
2177
- } else if (data.type === "cleanup") {
2178
- setProgressState((prev) => ({
2179
- ...prev,
2180
- status: "cleanup",
2181
- message: data.message
2182
- }));
2183
- } else if (data.type === "complete") {
2184
- setProgressState({
2185
- current: data.processed,
2186
- total: data.processed,
2187
- percent: 100,
2188
- status: data.errors > 0 ? "error" : "complete",
2189
- processed: data.processed,
2190
- message: data.message
2191
- });
2192
- clearSelection();
2193
- triggerRefresh();
2194
- } else if (data.type === "error") {
2195
- setProgressState((prev) => ({
2196
- ...prev,
2197
- status: "error",
2198
- message: data.message
2199
- }));
2200
- }
2201
- } catch (e4) {
2202
- }
2203
- }
2204
- }
2205
- }
2206
- }
2207
- }
2208
2017
  }
2209
- } catch (error) {
2210
- if (signal.aborted) {
2211
- setProgressState((prev) => ({
2212
- ...prev,
2213
- status: "stopped",
2214
- processed: prev.current
2215
- }));
2216
- triggerRefresh();
2217
- } else {
2218
- console.error("Processing error:", error);
2219
- setProgressState({
2220
- current: 0,
2221
- total: 0,
2222
- percent: 0,
2223
- status: "error",
2224
- message: "Processing failed. Check console for details."
2225
- });
2226
- }
2227
- } finally {
2228
- setProcessing(false);
2229
- abortControllerRef.current = null;
2230
2018
  }
2231
- }, [processMode, processCount, imagesToProcess, clearSelection, triggerRefresh]);
2019
+ }, [selectedItems, requestProcess]);
2232
2020
  const handleStopProcessing = _react.useCallback.call(void 0, () => {
2233
2021
  if (abortControllerRef.current) {
2234
2022
  abortControllerRef.current.abort();
@@ -2301,7 +2089,7 @@ function StudioToolbar() {
2301
2089
  const selectedPaths2 = Array.from(selectedItems);
2302
2090
  const imageExtensions = ["jpg", "jpeg", "png", "gif", "webp", "svg", "ico", "bmp", "tiff", "tif"];
2303
2091
  const selectedImagePaths = selectedPaths2.filter((p) => {
2304
- const ext = _optionalChain([p, 'access', _23 => _23.split, 'call', _24 => _24("."), 'access', _25 => _25.pop, 'call', _26 => _26(), 'optionalAccess', _27 => _27.toLowerCase, 'call', _28 => _28()]) || "";
2092
+ const ext = _optionalChain([p, 'access', _20 => _20.split, 'call', _21 => _21("."), 'access', _22 => _22.pop, 'call', _23 => _23(), 'optionalAccess', _24 => _24.toLowerCase, 'call', _25 => _25()]) || "";
2305
2093
  return imageExtensions.includes(ext);
2306
2094
  });
2307
2095
  const selectedFolders = selectedPaths2.filter((p) => !p.includes(".") || p.endsWith("/"));
@@ -2350,7 +2138,7 @@ function StudioToolbar() {
2350
2138
  const selectedPaths2 = Array.from(selectedItems);
2351
2139
  const imageExtensions = ["jpg", "jpeg", "png", "gif", "webp", "svg", "ico", "bmp", "tiff", "tif"];
2352
2140
  const selectedImagePaths = selectedPaths2.filter((p) => {
2353
- const ext = _optionalChain([p, 'access', _29 => _29.split, 'call', _30 => _30("."), 'access', _31 => _31.pop, 'call', _32 => _32(), 'optionalAccess', _33 => _33.toLowerCase, 'call', _34 => _34()]) || "";
2141
+ const ext = _optionalChain([p, 'access', _26 => _26.split, 'call', _27 => _27("."), 'access', _28 => _28.pop, 'call', _29 => _29(), 'optionalAccess', _30 => _30.toLowerCase, 'call', _31 => _31()]) || "";
2354
2142
  return imageExtensions.includes(ext);
2355
2143
  });
2356
2144
  const selectedFolders = selectedPaths2.filter((p) => !p.includes(".") || p.endsWith("/"));
@@ -2401,16 +2189,16 @@ function StudioToolbar() {
2401
2189
  });
2402
2190
  const data = await response.json();
2403
2191
  if (!response.ok) {
2404
- 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")])) {
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")])) {
2405
2193
  setShowProgress(false);
2406
2194
  setShowR2SetupModal(true);
2407
2195
  return;
2408
2196
  }
2409
2197
  errors++;
2410
2198
  errorMessages.push(data.error || `Failed: ${imageKey}`);
2411
- } else if (_optionalChain([data, 'access', _41 => _41.pushed, 'optionalAccess', _42 => _42.length]) > 0) {
2199
+ } else if (_optionalChain([data, 'access', _38 => _38.pushed, 'optionalAccess', _39 => _39.length]) > 0) {
2412
2200
  pushed++;
2413
- } else if (_optionalChain([data, 'access', _43 => _43.errors, 'optionalAccess', _44 => _44.length]) > 0) {
2201
+ } else if (_optionalChain([data, 'access', _40 => _40.errors, 'optionalAccess', _41 => _41.length]) > 0) {
2414
2202
  errors++;
2415
2203
  for (const errMsg of data.errors) {
2416
2204
  errorMessages.push(errMsg);
@@ -2532,7 +2320,7 @@ function StudioToolbar() {
2532
2320
  errorMessage: data.message
2533
2321
  }));
2534
2322
  }
2535
- } catch (e5) {
2323
+ } catch (e3) {
2536
2324
  }
2537
2325
  }
2538
2326
  }
@@ -2607,16 +2395,6 @@ function StudioToolbar() {
2607
2395
  onCancel: () => setShowSyncConfirm(false)
2608
2396
  }
2609
2397
  ),
2610
- showProcessConfirm && /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
2611
- ConfirmModal,
2612
- {
2613
- title: "Process Images",
2614
- message: processMode === "all" ? `Found ${processCount} image${processCount !== 1 ? "s" : ""} in the public folder. This will regenerate all thumbnails and remove any orphaned files from the images folder.` : `Process ${processCount} selected image${processCount !== 1 ? "s" : ""}? This will regenerate thumbnails for these files.`,
2615
- confirmLabel: processing ? "Processing..." : "Process",
2616
- onConfirm: handleProcessConfirm,
2617
- onCancel: () => setShowProcessConfirm(false)
2618
- }
2619
- ),
2620
2398
  showProgress && /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
2621
2399
  ProgressModal,
2622
2400
  {
@@ -2740,11 +2518,11 @@ function StudioToolbar() {
2740
2518
  {
2741
2519
  css: styles5.btn,
2742
2520
  onClick: handleProcessImages,
2743
- disabled: processing || isInImagesFolder,
2521
+ disabled: actionState.showProgress || isInImagesFolder,
2744
2522
  title: isInImagesFolder ? "Cannot process images folder" : void 0,
2745
2523
  children: [
2746
2524
  /* @__PURE__ */ _jsxruntime.jsx.call(void 0, ImageStackIcon, {}),
2747
- processing ? "Processing..." : "Process Images"
2525
+ "Process Images"
2748
2526
  ]
2749
2527
  }
2750
2528
  ),
@@ -4498,7 +4276,6 @@ function StudioDetailView() {
4498
4276
  requestMove,
4499
4277
  requestSync,
4500
4278
  requestProcess,
4501
- requestUnprocess,
4502
4279
  actionState
4503
4280
  } = useStudio();
4504
4281
  const [showRenameModal, setShowRenameModal] = _react.useState.call(void 0, false);
@@ -4698,19 +4475,6 @@ function StudioDetailView() {
4698
4475
  ]
4699
4476
  }
4700
4477
  ),
4701
- /* @__PURE__ */ _jsxruntime.jsxs.call(void 0,
4702
- "button",
4703
- {
4704
- css: styles8.actionBtn,
4705
- onClick: () => requestUnprocess([focusedItem.path]),
4706
- disabled: isActionInProgress || focusedItem.isProtected || !focusedItem.hasThumbnail,
4707
- title: !focusedItem.hasThumbnail ? "No thumbnails to remove" : void 0,
4708
- children: [
4709
- /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "svg", { css: styles8.actionIcon, fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16" }) }),
4710
- "Remove Thumbnails"
4711
- ]
4712
- }
4713
- ),
4714
4478
  /* @__PURE__ */ _jsxruntime.jsxs.call(void 0,
4715
4479
  "button",
4716
4480
  {
@@ -5310,11 +5074,11 @@ var defaultActionState2 = {
5310
5074
  showMoveModal: false,
5311
5075
  showSyncConfirm: false,
5312
5076
  showProcessConfirm: false,
5313
- showUnprocessConfirm: false,
5314
5077
  actionPaths: [],
5315
5078
  syncImageCount: 0,
5316
5079
  syncHasRemote: false,
5317
- syncHasLocal: false
5080
+ syncHasLocal: false,
5081
+ processMode: "generate"
5318
5082
  };
5319
5083
  function useStudioActions({
5320
5084
  triggerRefresh,
@@ -5371,14 +5135,15 @@ function useStudioActions({
5371
5135
  setActionState((prev) => ({
5372
5136
  ...prev,
5373
5137
  actionPaths: paths,
5374
- showProcessConfirm: true
5138
+ showProcessConfirm: true,
5139
+ processMode: "generate"
5140
+ // Reset to default when opening modal
5375
5141
  }));
5376
5142
  }, []);
5377
- const requestUnprocess = _react.useCallback.call(void 0, (paths) => {
5143
+ const setProcessMode = _react.useCallback.call(void 0, (mode) => {
5378
5144
  setActionState((prev) => ({
5379
5145
  ...prev,
5380
- actionPaths: paths,
5381
- showUnprocessConfirm: true
5146
+ processMode: mode
5382
5147
  }));
5383
5148
  }, []);
5384
5149
  const cancelAction = _react.useCallback.call(void 0, () => {
@@ -5387,8 +5152,7 @@ function useStudioActions({
5387
5152
  showDeleteConfirm: false,
5388
5153
  showMoveModal: false,
5389
5154
  showSyncConfirm: false,
5390
- showProcessConfirm: false,
5391
- showUnprocessConfirm: false
5155
+ showProcessConfirm: false
5392
5156
  }));
5393
5157
  }, []);
5394
5158
  const closeProgress = _react.useCallback.call(void 0, () => {
@@ -5450,7 +5214,7 @@ function useStudioActions({
5450
5214
  });
5451
5215
  return;
5452
5216
  }
5453
- const reader = _optionalChain([response, 'access', _45 => _45.body, 'optionalAccess', _46 => _46.getReader, 'call', _47 => _47()]);
5217
+ const reader = _optionalChain([response, 'access', _42 => _42.body, 'optionalAccess', _43 => _43.getReader, 'call', _44 => _44()]);
5454
5218
  const decoder = new TextDecoder();
5455
5219
  if (reader) {
5456
5220
  let buffer = "";
@@ -5480,7 +5244,7 @@ function useStudioActions({
5480
5244
  status: "complete",
5481
5245
  message: `Moved ${data.moved} file${data.moved !== 1 ? "s" : ""}${data.errors > 0 ? `, ${data.errors} error${data.errors !== 1 ? "s" : ""}` : ""}`
5482
5246
  }));
5483
- if (data.errors > 0 && _optionalChain([data, 'access', _48 => _48.errorMessages, 'optionalAccess', _49 => _49.length]) > 0) {
5247
+ if (data.errors > 0 && _optionalChain([data, 'access', _45 => _45.errorMessages, 'optionalAccess', _46 => _46.length]) > 0) {
5484
5248
  showError("Move Failed", data.errorMessages.join("\n"));
5485
5249
  }
5486
5250
  clearSelection();
@@ -5493,7 +5257,7 @@ function useStudioActions({
5493
5257
  message: data.message || "Unknown error"
5494
5258
  }));
5495
5259
  }
5496
- } catch (e6) {
5260
+ } catch (e4) {
5497
5261
  }
5498
5262
  }
5499
5263
  }
@@ -5566,27 +5330,32 @@ function useStudioActions({
5566
5330
  }, [actionState.actionPaths, clearSelection, triggerRefresh, showError, setProgressState]);
5567
5331
  const confirmProcess = _react.useCallback.call(void 0, async () => {
5568
5332
  const paths = actionState.actionPaths;
5333
+ const mode = actionState.processMode;
5569
5334
  const imageKeys = paths.map((p) => {
5570
5335
  const key = p.replace(/^public\//, "");
5571
5336
  return key.startsWith("/") ? key : `/${key}`;
5572
5337
  });
5338
+ const isRemove = mode === "remove";
5339
+ const endpoint = isRemove ? "/api/studio/unprocess-stream" : "/api/studio/reprocess-stream";
5340
+ const progressTitle = isRemove ? "Removing Thumbnails" : "Processing Images";
5341
+ const progressMessage = isRemove ? "Removing thumbnails..." : "Processing images...";
5573
5342
  setActionState((prev) => ({
5574
5343
  ...prev,
5575
5344
  showProcessConfirm: false,
5576
5345
  showProgress: true,
5577
- progressTitle: "Processing Images",
5346
+ progressTitle,
5578
5347
  progressState: {
5579
5348
  current: 0,
5580
5349
  total: imageKeys.length,
5581
5350
  percent: 0,
5582
5351
  status: "processing",
5583
- message: "Processing images..."
5352
+ message: progressMessage
5584
5353
  }
5585
5354
  }));
5586
5355
  abortControllerRef.current = new AbortController();
5587
5356
  const signal = abortControllerRef.current.signal;
5588
5357
  try {
5589
- const response = await fetch("/api/studio/reprocess-stream", {
5358
+ const response = await fetch(endpoint, {
5590
5359
  method: "POST",
5591
5360
  headers: { "Content-Type": "application/json" },
5592
5361
  body: JSON.stringify({ imageKeys }),
@@ -5599,11 +5368,11 @@ function useStudioActions({
5599
5368
  total: imageKeys.length,
5600
5369
  percent: 0,
5601
5370
  status: "error",
5602
- message: error.error || "Processing failed"
5371
+ message: error.error || (isRemove ? "Failed to remove thumbnails" : "Processing failed")
5603
5372
  });
5604
5373
  return;
5605
5374
  }
5606
- const reader = _optionalChain([response, 'access', _50 => _50.body, 'optionalAccess', _51 => _51.getReader, 'call', _52 => _52()]);
5375
+ const reader = _optionalChain([response, 'access', _47 => _47.body, 'optionalAccess', _48 => _48.getReader, 'call', _49 => _49()]);
5607
5376
  const decoder = new TextDecoder();
5608
5377
  if (reader) {
5609
5378
  let buffer = "";
@@ -5652,7 +5421,7 @@ function useStudioActions({
5652
5421
  message: data.message
5653
5422
  }));
5654
5423
  }
5655
- } catch (e7) {
5424
+ } catch (e5) {
5656
5425
  }
5657
5426
  }
5658
5427
  }
@@ -5663,7 +5432,7 @@ function useStudioActions({
5663
5432
  setProgressState((prev) => ({
5664
5433
  ...prev,
5665
5434
  status: "stopped",
5666
- message: "Processing stopped by user"
5435
+ message: isRemove ? "Removal stopped by user" : "Processing stopped by user"
5667
5436
  }));
5668
5437
  } else {
5669
5438
  console.error("Processing error:", error);
@@ -5672,115 +5441,13 @@ function useStudioActions({
5672
5441
  total: imageKeys.length,
5673
5442
  percent: 0,
5674
5443
  status: "error",
5675
- message: "Processing failed. Check console for details."
5444
+ message: isRemove ? "Failed to remove thumbnails. Check console for details." : "Processing failed. Check console for details."
5676
5445
  });
5677
5446
  }
5678
5447
  } finally {
5679
5448
  abortControllerRef.current = null;
5680
5449
  }
5681
- }, [actionState.actionPaths, triggerRefresh, setProgressState]);
5682
- const confirmUnprocess = _react.useCallback.call(void 0, async () => {
5683
- const paths = actionState.actionPaths;
5684
- const imageKeys = paths.map((p) => {
5685
- const key = p.replace(/^public\//, "");
5686
- return key.startsWith("/") ? key : `/${key}`;
5687
- });
5688
- setActionState((prev) => ({
5689
- ...prev,
5690
- showUnprocessConfirm: false,
5691
- showProgress: true,
5692
- progressTitle: "Removing Thumbnails",
5693
- progressState: {
5694
- current: 0,
5695
- total: imageKeys.length,
5696
- percent: 0,
5697
- status: "processing",
5698
- message: "Removing thumbnails..."
5699
- }
5700
- }));
5701
- try {
5702
- const response = await fetch("/api/studio/unprocess-stream", {
5703
- method: "POST",
5704
- headers: { "Content-Type": "application/json" },
5705
- body: JSON.stringify({ imageKeys })
5706
- });
5707
- if (!response.ok) {
5708
- const error = await response.json();
5709
- setProgressState({
5710
- current: 0,
5711
- total: imageKeys.length,
5712
- percent: 0,
5713
- status: "error",
5714
- message: error.error || "Failed to remove thumbnails"
5715
- });
5716
- return;
5717
- }
5718
- const reader = _optionalChain([response, 'access', _53 => _53.body, 'optionalAccess', _54 => _54.getReader, 'call', _55 => _55()]);
5719
- const decoder = new TextDecoder();
5720
- if (reader) {
5721
- let buffer = "";
5722
- while (true) {
5723
- const { done, value } = await reader.read();
5724
- if (done) break;
5725
- buffer += decoder.decode(value, { stream: true });
5726
- const lines = buffer.split("\n");
5727
- buffer = lines.pop() || "";
5728
- for (const line of lines) {
5729
- if (line.startsWith("data: ")) {
5730
- try {
5731
- const data = JSON.parse(line.slice(6));
5732
- if (data.type === "start") {
5733
- setProgressState((prev) => ({
5734
- ...prev,
5735
- total: data.total
5736
- }));
5737
- } else if (data.type === "progress") {
5738
- setProgressState({
5739
- current: data.current,
5740
- total: data.total,
5741
- percent: data.percent,
5742
- status: "processing",
5743
- message: data.message
5744
- });
5745
- } else if (data.type === "cleanup") {
5746
- setProgressState((prev) => ({
5747
- ...prev,
5748
- status: "cleanup",
5749
- message: data.message
5750
- }));
5751
- } else if (data.type === "complete") {
5752
- setProgressState({
5753
- current: data.processed,
5754
- total: data.processed,
5755
- percent: 100,
5756
- status: data.errors > 0 ? "error" : "complete",
5757
- message: data.message
5758
- });
5759
- triggerRefresh();
5760
- } else if (data.type === "error") {
5761
- setProgressState((prev) => ({
5762
- ...prev,
5763
- status: "error",
5764
- message: data.message
5765
- }));
5766
- }
5767
- } catch (e8) {
5768
- }
5769
- }
5770
- }
5771
- }
5772
- }
5773
- } catch (error) {
5774
- console.error("Unprocess error:", error);
5775
- setProgressState({
5776
- current: 0,
5777
- total: imageKeys.length,
5778
- percent: 0,
5779
- status: "error",
5780
- message: "Failed to remove thumbnails. Check console for details."
5781
- });
5782
- }
5783
- }, [actionState.actionPaths, triggerRefresh, setProgressState]);
5450
+ }, [actionState.actionPaths, actionState.processMode, triggerRefresh, setProgressState]);
5784
5451
  const deleteOrphans = _react.useCallback.call(void 0, async () => {
5785
5452
  const orphanedFiles = actionState.progressState.orphanedFiles;
5786
5453
  if (!orphanedFiles || orphanedFiles.length === 0) return;
@@ -5794,7 +5461,7 @@ function useStudioActions({
5794
5461
  setProgressState((prev) => ({
5795
5462
  ...prev,
5796
5463
  orphanedFiles: void 0,
5797
- message: _optionalChain([prev, 'access', _56 => _56.message, 'optionalAccess', _57 => _57.replace, 'call', _58 => _58(/Found \d+ orphaned thumbnail\(s\).*/, "Orphaned thumbnails deleted.")])
5464
+ message: _optionalChain([prev, 'access', _50 => _50.message, 'optionalAccess', _51 => _51.replace, 'call', _52 => _52(/Found \d+ orphaned thumbnail\(s\).*/, "Orphaned thumbnails deleted.")])
5798
5465
  }));
5799
5466
  triggerRefresh();
5800
5467
  } else {
@@ -5814,7 +5481,7 @@ function useStudioActions({
5814
5481
  requestMove,
5815
5482
  requestSync,
5816
5483
  requestProcess,
5817
- requestUnprocess,
5484
+ setProcessMode,
5818
5485
  cancelAction,
5819
5486
  closeProgress,
5820
5487
  stopProcessing,
@@ -5822,7 +5489,6 @@ function useStudioActions({
5822
5489
  confirmMove,
5823
5490
  confirmSync,
5824
5491
  confirmProcess,
5825
- confirmUnprocess,
5826
5492
  deleteOrphans
5827
5493
  };
5828
5494
  }
@@ -6153,12 +5819,11 @@ function StudioUI({ onClose, isVisible = true }) {
6153
5819
  requestMove: actions.requestMove,
6154
5820
  requestSync: actions.requestSync,
6155
5821
  requestProcess: actions.requestProcess,
6156
- requestUnprocess: actions.requestUnprocess,
5822
+ setProcessMode: actions.setProcessMode,
6157
5823
  confirmDelete: actions.confirmDelete,
6158
5824
  confirmMove: actions.confirmMove,
6159
5825
  confirmSync: actions.confirmSync,
6160
5826
  confirmProcess: actions.confirmProcess,
6161
- confirmUnprocess: actions.confirmUnprocess,
6162
5827
  cancelAction: actions.cancelAction,
6163
5828
  closeProgress: actions.closeProgress,
6164
5829
  stopProcessing: actions.stopProcessing,
@@ -6223,26 +5888,15 @@ function StudioUI({ onClose, isVisible = true }) {
6223
5888
  }
6224
5889
  ),
6225
5890
  actions.actionState.showProcessConfirm && /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
6226
- ConfirmModal,
5891
+ ProcessConfirmModal,
6227
5892
  {
6228
- title: "Process Images",
6229
- message: `Generate thumbnails for ${actions.actionState.actionPaths.length} image${actions.actionState.actionPaths.length !== 1 ? "s" : ""}?`,
6230
- confirmLabel: "Process",
5893
+ imageCount: actions.actionState.actionPaths.length,
5894
+ mode: actions.actionState.processMode,
5895
+ onModeChange: actions.setProcessMode,
6231
5896
  onConfirm: actions.confirmProcess,
6232
5897
  onCancel: actions.cancelAction
6233
5898
  }
6234
5899
  ),
6235
- actions.actionState.showUnprocessConfirm && /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
6236
- ConfirmModal,
6237
- {
6238
- title: "Remove Thumbnails",
6239
- message: `Remove generated thumbnails for ${actions.actionState.actionPaths.length} image${actions.actionState.actionPaths.length !== 1 ? "s" : ""}? Original images will be kept.`,
6240
- confirmLabel: "Remove",
6241
- variant: "danger",
6242
- onConfirm: actions.confirmUnprocess,
6243
- onCancel: actions.cancelAction
6244
- }
6245
- ),
6246
5900
  actions.actionState.showMoveModal && /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
6247
5901
  StudioFolderPicker,
6248
5902
  {
@@ -6264,6 +5918,150 @@ function StudioUI({ onClose, isVisible = true }) {
6264
5918
  )
6265
5919
  ] }) });
6266
5920
  }
5921
+ function ProcessConfirmModal({ imageCount, mode, onModeChange, onConfirm, onCancel }) {
5922
+ const processModalStyles = {
5923
+ overlay: _react3.css`
5924
+ position: fixed;
5925
+ inset: 0;
5926
+ background: rgba(0, 0, 0, 0.5);
5927
+ display: flex;
5928
+ align-items: center;
5929
+ justify-content: center;
5930
+ z-index: 10000;
5931
+ `,
5932
+ container: _react3.css`
5933
+ background: ${_chunkN6JYTJCBjs.colors.surface};
5934
+ border-radius: 12px;
5935
+ padding: 24px;
5936
+ max-width: 420px;
5937
+ width: 90%;
5938
+ box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04);
5939
+ `,
5940
+ title: _react3.css`
5941
+ font-size: ${_chunkN6JYTJCBjs.fontSize.lg};
5942
+ font-weight: 600;
5943
+ color: ${_chunkN6JYTJCBjs.colors.text};
5944
+ margin: 0 0 16px;
5945
+ `,
5946
+ modeToggle: _react3.css`
5947
+ display: flex;
5948
+ gap: 8px;
5949
+ margin-bottom: 16px;
5950
+ `,
5951
+ modeBtn: _react3.css`
5952
+ flex: 1;
5953
+ padding: 10px 16px;
5954
+ border: 2px solid ${_chunkN6JYTJCBjs.colors.border};
5955
+ border-radius: 8px;
5956
+ background: ${_chunkN6JYTJCBjs.colors.background};
5957
+ color: ${_chunkN6JYTJCBjs.colors.textSecondary};
5958
+ font-size: ${_chunkN6JYTJCBjs.fontSize.base};
5959
+ font-weight: 500;
5960
+ cursor: pointer;
5961
+ transition: all 0.15s ease;
5962
+
5963
+ &:hover {
5964
+ border-color: ${_chunkN6JYTJCBjs.colors.borderHover};
5965
+ }
5966
+ `,
5967
+ modeBtnActive: _react3.css`
5968
+ border-color: ${_chunkN6JYTJCBjs.colors.primary};
5969
+ background: rgba(99, 91, 255, 0.1);
5970
+ color: ${_chunkN6JYTJCBjs.colors.primary};
5971
+ `,
5972
+ modeBtnDanger: _react3.css`
5973
+ border-color: ${_chunkN6JYTJCBjs.colors.danger};
5974
+ background: rgba(239, 68, 68, 0.1);
5975
+ color: ${_chunkN6JYTJCBjs.colors.danger};
5976
+ `,
5977
+ message: _react3.css`
5978
+ font-size: ${_chunkN6JYTJCBjs.fontSize.base};
5979
+ color: ${_chunkN6JYTJCBjs.colors.textSecondary};
5980
+ margin: 0 0 20px;
5981
+ line-height: 1.5;
5982
+ `,
5983
+ actions: _react3.css`
5984
+ display: flex;
5985
+ gap: 12px;
5986
+ justify-content: flex-end;
5987
+ `,
5988
+ cancelBtn: _react3.css`
5989
+ padding: 10px 20px;
5990
+ border: 1px solid ${_chunkN6JYTJCBjs.colors.border};
5991
+ border-radius: 8px;
5992
+ background: ${_chunkN6JYTJCBjs.colors.background};
5993
+ color: ${_chunkN6JYTJCBjs.colors.text};
5994
+ font-size: ${_chunkN6JYTJCBjs.fontSize.base};
5995
+ font-weight: 500;
5996
+ cursor: pointer;
5997
+ transition: all 0.15s ease;
5998
+
5999
+ &:hover {
6000
+ background: ${_chunkN6JYTJCBjs.colors.surfaceHover};
6001
+ border-color: ${_chunkN6JYTJCBjs.colors.borderHover};
6002
+ }
6003
+ `,
6004
+ confirmBtn: _react3.css`
6005
+ padding: 10px 20px;
6006
+ border: none;
6007
+ border-radius: 8px;
6008
+ background: ${_chunkN6JYTJCBjs.colors.primary};
6009
+ color: white;
6010
+ font-size: ${_chunkN6JYTJCBjs.fontSize.base};
6011
+ font-weight: 500;
6012
+ cursor: pointer;
6013
+ transition: all 0.15s ease;
6014
+
6015
+ &:hover {
6016
+ background: ${_chunkN6JYTJCBjs.colors.primaryHover};
6017
+ }
6018
+ `,
6019
+ confirmBtnDanger: _react3.css`
6020
+ background: ${_chunkN6JYTJCBjs.colors.danger};
6021
+
6022
+ &:hover {
6023
+ background: #dc2626;
6024
+ }
6025
+ `
6026
+ };
6027
+ const isRemove = mode === "remove";
6028
+ const title = "Process Images";
6029
+ const message = isRemove ? `Remove generated thumbnails for ${imageCount} image${imageCount !== 1 ? "s" : ""}? Original images will be kept.` : `Generate thumbnails for ${imageCount} image${imageCount !== 1 ? "s" : ""}?`;
6030
+ const confirmLabel = isRemove ? "Remove" : "Process";
6031
+ return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { css: processModalStyles.overlay, onClick: onCancel, children: /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { css: processModalStyles.container, onClick: (e) => e.stopPropagation(), children: [
6032
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "h2", { css: processModalStyles.title, children: title }),
6033
+ /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { css: processModalStyles.modeToggle, children: [
6034
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
6035
+ "button",
6036
+ {
6037
+ css: [processModalStyles.modeBtn, mode === "generate" && processModalStyles.modeBtnActive],
6038
+ onClick: () => onModeChange("generate"),
6039
+ children: "Generate Thumbnails"
6040
+ }
6041
+ ),
6042
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
6043
+ "button",
6044
+ {
6045
+ css: [processModalStyles.modeBtn, mode === "remove" && processModalStyles.modeBtnDanger],
6046
+ onClick: () => onModeChange("remove"),
6047
+ children: "Remove Thumbnails"
6048
+ }
6049
+ )
6050
+ ] }),
6051
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "p", { css: processModalStyles.message, children: message }),
6052
+ /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { css: processModalStyles.actions, children: [
6053
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "button", { css: processModalStyles.cancelBtn, onClick: onCancel, children: "Cancel" }),
6054
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
6055
+ "button",
6056
+ {
6057
+ css: [processModalStyles.confirmBtn, isRemove && processModalStyles.confirmBtnDanger],
6058
+ onClick: onConfirm,
6059
+ children: confirmLabel
6060
+ }
6061
+ )
6062
+ ] })
6063
+ ] }) });
6064
+ }
6267
6065
  function Breadcrumbs({ currentPath, onNavigate }) {
6268
6066
  const parts = currentPath.split("/").filter(Boolean);
6269
6067
  const breadcrumbs = parts.map((part, index) => ({
@@ -6306,4 +6104,4 @@ var StudioUI_default = StudioUI;
6306
6104
 
6307
6105
 
6308
6106
  exports.StudioUI = StudioUI; exports.default = StudioUI_default;
6309
- //# sourceMappingURL=StudioUI-YRXPBDUX.js.map
6107
+ //# sourceMappingURL=StudioUI-MAOXE72P.js.map