@gallop.software/studio 0.1.0 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,1382 @@
1
+ "use client";
2
+
3
+ // src/components/StudioUI.tsx
4
+ import { useEffect as useEffect3, useCallback as useCallback2, useState as useState4 } from "react";
5
+ import { css as css7 } 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
+ selectAll: () => {
26
+ },
27
+ clearSelection: () => {
28
+ },
29
+ viewMode: "grid",
30
+ setViewMode: () => {
31
+ },
32
+ meta: null,
33
+ setMeta: () => {
34
+ },
35
+ isLoading: false,
36
+ setIsLoading: () => {
37
+ }
38
+ };
39
+ var StudioContext = createContext(defaultState);
40
+ function useStudio() {
41
+ return useContext(StudioContext);
42
+ }
43
+
44
+ // src/components/StudioToolbar.tsx
45
+ import { useCallback } from "react";
46
+ import { css } from "@emotion/react";
47
+ import { jsx, jsxs } from "@emotion/react/jsx-runtime";
48
+ var styles = {
49
+ toolbar: css`
50
+ display: flex;
51
+ align-items: center;
52
+ justify-content: space-between;
53
+ padding: 12px 24px;
54
+ background-color: #f9fafb;
55
+ border-bottom: 1px solid #e5e7eb;
56
+ `,
57
+ left: css`
58
+ display: flex;
59
+ align-items: center;
60
+ gap: 8px;
61
+ `,
62
+ right: css`
63
+ display: flex;
64
+ align-items: center;
65
+ gap: 16px;
66
+ `,
67
+ btn: css`
68
+ display: flex;
69
+ align-items: center;
70
+ gap: 8px;
71
+ padding: 8px 12px;
72
+ border-radius: 8px;
73
+ font-size: 14px;
74
+ font-weight: 500;
75
+ background: none;
76
+ border: none;
77
+ cursor: pointer;
78
+ transition: background-color 0.15s;
79
+
80
+ &:disabled {
81
+ cursor: not-allowed;
82
+ opacity: 0.5;
83
+ }
84
+ `,
85
+ btnDefault: css`
86
+ color: #374151;
87
+
88
+ &:hover:not(:disabled) {
89
+ background-color: white;
90
+ }
91
+ `,
92
+ btnDanger: css`
93
+ color: #dc2626;
94
+
95
+ &:hover:not(:disabled) {
96
+ background-color: #fef2f2;
97
+ }
98
+ `,
99
+ icon: css`
100
+ width: 16px;
101
+ height: 16px;
102
+ `,
103
+ selectionCount: css`
104
+ font-size: 14px;
105
+ color: #4b5563;
106
+ `,
107
+ clearBtn: css`
108
+ margin-left: 8px;
109
+ color: #9333ea;
110
+ background: none;
111
+ border: none;
112
+ cursor: pointer;
113
+ font-size: 14px;
114
+
115
+ &:hover {
116
+ text-decoration: underline;
117
+ }
118
+ `,
119
+ viewToggle: css`
120
+ display: flex;
121
+ align-items: center;
122
+ background-color: white;
123
+ border: 1px solid #e5e7eb;
124
+ border-radius: 8px;
125
+ overflow: hidden;
126
+ `,
127
+ viewBtn: css`
128
+ padding: 8px;
129
+ background: none;
130
+ border: none;
131
+ cursor: pointer;
132
+ color: #6b7280;
133
+ transition: all 0.15s;
134
+
135
+ &:hover {
136
+ background-color: #f9fafb;
137
+ }
138
+ `,
139
+ viewBtnActive: css`
140
+ background-color: #f3e8ff;
141
+ color: #7c3aed;
142
+ `
143
+ };
144
+ function StudioToolbar() {
145
+ const { selectedItems, viewMode, setViewMode, clearSelection } = useStudio();
146
+ const handleUpload = useCallback(() => {
147
+ console.log("Upload clicked");
148
+ }, []);
149
+ const handleReprocess = useCallback(() => {
150
+ console.log("Reprocess clicked", selectedItems);
151
+ }, [selectedItems]);
152
+ const handleDelete = useCallback(() => {
153
+ console.log("Delete clicked", selectedItems);
154
+ }, [selectedItems]);
155
+ const handleSyncCdn = useCallback(() => {
156
+ console.log("Sync CDN clicked", selectedItems);
157
+ }, [selectedItems]);
158
+ const handleScan = useCallback(() => {
159
+ console.log("Scan clicked");
160
+ }, []);
161
+ const hasSelection = selectedItems.size > 0;
162
+ return /* @__PURE__ */ jsxs("div", { css: styles.toolbar, children: [
163
+ /* @__PURE__ */ jsxs("div", { css: styles.left, children: [
164
+ /* @__PURE__ */ jsx(ToolbarButton, { onClick: handleUpload, icon: "upload", label: "Upload" }),
165
+ /* @__PURE__ */ jsx(
166
+ ToolbarButton,
167
+ {
168
+ onClick: handleReprocess,
169
+ icon: "refresh",
170
+ label: "Reprocess",
171
+ disabled: !hasSelection
172
+ }
173
+ ),
174
+ /* @__PURE__ */ jsx(
175
+ ToolbarButton,
176
+ {
177
+ onClick: handleDelete,
178
+ icon: "trash",
179
+ label: "Delete",
180
+ disabled: !hasSelection,
181
+ variant: "danger"
182
+ }
183
+ ),
184
+ /* @__PURE__ */ jsx(
185
+ ToolbarButton,
186
+ {
187
+ onClick: handleSyncCdn,
188
+ icon: "cloud",
189
+ label: "Sync CDN",
190
+ disabled: !hasSelection
191
+ }
192
+ ),
193
+ /* @__PURE__ */ jsx(ToolbarButton, { onClick: handleScan, icon: "scan", label: "Scan" })
194
+ ] }),
195
+ /* @__PURE__ */ jsxs("div", { css: styles.right, children: [
196
+ hasSelection && /* @__PURE__ */ jsxs("span", { css: styles.selectionCount, children: [
197
+ selectedItems.size,
198
+ " selected",
199
+ /* @__PURE__ */ jsx("button", { css: styles.clearBtn, onClick: clearSelection, children: "Clear" })
200
+ ] }),
201
+ /* @__PURE__ */ jsxs("div", { css: styles.viewToggle, children: [
202
+ /* @__PURE__ */ jsx(
203
+ "button",
204
+ {
205
+ css: [styles.viewBtn, viewMode === "grid" && styles.viewBtnActive],
206
+ onClick: () => setViewMode("grid"),
207
+ "aria-label": "Grid view",
208
+ children: /* @__PURE__ */ jsx(GridIcon, {})
209
+ }
210
+ ),
211
+ /* @__PURE__ */ jsx(
212
+ "button",
213
+ {
214
+ css: [styles.viewBtn, viewMode === "list" && styles.viewBtnActive],
215
+ onClick: () => setViewMode("list"),
216
+ "aria-label": "List view",
217
+ children: /* @__PURE__ */ jsx(ListIcon, {})
218
+ }
219
+ )
220
+ ] })
221
+ ] })
222
+ ] });
223
+ }
224
+ function ToolbarButton({
225
+ onClick,
226
+ icon,
227
+ label,
228
+ disabled,
229
+ variant = "default"
230
+ }) {
231
+ return /* @__PURE__ */ jsxs(
232
+ "button",
233
+ {
234
+ css: [styles.btn, variant === "danger" ? styles.btnDanger : styles.btnDefault],
235
+ onClick,
236
+ disabled,
237
+ children: [
238
+ /* @__PURE__ */ jsx(IconComponent, { icon }),
239
+ label
240
+ ]
241
+ }
242
+ );
243
+ }
244
+ function IconComponent({ icon }) {
245
+ switch (icon) {
246
+ case "upload":
247
+ return /* @__PURE__ */ jsx("svg", { css: styles.icon, fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ jsx("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" }) });
248
+ case "refresh":
249
+ return /* @__PURE__ */ jsx("svg", { css: styles.icon, fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ jsx("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" }) });
250
+ case "trash":
251
+ return /* @__PURE__ */ jsx("svg", { css: styles.icon, fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ jsx("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" }) });
252
+ case "cloud":
253
+ return /* @__PURE__ */ jsx("svg", { css: styles.icon, fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ jsx("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" }) });
254
+ case "scan":
255
+ return /* @__PURE__ */ jsx("svg", { css: styles.icon, fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" }) });
256
+ default:
257
+ return null;
258
+ }
259
+ }
260
+ function GridIcon() {
261
+ return /* @__PURE__ */ jsx("svg", { css: styles.icon, fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ jsx("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" }) });
262
+ }
263
+ function ListIcon() {
264
+ return /* @__PURE__ */ jsx("svg", { css: styles.icon, fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M4 6h16M4 10h16M4 14h16M4 18h16" }) });
265
+ }
266
+
267
+ // src/components/StudioBreadcrumb.tsx
268
+ import { css as css2 } from "@emotion/react";
269
+ import { jsx as jsx2, jsxs as jsxs2 } from "@emotion/react/jsx-runtime";
270
+ var styles2 = {
271
+ container: css2`
272
+ display: flex;
273
+ align-items: center;
274
+ gap: 8px;
275
+ padding: 8px 24px;
276
+ background-color: white;
277
+ border-bottom: 1px solid #f3f4f6;
278
+ `,
279
+ backBtn: css2`
280
+ padding: 4px;
281
+ background: none;
282
+ border: none;
283
+ border-radius: 4px;
284
+ cursor: pointer;
285
+ transition: background-color 0.15s;
286
+
287
+ &:hover {
288
+ background-color: #f3f4f6;
289
+ }
290
+ `,
291
+ backIcon: css2`
292
+ width: 16px;
293
+ height: 16px;
294
+ color: #6b7280;
295
+ `,
296
+ nav: css2`
297
+ display: flex;
298
+ align-items: center;
299
+ gap: 4px;
300
+ font-size: 14px;
301
+ `,
302
+ item: css2`
303
+ display: flex;
304
+ align-items: center;
305
+ gap: 4px;
306
+ `,
307
+ separator: css2`
308
+ color: #d1d5db;
309
+ `,
310
+ btn: css2`
311
+ padding: 2px 4px;
312
+ background: none;
313
+ border: none;
314
+ border-radius: 4px;
315
+ cursor: pointer;
316
+ transition: all 0.15s;
317
+
318
+ &:hover {
319
+ background-color: #f3f4f6;
320
+ }
321
+ `,
322
+ btnActive: css2`
323
+ color: #111827;
324
+ font-weight: 500;
325
+ `,
326
+ btnInactive: css2`
327
+ color: #6b7280;
328
+
329
+ &:hover {
330
+ color: #374151;
331
+ }
332
+ `
333
+ };
334
+ function StudioBreadcrumb() {
335
+ const { currentPath, setCurrentPath, navigateUp } = useStudio();
336
+ const parts = currentPath.split("/").filter(Boolean);
337
+ const handleClick = (index) => {
338
+ const newPath = parts.slice(0, index + 1).join("/");
339
+ setCurrentPath(newPath);
340
+ };
341
+ return /* @__PURE__ */ jsxs2("div", { css: styles2.container, children: [
342
+ currentPath !== "public" && /* @__PURE__ */ jsx2("button", { css: styles2.backBtn, onClick: navigateUp, "aria-label": "Go back", children: /* @__PURE__ */ jsx2("svg", { css: styles2.backIcon, fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ jsx2("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M15 19l-7-7 7-7" }) }) }),
343
+ /* @__PURE__ */ jsx2("nav", { css: styles2.nav, children: parts.map((part, index) => /* @__PURE__ */ jsxs2("span", { css: styles2.item, children: [
344
+ index > 0 && /* @__PURE__ */ jsx2("span", { css: styles2.separator, children: "/" }),
345
+ /* @__PURE__ */ jsx2(
346
+ "button",
347
+ {
348
+ css: [styles2.btn, index === parts.length - 1 ? styles2.btnActive : styles2.btnInactive],
349
+ onClick: () => handleClick(index),
350
+ children: part
351
+ }
352
+ )
353
+ ] }, index)) })
354
+ ] });
355
+ }
356
+
357
+ // src/components/StudioFileGrid.tsx
358
+ import { useEffect, useState } from "react";
359
+ import { css as css3, keyframes } from "@emotion/react";
360
+ import { jsx as jsx3, jsxs as jsxs3 } from "@emotion/react/jsx-runtime";
361
+ var spin = keyframes`
362
+ to { transform: rotate(360deg); }
363
+ `;
364
+ var styles3 = {
365
+ loading: css3`
366
+ display: flex;
367
+ align-items: center;
368
+ justify-content: center;
369
+ height: 256px;
370
+ `,
371
+ spinner: css3`
372
+ width: 32px;
373
+ height: 32px;
374
+ border-radius: 50%;
375
+ border: 2px solid transparent;
376
+ border-bottom-color: #9333ea;
377
+ animation: ${spin} 1s linear infinite;
378
+ `,
379
+ empty: css3`
380
+ display: flex;
381
+ flex-direction: column;
382
+ align-items: center;
383
+ justify-content: center;
384
+ height: 256px;
385
+ color: #6b7280;
386
+ `,
387
+ emptyIcon: css3`
388
+ width: 48px;
389
+ height: 48px;
390
+ margin-bottom: 16px;
391
+ `,
392
+ emptyText: css3`
393
+ font-size: 14px;
394
+ margin: 0;
395
+ `,
396
+ grid: css3`
397
+ display: grid;
398
+ grid-template-columns: repeat(2, 1fr);
399
+ gap: 16px;
400
+
401
+ @media (min-width: 640px) { grid-template-columns: repeat(3, 1fr); }
402
+ @media (min-width: 768px) { grid-template-columns: repeat(4, 1fr); }
403
+ @media (min-width: 1024px) { grid-template-columns: repeat(5, 1fr); }
404
+ @media (min-width: 1280px) { grid-template-columns: repeat(6, 1fr); }
405
+ `,
406
+ item: css3`
407
+ position: relative;
408
+ border-radius: 8px;
409
+ border: 2px solid transparent;
410
+ overflow: hidden;
411
+ cursor: pointer;
412
+ transition: all 0.15s;
413
+ background-color: #f9fafb;
414
+
415
+ &:hover {
416
+ border-color: #e5e7eb;
417
+ }
418
+ `,
419
+ itemSelected: css3`
420
+ border-color: #a855f7;
421
+ background-color: #faf5ff;
422
+ `,
423
+ checkbox: css3`
424
+ position: absolute;
425
+ top: 8px;
426
+ left: 8px;
427
+ z-index: 10;
428
+ width: 16px;
429
+ height: 16px;
430
+ accent-color: #9333ea;
431
+ `,
432
+ cdnBadge: css3`
433
+ position: absolute;
434
+ top: 8px;
435
+ right: 8px;
436
+ z-index: 10;
437
+ background-color: #dcfce7;
438
+ color: #15803d;
439
+ font-size: 12px;
440
+ padding: 2px 6px;
441
+ border-radius: 9999px;
442
+ `,
443
+ content: css3`
444
+ aspect-ratio: 1;
445
+ display: flex;
446
+ align-items: center;
447
+ justify-content: center;
448
+ padding: 16px;
449
+ `,
450
+ folderIcon: css3`
451
+ width: 64px;
452
+ height: 64px;
453
+ color: #facc15;
454
+ `,
455
+ image: css3`
456
+ max-width: 100%;
457
+ max-height: 100%;
458
+ object-fit: contain;
459
+ border-radius: 4px;
460
+ `,
461
+ label: css3`
462
+ padding: 6px 8px;
463
+ background-color: white;
464
+ border-top: 1px solid #e5e7eb;
465
+ `,
466
+ name: css3`
467
+ font-size: 12px;
468
+ color: #374151;
469
+ white-space: nowrap;
470
+ overflow: hidden;
471
+ text-overflow: ellipsis;
472
+ margin: 0;
473
+ `,
474
+ size: css3`
475
+ font-size: 12px;
476
+ color: #9ca3af;
477
+ margin: 0;
478
+ `
479
+ };
480
+ function StudioFileGrid() {
481
+ const { currentPath, setCurrentPath, selectedItems, toggleSelection } = useStudio();
482
+ const [items, setItems] = useState([]);
483
+ const [loading, setLoading] = useState(true);
484
+ useEffect(() => {
485
+ async function loadItems() {
486
+ setLoading(true);
487
+ try {
488
+ const response = await fetch(`/api/studio/list?path=${encodeURIComponent(currentPath)}`);
489
+ if (response.ok) {
490
+ const data = await response.json();
491
+ setItems(data.items || []);
492
+ }
493
+ } catch (error) {
494
+ console.error("Failed to load items:", error);
495
+ }
496
+ setLoading(false);
497
+ }
498
+ loadItems();
499
+ }, [currentPath]);
500
+ if (loading) {
501
+ return /* @__PURE__ */ jsx3("div", { css: styles3.loading, children: /* @__PURE__ */ jsx3("div", { css: styles3.spinner }) });
502
+ }
503
+ if (items.length === 0) {
504
+ return /* @__PURE__ */ jsxs3("div", { css: styles3.empty, children: [
505
+ /* @__PURE__ */ jsx3("svg", { css: styles3.emptyIcon, fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ jsx3("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" }) }),
506
+ /* @__PURE__ */ jsx3("p", { css: styles3.emptyText, children: "No files in this folder" }),
507
+ /* @__PURE__ */ jsx3("p", { css: styles3.emptyText, children: "Upload images to get started" })
508
+ ] });
509
+ }
510
+ const sortedItems = [...items].sort((a, b) => {
511
+ if (a.type === "folder" && b.type !== "folder") return -1;
512
+ if (a.type !== "folder" && b.type === "folder") return 1;
513
+ return a.name.localeCompare(b.name);
514
+ });
515
+ return /* @__PURE__ */ jsx3("div", { css: styles3.grid, children: sortedItems.map((item) => /* @__PURE__ */ jsx3(
516
+ GridItem,
517
+ {
518
+ item,
519
+ isSelected: selectedItems.has(item.path),
520
+ onSelect: () => toggleSelection(item.path),
521
+ onOpen: () => {
522
+ if (item.type === "folder") {
523
+ setCurrentPath(item.path);
524
+ }
525
+ }
526
+ },
527
+ item.path
528
+ )) });
529
+ }
530
+ function GridItem({ item, isSelected, onSelect, onOpen }) {
531
+ const isFolder = item.type === "folder";
532
+ return /* @__PURE__ */ jsxs3("div", { css: [styles3.item, isSelected && styles3.itemSelected], onDoubleClick: onOpen, children: [
533
+ /* @__PURE__ */ jsx3(
534
+ "input",
535
+ {
536
+ type: "checkbox",
537
+ css: styles3.checkbox,
538
+ checked: isSelected,
539
+ onChange: onSelect,
540
+ onClick: (e) => e.stopPropagation()
541
+ }
542
+ ),
543
+ item.cdnSynced && /* @__PURE__ */ jsx3("span", { css: styles3.cdnBadge, children: "CDN" }),
544
+ /* @__PURE__ */ jsx3("div", { css: styles3.content, children: isFolder ? /* @__PURE__ */ jsx3("svg", { css: styles3.folderIcon, fill: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ jsx3("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__ */ jsx3(
545
+ "img",
546
+ {
547
+ css: styles3.image,
548
+ src: item.path.replace("public", ""),
549
+ alt: item.name,
550
+ loading: "lazy"
551
+ }
552
+ ) }),
553
+ /* @__PURE__ */ jsxs3("div", { css: styles3.label, children: [
554
+ /* @__PURE__ */ jsx3("p", { css: styles3.name, title: item.name, children: item.name }),
555
+ item.size && /* @__PURE__ */ jsx3("p", { css: styles3.size, children: formatFileSize(item.size) })
556
+ ] })
557
+ ] });
558
+ }
559
+ function formatFileSize(bytes) {
560
+ if (bytes < 1024) return `${bytes} B`;
561
+ if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
562
+ return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
563
+ }
564
+
565
+ // src/components/StudioFileList.tsx
566
+ import { useEffect as useEffect2, useState as useState2 } from "react";
567
+ import { css as css4, keyframes as keyframes2 } from "@emotion/react";
568
+ import { jsx as jsx4, jsxs as jsxs4 } from "@emotion/react/jsx-runtime";
569
+ var spin2 = keyframes2`
570
+ to { transform: rotate(360deg); }
571
+ `;
572
+ var styles4 = {
573
+ loading: css4`
574
+ display: flex;
575
+ align-items: center;
576
+ justify-content: center;
577
+ height: 256px;
578
+ `,
579
+ spinner: css4`
580
+ width: 32px;
581
+ height: 32px;
582
+ border-radius: 50%;
583
+ border: 2px solid transparent;
584
+ border-bottom-color: #9333ea;
585
+ animation: ${spin2} 1s linear infinite;
586
+ `,
587
+ empty: css4`
588
+ display: flex;
589
+ flex-direction: column;
590
+ align-items: center;
591
+ justify-content: center;
592
+ height: 256px;
593
+ color: #6b7280;
594
+ `,
595
+ table: css4`
596
+ width: 100%;
597
+ border-collapse: collapse;
598
+ `,
599
+ th: css4`
600
+ text-align: left;
601
+ font-size: 12px;
602
+ color: #6b7280;
603
+ text-transform: uppercase;
604
+ letter-spacing: 0.05em;
605
+ padding-bottom: 8px;
606
+ font-weight: normal;
607
+ `,
608
+ thCheckbox: css4`
609
+ width: 32px;
610
+ `,
611
+ thSize: css4`
612
+ width: 96px;
613
+ `,
614
+ thDimensions: css4`
615
+ width: 128px;
616
+ `,
617
+ thCdn: css4`
618
+ width: 96px;
619
+ `,
620
+ tbody: css4`
621
+ border-top: 1px solid #f3f4f6;
622
+ `,
623
+ row: css4`
624
+ cursor: pointer;
625
+ transition: background-color 0.15s;
626
+
627
+ &:hover {
628
+ background-color: #f9fafb;
629
+ }
630
+ `,
631
+ rowSelected: css4`
632
+ background-color: #faf5ff;
633
+ `,
634
+ td: css4`
635
+ padding: 8px 0;
636
+ border-bottom: 1px solid #f3f4f6;
637
+ `,
638
+ checkbox: css4`
639
+ width: 16px;
640
+ height: 16px;
641
+ accent-color: #9333ea;
642
+ `,
643
+ nameCell: css4`
644
+ display: flex;
645
+ align-items: center;
646
+ gap: 8px;
647
+ `,
648
+ folderIcon: css4`
649
+ width: 20px;
650
+ height: 20px;
651
+ color: #facc15;
652
+ `,
653
+ fileIcon: css4`
654
+ width: 20px;
655
+ height: 20px;
656
+ color: #9ca3af;
657
+ `,
658
+ name: css4`
659
+ font-size: 14px;
660
+ color: #111827;
661
+ `,
662
+ meta: css4`
663
+ font-size: 14px;
664
+ color: #6b7280;
665
+ `,
666
+ cdnBadge: css4`
667
+ display: inline-flex;
668
+ align-items: center;
669
+ gap: 4px;
670
+ font-size: 12px;
671
+ color: #15803d;
672
+ `,
673
+ cdnIcon: css4`
674
+ width: 12px;
675
+ height: 12px;
676
+ `,
677
+ cdnEmpty: css4`
678
+ font-size: 12px;
679
+ color: #9ca3af;
680
+ `
681
+ };
682
+ function StudioFileList() {
683
+ const { currentPath, setCurrentPath, selectedItems, toggleSelection } = useStudio();
684
+ const [items, setItems] = useState2([]);
685
+ const [loading, setLoading] = useState2(true);
686
+ useEffect2(() => {
687
+ async function loadItems() {
688
+ setLoading(true);
689
+ try {
690
+ const response = await fetch(`/api/studio/list?path=${encodeURIComponent(currentPath)}`);
691
+ if (response.ok) {
692
+ const data = await response.json();
693
+ setItems(data.items || []);
694
+ }
695
+ } catch (error) {
696
+ console.error("Failed to load items:", error);
697
+ }
698
+ setLoading(false);
699
+ }
700
+ loadItems();
701
+ }, [currentPath]);
702
+ if (loading) {
703
+ return /* @__PURE__ */ jsx4("div", { css: styles4.loading, children: /* @__PURE__ */ jsx4("div", { css: styles4.spinner }) });
704
+ }
705
+ if (items.length === 0) {
706
+ return /* @__PURE__ */ jsx4("div", { css: styles4.empty, children: /* @__PURE__ */ jsx4("p", { children: "No files in this folder" }) });
707
+ }
708
+ const sortedItems = [...items].sort((a, b) => {
709
+ if (a.type === "folder" && b.type !== "folder") return -1;
710
+ if (a.type !== "folder" && b.type === "folder") return 1;
711
+ return a.name.localeCompare(b.name);
712
+ });
713
+ return /* @__PURE__ */ jsxs4("table", { css: styles4.table, children: [
714
+ /* @__PURE__ */ jsx4("thead", { children: /* @__PURE__ */ jsxs4("tr", { children: [
715
+ /* @__PURE__ */ jsx4("th", { css: [styles4.th, styles4.thCheckbox] }),
716
+ /* @__PURE__ */ jsx4("th", { css: styles4.th, children: "Name" }),
717
+ /* @__PURE__ */ jsx4("th", { css: [styles4.th, styles4.thSize], children: "Size" }),
718
+ /* @__PURE__ */ jsx4("th", { css: [styles4.th, styles4.thDimensions], children: "Dimensions" }),
719
+ /* @__PURE__ */ jsx4("th", { css: [styles4.th, styles4.thCdn], children: "CDN" })
720
+ ] }) }),
721
+ /* @__PURE__ */ jsx4("tbody", { css: styles4.tbody, children: sortedItems.map((item) => /* @__PURE__ */ jsx4(
722
+ ListRow,
723
+ {
724
+ item,
725
+ isSelected: selectedItems.has(item.path),
726
+ onSelect: () => toggleSelection(item.path),
727
+ onOpen: () => {
728
+ if (item.type === "folder") {
729
+ setCurrentPath(item.path);
730
+ }
731
+ }
732
+ },
733
+ item.path
734
+ )) })
735
+ ] });
736
+ }
737
+ function ListRow({ item, isSelected, onSelect, onOpen }) {
738
+ const isFolder = item.type === "folder";
739
+ return /* @__PURE__ */ jsxs4("tr", { css: [styles4.row, isSelected && styles4.rowSelected], onDoubleClick: onOpen, children: [
740
+ /* @__PURE__ */ jsx4("td", { css: styles4.td, children: /* @__PURE__ */ jsx4(
741
+ "input",
742
+ {
743
+ type: "checkbox",
744
+ css: styles4.checkbox,
745
+ checked: isSelected,
746
+ onChange: onSelect,
747
+ onClick: (e) => e.stopPropagation()
748
+ }
749
+ ) }),
750
+ /* @__PURE__ */ jsx4("td", { css: styles4.td, children: /* @__PURE__ */ jsxs4("div", { css: styles4.nameCell, children: [
751
+ 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("svg", { css: styles4.fileIcon, 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" }) }),
752
+ /* @__PURE__ */ jsx4("span", { css: styles4.name, children: item.name })
753
+ ] }) }),
754
+ /* @__PURE__ */ jsx4("td", { css: [styles4.td, styles4.meta], children: item.size ? formatFileSize2(item.size) : "--" }),
755
+ /* @__PURE__ */ jsx4("td", { css: [styles4.td, styles4.meta], children: item.dimensions ? `${item.dimensions.width}x${item.dimensions.height}` : "--" }),
756
+ /* @__PURE__ */ jsx4("td", { css: styles4.td, children: item.cdnSynced ? /* @__PURE__ */ jsxs4("span", { css: styles4.cdnBadge, children: [
757
+ /* @__PURE__ */ jsx4("svg", { css: styles4.cdnIcon, fill: "currentColor", viewBox: "0 0 20 20", children: /* @__PURE__ */ jsx4("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" }) }),
758
+ "Synced"
759
+ ] }) : /* @__PURE__ */ jsx4("span", { css: styles4.cdnEmpty, children: "--" }) })
760
+ ] });
761
+ }
762
+ function formatFileSize2(bytes) {
763
+ if (bytes < 1024) return `${bytes} B`;
764
+ if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
765
+ return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
766
+ }
767
+
768
+ // src/components/StudioPreview.tsx
769
+ import { css as css5 } from "@emotion/react";
770
+ import { Fragment, jsx as jsx5, jsxs as jsxs5 } from "@emotion/react/jsx-runtime";
771
+ var styles5 = {
772
+ panel: css5`
773
+ width: 320px;
774
+ border-left: 1px solid #e5e7eb;
775
+ background-color: #f9fafb;
776
+ padding: 16px;
777
+ overflow: auto;
778
+ `,
779
+ title: css5`
780
+ font-size: 14px;
781
+ font-weight: 500;
782
+ color: #111827;
783
+ margin: 0 0 16px 0;
784
+ `,
785
+ imageContainer: css5`
786
+ background-color: white;
787
+ border-radius: 8px;
788
+ border: 1px solid #e5e7eb;
789
+ padding: 8px;
790
+ margin-bottom: 16px;
791
+ `,
792
+ image: css5`
793
+ width: 100%;
794
+ height: auto;
795
+ border-radius: 4px;
796
+ `,
797
+ info: css5`
798
+ display: flex;
799
+ flex-direction: column;
800
+ gap: 12px;
801
+ `,
802
+ row: css5`
803
+ display: flex;
804
+ justify-content: space-between;
805
+ font-size: 12px;
806
+ `,
807
+ label: css5`
808
+ color: #6b7280;
809
+ `,
810
+ value: css5`
811
+ color: #111827;
812
+ `,
813
+ valueTruncate: css5`
814
+ max-width: 128px;
815
+ white-space: nowrap;
816
+ overflow: hidden;
817
+ text-overflow: ellipsis;
818
+ `,
819
+ section: css5`
820
+ padding-top: 8px;
821
+ border-top: 1px solid #e5e7eb;
822
+ `,
823
+ sectionTitle: css5`
824
+ font-size: 12px;
825
+ font-weight: 500;
826
+ color: #6b7280;
827
+ margin: 0 0 8px 0;
828
+ `,
829
+ cdnStatus: css5`
830
+ display: flex;
831
+ align-items: center;
832
+ gap: 8px;
833
+ font-size: 12px;
834
+ color: #16a34a;
835
+ `,
836
+ cdnIcon: css5`
837
+ width: 16px;
838
+ height: 16px;
839
+ `,
840
+ copyBtn: css5`
841
+ margin-top: 8px;
842
+ font-size: 12px;
843
+ color: #9333ea;
844
+ background: none;
845
+ border: none;
846
+ cursor: pointer;
847
+ padding: 0;
848
+
849
+ &:hover {
850
+ text-decoration: underline;
851
+ }
852
+ `,
853
+ colorSwatch: css5`
854
+ margin-top: 8px;
855
+ height: 32px;
856
+ border-radius: 4px;
857
+ `,
858
+ actions: css5`
859
+ margin-top: 16px;
860
+ padding-top: 16px;
861
+ border-top: 1px solid #e5e7eb;
862
+ display: flex;
863
+ flex-direction: column;
864
+ gap: 8px;
865
+ `,
866
+ actionBtn: css5`
867
+ width: 100%;
868
+ padding: 8px 12px;
869
+ font-size: 14px;
870
+ background-color: white;
871
+ border: 1px solid #e5e7eb;
872
+ border-radius: 8px;
873
+ cursor: pointer;
874
+ transition: background-color 0.15s;
875
+ color: #374151;
876
+
877
+ &:hover {
878
+ background-color: #f9fafb;
879
+ }
880
+ `,
881
+ actionBtnDanger: css5`
882
+ color: #dc2626;
883
+
884
+ &:hover {
885
+ background-color: #fef2f2;
886
+ }
887
+ `
888
+ };
889
+ function StudioPreview() {
890
+ const { selectedItems, meta } = useStudio();
891
+ if (selectedItems.size !== 1) {
892
+ return null;
893
+ }
894
+ const selectedPath = Array.from(selectedItems)[0];
895
+ const imageKey = selectedPath.replace(/^public\/images\//, "").replace(/^public\/originals\//, "");
896
+ const imageData = meta?.images?.[imageKey];
897
+ return /* @__PURE__ */ jsxs5("div", { css: styles5.panel, children: [
898
+ /* @__PURE__ */ jsx5("h3", { css: styles5.title, children: "Preview" }),
899
+ /* @__PURE__ */ jsx5("div", { css: styles5.imageContainer, children: /* @__PURE__ */ jsx5(
900
+ "img",
901
+ {
902
+ css: styles5.image,
903
+ src: selectedPath.replace("public", ""),
904
+ alt: "Preview"
905
+ }
906
+ ) }),
907
+ /* @__PURE__ */ jsxs5("div", { css: styles5.info, children: [
908
+ /* @__PURE__ */ jsx5(InfoRow, { label: "Filename", value: selectedPath.split("/").pop() || "" }),
909
+ imageData && /* @__PURE__ */ jsxs5(Fragment, { children: [
910
+ /* @__PURE__ */ jsx5(
911
+ InfoRow,
912
+ {
913
+ label: "Original",
914
+ value: `${imageData.original.width}x${imageData.original.height}`
915
+ }
916
+ ),
917
+ /* @__PURE__ */ jsx5(
918
+ InfoRow,
919
+ {
920
+ label: "File size",
921
+ value: formatFileSize3(imageData.original.fileSize)
922
+ }
923
+ ),
924
+ /* @__PURE__ */ jsxs5("div", { css: styles5.section, children: [
925
+ /* @__PURE__ */ jsx5("p", { css: styles5.sectionTitle, children: "Generated sizes" }),
926
+ Object.entries(imageData.sizes).map(([size, data]) => /* @__PURE__ */ jsx5(InfoRow, { label: size, value: `${data.width}x${data.height}` }, size))
927
+ ] }),
928
+ imageData.cdn?.synced && /* @__PURE__ */ jsxs5("div", { css: styles5.section, children: [
929
+ /* @__PURE__ */ jsx5("p", { css: styles5.sectionTitle, children: "CDN" }),
930
+ /* @__PURE__ */ jsxs5("div", { css: styles5.cdnStatus, children: [
931
+ /* @__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" }) }),
932
+ "Synced to CDN"
933
+ ] }),
934
+ /* @__PURE__ */ jsx5(
935
+ "button",
936
+ {
937
+ css: styles5.copyBtn,
938
+ onClick: () => {
939
+ navigator.clipboard.writeText(`${imageData.cdn?.baseUrl}${imageData.sizes.full.path}`);
940
+ },
941
+ children: "Copy CDN URL"
942
+ }
943
+ )
944
+ ] }),
945
+ imageData.blurhash && /* @__PURE__ */ jsxs5("div", { css: styles5.section, children: [
946
+ /* @__PURE__ */ jsx5(InfoRow, { label: "Blurhash", value: imageData.blurhash, truncate: true }),
947
+ /* @__PURE__ */ jsx5(
948
+ "div",
949
+ {
950
+ css: styles5.colorSwatch,
951
+ style: { backgroundColor: imageData.dominantColor },
952
+ title: `Dominant color: ${imageData.dominantColor}`
953
+ }
954
+ )
955
+ ] })
956
+ ] })
957
+ ] }),
958
+ /* @__PURE__ */ jsxs5("div", { css: styles5.actions, children: [
959
+ /* @__PURE__ */ jsx5("button", { css: styles5.actionBtn, children: "Rename" }),
960
+ /* @__PURE__ */ jsx5("button", { css: [styles5.actionBtn, styles5.actionBtnDanger], children: "Delete" })
961
+ ] })
962
+ ] });
963
+ }
964
+ function InfoRow({ label, value, truncate }) {
965
+ return /* @__PURE__ */ jsxs5("div", { css: styles5.row, children: [
966
+ /* @__PURE__ */ jsx5("span", { css: styles5.label, children: label }),
967
+ /* @__PURE__ */ jsx5("span", { css: [styles5.value, truncate && styles5.valueTruncate], title: truncate ? value : void 0, children: value })
968
+ ] });
969
+ }
970
+ function formatFileSize3(bytes) {
971
+ if (bytes < 1024) return `${bytes} B`;
972
+ if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
973
+ return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
974
+ }
975
+
976
+ // src/components/StudioSettings.tsx
977
+ import { useState as useState3 } from "react";
978
+ import { css as css6 } from "@emotion/react";
979
+ import { Fragment as Fragment2, jsx as jsx6, jsxs as jsxs6 } from "@emotion/react/jsx-runtime";
980
+ var styles6 = {
981
+ btn: css6`
982
+ padding: 8px;
983
+ background: none;
984
+ border: none;
985
+ border-radius: 8px;
986
+ cursor: pointer;
987
+ transition: background-color 0.15s;
988
+
989
+ &:hover {
990
+ background-color: #f3f4f6;
991
+ }
992
+ `,
993
+ icon: css6`
994
+ width: 20px;
995
+ height: 20px;
996
+ color: #6b7280;
997
+ `,
998
+ overlay: css6`
999
+ position: fixed;
1000
+ top: 0;
1001
+ right: 0;
1002
+ bottom: 0;
1003
+ left: 0;
1004
+ z-index: 10000;
1005
+ display: flex;
1006
+ align-items: center;
1007
+ justify-content: center;
1008
+ `,
1009
+ backdrop: css6`
1010
+ position: absolute;
1011
+ top: 0;
1012
+ right: 0;
1013
+ bottom: 0;
1014
+ left: 0;
1015
+ background-color: rgba(0, 0, 0, 0.3);
1016
+ `,
1017
+ panel: css6`
1018
+ position: relative;
1019
+ background-color: white;
1020
+ border-radius: 12px;
1021
+ box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.25);
1022
+ width: 100%;
1023
+ max-width: 512px;
1024
+ padding: 24px;
1025
+ `,
1026
+ header: css6`
1027
+ display: flex;
1028
+ align-items: center;
1029
+ justify-content: space-between;
1030
+ margin-bottom: 24px;
1031
+ `,
1032
+ title: css6`
1033
+ font-size: 18px;
1034
+ font-weight: 600;
1035
+ margin: 0;
1036
+ `,
1037
+ closeBtn: css6`
1038
+ padding: 4px;
1039
+ background: none;
1040
+ border: none;
1041
+ border-radius: 8px;
1042
+ cursor: pointer;
1043
+
1044
+ &:hover {
1045
+ background-color: #f3f4f6;
1046
+ }
1047
+ `,
1048
+ sections: css6`
1049
+ display: flex;
1050
+ flex-direction: column;
1051
+ gap: 24px;
1052
+ `,
1053
+ sectionTitle: css6`
1054
+ font-size: 14px;
1055
+ font-weight: 500;
1056
+ color: #111827;
1057
+ margin: 0 0 12px 0;
1058
+ `,
1059
+ description: css6`
1060
+ font-size: 12px;
1061
+ color: #6b7280;
1062
+ margin: 0 0 12px 0;
1063
+ `,
1064
+ code: css6`
1065
+ background-color: #f9fafb;
1066
+ border-radius: 8px;
1067
+ padding: 12px;
1068
+ font-family: monospace;
1069
+ font-size: 12px;
1070
+ color: #4b5563;
1071
+ `,
1072
+ codeLine: css6`
1073
+ margin: 0 0 4px 0;
1074
+
1075
+ &:last-child {
1076
+ margin: 0;
1077
+ }
1078
+ `,
1079
+ input: css6`
1080
+ width: 100%;
1081
+ padding: 8px 12px;
1082
+ border: 1px solid #e5e7eb;
1083
+ border-radius: 8px;
1084
+ font-size: 14px;
1085
+
1086
+ &:focus {
1087
+ outline: none;
1088
+ box-shadow: 0 0 0 2px #a855f7;
1089
+ }
1090
+ `,
1091
+ grid: css6`
1092
+ display: grid;
1093
+ grid-template-columns: repeat(3, 1fr);
1094
+ gap: 12px;
1095
+ `,
1096
+ label: css6`
1097
+ font-size: 12px;
1098
+ color: #6b7280;
1099
+ display: block;
1100
+ margin-bottom: 4px;
1101
+ `,
1102
+ footer: css6`
1103
+ margin-top: 24px;
1104
+ display: flex;
1105
+ justify-content: flex-end;
1106
+ gap: 12px;
1107
+ `,
1108
+ cancelBtn: css6`
1109
+ padding: 8px 16px;
1110
+ font-size: 14px;
1111
+ color: #4b5563;
1112
+ background: none;
1113
+ border: none;
1114
+ border-radius: 8px;
1115
+ cursor: pointer;
1116
+
1117
+ &:hover {
1118
+ background-color: #f3f4f6;
1119
+ }
1120
+ `,
1121
+ saveBtn: css6`
1122
+ padding: 8px 16px;
1123
+ font-size: 14px;
1124
+ color: white;
1125
+ background-color: #9333ea;
1126
+ border: none;
1127
+ border-radius: 8px;
1128
+ cursor: pointer;
1129
+
1130
+ &:hover {
1131
+ background-color: #7c3aed;
1132
+ }
1133
+ `
1134
+ };
1135
+ function StudioSettings() {
1136
+ const [isOpen, setIsOpen] = useState3(false);
1137
+ return /* @__PURE__ */ jsxs6(Fragment2, { children: [
1138
+ /* @__PURE__ */ jsx6("button", { css: styles6.btn, onClick: () => setIsOpen(true), "aria-label": "Settings", children: /* @__PURE__ */ jsxs6(
1139
+ "svg",
1140
+ {
1141
+ css: styles6.icon,
1142
+ xmlns: "http://www.w3.org/2000/svg",
1143
+ viewBox: "0 0 24 24",
1144
+ fill: "none",
1145
+ stroke: "currentColor",
1146
+ strokeWidth: 2,
1147
+ strokeLinecap: "round",
1148
+ strokeLinejoin: "round",
1149
+ children: [
1150
+ /* @__PURE__ */ jsx6("circle", { cx: "12", cy: "12", r: "3" }),
1151
+ /* @__PURE__ */ jsx6("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" })
1152
+ ]
1153
+ }
1154
+ ) }),
1155
+ isOpen && /* @__PURE__ */ jsx6(SettingsPanel, { onClose: () => setIsOpen(false) })
1156
+ ] });
1157
+ }
1158
+ function SettingsPanel({ onClose }) {
1159
+ return /* @__PURE__ */ jsxs6("div", { css: styles6.overlay, children: [
1160
+ /* @__PURE__ */ jsx6("div", { css: styles6.backdrop, onClick: onClose }),
1161
+ /* @__PURE__ */ jsxs6("div", { css: styles6.panel, children: [
1162
+ /* @__PURE__ */ jsxs6("div", { css: styles6.header, children: [
1163
+ /* @__PURE__ */ jsx6("h2", { css: styles6.title, children: "Settings" }),
1164
+ /* @__PURE__ */ jsx6("button", { css: styles6.closeBtn, onClick: onClose, children: /* @__PURE__ */ jsx6("svg", { css: styles6.icon, fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ jsx6("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M6 18L18 6M6 6l12 12" }) }) })
1165
+ ] }),
1166
+ /* @__PURE__ */ jsxs6("div", { css: styles6.sections, children: [
1167
+ /* @__PURE__ */ jsxs6("section", { children: [
1168
+ /* @__PURE__ */ jsx6("h3", { css: styles6.sectionTitle, children: "Cloudflare R2" }),
1169
+ /* @__PURE__ */ jsx6("p", { css: styles6.description, children: "Configure in .env.local file:" }),
1170
+ /* @__PURE__ */ jsxs6("div", { css: styles6.code, children: [
1171
+ /* @__PURE__ */ jsx6("p", { css: styles6.codeLine, children: "CLOUDFLARE_R2_ACCOUNT_ID" }),
1172
+ /* @__PURE__ */ jsx6("p", { css: styles6.codeLine, children: "CLOUDFLARE_R2_ACCESS_KEY_ID" }),
1173
+ /* @__PURE__ */ jsx6("p", { css: styles6.codeLine, children: "CLOUDFLARE_R2_SECRET_ACCESS_KEY" }),
1174
+ /* @__PURE__ */ jsx6("p", { css: styles6.codeLine, children: "CLOUDFLARE_R2_BUCKET_NAME" }),
1175
+ /* @__PURE__ */ jsx6("p", { css: styles6.codeLine, children: "CLOUDFLARE_R2_PUBLIC_URL" })
1176
+ ] })
1177
+ ] }),
1178
+ /* @__PURE__ */ jsxs6("section", { children: [
1179
+ /* @__PURE__ */ jsx6("h3", { css: styles6.sectionTitle, children: "Custom CDN URL" }),
1180
+ /* @__PURE__ */ jsx6("p", { css: styles6.description, children: "Override the default R2 URL with a custom domain:" }),
1181
+ /* @__PURE__ */ jsx6("input", { css: styles6.input, type: "text", placeholder: "https://cdn.yourdomain.com" })
1182
+ ] }),
1183
+ /* @__PURE__ */ jsxs6("section", { children: [
1184
+ /* @__PURE__ */ jsx6("h3", { css: styles6.sectionTitle, children: "Thumbnail Sizes" }),
1185
+ /* @__PURE__ */ jsxs6("div", { css: styles6.grid, children: [
1186
+ /* @__PURE__ */ jsxs6("div", { children: [
1187
+ /* @__PURE__ */ jsx6("label", { css: styles6.label, children: "Small" }),
1188
+ /* @__PURE__ */ jsx6("input", { css: styles6.input, type: "number", defaultValue: 300 })
1189
+ ] }),
1190
+ /* @__PURE__ */ jsxs6("div", { children: [
1191
+ /* @__PURE__ */ jsx6("label", { css: styles6.label, children: "Medium" }),
1192
+ /* @__PURE__ */ jsx6("input", { css: styles6.input, type: "number", defaultValue: 700 })
1193
+ ] }),
1194
+ /* @__PURE__ */ jsxs6("div", { children: [
1195
+ /* @__PURE__ */ jsx6("label", { css: styles6.label, children: "Large" }),
1196
+ /* @__PURE__ */ jsx6("input", { css: styles6.input, type: "number", defaultValue: 1400 })
1197
+ ] })
1198
+ ] })
1199
+ ] })
1200
+ ] }),
1201
+ /* @__PURE__ */ jsxs6("div", { css: styles6.footer, children: [
1202
+ /* @__PURE__ */ jsx6("button", { css: styles6.cancelBtn, onClick: onClose, children: "Cancel" }),
1203
+ /* @__PURE__ */ jsx6("button", { css: styles6.saveBtn, children: "Save Changes" })
1204
+ ] })
1205
+ ] })
1206
+ ] });
1207
+ }
1208
+
1209
+ // src/components/StudioUI.tsx
1210
+ import { jsx as jsx7, jsxs as jsxs7 } from "@emotion/react/jsx-runtime";
1211
+ var styles7 = {
1212
+ container: css7`
1213
+ display: flex;
1214
+ flex-direction: column;
1215
+ height: 100%;
1216
+ font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
1217
+ `,
1218
+ header: css7`
1219
+ display: flex;
1220
+ align-items: center;
1221
+ justify-content: space-between;
1222
+ padding: 16px 24px;
1223
+ border-bottom: 1px solid #e5e7eb;
1224
+ `,
1225
+ title: css7`
1226
+ font-size: 20px;
1227
+ font-weight: 600;
1228
+ color: #111827;
1229
+ margin: 0;
1230
+ `,
1231
+ headerActions: css7`
1232
+ display: flex;
1233
+ align-items: center;
1234
+ gap: 8px;
1235
+ `,
1236
+ closeBtn: css7`
1237
+ padding: 8px;
1238
+ background: none;
1239
+ border: none;
1240
+ border-radius: 8px;
1241
+ cursor: pointer;
1242
+ transition: background-color 0.15s;
1243
+
1244
+ &:hover {
1245
+ background-color: #f3f4f6;
1246
+ }
1247
+ `,
1248
+ closeIcon: css7`
1249
+ width: 20px;
1250
+ height: 20px;
1251
+ color: #6b7280;
1252
+ `,
1253
+ content: css7`
1254
+ flex: 1;
1255
+ display: flex;
1256
+ overflow: hidden;
1257
+ `,
1258
+ fileBrowser: css7`
1259
+ flex: 1;
1260
+ overflow: auto;
1261
+ padding: 16px;
1262
+ `
1263
+ };
1264
+ function StudioUI({ onClose }) {
1265
+ const [currentPath, setCurrentPathInternal] = useState4("public");
1266
+ const [selectedItems, setSelectedItems] = useState4(/* @__PURE__ */ new Set());
1267
+ const [viewMode, setViewMode] = useState4("grid");
1268
+ const [meta, setMeta] = useState4(null);
1269
+ const [isLoading, setIsLoading] = useState4(false);
1270
+ const navigateUp = useCallback2(() => {
1271
+ if (currentPath === "public") return;
1272
+ const parts = currentPath.split("/");
1273
+ parts.pop();
1274
+ setCurrentPathInternal(parts.join("/") || "public");
1275
+ setSelectedItems(/* @__PURE__ */ new Set());
1276
+ }, [currentPath]);
1277
+ const setCurrentPath = useCallback2((path) => {
1278
+ setCurrentPathInternal(path);
1279
+ setSelectedItems(/* @__PURE__ */ new Set());
1280
+ }, []);
1281
+ const toggleSelection = useCallback2((path) => {
1282
+ setSelectedItems((prev) => {
1283
+ const next = new Set(prev);
1284
+ if (next.has(path)) {
1285
+ next.delete(path);
1286
+ } else {
1287
+ next.add(path);
1288
+ }
1289
+ return next;
1290
+ });
1291
+ }, []);
1292
+ const selectAll = useCallback2((items) => {
1293
+ setSelectedItems(new Set(items.map((item) => item.path)));
1294
+ }, []);
1295
+ const clearSelection = useCallback2(() => {
1296
+ setSelectedItems(/* @__PURE__ */ new Set());
1297
+ }, []);
1298
+ const handleKeyDown = useCallback2(
1299
+ (e) => {
1300
+ if (e.key === "Escape") {
1301
+ onClose();
1302
+ }
1303
+ },
1304
+ [onClose]
1305
+ );
1306
+ useEffect3(() => {
1307
+ document.addEventListener("keydown", handleKeyDown);
1308
+ document.body.style.overflow = "hidden";
1309
+ return () => {
1310
+ document.removeEventListener("keydown", handleKeyDown);
1311
+ document.body.style.overflow = "";
1312
+ };
1313
+ }, [handleKeyDown]);
1314
+ const contextValue = {
1315
+ isOpen: true,
1316
+ openStudio: () => {
1317
+ },
1318
+ closeStudio: onClose,
1319
+ toggleStudio: onClose,
1320
+ currentPath,
1321
+ setCurrentPath,
1322
+ navigateUp,
1323
+ selectedItems,
1324
+ toggleSelection,
1325
+ selectAll,
1326
+ clearSelection,
1327
+ viewMode,
1328
+ setViewMode,
1329
+ meta,
1330
+ setMeta,
1331
+ isLoading,
1332
+ setIsLoading
1333
+ };
1334
+ return /* @__PURE__ */ jsx7(StudioContext.Provider, { value: contextValue, children: /* @__PURE__ */ jsxs7("div", { css: styles7.container, children: [
1335
+ /* @__PURE__ */ jsxs7("div", { css: styles7.header, children: [
1336
+ /* @__PURE__ */ jsx7("h1", { css: styles7.title, children: "Studio" }),
1337
+ /* @__PURE__ */ jsxs7("div", { css: styles7.headerActions, children: [
1338
+ /* @__PURE__ */ jsx7(StudioSettings, {}),
1339
+ /* @__PURE__ */ jsx7(
1340
+ "button",
1341
+ {
1342
+ css: styles7.closeBtn,
1343
+ onClick: onClose,
1344
+ "aria-label": "Close Studio",
1345
+ children: /* @__PURE__ */ jsx7(CloseIcon, {})
1346
+ }
1347
+ )
1348
+ ] })
1349
+ ] }),
1350
+ /* @__PURE__ */ jsx7(StudioToolbar, {}),
1351
+ /* @__PURE__ */ jsx7(StudioBreadcrumb, {}),
1352
+ /* @__PURE__ */ jsxs7("div", { css: styles7.content, children: [
1353
+ /* @__PURE__ */ jsx7("div", { css: styles7.fileBrowser, children: viewMode === "grid" ? /* @__PURE__ */ jsx7(StudioFileGrid, {}) : /* @__PURE__ */ jsx7(StudioFileList, {}) }),
1354
+ /* @__PURE__ */ jsx7(StudioPreview, {})
1355
+ ] })
1356
+ ] }) });
1357
+ }
1358
+ function CloseIcon() {
1359
+ return /* @__PURE__ */ jsxs7(
1360
+ "svg",
1361
+ {
1362
+ css: styles7.closeIcon,
1363
+ xmlns: "http://www.w3.org/2000/svg",
1364
+ viewBox: "0 0 24 24",
1365
+ fill: "none",
1366
+ stroke: "currentColor",
1367
+ strokeWidth: 2,
1368
+ strokeLinecap: "round",
1369
+ strokeLinejoin: "round",
1370
+ children: [
1371
+ /* @__PURE__ */ jsx7("line", { x1: "18", y1: "6", x2: "6", y2: "18" }),
1372
+ /* @__PURE__ */ jsx7("line", { x1: "6", y1: "6", x2: "18", y2: "18" })
1373
+ ]
1374
+ }
1375
+ );
1376
+ }
1377
+ var StudioUI_default = StudioUI;
1378
+ export {
1379
+ StudioUI,
1380
+ StudioUI_default as default
1381
+ };
1382
+ //# sourceMappingURL=StudioUI-BBXJCQ3O.mjs.map