@gallop.software/studio 0.1.6 → 0.1.8

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.
@@ -22,10 +22,13 @@ var defaultState = {
22
22
  selectedItems: /* @__PURE__ */ new Set(),
23
23
  toggleSelection: () => {
24
24
  },
25
+ selectRange: () => {
26
+ },
25
27
  selectAll: () => {
26
28
  },
27
29
  clearSelection: () => {
28
30
  },
31
+ lastSelectedPath: null,
29
32
  viewMode: "grid",
30
33
  setViewMode: () => {
31
34
  },
@@ -47,8 +50,147 @@ function useStudio() {
47
50
  // src/components/StudioToolbar.tsx
48
51
 
49
52
 
53
+
54
+ // src/components/StudioModal.tsx
55
+
50
56
  var _jsxruntime = require('@emotion/react/jsx-runtime');
57
+ var fadeIn = _react3.keyframes`
58
+ from { opacity: 0; }
59
+ to { opacity: 1; }
60
+ `;
61
+ var slideIn = _react3.keyframes`
62
+ from {
63
+ opacity: 0;
64
+ transform: scale(0.95);
65
+ }
66
+ to {
67
+ opacity: 1;
68
+ transform: scale(1);
69
+ }
70
+ `;
51
71
  var styles = {
72
+ overlay: _react3.css`
73
+ position: fixed;
74
+ inset: 0;
75
+ background-color: rgba(0, 0, 0, 0.5);
76
+ display: flex;
77
+ align-items: center;
78
+ justify-content: center;
79
+ z-index: 10000;
80
+ animation: ${fadeIn} 0.15s ease-out;
81
+ `,
82
+ modal: _react3.css`
83
+ background-color: white;
84
+ border-radius: 12px;
85
+ box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.25);
86
+ max-width: 400px;
87
+ width: 90%;
88
+ animation: ${slideIn} 0.15s ease-out;
89
+ `,
90
+ header: _react3.css`
91
+ padding: 20px 24px 0;
92
+ `,
93
+ title: _react3.css`
94
+ font-size: 18px;
95
+ font-weight: 600;
96
+ color: #111827;
97
+ margin: 0;
98
+ `,
99
+ body: _react3.css`
100
+ padding: 12px 24px 24px;
101
+ `,
102
+ message: _react3.css`
103
+ font-size: 14px;
104
+ color: #6b7280;
105
+ margin: 0;
106
+ line-height: 1.5;
107
+ `,
108
+ footer: _react3.css`
109
+ display: flex;
110
+ justify-content: flex-end;
111
+ gap: 12px;
112
+ padding: 16px 24px;
113
+ border-top: 1px solid #e5e7eb;
114
+ background-color: #f9fafb;
115
+ border-radius: 0 0 12px 12px;
116
+ `,
117
+ btn: _react3.css`
118
+ padding: 8px 16px;
119
+ font-size: 14px;
120
+ font-weight: 500;
121
+ border-radius: 8px;
122
+ cursor: pointer;
123
+ transition: all 0.15s;
124
+ `,
125
+ btnCancel: _react3.css`
126
+ background-color: white;
127
+ border: 1px solid #d1d5db;
128
+ color: #374151;
129
+
130
+ &:hover {
131
+ background-color: #f9fafb;
132
+ }
133
+ `,
134
+ btnConfirm: _react3.css`
135
+ background-color: #9333ea;
136
+ border: 1px solid #9333ea;
137
+ color: white;
138
+
139
+ &:hover {
140
+ background-color: #7c3aed;
141
+ }
142
+ `,
143
+ btnDanger: _react3.css`
144
+ background-color: #dc2626;
145
+ border: 1px solid #dc2626;
146
+ color: white;
147
+
148
+ &:hover {
149
+ background-color: #b91c1c;
150
+ }
151
+ `
152
+ };
153
+ function ConfirmModal({
154
+ title,
155
+ message,
156
+ confirmLabel = "Confirm",
157
+ cancelLabel = "Cancel",
158
+ variant = "default",
159
+ onConfirm,
160
+ onCancel
161
+ }) {
162
+ return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { css: styles.overlay, onClick: onCancel, children: /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { css: styles.modal, onClick: (e) => e.stopPropagation(), children: [
163
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { css: styles.header, children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "h3", { css: styles.title, children: title }) }),
164
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { css: styles.body, children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "p", { css: styles.message, children: message }) }),
165
+ /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { css: styles.footer, children: [
166
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "button", { css: [styles.btn, styles.btnCancel], onClick: onCancel, children: cancelLabel }),
167
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
168
+ "button",
169
+ {
170
+ css: [styles.btn, variant === "danger" ? styles.btnDanger : styles.btnConfirm],
171
+ onClick: onConfirm,
172
+ children: confirmLabel
173
+ }
174
+ )
175
+ ] })
176
+ ] }) });
177
+ }
178
+ function AlertModal({
179
+ title,
180
+ message,
181
+ buttonLabel = "OK",
182
+ onClose
183
+ }) {
184
+ return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { css: styles.overlay, onClick: onClose, children: /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { css: styles.modal, onClick: (e) => e.stopPropagation(), children: [
185
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { css: styles.header, children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "h3", { css: styles.title, children: title }) }),
186
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { css: styles.body, children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "p", { css: styles.message, children: message }) }),
187
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { css: styles.footer, children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "button", { css: [styles.btn, styles.btnConfirm], onClick: onClose, children: buttonLabel }) })
188
+ ] }) });
189
+ }
190
+
191
+ // src/components/StudioToolbar.tsx
192
+
193
+ var styles2 = {
52
194
  toolbar: _react3.css`
53
195
  display: flex;
54
196
  align-items: center;
@@ -148,6 +290,8 @@ function StudioToolbar() {
148
290
  const { selectedItems, viewMode, setViewMode, clearSelection, currentPath, triggerRefresh } = useStudio();
149
291
  const fileInputRef = _react.useRef.call(void 0, null);
150
292
  const [uploading, setUploading] = _react.useState.call(void 0, false);
293
+ const [showDeleteConfirm, setShowDeleteConfirm] = _react.useState.call(void 0, false);
294
+ const [alertMessage, setAlertMessage] = _react.useState.call(void 0, null);
151
295
  const handleUpload = _react.useCallback.call(void 0, () => {
152
296
  _optionalChain([fileInputRef, 'access', _ => _.current, 'optionalAccess', _2 => _2.click, 'call', _3 => _3()]);
153
297
  }, []);
@@ -167,13 +311,19 @@ function StudioToolbar() {
167
311
  if (!response.ok) {
168
312
  const error = await response.json();
169
313
  console.error("Upload failed:", error);
170
- alert(`Failed to upload ${file.name}: ${error.error || "Unknown error"}`);
314
+ setAlertMessage({
315
+ title: "Upload Failed",
316
+ message: `Failed to upload ${file.name}: ${error.error || "Unknown error"}`
317
+ });
171
318
  }
172
319
  }
173
320
  triggerRefresh();
174
321
  } catch (error) {
175
322
  console.error("Upload error:", error);
176
- alert("Upload failed. Check console for details.");
323
+ setAlertMessage({
324
+ title: "Upload Failed",
325
+ message: "Upload failed. Check console for details."
326
+ });
177
327
  } finally {
178
328
  setUploading(false);
179
329
  if (fileInputRef.current) {
@@ -184,9 +334,12 @@ function StudioToolbar() {
184
334
  const handleReprocess = _react.useCallback.call(void 0, () => {
185
335
  console.log("Reprocess clicked", selectedItems);
186
336
  }, [selectedItems]);
187
- const handleDelete = _react.useCallback.call(void 0, async () => {
337
+ const handleDeleteClick = _react.useCallback.call(void 0, () => {
188
338
  if (selectedItems.size === 0) return;
189
- if (!confirm(`Delete ${selectedItems.size} item(s)?`)) return;
339
+ setShowDeleteConfirm(true);
340
+ }, [selectedItems]);
341
+ const handleDeleteConfirm = _react.useCallback.call(void 0, async () => {
342
+ setShowDeleteConfirm(false);
190
343
  try {
191
344
  const response = await fetch("/api/studio/delete", {
192
345
  method: "POST",
@@ -198,11 +351,17 @@ function StudioToolbar() {
198
351
  triggerRefresh();
199
352
  } else {
200
353
  const error = await response.json();
201
- alert(`Delete failed: ${error.error || "Unknown error"}`);
354
+ setAlertMessage({
355
+ title: "Delete Failed",
356
+ message: error.error || "Unknown error"
357
+ });
202
358
  }
203
359
  } catch (error) {
204
360
  console.error("Delete error:", error);
205
- alert("Delete failed. Check console for details.");
361
+ setAlertMessage({
362
+ title: "Delete Failed",
363
+ message: "Delete failed. Check console for details."
364
+ });
206
365
  }
207
366
  }, [selectedItems, clearSelection, triggerRefresh]);
208
367
  const handleSyncCdn = _react.useCallback.call(void 0, () => {
@@ -212,83 +371,104 @@ function StudioToolbar() {
212
371
  console.log("Scan clicked");
213
372
  }, []);
214
373
  const hasSelection = selectedItems.size > 0;
215
- return /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { css: styles.toolbar, children: [
216
- /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
217
- "input",
374
+ return /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, _jsxruntime.Fragment, { children: [
375
+ showDeleteConfirm && /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
376
+ ConfirmModal,
218
377
  {
219
- ref: fileInputRef,
220
- type: "file",
221
- multiple: true,
222
- accept: "image/*",
223
- onChange: handleFileChange,
224
- style: { display: "none" }
378
+ title: "Delete Items",
379
+ message: `Are you sure you want to delete ${selectedItems.size} item(s)? This action cannot be undone.`,
380
+ confirmLabel: "Delete",
381
+ variant: "danger",
382
+ onConfirm: handleDeleteConfirm,
383
+ onCancel: () => setShowDeleteConfirm(false)
225
384
  }
226
385
  ),
227
- /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { css: styles.left, children: [
228
- /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
229
- ToolbarButton,
230
- {
231
- onClick: handleUpload,
232
- icon: "upload",
233
- label: uploading ? "Uploading..." : "Upload",
234
- disabled: uploading
235
- }
236
- ),
237
- /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
238
- ToolbarButton,
239
- {
240
- onClick: handleReprocess,
241
- icon: "refresh",
242
- label: "Reprocess",
243
- disabled: !hasSelection
244
- }
245
- ),
246
- /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
247
- ToolbarButton,
248
- {
249
- onClick: handleDelete,
250
- icon: "trash",
251
- label: "Delete",
252
- disabled: !hasSelection,
253
- variant: "danger"
254
- }
255
- ),
386
+ alertMessage && /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
387
+ AlertModal,
388
+ {
389
+ title: alertMessage.title,
390
+ message: alertMessage.message,
391
+ onClose: () => setAlertMessage(null)
392
+ }
393
+ ),
394
+ /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { css: styles2.toolbar, children: [
256
395
  /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
257
- ToolbarButton,
396
+ "input",
258
397
  {
259
- onClick: handleSyncCdn,
260
- icon: "cloud",
261
- label: "Sync CDN",
262
- disabled: !hasSelection
398
+ ref: fileInputRef,
399
+ type: "file",
400
+ multiple: true,
401
+ accept: "image/*",
402
+ onChange: handleFileChange,
403
+ style: { display: "none" }
263
404
  }
264
405
  ),
265
- /* @__PURE__ */ _jsxruntime.jsx.call(void 0, ToolbarButton, { onClick: handleScan, icon: "scan", label: "Scan" })
266
- ] }),
267
- /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { css: styles.right, children: [
268
- hasSelection && /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "span", { css: styles.selectionCount, children: [
269
- selectedItems.size,
270
- " selected",
271
- /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "button", { css: styles.clearBtn, onClick: clearSelection, children: "Clear" })
272
- ] }),
273
- /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { css: styles.viewToggle, children: [
406
+ /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { css: styles2.left, children: [
274
407
  /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
275
- "button",
408
+ ToolbarButton,
276
409
  {
277
- css: [styles.viewBtn, viewMode === "grid" && styles.viewBtnActive],
278
- onClick: () => setViewMode("grid"),
279
- "aria-label": "Grid view",
280
- children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, GridIcon, {})
410
+ onClick: handleUpload,
411
+ icon: "upload",
412
+ label: uploading ? "Uploading..." : "Upload",
413
+ disabled: uploading
281
414
  }
282
415
  ),
283
416
  /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
284
- "button",
417
+ ToolbarButton,
285
418
  {
286
- css: [styles.viewBtn, viewMode === "list" && styles.viewBtnActive],
287
- onClick: () => setViewMode("list"),
288
- "aria-label": "List view",
289
- children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, ListIcon, {})
419
+ onClick: handleReprocess,
420
+ icon: "refresh",
421
+ label: "Reprocess",
422
+ disabled: !hasSelection
290
423
  }
291
- )
424
+ ),
425
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
426
+ ToolbarButton,
427
+ {
428
+ onClick: handleDeleteClick,
429
+ icon: "trash",
430
+ label: "Delete",
431
+ disabled: !hasSelection,
432
+ variant: "danger"
433
+ }
434
+ ),
435
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
436
+ ToolbarButton,
437
+ {
438
+ onClick: handleSyncCdn,
439
+ icon: "cloud",
440
+ label: "Sync CDN",
441
+ disabled: !hasSelection
442
+ }
443
+ ),
444
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, ToolbarButton, { onClick: handleScan, icon: "scan", label: "Scan" })
445
+ ] }),
446
+ /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { css: styles2.right, children: [
447
+ hasSelection && /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "span", { css: styles2.selectionCount, children: [
448
+ selectedItems.size,
449
+ " selected",
450
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "button", { css: styles2.clearBtn, onClick: clearSelection, children: "Clear" })
451
+ ] }),
452
+ /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { css: styles2.viewToggle, children: [
453
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
454
+ "button",
455
+ {
456
+ css: [styles2.viewBtn, viewMode === "grid" && styles2.viewBtnActive],
457
+ onClick: () => setViewMode("grid"),
458
+ "aria-label": "Grid view",
459
+ children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, GridIcon, {})
460
+ }
461
+ ),
462
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
463
+ "button",
464
+ {
465
+ css: [styles2.viewBtn, viewMode === "list" && styles2.viewBtnActive],
466
+ onClick: () => setViewMode("list"),
467
+ "aria-label": "List view",
468
+ children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, ListIcon, {})
469
+ }
470
+ )
471
+ ] })
292
472
  ] })
293
473
  ] })
294
474
  ] });
@@ -303,7 +483,7 @@ function ToolbarButton({
303
483
  return /* @__PURE__ */ _jsxruntime.jsxs.call(void 0,
304
484
  "button",
305
485
  {
306
- css: [styles.btn, variant === "danger" ? styles.btnDanger : styles.btnDefault],
486
+ css: [styles2.btn, variant === "danger" ? styles2.btnDanger : styles2.btnDefault],
307
487
  onClick,
308
488
  disabled,
309
489
  children: [
@@ -316,30 +496,30 @@ function ToolbarButton({
316
496
  function IconComponent({ icon }) {
317
497
  switch (icon) {
318
498
  case "upload":
319
- return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "svg", { css: styles.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 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-8l-4-4m0 0L8 8m4-4v12" }) });
499
+ return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "svg", { css: styles2.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 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-8l-4-4m0 0L8 8m4-4v12" }) });
320
500
  case "refresh":
321
- return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "svg", { css: styles.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 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15" }) });
501
+ return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "svg", { css: styles2.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 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15" }) });
322
502
  case "trash":
323
- return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "svg", { css: styles.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: "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" }) });
503
+ return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "svg", { css: styles2.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: "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" }) });
324
504
  case "cloud":
325
- return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "svg", { css: styles.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" }) });
505
+ return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "svg", { css: styles2.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" }) });
326
506
  case "scan":
327
- return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "svg", { css: styles.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: "M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" }) });
507
+ return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "svg", { css: styles2.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: "M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" }) });
328
508
  default:
329
509
  return null;
330
510
  }
331
511
  }
332
512
  function GridIcon() {
333
- return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "svg", { css: styles.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" }) });
513
+ return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "svg", { css: styles2.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" }) });
334
514
  }
335
515
  function ListIcon() {
336
- return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "svg", { css: styles.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 6h16M4 10h16M4 14h16M4 18h16" }) });
516
+ return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "svg", { css: styles2.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 6h16M4 10h16M4 14h16M4 18h16" }) });
337
517
  }
338
518
 
339
519
  // src/components/StudioBreadcrumb.tsx
340
520
 
341
521
 
342
- var styles2 = {
522
+ var styles3 = {
343
523
  container: _react3.css`
344
524
  display: flex;
345
525
  align-items: center;
@@ -410,14 +590,14 @@ function StudioBreadcrumb() {
410
590
  const newPath = parts.slice(0, index + 1).join("/");
411
591
  setCurrentPath(newPath);
412
592
  };
413
- return /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { css: styles2.container, children: [
414
- currentPath !== "public" && /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "button", { css: styles2.backBtn, onClick: navigateUp, "aria-label": "Go back", children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "svg", { css: styles2.backIcon, fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M15 19l-7-7 7-7" }) }) }),
415
- /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "nav", { css: styles2.nav, children: parts.map((part, index) => /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "span", { css: styles2.item, children: [
416
- index > 0 && /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "span", { css: styles2.separator, children: "/" }),
593
+ return /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { css: styles3.container, children: [
594
+ currentPath !== "public" && /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "button", { css: styles3.backBtn, onClick: navigateUp, "aria-label": "Go back", children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "svg", { css: styles3.backIcon, fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M15 19l-7-7 7-7" }) }) }),
595
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "nav", { css: styles3.nav, children: parts.map((part, index) => /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "span", { css: styles3.item, children: [
596
+ index > 0 && /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "span", { css: styles3.separator, children: "/" }),
417
597
  /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
418
598
  "button",
419
599
  {
420
- css: [styles2.btn, index === parts.length - 1 ? styles2.btnActive : styles2.btnInactive],
600
+ css: [styles3.btn, index === parts.length - 1 ? styles3.btnActive : styles3.btnInactive],
421
601
  onClick: () => handleClick(index),
422
602
  children: part
423
603
  }
@@ -433,7 +613,7 @@ function StudioBreadcrumb() {
433
613
  var spin = _react3.keyframes`
434
614
  to { transform: rotate(360deg); }
435
615
  `;
436
- var styles3 = {
616
+ var styles4 = {
437
617
  loading: _react3.css`
438
618
  display: flex;
439
619
  align-items: center;
@@ -547,10 +727,34 @@ var styles3 = {
547
727
  font-size: 12px;
548
728
  color: #9ca3af;
549
729
  margin: 0;
730
+ `,
731
+ selectAllRow: _react3.css`
732
+ display: flex;
733
+ align-items: center;
734
+ margin-bottom: 12px;
735
+ padding-bottom: 12px;
736
+ border-bottom: 1px solid #e5e7eb;
737
+ `,
738
+ selectAllLabel: _react3.css`
739
+ display: flex;
740
+ align-items: center;
741
+ gap: 8px;
742
+ font-size: 14px;
743
+ color: #6b7280;
744
+ cursor: pointer;
745
+
746
+ &:hover {
747
+ color: #374151;
748
+ }
749
+ `,
750
+ selectAllCheckbox: _react3.css`
751
+ width: 16px;
752
+ height: 16px;
753
+ accent-color: #9333ea;
550
754
  `
551
755
  };
552
756
  function StudioFileGrid() {
553
- const { currentPath, setCurrentPath, selectedItems, toggleSelection, refreshKey } = useStudio();
757
+ const { currentPath, setCurrentPath, selectedItems, toggleSelection, selectRange, lastSelectedPath, selectAll, clearSelection, refreshKey } = useStudio();
554
758
  const [items, setItems] = _react.useState.call(void 0, []);
555
759
  const [loading, setLoading] = _react.useState.call(void 0, true);
556
760
  _react.useEffect.call(void 0, () => {
@@ -570,13 +774,13 @@ function StudioFileGrid() {
570
774
  loadItems();
571
775
  }, [currentPath, refreshKey]);
572
776
  if (loading) {
573
- return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { css: styles3.loading, children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { css: styles3.spinner }) });
777
+ return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { css: styles4.loading, children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { css: styles4.spinner }) });
574
778
  }
575
779
  if (items.length === 0) {
576
- return /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { css: styles3.empty, children: [
577
- /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "svg", { css: styles3.emptyIcon, fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 1.5, d: "M4 16l4.586-4.586a2 2 0 012.828 0L16 16m-2-2l1.586-1.586a2 2 0 012.828 0L20 14m-6-6h.01M6 20h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z" }) }),
578
- /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "p", { css: styles3.emptyText, children: "No files in this folder" }),
579
- /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "p", { css: styles3.emptyText, children: "Upload images to get started" })
780
+ return /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { css: styles4.empty, children: [
781
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "svg", { css: styles4.emptyIcon, fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 1.5, d: "M4 16l4.586-4.586a2 2 0 012.828 0L16 16m-2-2l1.586-1.586a2 2 0 012.828 0L20 14m-6-6h.01M6 20h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z" }) }),
782
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "p", { css: styles4.emptyText, children: "No files in this folder" }),
783
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "p", { css: styles4.emptyText, children: "Upload images to get started" })
580
784
  ] });
581
785
  }
582
786
  const sortedItems = [...items].sort((a, b) => {
@@ -584,54 +788,83 @@ function StudioFileGrid() {
584
788
  if (a.type !== "folder" && b.type === "folder") return 1;
585
789
  return a.name.localeCompare(b.name);
586
790
  });
587
- return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { css: styles3.grid, children: sortedItems.map((item) => /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
588
- GridItem,
589
- {
590
- item,
591
- isSelected: selectedItems.has(item.path),
592
- onSelect: () => toggleSelection(item.path),
593
- onOpen: () => {
594
- if (item.type === "folder") {
595
- setCurrentPath(item.path);
596
- }
597
- }
598
- },
599
- item.path
600
- )) });
601
- }
602
- function GridItem({ item, isSelected, onSelect, onOpen }) {
603
- const isFolder = item.type === "folder";
604
- const handleClick = () => {
605
- if (isFolder) {
606
- onOpen();
791
+ const files = sortedItems.filter((item) => item.type !== "folder");
792
+ const allFilesSelected = files.length > 0 && files.every((item) => selectedItems.has(item.path));
793
+ const someFilesSelected = files.some((item) => selectedItems.has(item.path));
794
+ const handleSelectAll = () => {
795
+ if (allFilesSelected) {
796
+ clearSelection();
797
+ } else {
798
+ selectAll(files);
799
+ }
800
+ };
801
+ const handleItemClick = (item, e) => {
802
+ if (item.type === "folder") {
803
+ setCurrentPath(item.path);
804
+ return;
805
+ }
806
+ if (e.shiftKey && lastSelectedPath) {
807
+ selectRange(lastSelectedPath, item.path, sortedItems);
607
808
  } else {
608
- onSelect();
809
+ toggleSelection(item.path);
609
810
  }
610
811
  };
611
- return /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { css: [styles3.item, isSelected && styles3.itemSelected], onClick: handleClick, children: [
812
+ return /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { children: [
813
+ files.length > 0 && /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { css: styles4.selectAllRow, children: /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "label", { css: styles4.selectAllLabel, children: [
814
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
815
+ "input",
816
+ {
817
+ type: "checkbox",
818
+ css: styles4.selectAllCheckbox,
819
+ checked: allFilesSelected,
820
+ ref: (el) => {
821
+ if (el) el.indeterminate = someFilesSelected && !allFilesSelected;
822
+ },
823
+ onChange: handleSelectAll
824
+ }
825
+ ),
826
+ "Select all (",
827
+ files.length,
828
+ ")"
829
+ ] }) }),
830
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { css: styles4.grid, children: sortedItems.map((item) => /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
831
+ GridItem,
832
+ {
833
+ item,
834
+ isSelected: selectedItems.has(item.path),
835
+ onClick: (e) => handleItemClick(item, e)
836
+ },
837
+ item.path
838
+ )) })
839
+ ] });
840
+ }
841
+ function GridItem({ item, isSelected, onClick }) {
842
+ const isFolder = item.type === "folder";
843
+ return /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { css: [styles4.item, isSelected && styles4.itemSelected], onClick, children: [
612
844
  !isFolder && /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
613
845
  "input",
614
846
  {
615
847
  type: "checkbox",
616
- css: styles3.checkbox,
848
+ css: styles4.checkbox,
617
849
  checked: isSelected,
618
- onChange: onSelect,
850
+ onChange: () => {
851
+ },
619
852
  onClick: (e) => e.stopPropagation()
620
853
  }
621
854
  ),
622
- item.cdnSynced && /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "span", { css: styles3.cdnBadge, children: "CDN" }),
623
- /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { css: styles3.content, children: isFolder ? /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "svg", { css: styles3.folderIcon, fill: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "path", { d: "M10 4H4a2 2 0 00-2 2v12a2 2 0 002 2h16a2 2 0 002-2V8a2 2 0 00-2-2h-8l-2-2z" }) }) : /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
855
+ item.cdnSynced && /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "span", { css: styles4.cdnBadge, children: "CDN" }),
856
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { css: styles4.content, children: isFolder ? /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "svg", { css: styles4.folderIcon, fill: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "path", { d: "M10 4H4a2 2 0 00-2 2v12a2 2 0 002 2h16a2 2 0 002-2V8a2 2 0 00-2-2h-8l-2-2z" }) }) : /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
624
857
  "img",
625
858
  {
626
- css: styles3.image,
859
+ css: styles4.image,
627
860
  src: item.path.replace("public", ""),
628
861
  alt: item.name,
629
862
  loading: "lazy"
630
863
  }
631
864
  ) }),
632
- /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { css: styles3.label, children: [
633
- /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "p", { css: styles3.name, title: item.name, children: item.name }),
634
- item.size && /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "p", { css: styles3.size, children: formatFileSize(item.size) })
865
+ /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { css: styles4.label, children: [
866
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "p", { css: styles4.name, title: item.name, children: item.name }),
867
+ item.size && /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "p", { css: styles4.size, children: formatFileSize(item.size) })
635
868
  ] })
636
869
  ] });
637
870
  }
@@ -648,7 +881,7 @@ function formatFileSize(bytes) {
648
881
  var spin2 = _react3.keyframes`
649
882
  to { transform: rotate(360deg); }
650
883
  `;
651
- var styles4 = {
884
+ var styles5 = {
652
885
  loading: _react3.css`
653
886
  display: flex;
654
887
  align-items: center;
@@ -759,7 +992,7 @@ var styles4 = {
759
992
  `
760
993
  };
761
994
  function StudioFileList() {
762
- const { currentPath, setCurrentPath, selectedItems, toggleSelection, refreshKey } = useStudio();
995
+ const { currentPath, setCurrentPath, selectedItems, toggleSelection, selectRange, lastSelectedPath, selectAll, clearSelection, refreshKey } = useStudio();
763
996
  const [items, setItems] = _react.useState.call(void 0, []);
764
997
  const [loading, setLoading] = _react.useState.call(void 0, true);
765
998
  _react.useEffect.call(void 0, () => {
@@ -779,70 +1012,91 @@ function StudioFileList() {
779
1012
  loadItems();
780
1013
  }, [currentPath, refreshKey]);
781
1014
  if (loading) {
782
- return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { css: styles4.loading, children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { css: styles4.spinner }) });
1015
+ return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { css: styles5.loading, children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { css: styles5.spinner }) });
783
1016
  }
784
1017
  if (items.length === 0) {
785
- return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { css: styles4.empty, children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "p", { children: "No files in this folder" }) });
1018
+ return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { css: styles5.empty, children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "p", { children: "No files in this folder" }) });
786
1019
  }
787
1020
  const sortedItems = [...items].sort((a, b) => {
788
1021
  if (a.type === "folder" && b.type !== "folder") return -1;
789
1022
  if (a.type !== "folder" && b.type === "folder") return 1;
790
1023
  return a.name.localeCompare(b.name);
791
1024
  });
792
- return /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "table", { css: styles4.table, children: [
1025
+ const files = sortedItems.filter((item) => item.type !== "folder");
1026
+ const allFilesSelected = files.length > 0 && files.every((item) => selectedItems.has(item.path));
1027
+ const someFilesSelected = files.some((item) => selectedItems.has(item.path));
1028
+ const handleSelectAll = () => {
1029
+ if (allFilesSelected) {
1030
+ clearSelection();
1031
+ } else {
1032
+ selectAll(files);
1033
+ }
1034
+ };
1035
+ const handleItemClick = (item, e) => {
1036
+ if (item.type === "folder") {
1037
+ setCurrentPath(item.path);
1038
+ return;
1039
+ }
1040
+ if (e.shiftKey && lastSelectedPath) {
1041
+ selectRange(lastSelectedPath, item.path, sortedItems);
1042
+ } else {
1043
+ toggleSelection(item.path);
1044
+ }
1045
+ };
1046
+ return /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "table", { css: styles5.table, children: [
793
1047
  /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "thead", { children: /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "tr", { children: [
794
- /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "th", { css: [styles4.th, styles4.thCheckbox] }),
795
- /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "th", { css: styles4.th, children: "Name" }),
796
- /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "th", { css: [styles4.th, styles4.thSize], children: "Size" }),
797
- /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "th", { css: [styles4.th, styles4.thDimensions], children: "Dimensions" }),
798
- /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "th", { css: [styles4.th, styles4.thCdn], children: "CDN" })
1048
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "th", { css: [styles5.th, styles5.thCheckbox], children: files.length > 0 && /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
1049
+ "input",
1050
+ {
1051
+ type: "checkbox",
1052
+ css: styles5.checkbox,
1053
+ checked: allFilesSelected,
1054
+ ref: (el) => {
1055
+ if (el) el.indeterminate = someFilesSelected && !allFilesSelected;
1056
+ },
1057
+ onChange: handleSelectAll
1058
+ }
1059
+ ) }),
1060
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "th", { css: styles5.th, children: "Name" }),
1061
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "th", { css: [styles5.th, styles5.thSize], children: "Size" }),
1062
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "th", { css: [styles5.th, styles5.thDimensions], children: "Dimensions" }),
1063
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "th", { css: [styles5.th, styles5.thCdn], children: "CDN" })
799
1064
  ] }) }),
800
- /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "tbody", { css: styles4.tbody, children: sortedItems.map((item) => /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
1065
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "tbody", { css: styles5.tbody, children: sortedItems.map((item) => /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
801
1066
  ListRow,
802
1067
  {
803
1068
  item,
804
1069
  isSelected: selectedItems.has(item.path),
805
- onSelect: () => toggleSelection(item.path),
806
- onOpen: () => {
807
- if (item.type === "folder") {
808
- setCurrentPath(item.path);
809
- }
810
- }
1070
+ onClick: (e) => handleItemClick(item, e)
811
1071
  },
812
1072
  item.path
813
1073
  )) })
814
1074
  ] });
815
1075
  }
816
- function ListRow({ item, isSelected, onSelect, onOpen }) {
1076
+ function ListRow({ item, isSelected, onClick }) {
817
1077
  const isFolder = item.type === "folder";
818
- const handleClick = () => {
819
- if (isFolder) {
820
- onOpen();
821
- } else {
822
- onSelect();
823
- }
824
- };
825
- return /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "tr", { css: [styles4.row, isSelected && styles4.rowSelected], onClick: handleClick, children: [
826
- /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "td", { css: styles4.td, children: !isFolder && /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
1078
+ return /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "tr", { css: [styles5.row, isSelected && styles5.rowSelected], onClick, children: [
1079
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "td", { css: styles5.td, children: !isFolder && /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
827
1080
  "input",
828
1081
  {
829
1082
  type: "checkbox",
830
- css: styles4.checkbox,
1083
+ css: styles5.checkbox,
831
1084
  checked: isSelected,
832
- onChange: onSelect,
1085
+ onChange: () => {
1086
+ },
833
1087
  onClick: (e) => e.stopPropagation()
834
1088
  }
835
1089
  ) }),
836
- /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "td", { css: styles4.td, children: /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { css: styles4.nameCell, children: [
837
- isFolder ? /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "svg", { css: styles4.folderIcon, fill: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "path", { d: "M10 4H4a2 2 0 00-2 2v12a2 2 0 002 2h16a2 2 0 002-2V8a2 2 0 00-2-2h-8l-2-2z" }) }) : /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "svg", { css: styles4.fileIcon, fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 1.5, d: "M4 16l4.586-4.586a2 2 0 012.828 0L16 16m-2-2l1.586-1.586a2 2 0 012.828 0L20 14m-6-6h.01M6 20h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z" }) }),
838
- /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "span", { css: styles4.name, children: item.name })
1090
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "td", { css: styles5.td, children: /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { css: styles5.nameCell, children: [
1091
+ isFolder ? /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "svg", { css: styles5.folderIcon, fill: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "path", { d: "M10 4H4a2 2 0 00-2 2v12a2 2 0 002 2h16a2 2 0 002-2V8a2 2 0 00-2-2h-8l-2-2z" }) }) : /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "svg", { css: styles5.fileIcon, fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 1.5, d: "M4 16l4.586-4.586a2 2 0 012.828 0L16 16m-2-2l1.586-1.586a2 2 0 012.828 0L20 14m-6-6h.01M6 20h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z" }) }),
1092
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "span", { css: styles5.name, children: item.name })
839
1093
  ] }) }),
840
- /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "td", { css: [styles4.td, styles4.meta], children: item.size ? formatFileSize2(item.size) : "--" }),
841
- /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "td", { css: [styles4.td, styles4.meta], children: item.dimensions ? `${item.dimensions.width}x${item.dimensions.height}` : "--" }),
842
- /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "td", { css: styles4.td, children: item.cdnSynced ? /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "span", { css: styles4.cdnBadge, children: [
843
- /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "svg", { css: styles4.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" }) }),
1094
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "td", { css: [styles5.td, styles5.meta], children: item.size ? formatFileSize2(item.size) : "--" }),
1095
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "td", { css: [styles5.td, styles5.meta], children: item.dimensions ? `${item.dimensions.width}x${item.dimensions.height}` : "--" }),
1096
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "td", { css: styles5.td, children: item.cdnSynced ? /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "span", { css: styles5.cdnBadge, children: [
1097
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "svg", { css: styles5.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" }) }),
844
1098
  "Synced"
845
- ] }) : /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "span", { css: styles4.cdnEmpty, children: "--" }) })
1099
+ ] }) : /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "span", { css: styles5.cdnEmpty, children: "--" }) })
846
1100
  ] });
847
1101
  }
848
1102
  function formatFileSize2(bytes) {
@@ -854,7 +1108,8 @@ function formatFileSize2(bytes) {
854
1108
  // src/components/StudioPreview.tsx
855
1109
 
856
1110
 
857
- var styles5 = {
1111
+
1112
+ var styles6 = {
858
1113
  panel: _react3.css`
859
1114
  width: 320px;
860
1115
  border-left: 1px solid #e5e7eb;
@@ -985,9 +1240,14 @@ var styles5 = {
985
1240
  };
986
1241
  function StudioPreview() {
987
1242
  const { selectedItems, meta, triggerRefresh, clearSelection } = useStudio();
988
- const handleDelete = async () => {
1243
+ const [showDeleteConfirm, setShowDeleteConfirm] = _react.useState.call(void 0, false);
1244
+ const [alertMessage, setAlertMessage] = _react.useState.call(void 0, null);
1245
+ const handleDeleteClick = () => {
989
1246
  if (selectedItems.size === 0) return;
990
- if (!confirm(`Delete ${selectedItems.size} item(s)?`)) return;
1247
+ setShowDeleteConfirm(true);
1248
+ };
1249
+ const handleDeleteConfirm = async () => {
1250
+ setShowDeleteConfirm(false);
991
1251
  try {
992
1252
  const response = await fetch("/api/studio/delete", {
993
1253
  method: "POST",
@@ -999,106 +1259,142 @@ function StudioPreview() {
999
1259
  triggerRefresh();
1000
1260
  } else {
1001
1261
  const error = await response.json();
1002
- alert(`Delete failed: ${error.error || "Unknown error"}`);
1262
+ setAlertMessage({
1263
+ title: "Delete Failed",
1264
+ message: error.error || "Unknown error"
1265
+ });
1003
1266
  }
1004
1267
  } catch (error) {
1005
1268
  console.error("Delete error:", error);
1006
- alert("Delete failed. Check console for details.");
1269
+ setAlertMessage({
1270
+ title: "Delete Failed",
1271
+ message: "Delete failed. Check console for details."
1272
+ });
1007
1273
  }
1008
1274
  };
1275
+ const modals = /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, _jsxruntime.Fragment, { children: [
1276
+ showDeleteConfirm && /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
1277
+ ConfirmModal,
1278
+ {
1279
+ title: "Delete Items",
1280
+ message: `Are you sure you want to delete ${selectedItems.size} item(s)? This action cannot be undone.`,
1281
+ confirmLabel: "Delete",
1282
+ variant: "danger",
1283
+ onConfirm: handleDeleteConfirm,
1284
+ onCancel: () => setShowDeleteConfirm(false)
1285
+ }
1286
+ ),
1287
+ alertMessage && /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
1288
+ AlertModal,
1289
+ {
1290
+ title: alertMessage.title,
1291
+ message: alertMessage.message,
1292
+ onClose: () => setAlertMessage(null)
1293
+ }
1294
+ )
1295
+ ] });
1009
1296
  if (selectedItems.size === 0) {
1010
- return /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { css: styles5.panel, children: [
1011
- /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "h3", { css: styles5.title, children: "Preview" }),
1012
- /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { css: styles5.emptyState, children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "p", { css: styles5.emptyText, children: "Select an image to preview" }) })
1297
+ return /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, _jsxruntime.Fragment, { children: [
1298
+ modals,
1299
+ /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { css: styles6.panel, children: [
1300
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "h3", { css: styles6.title, children: "Preview" }),
1301
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { css: styles6.emptyState, children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "p", { css: styles6.emptyText, children: "Select an image to preview" }) })
1302
+ ] })
1013
1303
  ] });
1014
1304
  }
1015
1305
  if (selectedItems.size > 1) {
1016
- return /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { css: styles5.panel, children: [
1017
- /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "h3", { css: styles5.title, children: [
1018
- selectedItems.size,
1019
- " items selected"
1020
- ] }),
1021
- /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { css: styles5.actions, children: /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "button", { css: [styles5.actionBtn, styles5.actionBtnDanger], onClick: handleDelete, children: [
1022
- "Delete ",
1023
- selectedItems.size,
1024
- " items"
1025
- ] }) })
1306
+ return /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, _jsxruntime.Fragment, { children: [
1307
+ modals,
1308
+ /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { css: styles6.panel, children: [
1309
+ /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "h3", { css: styles6.title, children: [
1310
+ selectedItems.size,
1311
+ " items selected"
1312
+ ] }),
1313
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { css: styles6.actions, children: /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "button", { css: [styles6.actionBtn, styles6.actionBtnDanger], onClick: handleDeleteClick, children: [
1314
+ "Delete ",
1315
+ selectedItems.size,
1316
+ " items"
1317
+ ] }) })
1318
+ ] })
1026
1319
  ] });
1027
1320
  }
1028
1321
  const selectedPath = Array.from(selectedItems)[0];
1029
1322
  const imageKey = selectedPath.replace(/^public\/images\//, "").replace(/^public\/originals\//, "");
1030
1323
  const imageData = _optionalChain([meta, 'optionalAccess', _4 => _4.images, 'optionalAccess', _5 => _5[imageKey]]);
1031
- return /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { css: styles5.panel, children: [
1032
- /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "h3", { css: styles5.title, children: "Preview" }),
1033
- /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { css: styles5.imageContainer, children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
1034
- "img",
1035
- {
1036
- css: styles5.image,
1037
- src: selectedPath.replace("public", ""),
1038
- alt: "Preview"
1039
- }
1040
- ) }),
1041
- /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { css: styles5.info, children: [
1042
- /* @__PURE__ */ _jsxruntime.jsx.call(void 0, InfoRow, { label: "Filename", value: selectedPath.split("/").pop() || "" }),
1043
- imageData && /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, _jsxruntime.Fragment, { children: [
1044
- /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
1045
- InfoRow,
1046
- {
1047
- label: "Original",
1048
- value: `${imageData.original.width}x${imageData.original.height}`
1049
- }
1050
- ),
1051
- /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
1052
- InfoRow,
1053
- {
1054
- label: "File size",
1055
- value: formatFileSize3(imageData.original.fileSize)
1056
- }
1057
- ),
1058
- /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { css: styles5.section, children: [
1059
- /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "p", { css: styles5.sectionTitle, children: "Generated sizes" }),
1060
- Object.entries(imageData.sizes).map(([size, data]) => /* @__PURE__ */ _jsxruntime.jsx.call(void 0, InfoRow, { label: size, value: `${data.width}x${data.height}` }, size))
1061
- ] }),
1062
- _optionalChain([imageData, 'access', _6 => _6.cdn, 'optionalAccess', _7 => _7.synced]) && /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { css: styles5.section, children: [
1063
- /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "p", { css: styles5.sectionTitle, children: "CDN" }),
1064
- /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { css: styles5.cdnStatus, children: [
1065
- /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "svg", { css: styles5.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" }) }),
1066
- "Synced to CDN"
1067
- ] }),
1324
+ return /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, _jsxruntime.Fragment, { children: [
1325
+ modals,
1326
+ /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { css: styles6.panel, children: [
1327
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "h3", { css: styles6.title, children: "Preview" }),
1328
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { css: styles6.imageContainer, children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
1329
+ "img",
1330
+ {
1331
+ css: styles6.image,
1332
+ src: selectedPath.replace("public", ""),
1333
+ alt: "Preview"
1334
+ }
1335
+ ) }),
1336
+ /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { css: styles6.info, children: [
1337
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, InfoRow, { label: "Filename", value: selectedPath.split("/").pop() || "" }),
1338
+ imageData && /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, _jsxruntime.Fragment, { children: [
1068
1339
  /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
1069
- "button",
1340
+ InfoRow,
1070
1341
  {
1071
- css: styles5.copyBtn,
1072
- onClick: () => {
1073
- navigator.clipboard.writeText(`${_optionalChain([imageData, 'access', _8 => _8.cdn, 'optionalAccess', _9 => _9.baseUrl])}${imageData.sizes.full.path}`);
1074
- },
1075
- children: "Copy CDN URL"
1342
+ label: "Original",
1343
+ value: `${imageData.original.width}x${imageData.original.height}`
1076
1344
  }
1077
- )
1078
- ] }),
1079
- imageData.blurhash && /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { css: styles5.section, children: [
1080
- /* @__PURE__ */ _jsxruntime.jsx.call(void 0, InfoRow, { label: "Blurhash", value: imageData.blurhash, truncate: true }),
1345
+ ),
1081
1346
  /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
1082
- "div",
1347
+ InfoRow,
1083
1348
  {
1084
- css: styles5.colorSwatch,
1085
- style: { backgroundColor: imageData.dominantColor },
1086
- title: `Dominant color: ${imageData.dominantColor}`
1349
+ label: "File size",
1350
+ value: formatFileSize3(imageData.original.fileSize)
1087
1351
  }
1088
- )
1352
+ ),
1353
+ /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { css: styles6.section, children: [
1354
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "p", { css: styles6.sectionTitle, children: "Generated sizes" }),
1355
+ Object.entries(imageData.sizes).map(([size, data]) => /* @__PURE__ */ _jsxruntime.jsx.call(void 0, InfoRow, { label: size, value: `${data.width}x${data.height}` }, size))
1356
+ ] }),
1357
+ _optionalChain([imageData, 'access', _6 => _6.cdn, 'optionalAccess', _7 => _7.synced]) && /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { css: styles6.section, children: [
1358
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "p", { css: styles6.sectionTitle, children: "CDN" }),
1359
+ /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { css: styles6.cdnStatus, children: [
1360
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "svg", { css: styles6.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" }) }),
1361
+ "Synced to CDN"
1362
+ ] }),
1363
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
1364
+ "button",
1365
+ {
1366
+ css: styles6.copyBtn,
1367
+ onClick: () => {
1368
+ navigator.clipboard.writeText(`${_optionalChain([imageData, 'access', _8 => _8.cdn, 'optionalAccess', _9 => _9.baseUrl])}${imageData.sizes.full.path}`);
1369
+ },
1370
+ children: "Copy CDN URL"
1371
+ }
1372
+ )
1373
+ ] }),
1374
+ imageData.blurhash && /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { css: styles6.section, children: [
1375
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, InfoRow, { label: "Blurhash", value: imageData.blurhash, truncate: true }),
1376
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
1377
+ "div",
1378
+ {
1379
+ css: styles6.colorSwatch,
1380
+ style: { backgroundColor: imageData.dominantColor },
1381
+ title: `Dominant color: ${imageData.dominantColor}`
1382
+ }
1383
+ )
1384
+ ] })
1089
1385
  ] })
1386
+ ] }),
1387
+ /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { css: styles6.actions, children: [
1388
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "button", { css: styles6.actionBtn, children: "Rename" }),
1389
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "button", { css: [styles6.actionBtn, styles6.actionBtnDanger], onClick: handleDeleteClick, children: "Delete" })
1090
1390
  ] })
1091
- ] }),
1092
- /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { css: styles5.actions, children: [
1093
- /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "button", { css: styles5.actionBtn, children: "Rename" }),
1094
- /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "button", { css: [styles5.actionBtn, styles5.actionBtnDanger], onClick: handleDelete, children: "Delete" })
1095
1391
  ] })
1096
1392
  ] });
1097
1393
  }
1098
1394
  function InfoRow({ label, value, truncate }) {
1099
- return /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { css: styles5.row, children: [
1100
- /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "span", { css: styles5.label, children: label }),
1101
- /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "span", { css: [styles5.value, truncate && styles5.valueTruncate], title: truncate ? value : void 0, children: value })
1395
+ return /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { css: styles6.row, children: [
1396
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "span", { css: styles6.label, children: label }),
1397
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "span", { css: [styles6.value, truncate && styles6.valueTruncate], title: truncate ? value : void 0, children: value })
1102
1398
  ] });
1103
1399
  }
1104
1400
  function formatFileSize3(bytes) {
@@ -1111,7 +1407,7 @@ function formatFileSize3(bytes) {
1111
1407
 
1112
1408
 
1113
1409
 
1114
- var styles6 = {
1410
+ var styles7 = {
1115
1411
  btn: _react3.css`
1116
1412
  padding: 8px;
1117
1413
  background: none;
@@ -1269,10 +1565,10 @@ var styles6 = {
1269
1565
  function StudioSettings() {
1270
1566
  const [isOpen, setIsOpen] = _react.useState.call(void 0, false);
1271
1567
  return /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, _jsxruntime.Fragment, { children: [
1272
- /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "button", { css: styles6.btn, onClick: () => setIsOpen(true), "aria-label": "Settings", children: /* @__PURE__ */ _jsxruntime.jsxs.call(void 0,
1568
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "button", { css: styles7.btn, onClick: () => setIsOpen(true), "aria-label": "Settings", children: /* @__PURE__ */ _jsxruntime.jsxs.call(void 0,
1273
1569
  "svg",
1274
1570
  {
1275
- css: styles6.icon,
1571
+ css: styles7.icon,
1276
1572
  xmlns: "http://www.w3.org/2000/svg",
1277
1573
  viewBox: "0 0 24 24",
1278
1574
  fill: "none",
@@ -1290,51 +1586,51 @@ function StudioSettings() {
1290
1586
  ] });
1291
1587
  }
1292
1588
  function SettingsPanel({ onClose }) {
1293
- return /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { css: styles6.overlay, children: [
1294
- /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { css: styles6.backdrop, onClick: onClose }),
1295
- /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { css: styles6.panel, children: [
1296
- /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { css: styles6.header, children: [
1297
- /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "h2", { css: styles6.title, children: "Settings" }),
1298
- /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "button", { css: styles6.closeBtn, onClick: onClose, children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "svg", { css: styles6.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: "M6 18L18 6M6 6l12 12" }) }) })
1589
+ return /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { css: styles7.overlay, children: [
1590
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { css: styles7.backdrop, onClick: onClose }),
1591
+ /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { css: styles7.panel, children: [
1592
+ /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { css: styles7.header, children: [
1593
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "h2", { css: styles7.title, children: "Settings" }),
1594
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "button", { css: styles7.closeBtn, onClick: onClose, children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "svg", { css: styles7.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: "M6 18L18 6M6 6l12 12" }) }) })
1299
1595
  ] }),
1300
- /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { css: styles6.sections, children: [
1596
+ /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { css: styles7.sections, children: [
1301
1597
  /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "section", { children: [
1302
- /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "h3", { css: styles6.sectionTitle, children: "Cloudflare R2" }),
1303
- /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "p", { css: styles6.description, children: "Configure in .env.local file:" }),
1304
- /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { css: styles6.code, children: [
1305
- /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "p", { css: styles6.codeLine, children: "CLOUDFLARE_R2_ACCOUNT_ID" }),
1306
- /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "p", { css: styles6.codeLine, children: "CLOUDFLARE_R2_ACCESS_KEY_ID" }),
1307
- /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "p", { css: styles6.codeLine, children: "CLOUDFLARE_R2_SECRET_ACCESS_KEY" }),
1308
- /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "p", { css: styles6.codeLine, children: "CLOUDFLARE_R2_BUCKET_NAME" }),
1309
- /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "p", { css: styles6.codeLine, children: "CLOUDFLARE_R2_PUBLIC_URL" })
1598
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "h3", { css: styles7.sectionTitle, children: "Cloudflare R2" }),
1599
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "p", { css: styles7.description, children: "Configure in .env.local file:" }),
1600
+ /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { css: styles7.code, children: [
1601
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "p", { css: styles7.codeLine, children: "CLOUDFLARE_R2_ACCOUNT_ID" }),
1602
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "p", { css: styles7.codeLine, children: "CLOUDFLARE_R2_ACCESS_KEY_ID" }),
1603
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "p", { css: styles7.codeLine, children: "CLOUDFLARE_R2_SECRET_ACCESS_KEY" }),
1604
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "p", { css: styles7.codeLine, children: "CLOUDFLARE_R2_BUCKET_NAME" }),
1605
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "p", { css: styles7.codeLine, children: "CLOUDFLARE_R2_PUBLIC_URL" })
1310
1606
  ] })
1311
1607
  ] }),
1312
1608
  /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "section", { children: [
1313
- /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "h3", { css: styles6.sectionTitle, children: "Custom CDN URL" }),
1314
- /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "p", { css: styles6.description, children: "Override the default R2 URL with a custom domain:" }),
1315
- /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "input", { css: styles6.input, type: "text", placeholder: "https://cdn.yourdomain.com" })
1609
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "h3", { css: styles7.sectionTitle, children: "Custom CDN URL" }),
1610
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "p", { css: styles7.description, children: "Override the default R2 URL with a custom domain:" }),
1611
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "input", { css: styles7.input, type: "text", placeholder: "https://cdn.yourdomain.com" })
1316
1612
  ] }),
1317
1613
  /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "section", { children: [
1318
- /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "h3", { css: styles6.sectionTitle, children: "Thumbnail Sizes" }),
1319
- /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { css: styles6.grid, children: [
1614
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "h3", { css: styles7.sectionTitle, children: "Thumbnail Sizes" }),
1615
+ /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { css: styles7.grid, children: [
1320
1616
  /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { children: [
1321
- /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "label", { css: styles6.label, children: "Small" }),
1322
- /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "input", { css: styles6.input, type: "number", defaultValue: 300 })
1617
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "label", { css: styles7.label, children: "Small" }),
1618
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "input", { css: styles7.input, type: "number", defaultValue: 300 })
1323
1619
  ] }),
1324
1620
  /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { children: [
1325
- /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "label", { css: styles6.label, children: "Medium" }),
1326
- /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "input", { css: styles6.input, type: "number", defaultValue: 700 })
1621
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "label", { css: styles7.label, children: "Medium" }),
1622
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "input", { css: styles7.input, type: "number", defaultValue: 700 })
1327
1623
  ] }),
1328
1624
  /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { children: [
1329
- /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "label", { css: styles6.label, children: "Large" }),
1330
- /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "input", { css: styles6.input, type: "number", defaultValue: 1400 })
1625
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "label", { css: styles7.label, children: "Large" }),
1626
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "input", { css: styles7.input, type: "number", defaultValue: 1400 })
1331
1627
  ] })
1332
1628
  ] })
1333
1629
  ] })
1334
1630
  ] }),
1335
- /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { css: styles6.footer, children: [
1336
- /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "button", { css: styles6.cancelBtn, onClick: onClose, children: "Cancel" }),
1337
- /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "button", { css: styles6.saveBtn, children: "Save Changes" })
1631
+ /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { css: styles7.footer, children: [
1632
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "button", { css: styles7.cancelBtn, onClick: onClose, children: "Cancel" }),
1633
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "button", { css: styles7.saveBtn, children: "Save Changes" })
1338
1634
  ] })
1339
1635
  ] })
1340
1636
  ] });
@@ -1342,7 +1638,7 @@ function SettingsPanel({ onClose }) {
1342
1638
 
1343
1639
  // src/components/StudioUI.tsx
1344
1640
 
1345
- var styles7 = {
1641
+ var styles8 = {
1346
1642
  container: _react3.css`
1347
1643
  display: flex;
1348
1644
  flex-direction: column;
@@ -1399,6 +1695,7 @@ var styles7 = {
1399
1695
  function StudioUI({ onClose }) {
1400
1696
  const [currentPath, setCurrentPathInternal] = _react.useState.call(void 0, "public");
1401
1697
  const [selectedItems, setSelectedItems] = _react.useState.call(void 0, /* @__PURE__ */ new Set());
1698
+ const [lastSelectedPath, setLastSelectedPath] = _react.useState.call(void 0, null);
1402
1699
  const [viewMode, setViewMode] = _react.useState.call(void 0, "grid");
1403
1700
  const [meta, setMeta] = _react.useState.call(void 0, null);
1404
1701
  const [isLoading, setIsLoading] = _react.useState.call(void 0, false);
@@ -1427,6 +1724,23 @@ function StudioUI({ onClose }) {
1427
1724
  }
1428
1725
  return next;
1429
1726
  });
1727
+ setLastSelectedPath(path);
1728
+ }, []);
1729
+ const selectRange = _react.useCallback.call(void 0, (fromPath, toPath, allItems) => {
1730
+ const files = allItems.filter((item) => item.type !== "folder");
1731
+ const fromIndex = files.findIndex((item) => item.path === fromPath);
1732
+ const toIndex = files.findIndex((item) => item.path === toPath);
1733
+ if (fromIndex === -1 || toIndex === -1) return;
1734
+ const start = Math.min(fromIndex, toIndex);
1735
+ const end = Math.max(fromIndex, toIndex);
1736
+ setSelectedItems((prev) => {
1737
+ const next = new Set(prev);
1738
+ for (let i = start; i <= end; i++) {
1739
+ next.add(files[i].path);
1740
+ }
1741
+ return next;
1742
+ });
1743
+ setLastSelectedPath(toPath);
1430
1744
  }, []);
1431
1745
  const selectAll = _react.useCallback.call(void 0, (items) => {
1432
1746
  setSelectedItems(new Set(items.map((item) => item.path)));
@@ -1461,8 +1775,10 @@ function StudioUI({ onClose }) {
1461
1775
  navigateUp,
1462
1776
  selectedItems,
1463
1777
  toggleSelection,
1778
+ selectRange,
1464
1779
  selectAll,
1465
1780
  clearSelection,
1781
+ lastSelectedPath,
1466
1782
  viewMode,
1467
1783
  setViewMode,
1468
1784
  meta,
@@ -1472,15 +1788,15 @@ function StudioUI({ onClose }) {
1472
1788
  refreshKey,
1473
1789
  triggerRefresh
1474
1790
  };
1475
- return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, StudioContext.Provider, { value: contextValue, children: /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { css: styles7.container, children: [
1476
- /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { css: styles7.header, children: [
1477
- /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "h1", { css: styles7.title, children: "Studio" }),
1478
- /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { css: styles7.headerActions, children: [
1791
+ return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, StudioContext.Provider, { value: contextValue, children: /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { css: styles8.container, children: [
1792
+ /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { css: styles8.header, children: [
1793
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "h1", { css: styles8.title, children: "Studio" }),
1794
+ /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { css: styles8.headerActions, children: [
1479
1795
  /* @__PURE__ */ _jsxruntime.jsx.call(void 0, StudioSettings, {}),
1480
1796
  /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
1481
1797
  "button",
1482
1798
  {
1483
- css: styles7.closeBtn,
1799
+ css: styles8.closeBtn,
1484
1800
  onClick: onClose,
1485
1801
  "aria-label": "Close Studio",
1486
1802
  children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, CloseIcon, {})
@@ -1490,8 +1806,8 @@ function StudioUI({ onClose }) {
1490
1806
  ] }),
1491
1807
  /* @__PURE__ */ _jsxruntime.jsx.call(void 0, StudioToolbar, {}),
1492
1808
  /* @__PURE__ */ _jsxruntime.jsx.call(void 0, StudioBreadcrumb, {}),
1493
- /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { css: styles7.content, children: [
1494
- /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { css: styles7.fileBrowser, children: viewMode === "grid" ? /* @__PURE__ */ _jsxruntime.jsx.call(void 0, StudioFileGrid, {}) : /* @__PURE__ */ _jsxruntime.jsx.call(void 0, StudioFileList, {}) }),
1809
+ /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { css: styles8.content, children: [
1810
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { css: styles8.fileBrowser, children: viewMode === "grid" ? /* @__PURE__ */ _jsxruntime.jsx.call(void 0, StudioFileGrid, {}) : /* @__PURE__ */ _jsxruntime.jsx.call(void 0, StudioFileList, {}) }),
1495
1811
  /* @__PURE__ */ _jsxruntime.jsx.call(void 0, StudioPreview, {})
1496
1812
  ] })
1497
1813
  ] }) });
@@ -1500,7 +1816,7 @@ function CloseIcon() {
1500
1816
  return /* @__PURE__ */ _jsxruntime.jsxs.call(void 0,
1501
1817
  "svg",
1502
1818
  {
1503
- css: styles7.closeIcon,
1819
+ css: styles8.closeIcon,
1504
1820
  xmlns: "http://www.w3.org/2000/svg",
1505
1821
  viewBox: "0 0 24 24",
1506
1822
  fill: "none",
@@ -1520,4 +1836,4 @@ var StudioUI_default = StudioUI;
1520
1836
 
1521
1837
 
1522
1838
  exports.StudioUI = StudioUI; exports.default = StudioUI_default;
1523
- //# sourceMappingURL=StudioUI-P5VY2DPS.js.map
1839
+ //# sourceMappingURL=StudioUI-ELH3QOUT.js.map