@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.
@@ -0,0 +1,1839 @@
1
+ "use client";
2
+
3
+ // src/components/StudioUI.tsx
4
+ import { useEffect as useEffect3, useCallback as useCallback2, useState as useState6 } from "react";
5
+ import { css as css8 } from "@emotion/react";
6
+
7
+ // src/components/StudioContext.tsx
8
+ import { createContext, useContext } from "react";
9
+ var defaultState = {
10
+ isOpen: false,
11
+ openStudio: () => {
12
+ },
13
+ closeStudio: () => {
14
+ },
15
+ toggleStudio: () => {
16
+ },
17
+ currentPath: "public",
18
+ setCurrentPath: () => {
19
+ },
20
+ navigateUp: () => {
21
+ },
22
+ selectedItems: /* @__PURE__ */ new Set(),
23
+ toggleSelection: () => {
24
+ },
25
+ selectRange: () => {
26
+ },
27
+ selectAll: () => {
28
+ },
29
+ clearSelection: () => {
30
+ },
31
+ lastSelectedPath: null,
32
+ viewMode: "grid",
33
+ setViewMode: () => {
34
+ },
35
+ meta: null,
36
+ setMeta: () => {
37
+ },
38
+ isLoading: false,
39
+ setIsLoading: () => {
40
+ },
41
+ refreshKey: 0,
42
+ triggerRefresh: () => {
43
+ }
44
+ };
45
+ var StudioContext = createContext(defaultState);
46
+ function useStudio() {
47
+ return useContext(StudioContext);
48
+ }
49
+
50
+ // src/components/StudioToolbar.tsx
51
+ import { useCallback, useRef, useState } from "react";
52
+ import { css as css2 } from "@emotion/react";
53
+
54
+ // src/components/StudioModal.tsx
55
+ import { css, keyframes } from "@emotion/react";
56
+ import { jsx, jsxs } from "@emotion/react/jsx-runtime";
57
+ var fadeIn = keyframes`
58
+ from { opacity: 0; }
59
+ to { opacity: 1; }
60
+ `;
61
+ var slideIn = keyframes`
62
+ from {
63
+ opacity: 0;
64
+ transform: scale(0.95);
65
+ }
66
+ to {
67
+ opacity: 1;
68
+ transform: scale(1);
69
+ }
70
+ `;
71
+ var styles = {
72
+ overlay: 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: 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: css`
91
+ padding: 20px 24px 0;
92
+ `,
93
+ title: css`
94
+ font-size: 18px;
95
+ font-weight: 600;
96
+ color: #111827;
97
+ margin: 0;
98
+ `,
99
+ body: css`
100
+ padding: 12px 24px 24px;
101
+ `,
102
+ message: css`
103
+ font-size: 14px;
104
+ color: #6b7280;
105
+ margin: 0;
106
+ line-height: 1.5;
107
+ `,
108
+ footer: 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: 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: css`
126
+ background-color: white;
127
+ border: 1px solid #d1d5db;
128
+ color: #374151;
129
+
130
+ &:hover {
131
+ background-color: #f9fafb;
132
+ }
133
+ `,
134
+ btnConfirm: css`
135
+ background-color: #9333ea;
136
+ border: 1px solid #9333ea;
137
+ color: white;
138
+
139
+ &:hover {
140
+ background-color: #7c3aed;
141
+ }
142
+ `,
143
+ btnDanger: 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__ */ jsx("div", { css: styles.overlay, onClick: onCancel, children: /* @__PURE__ */ jsxs("div", { css: styles.modal, onClick: (e) => e.stopPropagation(), children: [
163
+ /* @__PURE__ */ jsx("div", { css: styles.header, children: /* @__PURE__ */ jsx("h3", { css: styles.title, children: title }) }),
164
+ /* @__PURE__ */ jsx("div", { css: styles.body, children: /* @__PURE__ */ jsx("p", { css: styles.message, children: message }) }),
165
+ /* @__PURE__ */ jsxs("div", { css: styles.footer, children: [
166
+ /* @__PURE__ */ jsx("button", { css: [styles.btn, styles.btnCancel], onClick: onCancel, children: cancelLabel }),
167
+ /* @__PURE__ */ jsx(
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__ */ jsx("div", { css: styles.overlay, onClick: onClose, children: /* @__PURE__ */ jsxs("div", { css: styles.modal, onClick: (e) => e.stopPropagation(), children: [
185
+ /* @__PURE__ */ jsx("div", { css: styles.header, children: /* @__PURE__ */ jsx("h3", { css: styles.title, children: title }) }),
186
+ /* @__PURE__ */ jsx("div", { css: styles.body, children: /* @__PURE__ */ jsx("p", { css: styles.message, children: message }) }),
187
+ /* @__PURE__ */ jsx("div", { css: styles.footer, children: /* @__PURE__ */ jsx("button", { css: [styles.btn, styles.btnConfirm], onClick: onClose, children: buttonLabel }) })
188
+ ] }) });
189
+ }
190
+
191
+ // src/components/StudioToolbar.tsx
192
+ import { Fragment, jsx as jsx2, jsxs as jsxs2 } from "@emotion/react/jsx-runtime";
193
+ var styles2 = {
194
+ toolbar: css2`
195
+ display: flex;
196
+ align-items: center;
197
+ justify-content: space-between;
198
+ padding: 12px 24px;
199
+ background-color: #f9fafb;
200
+ border-bottom: 1px solid #e5e7eb;
201
+ `,
202
+ left: css2`
203
+ display: flex;
204
+ align-items: center;
205
+ gap: 8px;
206
+ `,
207
+ right: css2`
208
+ display: flex;
209
+ align-items: center;
210
+ gap: 16px;
211
+ `,
212
+ btn: css2`
213
+ display: flex;
214
+ align-items: center;
215
+ gap: 8px;
216
+ padding: 8px 12px;
217
+ border-radius: 8px;
218
+ font-size: 14px;
219
+ font-weight: 500;
220
+ background: none;
221
+ border: none;
222
+ cursor: pointer;
223
+ transition: background-color 0.15s;
224
+
225
+ &:disabled {
226
+ cursor: not-allowed;
227
+ opacity: 0.5;
228
+ }
229
+ `,
230
+ btnDefault: css2`
231
+ color: #374151;
232
+
233
+ &:hover:not(:disabled) {
234
+ background-color: white;
235
+ }
236
+ `,
237
+ btnDanger: css2`
238
+ color: #dc2626;
239
+
240
+ &:hover:not(:disabled) {
241
+ background-color: #fef2f2;
242
+ }
243
+ `,
244
+ icon: css2`
245
+ width: 16px;
246
+ height: 16px;
247
+ `,
248
+ selectionCount: css2`
249
+ font-size: 14px;
250
+ color: #4b5563;
251
+ `,
252
+ clearBtn: css2`
253
+ margin-left: 8px;
254
+ color: #9333ea;
255
+ background: none;
256
+ border: none;
257
+ cursor: pointer;
258
+ font-size: 14px;
259
+
260
+ &:hover {
261
+ text-decoration: underline;
262
+ }
263
+ `,
264
+ viewToggle: css2`
265
+ display: flex;
266
+ align-items: center;
267
+ background-color: white;
268
+ border: 1px solid #e5e7eb;
269
+ border-radius: 8px;
270
+ overflow: hidden;
271
+ `,
272
+ viewBtn: css2`
273
+ padding: 8px;
274
+ background: none;
275
+ border: none;
276
+ cursor: pointer;
277
+ color: #6b7280;
278
+ transition: all 0.15s;
279
+
280
+ &:hover {
281
+ background-color: #f9fafb;
282
+ }
283
+ `,
284
+ viewBtnActive: css2`
285
+ background-color: #f3e8ff;
286
+ color: #7c3aed;
287
+ `
288
+ };
289
+ function StudioToolbar() {
290
+ const { selectedItems, viewMode, setViewMode, clearSelection, currentPath, triggerRefresh } = useStudio();
291
+ const fileInputRef = useRef(null);
292
+ const [uploading, setUploading] = useState(false);
293
+ const [showDeleteConfirm, setShowDeleteConfirm] = useState(false);
294
+ const [alertMessage, setAlertMessage] = useState(null);
295
+ const handleUpload = useCallback(() => {
296
+ fileInputRef.current?.click();
297
+ }, []);
298
+ const handleFileChange = useCallback(async (e) => {
299
+ const files = e.target.files;
300
+ if (!files || files.length === 0) return;
301
+ setUploading(true);
302
+ try {
303
+ for (const file of Array.from(files)) {
304
+ const formData = new FormData();
305
+ formData.append("file", file);
306
+ formData.append("path", currentPath);
307
+ const response = await fetch("/api/studio/upload", {
308
+ method: "POST",
309
+ body: formData
310
+ });
311
+ if (!response.ok) {
312
+ const error = await response.json();
313
+ console.error("Upload failed:", error);
314
+ setAlertMessage({
315
+ title: "Upload Failed",
316
+ message: `Failed to upload ${file.name}: ${error.error || "Unknown error"}`
317
+ });
318
+ }
319
+ }
320
+ triggerRefresh();
321
+ } catch (error) {
322
+ console.error("Upload error:", error);
323
+ setAlertMessage({
324
+ title: "Upload Failed",
325
+ message: "Upload failed. Check console for details."
326
+ });
327
+ } finally {
328
+ setUploading(false);
329
+ if (fileInputRef.current) {
330
+ fileInputRef.current.value = "";
331
+ }
332
+ }
333
+ }, [currentPath, triggerRefresh]);
334
+ const handleReprocess = useCallback(() => {
335
+ console.log("Reprocess clicked", selectedItems);
336
+ }, [selectedItems]);
337
+ const handleDeleteClick = useCallback(() => {
338
+ if (selectedItems.size === 0) return;
339
+ setShowDeleteConfirm(true);
340
+ }, [selectedItems]);
341
+ const handleDeleteConfirm = useCallback(async () => {
342
+ setShowDeleteConfirm(false);
343
+ try {
344
+ const response = await fetch("/api/studio/delete", {
345
+ method: "POST",
346
+ headers: { "Content-Type": "application/json" },
347
+ body: JSON.stringify({ paths: Array.from(selectedItems) })
348
+ });
349
+ if (response.ok) {
350
+ clearSelection();
351
+ triggerRefresh();
352
+ } else {
353
+ const error = await response.json();
354
+ setAlertMessage({
355
+ title: "Delete Failed",
356
+ message: error.error || "Unknown error"
357
+ });
358
+ }
359
+ } catch (error) {
360
+ console.error("Delete error:", error);
361
+ setAlertMessage({
362
+ title: "Delete Failed",
363
+ message: "Delete failed. Check console for details."
364
+ });
365
+ }
366
+ }, [selectedItems, clearSelection, triggerRefresh]);
367
+ const handleSyncCdn = useCallback(() => {
368
+ console.log("Sync CDN clicked", selectedItems);
369
+ }, [selectedItems]);
370
+ const handleScan = useCallback(() => {
371
+ console.log("Scan clicked");
372
+ }, []);
373
+ const hasSelection = selectedItems.size > 0;
374
+ return /* @__PURE__ */ jsxs2(Fragment, { children: [
375
+ showDeleteConfirm && /* @__PURE__ */ jsx2(
376
+ ConfirmModal,
377
+ {
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)
384
+ }
385
+ ),
386
+ alertMessage && /* @__PURE__ */ jsx2(
387
+ AlertModal,
388
+ {
389
+ title: alertMessage.title,
390
+ message: alertMessage.message,
391
+ onClose: () => setAlertMessage(null)
392
+ }
393
+ ),
394
+ /* @__PURE__ */ jsxs2("div", { css: styles2.toolbar, children: [
395
+ /* @__PURE__ */ jsx2(
396
+ "input",
397
+ {
398
+ ref: fileInputRef,
399
+ type: "file",
400
+ multiple: true,
401
+ accept: "image/*",
402
+ onChange: handleFileChange,
403
+ style: { display: "none" }
404
+ }
405
+ ),
406
+ /* @__PURE__ */ jsxs2("div", { css: styles2.left, children: [
407
+ /* @__PURE__ */ jsx2(
408
+ ToolbarButton,
409
+ {
410
+ onClick: handleUpload,
411
+ icon: "upload",
412
+ label: uploading ? "Uploading..." : "Upload",
413
+ disabled: uploading
414
+ }
415
+ ),
416
+ /* @__PURE__ */ jsx2(
417
+ ToolbarButton,
418
+ {
419
+ onClick: handleReprocess,
420
+ icon: "refresh",
421
+ label: "Reprocess",
422
+ disabled: !hasSelection
423
+ }
424
+ ),
425
+ /* @__PURE__ */ jsx2(
426
+ ToolbarButton,
427
+ {
428
+ onClick: handleDeleteClick,
429
+ icon: "trash",
430
+ label: "Delete",
431
+ disabled: !hasSelection,
432
+ variant: "danger"
433
+ }
434
+ ),
435
+ /* @__PURE__ */ jsx2(
436
+ ToolbarButton,
437
+ {
438
+ onClick: handleSyncCdn,
439
+ icon: "cloud",
440
+ label: "Sync CDN",
441
+ disabled: !hasSelection
442
+ }
443
+ ),
444
+ /* @__PURE__ */ jsx2(ToolbarButton, { onClick: handleScan, icon: "scan", label: "Scan" })
445
+ ] }),
446
+ /* @__PURE__ */ jsxs2("div", { css: styles2.right, children: [
447
+ hasSelection && /* @__PURE__ */ jsxs2("span", { css: styles2.selectionCount, children: [
448
+ selectedItems.size,
449
+ " selected",
450
+ /* @__PURE__ */ jsx2("button", { css: styles2.clearBtn, onClick: clearSelection, children: "Clear" })
451
+ ] }),
452
+ /* @__PURE__ */ jsxs2("div", { css: styles2.viewToggle, children: [
453
+ /* @__PURE__ */ jsx2(
454
+ "button",
455
+ {
456
+ css: [styles2.viewBtn, viewMode === "grid" && styles2.viewBtnActive],
457
+ onClick: () => setViewMode("grid"),
458
+ "aria-label": "Grid view",
459
+ children: /* @__PURE__ */ jsx2(GridIcon, {})
460
+ }
461
+ ),
462
+ /* @__PURE__ */ jsx2(
463
+ "button",
464
+ {
465
+ css: [styles2.viewBtn, viewMode === "list" && styles2.viewBtnActive],
466
+ onClick: () => setViewMode("list"),
467
+ "aria-label": "List view",
468
+ children: /* @__PURE__ */ jsx2(ListIcon, {})
469
+ }
470
+ )
471
+ ] })
472
+ ] })
473
+ ] })
474
+ ] });
475
+ }
476
+ function ToolbarButton({
477
+ onClick,
478
+ icon,
479
+ label,
480
+ disabled,
481
+ variant = "default"
482
+ }) {
483
+ return /* @__PURE__ */ jsxs2(
484
+ "button",
485
+ {
486
+ css: [styles2.btn, variant === "danger" ? styles2.btnDanger : styles2.btnDefault],
487
+ onClick,
488
+ disabled,
489
+ children: [
490
+ /* @__PURE__ */ jsx2(IconComponent, { icon }),
491
+ label
492
+ ]
493
+ }
494
+ );
495
+ }
496
+ function IconComponent({ icon }) {
497
+ switch (icon) {
498
+ case "upload":
499
+ return /* @__PURE__ */ jsx2("svg", { css: styles2.icon, fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ jsx2("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" }) });
500
+ case "refresh":
501
+ return /* @__PURE__ */ jsx2("svg", { css: styles2.icon, fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ jsx2("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" }) });
502
+ case "trash":
503
+ return /* @__PURE__ */ jsx2("svg", { css: styles2.icon, fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ jsx2("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" }) });
504
+ case "cloud":
505
+ return /* @__PURE__ */ jsx2("svg", { css: styles2.icon, fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ jsx2("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" }) });
506
+ case "scan":
507
+ return /* @__PURE__ */ jsx2("svg", { css: styles2.icon, fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ jsx2("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" }) });
508
+ default:
509
+ return null;
510
+ }
511
+ }
512
+ function GridIcon() {
513
+ return /* @__PURE__ */ jsx2("svg", { css: styles2.icon, fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ jsx2("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" }) });
514
+ }
515
+ function ListIcon() {
516
+ return /* @__PURE__ */ jsx2("svg", { css: styles2.icon, fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ jsx2("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M4 6h16M4 10h16M4 14h16M4 18h16" }) });
517
+ }
518
+
519
+ // src/components/StudioBreadcrumb.tsx
520
+ import { css as css3 } from "@emotion/react";
521
+ import { jsx as jsx3, jsxs as jsxs3 } from "@emotion/react/jsx-runtime";
522
+ var styles3 = {
523
+ container: css3`
524
+ display: flex;
525
+ align-items: center;
526
+ gap: 8px;
527
+ padding: 8px 24px;
528
+ background-color: white;
529
+ border-bottom: 1px solid #f3f4f6;
530
+ `,
531
+ backBtn: css3`
532
+ padding: 4px;
533
+ background: none;
534
+ border: none;
535
+ border-radius: 4px;
536
+ cursor: pointer;
537
+ transition: background-color 0.15s;
538
+
539
+ &:hover {
540
+ background-color: #f3f4f6;
541
+ }
542
+ `,
543
+ backIcon: css3`
544
+ width: 16px;
545
+ height: 16px;
546
+ color: #6b7280;
547
+ `,
548
+ nav: css3`
549
+ display: flex;
550
+ align-items: center;
551
+ gap: 4px;
552
+ font-size: 14px;
553
+ `,
554
+ item: css3`
555
+ display: flex;
556
+ align-items: center;
557
+ gap: 4px;
558
+ `,
559
+ separator: css3`
560
+ color: #d1d5db;
561
+ `,
562
+ btn: css3`
563
+ padding: 2px 4px;
564
+ background: none;
565
+ border: none;
566
+ border-radius: 4px;
567
+ cursor: pointer;
568
+ transition: all 0.15s;
569
+
570
+ &:hover {
571
+ background-color: #f3f4f6;
572
+ }
573
+ `,
574
+ btnActive: css3`
575
+ color: #111827;
576
+ font-weight: 500;
577
+ `,
578
+ btnInactive: css3`
579
+ color: #6b7280;
580
+
581
+ &:hover {
582
+ color: #374151;
583
+ }
584
+ `
585
+ };
586
+ function StudioBreadcrumb() {
587
+ const { currentPath, setCurrentPath, navigateUp } = useStudio();
588
+ const parts = currentPath.split("/").filter(Boolean);
589
+ const handleClick = (index) => {
590
+ const newPath = parts.slice(0, index + 1).join("/");
591
+ setCurrentPath(newPath);
592
+ };
593
+ return /* @__PURE__ */ jsxs3("div", { css: styles3.container, children: [
594
+ currentPath !== "public" && /* @__PURE__ */ jsx3("button", { css: styles3.backBtn, onClick: navigateUp, "aria-label": "Go back", children: /* @__PURE__ */ jsx3("svg", { css: styles3.backIcon, fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ jsx3("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M15 19l-7-7 7-7" }) }) }),
595
+ /* @__PURE__ */ jsx3("nav", { css: styles3.nav, children: parts.map((part, index) => /* @__PURE__ */ jsxs3("span", { css: styles3.item, children: [
596
+ index > 0 && /* @__PURE__ */ jsx3("span", { css: styles3.separator, children: "/" }),
597
+ /* @__PURE__ */ jsx3(
598
+ "button",
599
+ {
600
+ css: [styles3.btn, index === parts.length - 1 ? styles3.btnActive : styles3.btnInactive],
601
+ onClick: () => handleClick(index),
602
+ children: part
603
+ }
604
+ )
605
+ ] }, index)) })
606
+ ] });
607
+ }
608
+
609
+ // src/components/StudioFileGrid.tsx
610
+ import { useEffect, useState as useState2 } from "react";
611
+ import { css as css4, keyframes as keyframes2 } from "@emotion/react";
612
+ import { jsx as jsx4, jsxs as jsxs4 } from "@emotion/react/jsx-runtime";
613
+ var spin = keyframes2`
614
+ to { transform: rotate(360deg); }
615
+ `;
616
+ var styles4 = {
617
+ loading: css4`
618
+ display: flex;
619
+ align-items: center;
620
+ justify-content: center;
621
+ height: 256px;
622
+ `,
623
+ spinner: css4`
624
+ width: 32px;
625
+ height: 32px;
626
+ border-radius: 50%;
627
+ border: 2px solid transparent;
628
+ border-bottom-color: #9333ea;
629
+ animation: ${spin} 1s linear infinite;
630
+ `,
631
+ empty: css4`
632
+ display: flex;
633
+ flex-direction: column;
634
+ align-items: center;
635
+ justify-content: center;
636
+ height: 256px;
637
+ color: #6b7280;
638
+ `,
639
+ emptyIcon: css4`
640
+ width: 48px;
641
+ height: 48px;
642
+ margin-bottom: 16px;
643
+ `,
644
+ emptyText: css4`
645
+ font-size: 14px;
646
+ margin: 0;
647
+ `,
648
+ grid: css4`
649
+ display: grid;
650
+ grid-template-columns: repeat(2, 1fr);
651
+ gap: 16px;
652
+
653
+ @media (min-width: 640px) { grid-template-columns: repeat(3, 1fr); }
654
+ @media (min-width: 768px) { grid-template-columns: repeat(4, 1fr); }
655
+ @media (min-width: 1024px) { grid-template-columns: repeat(5, 1fr); }
656
+ @media (min-width: 1280px) { grid-template-columns: repeat(6, 1fr); }
657
+ `,
658
+ item: css4`
659
+ position: relative;
660
+ border-radius: 8px;
661
+ border: 2px solid transparent;
662
+ overflow: hidden;
663
+ cursor: pointer;
664
+ transition: all 0.15s;
665
+ background-color: #f9fafb;
666
+
667
+ &:hover {
668
+ border-color: #e5e7eb;
669
+ }
670
+ `,
671
+ itemSelected: css4`
672
+ border-color: #a855f7;
673
+ background-color: #faf5ff;
674
+ `,
675
+ checkbox: css4`
676
+ position: absolute;
677
+ top: 8px;
678
+ left: 8px;
679
+ z-index: 10;
680
+ width: 16px;
681
+ height: 16px;
682
+ accent-color: #9333ea;
683
+ `,
684
+ cdnBadge: css4`
685
+ position: absolute;
686
+ top: 8px;
687
+ right: 8px;
688
+ z-index: 10;
689
+ background-color: #dcfce7;
690
+ color: #15803d;
691
+ font-size: 12px;
692
+ padding: 2px 6px;
693
+ border-radius: 9999px;
694
+ `,
695
+ content: css4`
696
+ aspect-ratio: 1;
697
+ display: flex;
698
+ align-items: center;
699
+ justify-content: center;
700
+ padding: 16px;
701
+ `,
702
+ folderIcon: css4`
703
+ width: 64px;
704
+ height: 64px;
705
+ color: #facc15;
706
+ `,
707
+ image: css4`
708
+ max-width: 100%;
709
+ max-height: 100%;
710
+ object-fit: contain;
711
+ border-radius: 4px;
712
+ `,
713
+ label: css4`
714
+ padding: 6px 8px;
715
+ background-color: white;
716
+ border-top: 1px solid #e5e7eb;
717
+ `,
718
+ name: css4`
719
+ font-size: 12px;
720
+ color: #374151;
721
+ white-space: nowrap;
722
+ overflow: hidden;
723
+ text-overflow: ellipsis;
724
+ margin: 0;
725
+ `,
726
+ size: css4`
727
+ font-size: 12px;
728
+ color: #9ca3af;
729
+ margin: 0;
730
+ `,
731
+ selectAllRow: css4`
732
+ display: flex;
733
+ align-items: center;
734
+ margin-bottom: 12px;
735
+ padding-bottom: 12px;
736
+ border-bottom: 1px solid #e5e7eb;
737
+ `,
738
+ selectAllLabel: css4`
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: css4`
751
+ width: 16px;
752
+ height: 16px;
753
+ accent-color: #9333ea;
754
+ `
755
+ };
756
+ function StudioFileGrid() {
757
+ const { currentPath, setCurrentPath, selectedItems, toggleSelection, selectRange, lastSelectedPath, selectAll, clearSelection, refreshKey } = useStudio();
758
+ const [items, setItems] = useState2([]);
759
+ const [loading, setLoading] = useState2(true);
760
+ useEffect(() => {
761
+ async function loadItems() {
762
+ setLoading(true);
763
+ try {
764
+ const response = await fetch(`/api/studio/list?path=${encodeURIComponent(currentPath)}`);
765
+ if (response.ok) {
766
+ const data = await response.json();
767
+ setItems(data.items || []);
768
+ }
769
+ } catch (error) {
770
+ console.error("Failed to load items:", error);
771
+ }
772
+ setLoading(false);
773
+ }
774
+ loadItems();
775
+ }, [currentPath, refreshKey]);
776
+ if (loading) {
777
+ return /* @__PURE__ */ jsx4("div", { css: styles4.loading, children: /* @__PURE__ */ jsx4("div", { css: styles4.spinner }) });
778
+ }
779
+ if (items.length === 0) {
780
+ return /* @__PURE__ */ jsxs4("div", { css: styles4.empty, children: [
781
+ /* @__PURE__ */ jsx4("svg", { css: styles4.emptyIcon, fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ jsx4("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__ */ jsx4("p", { css: styles4.emptyText, children: "No files in this folder" }),
783
+ /* @__PURE__ */ jsx4("p", { css: styles4.emptyText, children: "Upload images to get started" })
784
+ ] });
785
+ }
786
+ const sortedItems = [...items].sort((a, b) => {
787
+ if (a.type === "folder" && b.type !== "folder") return -1;
788
+ if (a.type !== "folder" && b.type === "folder") return 1;
789
+ return a.name.localeCompare(b.name);
790
+ });
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);
808
+ } else {
809
+ toggleSelection(item.path);
810
+ }
811
+ };
812
+ return /* @__PURE__ */ jsxs4("div", { children: [
813
+ files.length > 0 && /* @__PURE__ */ jsx4("div", { css: styles4.selectAllRow, children: /* @__PURE__ */ jsxs4("label", { css: styles4.selectAllLabel, children: [
814
+ /* @__PURE__ */ jsx4(
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__ */ jsx4("div", { css: styles4.grid, children: sortedItems.map((item) => /* @__PURE__ */ jsx4(
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__ */ jsxs4("div", { css: [styles4.item, isSelected && styles4.itemSelected], onClick, children: [
844
+ !isFolder && /* @__PURE__ */ jsx4(
845
+ "input",
846
+ {
847
+ type: "checkbox",
848
+ css: styles4.checkbox,
849
+ checked: isSelected,
850
+ onChange: () => {
851
+ },
852
+ onClick: (e) => e.stopPropagation()
853
+ }
854
+ ),
855
+ item.cdnSynced && /* @__PURE__ */ jsx4("span", { css: styles4.cdnBadge, children: "CDN" }),
856
+ /* @__PURE__ */ jsx4("div", { css: styles4.content, children: isFolder ? /* @__PURE__ */ jsx4("svg", { css: styles4.folderIcon, fill: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ jsx4("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__ */ jsx4(
857
+ "img",
858
+ {
859
+ css: styles4.image,
860
+ src: item.path.replace("public", ""),
861
+ alt: item.name,
862
+ loading: "lazy"
863
+ }
864
+ ) }),
865
+ /* @__PURE__ */ jsxs4("div", { css: styles4.label, children: [
866
+ /* @__PURE__ */ jsx4("p", { css: styles4.name, title: item.name, children: item.name }),
867
+ item.size && /* @__PURE__ */ jsx4("p", { css: styles4.size, children: formatFileSize(item.size) })
868
+ ] })
869
+ ] });
870
+ }
871
+ function formatFileSize(bytes) {
872
+ if (bytes < 1024) return `${bytes} B`;
873
+ if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
874
+ return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
875
+ }
876
+
877
+ // src/components/StudioFileList.tsx
878
+ import { useEffect as useEffect2, useState as useState3 } from "react";
879
+ import { css as css5, keyframes as keyframes3 } from "@emotion/react";
880
+ import { jsx as jsx5, jsxs as jsxs5 } from "@emotion/react/jsx-runtime";
881
+ var spin2 = keyframes3`
882
+ to { transform: rotate(360deg); }
883
+ `;
884
+ var styles5 = {
885
+ loading: css5`
886
+ display: flex;
887
+ align-items: center;
888
+ justify-content: center;
889
+ height: 256px;
890
+ `,
891
+ spinner: css5`
892
+ width: 32px;
893
+ height: 32px;
894
+ border-radius: 50%;
895
+ border: 2px solid transparent;
896
+ border-bottom-color: #9333ea;
897
+ animation: ${spin2} 1s linear infinite;
898
+ `,
899
+ empty: css5`
900
+ display: flex;
901
+ flex-direction: column;
902
+ align-items: center;
903
+ justify-content: center;
904
+ height: 256px;
905
+ color: #6b7280;
906
+ `,
907
+ table: css5`
908
+ width: 100%;
909
+ border-collapse: collapse;
910
+ `,
911
+ th: css5`
912
+ text-align: left;
913
+ font-size: 12px;
914
+ color: #6b7280;
915
+ text-transform: uppercase;
916
+ letter-spacing: 0.05em;
917
+ padding-bottom: 8px;
918
+ font-weight: normal;
919
+ `,
920
+ thCheckbox: css5`
921
+ width: 32px;
922
+ `,
923
+ thSize: css5`
924
+ width: 96px;
925
+ `,
926
+ thDimensions: css5`
927
+ width: 128px;
928
+ `,
929
+ thCdn: css5`
930
+ width: 96px;
931
+ `,
932
+ tbody: css5`
933
+ border-top: 1px solid #f3f4f6;
934
+ `,
935
+ row: css5`
936
+ cursor: pointer;
937
+ transition: background-color 0.15s;
938
+
939
+ &:hover {
940
+ background-color: #f9fafb;
941
+ }
942
+ `,
943
+ rowSelected: css5`
944
+ background-color: #faf5ff;
945
+ `,
946
+ td: css5`
947
+ padding: 8px 0;
948
+ border-bottom: 1px solid #f3f4f6;
949
+ `,
950
+ checkbox: css5`
951
+ width: 16px;
952
+ height: 16px;
953
+ accent-color: #9333ea;
954
+ `,
955
+ nameCell: css5`
956
+ display: flex;
957
+ align-items: center;
958
+ gap: 8px;
959
+ `,
960
+ folderIcon: css5`
961
+ width: 20px;
962
+ height: 20px;
963
+ color: #facc15;
964
+ `,
965
+ fileIcon: css5`
966
+ width: 20px;
967
+ height: 20px;
968
+ color: #9ca3af;
969
+ `,
970
+ name: css5`
971
+ font-size: 14px;
972
+ color: #111827;
973
+ `,
974
+ meta: css5`
975
+ font-size: 14px;
976
+ color: #6b7280;
977
+ `,
978
+ cdnBadge: css5`
979
+ display: inline-flex;
980
+ align-items: center;
981
+ gap: 4px;
982
+ font-size: 12px;
983
+ color: #15803d;
984
+ `,
985
+ cdnIcon: css5`
986
+ width: 12px;
987
+ height: 12px;
988
+ `,
989
+ cdnEmpty: css5`
990
+ font-size: 12px;
991
+ color: #9ca3af;
992
+ `
993
+ };
994
+ function StudioFileList() {
995
+ const { currentPath, setCurrentPath, selectedItems, toggleSelection, selectRange, lastSelectedPath, selectAll, clearSelection, refreshKey } = useStudio();
996
+ const [items, setItems] = useState3([]);
997
+ const [loading, setLoading] = useState3(true);
998
+ useEffect2(() => {
999
+ async function loadItems() {
1000
+ setLoading(true);
1001
+ try {
1002
+ const response = await fetch(`/api/studio/list?path=${encodeURIComponent(currentPath)}`);
1003
+ if (response.ok) {
1004
+ const data = await response.json();
1005
+ setItems(data.items || []);
1006
+ }
1007
+ } catch (error) {
1008
+ console.error("Failed to load items:", error);
1009
+ }
1010
+ setLoading(false);
1011
+ }
1012
+ loadItems();
1013
+ }, [currentPath, refreshKey]);
1014
+ if (loading) {
1015
+ return /* @__PURE__ */ jsx5("div", { css: styles5.loading, children: /* @__PURE__ */ jsx5("div", { css: styles5.spinner }) });
1016
+ }
1017
+ if (items.length === 0) {
1018
+ return /* @__PURE__ */ jsx5("div", { css: styles5.empty, children: /* @__PURE__ */ jsx5("p", { children: "No files in this folder" }) });
1019
+ }
1020
+ const sortedItems = [...items].sort((a, b) => {
1021
+ if (a.type === "folder" && b.type !== "folder") return -1;
1022
+ if (a.type !== "folder" && b.type === "folder") return 1;
1023
+ return a.name.localeCompare(b.name);
1024
+ });
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__ */ jsxs5("table", { css: styles5.table, children: [
1047
+ /* @__PURE__ */ jsx5("thead", { children: /* @__PURE__ */ jsxs5("tr", { children: [
1048
+ /* @__PURE__ */ jsx5("th", { css: [styles5.th, styles5.thCheckbox], children: files.length > 0 && /* @__PURE__ */ jsx5(
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__ */ jsx5("th", { css: styles5.th, children: "Name" }),
1061
+ /* @__PURE__ */ jsx5("th", { css: [styles5.th, styles5.thSize], children: "Size" }),
1062
+ /* @__PURE__ */ jsx5("th", { css: [styles5.th, styles5.thDimensions], children: "Dimensions" }),
1063
+ /* @__PURE__ */ jsx5("th", { css: [styles5.th, styles5.thCdn], children: "CDN" })
1064
+ ] }) }),
1065
+ /* @__PURE__ */ jsx5("tbody", { css: styles5.tbody, children: sortedItems.map((item) => /* @__PURE__ */ jsx5(
1066
+ ListRow,
1067
+ {
1068
+ item,
1069
+ isSelected: selectedItems.has(item.path),
1070
+ onClick: (e) => handleItemClick(item, e)
1071
+ },
1072
+ item.path
1073
+ )) })
1074
+ ] });
1075
+ }
1076
+ function ListRow({ item, isSelected, onClick }) {
1077
+ const isFolder = item.type === "folder";
1078
+ return /* @__PURE__ */ jsxs5("tr", { css: [styles5.row, isSelected && styles5.rowSelected], onClick, children: [
1079
+ /* @__PURE__ */ jsx5("td", { css: styles5.td, children: !isFolder && /* @__PURE__ */ jsx5(
1080
+ "input",
1081
+ {
1082
+ type: "checkbox",
1083
+ css: styles5.checkbox,
1084
+ checked: isSelected,
1085
+ onChange: () => {
1086
+ },
1087
+ onClick: (e) => e.stopPropagation()
1088
+ }
1089
+ ) }),
1090
+ /* @__PURE__ */ jsx5("td", { css: styles5.td, children: /* @__PURE__ */ jsxs5("div", { css: styles5.nameCell, children: [
1091
+ isFolder ? /* @__PURE__ */ jsx5("svg", { css: styles5.folderIcon, fill: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ jsx5("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__ */ jsx5("svg", { css: styles5.fileIcon, fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ jsx5("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__ */ jsx5("span", { css: styles5.name, children: item.name })
1093
+ ] }) }),
1094
+ /* @__PURE__ */ jsx5("td", { css: [styles5.td, styles5.meta], children: item.size ? formatFileSize2(item.size) : "--" }),
1095
+ /* @__PURE__ */ jsx5("td", { css: [styles5.td, styles5.meta], children: item.dimensions ? `${item.dimensions.width}x${item.dimensions.height}` : "--" }),
1096
+ /* @__PURE__ */ jsx5("td", { css: styles5.td, children: item.cdnSynced ? /* @__PURE__ */ jsxs5("span", { css: styles5.cdnBadge, children: [
1097
+ /* @__PURE__ */ jsx5("svg", { css: styles5.cdnIcon, fill: "currentColor", viewBox: "0 0 20 20", children: /* @__PURE__ */ jsx5("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" }) }),
1098
+ "Synced"
1099
+ ] }) : /* @__PURE__ */ jsx5("span", { css: styles5.cdnEmpty, children: "--" }) })
1100
+ ] });
1101
+ }
1102
+ function formatFileSize2(bytes) {
1103
+ if (bytes < 1024) return `${bytes} B`;
1104
+ if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
1105
+ return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
1106
+ }
1107
+
1108
+ // src/components/StudioPreview.tsx
1109
+ import { useState as useState4 } from "react";
1110
+ import { css as css6 } from "@emotion/react";
1111
+ import { Fragment as Fragment2, jsx as jsx6, jsxs as jsxs6 } from "@emotion/react/jsx-runtime";
1112
+ var styles6 = {
1113
+ panel: css6`
1114
+ width: 320px;
1115
+ border-left: 1px solid #e5e7eb;
1116
+ background-color: #f9fafb;
1117
+ padding: 16px;
1118
+ overflow: auto;
1119
+ `,
1120
+ title: css6`
1121
+ font-size: 14px;
1122
+ font-weight: 500;
1123
+ color: #111827;
1124
+ margin: 0 0 16px 0;
1125
+ `,
1126
+ imageContainer: css6`
1127
+ background-color: white;
1128
+ border-radius: 8px;
1129
+ border: 1px solid #e5e7eb;
1130
+ padding: 8px;
1131
+ margin-bottom: 16px;
1132
+ `,
1133
+ image: css6`
1134
+ width: 100%;
1135
+ height: auto;
1136
+ border-radius: 4px;
1137
+ `,
1138
+ info: css6`
1139
+ display: flex;
1140
+ flex-direction: column;
1141
+ gap: 12px;
1142
+ `,
1143
+ row: css6`
1144
+ display: flex;
1145
+ justify-content: space-between;
1146
+ font-size: 12px;
1147
+ `,
1148
+ label: css6`
1149
+ color: #6b7280;
1150
+ `,
1151
+ value: css6`
1152
+ color: #111827;
1153
+ `,
1154
+ valueTruncate: css6`
1155
+ max-width: 128px;
1156
+ white-space: nowrap;
1157
+ overflow: hidden;
1158
+ text-overflow: ellipsis;
1159
+ `,
1160
+ section: css6`
1161
+ padding-top: 8px;
1162
+ border-top: 1px solid #e5e7eb;
1163
+ `,
1164
+ sectionTitle: css6`
1165
+ font-size: 12px;
1166
+ font-weight: 500;
1167
+ color: #6b7280;
1168
+ margin: 0 0 8px 0;
1169
+ `,
1170
+ cdnStatus: css6`
1171
+ display: flex;
1172
+ align-items: center;
1173
+ gap: 8px;
1174
+ font-size: 12px;
1175
+ color: #16a34a;
1176
+ `,
1177
+ cdnIcon: css6`
1178
+ width: 16px;
1179
+ height: 16px;
1180
+ `,
1181
+ copyBtn: css6`
1182
+ margin-top: 8px;
1183
+ font-size: 12px;
1184
+ color: #9333ea;
1185
+ background: none;
1186
+ border: none;
1187
+ cursor: pointer;
1188
+ padding: 0;
1189
+
1190
+ &:hover {
1191
+ text-decoration: underline;
1192
+ }
1193
+ `,
1194
+ colorSwatch: css6`
1195
+ margin-top: 8px;
1196
+ height: 32px;
1197
+ border-radius: 4px;
1198
+ `,
1199
+ emptyState: css6`
1200
+ display: flex;
1201
+ align-items: center;
1202
+ justify-content: center;
1203
+ height: 200px;
1204
+ `,
1205
+ emptyText: css6`
1206
+ font-size: 14px;
1207
+ color: #9ca3af;
1208
+ margin: 0;
1209
+ `,
1210
+ actions: css6`
1211
+ margin-top: 16px;
1212
+ padding-top: 16px;
1213
+ border-top: 1px solid #e5e7eb;
1214
+ display: flex;
1215
+ flex-direction: column;
1216
+ gap: 8px;
1217
+ `,
1218
+ actionBtn: css6`
1219
+ width: 100%;
1220
+ padding: 8px 12px;
1221
+ font-size: 14px;
1222
+ background-color: white;
1223
+ border: 1px solid #e5e7eb;
1224
+ border-radius: 8px;
1225
+ cursor: pointer;
1226
+ transition: background-color 0.15s;
1227
+ color: #374151;
1228
+
1229
+ &:hover {
1230
+ background-color: #f9fafb;
1231
+ }
1232
+ `,
1233
+ actionBtnDanger: css6`
1234
+ color: #dc2626;
1235
+
1236
+ &:hover {
1237
+ background-color: #fef2f2;
1238
+ }
1239
+ `
1240
+ };
1241
+ function StudioPreview() {
1242
+ const { selectedItems, meta, triggerRefresh, clearSelection } = useStudio();
1243
+ const [showDeleteConfirm, setShowDeleteConfirm] = useState4(false);
1244
+ const [alertMessage, setAlertMessage] = useState4(null);
1245
+ const handleDeleteClick = () => {
1246
+ if (selectedItems.size === 0) return;
1247
+ setShowDeleteConfirm(true);
1248
+ };
1249
+ const handleDeleteConfirm = async () => {
1250
+ setShowDeleteConfirm(false);
1251
+ try {
1252
+ const response = await fetch("/api/studio/delete", {
1253
+ method: "POST",
1254
+ headers: { "Content-Type": "application/json" },
1255
+ body: JSON.stringify({ paths: Array.from(selectedItems) })
1256
+ });
1257
+ if (response.ok) {
1258
+ clearSelection();
1259
+ triggerRefresh();
1260
+ } else {
1261
+ const error = await response.json();
1262
+ setAlertMessage({
1263
+ title: "Delete Failed",
1264
+ message: error.error || "Unknown error"
1265
+ });
1266
+ }
1267
+ } catch (error) {
1268
+ console.error("Delete error:", error);
1269
+ setAlertMessage({
1270
+ title: "Delete Failed",
1271
+ message: "Delete failed. Check console for details."
1272
+ });
1273
+ }
1274
+ };
1275
+ const modals = /* @__PURE__ */ jsxs6(Fragment2, { children: [
1276
+ showDeleteConfirm && /* @__PURE__ */ jsx6(
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__ */ jsx6(
1288
+ AlertModal,
1289
+ {
1290
+ title: alertMessage.title,
1291
+ message: alertMessage.message,
1292
+ onClose: () => setAlertMessage(null)
1293
+ }
1294
+ )
1295
+ ] });
1296
+ if (selectedItems.size === 0) {
1297
+ return /* @__PURE__ */ jsxs6(Fragment2, { children: [
1298
+ modals,
1299
+ /* @__PURE__ */ jsxs6("div", { css: styles6.panel, children: [
1300
+ /* @__PURE__ */ jsx6("h3", { css: styles6.title, children: "Preview" }),
1301
+ /* @__PURE__ */ jsx6("div", { css: styles6.emptyState, children: /* @__PURE__ */ jsx6("p", { css: styles6.emptyText, children: "Select an image to preview" }) })
1302
+ ] })
1303
+ ] });
1304
+ }
1305
+ if (selectedItems.size > 1) {
1306
+ return /* @__PURE__ */ jsxs6(Fragment2, { children: [
1307
+ modals,
1308
+ /* @__PURE__ */ jsxs6("div", { css: styles6.panel, children: [
1309
+ /* @__PURE__ */ jsxs6("h3", { css: styles6.title, children: [
1310
+ selectedItems.size,
1311
+ " items selected"
1312
+ ] }),
1313
+ /* @__PURE__ */ jsx6("div", { css: styles6.actions, children: /* @__PURE__ */ jsxs6("button", { css: [styles6.actionBtn, styles6.actionBtnDanger], onClick: handleDeleteClick, children: [
1314
+ "Delete ",
1315
+ selectedItems.size,
1316
+ " items"
1317
+ ] }) })
1318
+ ] })
1319
+ ] });
1320
+ }
1321
+ const selectedPath = Array.from(selectedItems)[0];
1322
+ const imageKey = selectedPath.replace(/^public\/images\//, "").replace(/^public\/originals\//, "");
1323
+ const imageData = meta?.images?.[imageKey];
1324
+ return /* @__PURE__ */ jsxs6(Fragment2, { children: [
1325
+ modals,
1326
+ /* @__PURE__ */ jsxs6("div", { css: styles6.panel, children: [
1327
+ /* @__PURE__ */ jsx6("h3", { css: styles6.title, children: "Preview" }),
1328
+ /* @__PURE__ */ jsx6("div", { css: styles6.imageContainer, children: /* @__PURE__ */ jsx6(
1329
+ "img",
1330
+ {
1331
+ css: styles6.image,
1332
+ src: selectedPath.replace("public", ""),
1333
+ alt: "Preview"
1334
+ }
1335
+ ) }),
1336
+ /* @__PURE__ */ jsxs6("div", { css: styles6.info, children: [
1337
+ /* @__PURE__ */ jsx6(InfoRow, { label: "Filename", value: selectedPath.split("/").pop() || "" }),
1338
+ imageData && /* @__PURE__ */ jsxs6(Fragment2, { children: [
1339
+ /* @__PURE__ */ jsx6(
1340
+ InfoRow,
1341
+ {
1342
+ label: "Original",
1343
+ value: `${imageData.original.width}x${imageData.original.height}`
1344
+ }
1345
+ ),
1346
+ /* @__PURE__ */ jsx6(
1347
+ InfoRow,
1348
+ {
1349
+ label: "File size",
1350
+ value: formatFileSize3(imageData.original.fileSize)
1351
+ }
1352
+ ),
1353
+ /* @__PURE__ */ jsxs6("div", { css: styles6.section, children: [
1354
+ /* @__PURE__ */ jsx6("p", { css: styles6.sectionTitle, children: "Generated sizes" }),
1355
+ Object.entries(imageData.sizes).map(([size, data]) => /* @__PURE__ */ jsx6(InfoRow, { label: size, value: `${data.width}x${data.height}` }, size))
1356
+ ] }),
1357
+ imageData.cdn?.synced && /* @__PURE__ */ jsxs6("div", { css: styles6.section, children: [
1358
+ /* @__PURE__ */ jsx6("p", { css: styles6.sectionTitle, children: "CDN" }),
1359
+ /* @__PURE__ */ jsxs6("div", { css: styles6.cdnStatus, children: [
1360
+ /* @__PURE__ */ jsx6("svg", { css: styles6.cdnIcon, fill: "currentColor", viewBox: "0 0 20 20", children: /* @__PURE__ */ jsx6("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__ */ jsx6(
1364
+ "button",
1365
+ {
1366
+ css: styles6.copyBtn,
1367
+ onClick: () => {
1368
+ navigator.clipboard.writeText(`${imageData.cdn?.baseUrl}${imageData.sizes.full.path}`);
1369
+ },
1370
+ children: "Copy CDN URL"
1371
+ }
1372
+ )
1373
+ ] }),
1374
+ imageData.blurhash && /* @__PURE__ */ jsxs6("div", { css: styles6.section, children: [
1375
+ /* @__PURE__ */ jsx6(InfoRow, { label: "Blurhash", value: imageData.blurhash, truncate: true }),
1376
+ /* @__PURE__ */ jsx6(
1377
+ "div",
1378
+ {
1379
+ css: styles6.colorSwatch,
1380
+ style: { backgroundColor: imageData.dominantColor },
1381
+ title: `Dominant color: ${imageData.dominantColor}`
1382
+ }
1383
+ )
1384
+ ] })
1385
+ ] })
1386
+ ] }),
1387
+ /* @__PURE__ */ jsxs6("div", { css: styles6.actions, children: [
1388
+ /* @__PURE__ */ jsx6("button", { css: styles6.actionBtn, children: "Rename" }),
1389
+ /* @__PURE__ */ jsx6("button", { css: [styles6.actionBtn, styles6.actionBtnDanger], onClick: handleDeleteClick, children: "Delete" })
1390
+ ] })
1391
+ ] })
1392
+ ] });
1393
+ }
1394
+ function InfoRow({ label, value, truncate }) {
1395
+ return /* @__PURE__ */ jsxs6("div", { css: styles6.row, children: [
1396
+ /* @__PURE__ */ jsx6("span", { css: styles6.label, children: label }),
1397
+ /* @__PURE__ */ jsx6("span", { css: [styles6.value, truncate && styles6.valueTruncate], title: truncate ? value : void 0, children: value })
1398
+ ] });
1399
+ }
1400
+ function formatFileSize3(bytes) {
1401
+ if (bytes < 1024) return `${bytes} B`;
1402
+ if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
1403
+ return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
1404
+ }
1405
+
1406
+ // src/components/StudioSettings.tsx
1407
+ import { useState as useState5 } from "react";
1408
+ import { css as css7 } from "@emotion/react";
1409
+ import { Fragment as Fragment3, jsx as jsx7, jsxs as jsxs7 } from "@emotion/react/jsx-runtime";
1410
+ var styles7 = {
1411
+ btn: css7`
1412
+ padding: 8px;
1413
+ background: none;
1414
+ border: none;
1415
+ border-radius: 8px;
1416
+ cursor: pointer;
1417
+ transition: background-color 0.15s;
1418
+
1419
+ &:hover {
1420
+ background-color: #f3f4f6;
1421
+ }
1422
+ `,
1423
+ icon: css7`
1424
+ width: 20px;
1425
+ height: 20px;
1426
+ color: #6b7280;
1427
+ `,
1428
+ overlay: css7`
1429
+ position: fixed;
1430
+ top: 0;
1431
+ right: 0;
1432
+ bottom: 0;
1433
+ left: 0;
1434
+ z-index: 10000;
1435
+ display: flex;
1436
+ align-items: center;
1437
+ justify-content: center;
1438
+ `,
1439
+ backdrop: css7`
1440
+ position: absolute;
1441
+ top: 0;
1442
+ right: 0;
1443
+ bottom: 0;
1444
+ left: 0;
1445
+ background-color: rgba(0, 0, 0, 0.3);
1446
+ `,
1447
+ panel: css7`
1448
+ position: relative;
1449
+ background-color: white;
1450
+ border-radius: 12px;
1451
+ box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.25);
1452
+ width: 100%;
1453
+ max-width: 512px;
1454
+ padding: 24px;
1455
+ `,
1456
+ header: css7`
1457
+ display: flex;
1458
+ align-items: center;
1459
+ justify-content: space-between;
1460
+ margin-bottom: 24px;
1461
+ `,
1462
+ title: css7`
1463
+ font-size: 18px;
1464
+ font-weight: 600;
1465
+ margin: 0;
1466
+ `,
1467
+ closeBtn: css7`
1468
+ padding: 4px;
1469
+ background: none;
1470
+ border: none;
1471
+ border-radius: 8px;
1472
+ cursor: pointer;
1473
+
1474
+ &:hover {
1475
+ background-color: #f3f4f6;
1476
+ }
1477
+ `,
1478
+ sections: css7`
1479
+ display: flex;
1480
+ flex-direction: column;
1481
+ gap: 24px;
1482
+ `,
1483
+ sectionTitle: css7`
1484
+ font-size: 14px;
1485
+ font-weight: 500;
1486
+ color: #111827;
1487
+ margin: 0 0 12px 0;
1488
+ `,
1489
+ description: css7`
1490
+ font-size: 12px;
1491
+ color: #6b7280;
1492
+ margin: 0 0 12px 0;
1493
+ `,
1494
+ code: css7`
1495
+ background-color: #f9fafb;
1496
+ border-radius: 8px;
1497
+ padding: 12px;
1498
+ font-family: monospace;
1499
+ font-size: 12px;
1500
+ color: #4b5563;
1501
+ `,
1502
+ codeLine: css7`
1503
+ margin: 0 0 4px 0;
1504
+
1505
+ &:last-child {
1506
+ margin: 0;
1507
+ }
1508
+ `,
1509
+ input: css7`
1510
+ width: 100%;
1511
+ padding: 8px 12px;
1512
+ border: 1px solid #e5e7eb;
1513
+ border-radius: 8px;
1514
+ font-size: 14px;
1515
+
1516
+ &:focus {
1517
+ outline: none;
1518
+ box-shadow: 0 0 0 2px #a855f7;
1519
+ }
1520
+ `,
1521
+ grid: css7`
1522
+ display: grid;
1523
+ grid-template-columns: repeat(3, 1fr);
1524
+ gap: 12px;
1525
+ `,
1526
+ label: css7`
1527
+ font-size: 12px;
1528
+ color: #6b7280;
1529
+ display: block;
1530
+ margin-bottom: 4px;
1531
+ `,
1532
+ footer: css7`
1533
+ margin-top: 24px;
1534
+ display: flex;
1535
+ justify-content: flex-end;
1536
+ gap: 12px;
1537
+ `,
1538
+ cancelBtn: css7`
1539
+ padding: 8px 16px;
1540
+ font-size: 14px;
1541
+ color: #4b5563;
1542
+ background: none;
1543
+ border: none;
1544
+ border-radius: 8px;
1545
+ cursor: pointer;
1546
+
1547
+ &:hover {
1548
+ background-color: #f3f4f6;
1549
+ }
1550
+ `,
1551
+ saveBtn: css7`
1552
+ padding: 8px 16px;
1553
+ font-size: 14px;
1554
+ color: white;
1555
+ background-color: #9333ea;
1556
+ border: none;
1557
+ border-radius: 8px;
1558
+ cursor: pointer;
1559
+
1560
+ &:hover {
1561
+ background-color: #7c3aed;
1562
+ }
1563
+ `
1564
+ };
1565
+ function StudioSettings() {
1566
+ const [isOpen, setIsOpen] = useState5(false);
1567
+ return /* @__PURE__ */ jsxs7(Fragment3, { children: [
1568
+ /* @__PURE__ */ jsx7("button", { css: styles7.btn, onClick: () => setIsOpen(true), "aria-label": "Settings", children: /* @__PURE__ */ jsxs7(
1569
+ "svg",
1570
+ {
1571
+ css: styles7.icon,
1572
+ xmlns: "http://www.w3.org/2000/svg",
1573
+ viewBox: "0 0 24 24",
1574
+ fill: "none",
1575
+ stroke: "currentColor",
1576
+ strokeWidth: 2,
1577
+ strokeLinecap: "round",
1578
+ strokeLinejoin: "round",
1579
+ children: [
1580
+ /* @__PURE__ */ jsx7("circle", { cx: "12", cy: "12", r: "3" }),
1581
+ /* @__PURE__ */ jsx7("path", { d: "M19.4 15a1.65 1.65 0 00.33 1.82l.06.06a2 2 0 010 2.83 2 2 0 01-2.83 0l-.06-.06a1.65 1.65 0 00-1.82-.33 1.65 1.65 0 00-1 1.51V21a2 2 0 01-2 2 2 2 0 01-2-2v-.09A1.65 1.65 0 009 19.4a1.65 1.65 0 00-1.82.33l-.06.06a2 2 0 01-2.83 0 2 2 0 010-2.83l.06-.06a1.65 1.65 0 00.33-1.82 1.65 1.65 0 00-1.51-1H3a2 2 0 01-2-2 2 2 0 012-2h.09A1.65 1.65 0 004.6 9a1.65 1.65 0 00-.33-1.82l-.06-.06a2 2 0 010-2.83 2 2 0 012.83 0l.06.06a1.65 1.65 0 001.82.33H9a1.65 1.65 0 001-1.51V3a2 2 0 012-2 2 2 0 012 2v.09a1.65 1.65 0 001 1.51 1.65 1.65 0 001.82-.33l.06-.06a2 2 0 012.83 0 2 2 0 010 2.83l-.06.06a1.65 1.65 0 00-.33 1.82V9a1.65 1.65 0 001.51 1H21a2 2 0 012 2 2 2 0 01-2 2h-.09a1.65 1.65 0 00-1.51 1z" })
1582
+ ]
1583
+ }
1584
+ ) }),
1585
+ isOpen && /* @__PURE__ */ jsx7(SettingsPanel, { onClose: () => setIsOpen(false) })
1586
+ ] });
1587
+ }
1588
+ function SettingsPanel({ onClose }) {
1589
+ return /* @__PURE__ */ jsxs7("div", { css: styles7.overlay, children: [
1590
+ /* @__PURE__ */ jsx7("div", { css: styles7.backdrop, onClick: onClose }),
1591
+ /* @__PURE__ */ jsxs7("div", { css: styles7.panel, children: [
1592
+ /* @__PURE__ */ jsxs7("div", { css: styles7.header, children: [
1593
+ /* @__PURE__ */ jsx7("h2", { css: styles7.title, children: "Settings" }),
1594
+ /* @__PURE__ */ jsx7("button", { css: styles7.closeBtn, onClick: onClose, children: /* @__PURE__ */ jsx7("svg", { css: styles7.icon, fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ jsx7("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M6 18L18 6M6 6l12 12" }) }) })
1595
+ ] }),
1596
+ /* @__PURE__ */ jsxs7("div", { css: styles7.sections, children: [
1597
+ /* @__PURE__ */ jsxs7("section", { children: [
1598
+ /* @__PURE__ */ jsx7("h3", { css: styles7.sectionTitle, children: "Cloudflare R2" }),
1599
+ /* @__PURE__ */ jsx7("p", { css: styles7.description, children: "Configure in .env.local file:" }),
1600
+ /* @__PURE__ */ jsxs7("div", { css: styles7.code, children: [
1601
+ /* @__PURE__ */ jsx7("p", { css: styles7.codeLine, children: "CLOUDFLARE_R2_ACCOUNT_ID" }),
1602
+ /* @__PURE__ */ jsx7("p", { css: styles7.codeLine, children: "CLOUDFLARE_R2_ACCESS_KEY_ID" }),
1603
+ /* @__PURE__ */ jsx7("p", { css: styles7.codeLine, children: "CLOUDFLARE_R2_SECRET_ACCESS_KEY" }),
1604
+ /* @__PURE__ */ jsx7("p", { css: styles7.codeLine, children: "CLOUDFLARE_R2_BUCKET_NAME" }),
1605
+ /* @__PURE__ */ jsx7("p", { css: styles7.codeLine, children: "CLOUDFLARE_R2_PUBLIC_URL" })
1606
+ ] })
1607
+ ] }),
1608
+ /* @__PURE__ */ jsxs7("section", { children: [
1609
+ /* @__PURE__ */ jsx7("h3", { css: styles7.sectionTitle, children: "Custom CDN URL" }),
1610
+ /* @__PURE__ */ jsx7("p", { css: styles7.description, children: "Override the default R2 URL with a custom domain:" }),
1611
+ /* @__PURE__ */ jsx7("input", { css: styles7.input, type: "text", placeholder: "https://cdn.yourdomain.com" })
1612
+ ] }),
1613
+ /* @__PURE__ */ jsxs7("section", { children: [
1614
+ /* @__PURE__ */ jsx7("h3", { css: styles7.sectionTitle, children: "Thumbnail Sizes" }),
1615
+ /* @__PURE__ */ jsxs7("div", { css: styles7.grid, children: [
1616
+ /* @__PURE__ */ jsxs7("div", { children: [
1617
+ /* @__PURE__ */ jsx7("label", { css: styles7.label, children: "Small" }),
1618
+ /* @__PURE__ */ jsx7("input", { css: styles7.input, type: "number", defaultValue: 300 })
1619
+ ] }),
1620
+ /* @__PURE__ */ jsxs7("div", { children: [
1621
+ /* @__PURE__ */ jsx7("label", { css: styles7.label, children: "Medium" }),
1622
+ /* @__PURE__ */ jsx7("input", { css: styles7.input, type: "number", defaultValue: 700 })
1623
+ ] }),
1624
+ /* @__PURE__ */ jsxs7("div", { children: [
1625
+ /* @__PURE__ */ jsx7("label", { css: styles7.label, children: "Large" }),
1626
+ /* @__PURE__ */ jsx7("input", { css: styles7.input, type: "number", defaultValue: 1400 })
1627
+ ] })
1628
+ ] })
1629
+ ] })
1630
+ ] }),
1631
+ /* @__PURE__ */ jsxs7("div", { css: styles7.footer, children: [
1632
+ /* @__PURE__ */ jsx7("button", { css: styles7.cancelBtn, onClick: onClose, children: "Cancel" }),
1633
+ /* @__PURE__ */ jsx7("button", { css: styles7.saveBtn, children: "Save Changes" })
1634
+ ] })
1635
+ ] })
1636
+ ] });
1637
+ }
1638
+
1639
+ // src/components/StudioUI.tsx
1640
+ import { jsx as jsx8, jsxs as jsxs8 } from "@emotion/react/jsx-runtime";
1641
+ var styles8 = {
1642
+ container: css8`
1643
+ display: flex;
1644
+ flex-direction: column;
1645
+ height: 100%;
1646
+ font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
1647
+ `,
1648
+ header: css8`
1649
+ display: flex;
1650
+ align-items: center;
1651
+ justify-content: space-between;
1652
+ padding: 16px 24px;
1653
+ border-bottom: 1px solid #e5e7eb;
1654
+ `,
1655
+ title: css8`
1656
+ font-size: 20px;
1657
+ font-weight: 600;
1658
+ color: #111827;
1659
+ margin: 0;
1660
+ `,
1661
+ headerActions: css8`
1662
+ display: flex;
1663
+ align-items: center;
1664
+ gap: 8px;
1665
+ `,
1666
+ closeBtn: css8`
1667
+ padding: 8px;
1668
+ background: none;
1669
+ border: none;
1670
+ border-radius: 8px;
1671
+ cursor: pointer;
1672
+ transition: background-color 0.15s;
1673
+
1674
+ &:hover {
1675
+ background-color: #f3f4f6;
1676
+ }
1677
+ `,
1678
+ closeIcon: css8`
1679
+ width: 20px;
1680
+ height: 20px;
1681
+ color: #6b7280;
1682
+ `,
1683
+ content: css8`
1684
+ flex: 1;
1685
+ display: flex;
1686
+ overflow: hidden;
1687
+ `,
1688
+ fileBrowser: css8`
1689
+ flex: 1;
1690
+ min-width: 0;
1691
+ overflow: auto;
1692
+ padding: 16px;
1693
+ `
1694
+ };
1695
+ function StudioUI({ onClose }) {
1696
+ const [currentPath, setCurrentPathInternal] = useState6("public");
1697
+ const [selectedItems, setSelectedItems] = useState6(/* @__PURE__ */ new Set());
1698
+ const [lastSelectedPath, setLastSelectedPath] = useState6(null);
1699
+ const [viewMode, setViewMode] = useState6("grid");
1700
+ const [meta, setMeta] = useState6(null);
1701
+ const [isLoading, setIsLoading] = useState6(false);
1702
+ const [refreshKey, setRefreshKey] = useState6(0);
1703
+ const triggerRefresh = useCallback2(() => {
1704
+ setRefreshKey((k) => k + 1);
1705
+ }, []);
1706
+ const navigateUp = useCallback2(() => {
1707
+ if (currentPath === "public") return;
1708
+ const parts = currentPath.split("/");
1709
+ parts.pop();
1710
+ setCurrentPathInternal(parts.join("/") || "public");
1711
+ setSelectedItems(/* @__PURE__ */ new Set());
1712
+ }, [currentPath]);
1713
+ const setCurrentPath = useCallback2((path) => {
1714
+ setCurrentPathInternal(path);
1715
+ setSelectedItems(/* @__PURE__ */ new Set());
1716
+ }, []);
1717
+ const toggleSelection = useCallback2((path) => {
1718
+ setSelectedItems((prev) => {
1719
+ const next = new Set(prev);
1720
+ if (next.has(path)) {
1721
+ next.delete(path);
1722
+ } else {
1723
+ next.add(path);
1724
+ }
1725
+ return next;
1726
+ });
1727
+ setLastSelectedPath(path);
1728
+ }, []);
1729
+ const selectRange = useCallback2((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);
1744
+ }, []);
1745
+ const selectAll = useCallback2((items) => {
1746
+ setSelectedItems(new Set(items.map((item) => item.path)));
1747
+ }, []);
1748
+ const clearSelection = useCallback2(() => {
1749
+ setSelectedItems(/* @__PURE__ */ new Set());
1750
+ }, []);
1751
+ const handleKeyDown = useCallback2(
1752
+ (e) => {
1753
+ if (e.key === "Escape") {
1754
+ onClose();
1755
+ }
1756
+ },
1757
+ [onClose]
1758
+ );
1759
+ useEffect3(() => {
1760
+ document.addEventListener("keydown", handleKeyDown);
1761
+ document.body.style.overflow = "hidden";
1762
+ return () => {
1763
+ document.removeEventListener("keydown", handleKeyDown);
1764
+ document.body.style.overflow = "";
1765
+ };
1766
+ }, [handleKeyDown]);
1767
+ const contextValue = {
1768
+ isOpen: true,
1769
+ openStudio: () => {
1770
+ },
1771
+ closeStudio: onClose,
1772
+ toggleStudio: onClose,
1773
+ currentPath,
1774
+ setCurrentPath,
1775
+ navigateUp,
1776
+ selectedItems,
1777
+ toggleSelection,
1778
+ selectRange,
1779
+ selectAll,
1780
+ clearSelection,
1781
+ lastSelectedPath,
1782
+ viewMode,
1783
+ setViewMode,
1784
+ meta,
1785
+ setMeta,
1786
+ isLoading,
1787
+ setIsLoading,
1788
+ refreshKey,
1789
+ triggerRefresh
1790
+ };
1791
+ return /* @__PURE__ */ jsx8(StudioContext.Provider, { value: contextValue, children: /* @__PURE__ */ jsxs8("div", { css: styles8.container, children: [
1792
+ /* @__PURE__ */ jsxs8("div", { css: styles8.header, children: [
1793
+ /* @__PURE__ */ jsx8("h1", { css: styles8.title, children: "Studio" }),
1794
+ /* @__PURE__ */ jsxs8("div", { css: styles8.headerActions, children: [
1795
+ /* @__PURE__ */ jsx8(StudioSettings, {}),
1796
+ /* @__PURE__ */ jsx8(
1797
+ "button",
1798
+ {
1799
+ css: styles8.closeBtn,
1800
+ onClick: onClose,
1801
+ "aria-label": "Close Studio",
1802
+ children: /* @__PURE__ */ jsx8(CloseIcon, {})
1803
+ }
1804
+ )
1805
+ ] })
1806
+ ] }),
1807
+ /* @__PURE__ */ jsx8(StudioToolbar, {}),
1808
+ /* @__PURE__ */ jsx8(StudioBreadcrumb, {}),
1809
+ /* @__PURE__ */ jsxs8("div", { css: styles8.content, children: [
1810
+ /* @__PURE__ */ jsx8("div", { css: styles8.fileBrowser, children: viewMode === "grid" ? /* @__PURE__ */ jsx8(StudioFileGrid, {}) : /* @__PURE__ */ jsx8(StudioFileList, {}) }),
1811
+ /* @__PURE__ */ jsx8(StudioPreview, {})
1812
+ ] })
1813
+ ] }) });
1814
+ }
1815
+ function CloseIcon() {
1816
+ return /* @__PURE__ */ jsxs8(
1817
+ "svg",
1818
+ {
1819
+ css: styles8.closeIcon,
1820
+ xmlns: "http://www.w3.org/2000/svg",
1821
+ viewBox: "0 0 24 24",
1822
+ fill: "none",
1823
+ stroke: "currentColor",
1824
+ strokeWidth: 2,
1825
+ strokeLinecap: "round",
1826
+ strokeLinejoin: "round",
1827
+ children: [
1828
+ /* @__PURE__ */ jsx8("line", { x1: "18", y1: "6", x2: "6", y2: "18" }),
1829
+ /* @__PURE__ */ jsx8("line", { x1: "6", y1: "6", x2: "18", y2: "18" })
1830
+ ]
1831
+ }
1832
+ );
1833
+ }
1834
+ var StudioUI_default = StudioUI;
1835
+ export {
1836
+ StudioUI,
1837
+ StudioUI_default as default
1838
+ };
1839
+ //# sourceMappingURL=StudioUI-5I7VRE4F.mjs.map