@gallop.software/studio 0.1.3 → 0.1.4
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.
- package/dist/{StudioUI-BBXJCQ3O.mjs → StudioUI-DSMTGRJ3.mjs} +84 -24
- package/dist/StudioUI-DSMTGRJ3.mjs.map +1 -0
- package/dist/{StudioUI-5I3O5VF7.js → StudioUI-Z3DOQO7P.js} +72 -12
- package/dist/StudioUI-Z3DOQO7P.js.map +1 -0
- package/dist/index.js +1 -1
- package/dist/index.mjs +1 -1
- package/package.json +1 -1
- package/dist/StudioUI-5I3O5VF7.js.map +0 -1
- package/dist/StudioUI-BBXJCQ3O.mjs.map +0 -1
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
3
|
// src/components/StudioUI.tsx
|
|
4
|
-
import { useEffect as useEffect3, useCallback as useCallback2, useState as
|
|
4
|
+
import { useEffect as useEffect3, useCallback as useCallback2, useState as useState5 } from "react";
|
|
5
5
|
import { css as css7 } from "@emotion/react";
|
|
6
6
|
|
|
7
7
|
// src/components/StudioContext.tsx
|
|
@@ -34,6 +34,9 @@ var defaultState = {
|
|
|
34
34
|
},
|
|
35
35
|
isLoading: false,
|
|
36
36
|
setIsLoading: () => {
|
|
37
|
+
},
|
|
38
|
+
refreshKey: 0,
|
|
39
|
+
triggerRefresh: () => {
|
|
37
40
|
}
|
|
38
41
|
};
|
|
39
42
|
var StudioContext = createContext(defaultState);
|
|
@@ -42,7 +45,7 @@ function useStudio() {
|
|
|
42
45
|
}
|
|
43
46
|
|
|
44
47
|
// src/components/StudioToolbar.tsx
|
|
45
|
-
import { useCallback } from "react";
|
|
48
|
+
import { useCallback, useRef, useState } from "react";
|
|
46
49
|
import { css } from "@emotion/react";
|
|
47
50
|
import { jsx, jsxs } from "@emotion/react/jsx-runtime";
|
|
48
51
|
var styles = {
|
|
@@ -142,10 +145,42 @@ var styles = {
|
|
|
142
145
|
`
|
|
143
146
|
};
|
|
144
147
|
function StudioToolbar() {
|
|
145
|
-
const { selectedItems, viewMode, setViewMode, clearSelection } = useStudio();
|
|
148
|
+
const { selectedItems, viewMode, setViewMode, clearSelection, currentPath, triggerRefresh } = useStudio();
|
|
149
|
+
const fileInputRef = useRef(null);
|
|
150
|
+
const [uploading, setUploading] = useState(false);
|
|
146
151
|
const handleUpload = useCallback(() => {
|
|
147
|
-
|
|
152
|
+
fileInputRef.current?.click();
|
|
148
153
|
}, []);
|
|
154
|
+
const handleFileChange = useCallback(async (e) => {
|
|
155
|
+
const files = e.target.files;
|
|
156
|
+
if (!files || files.length === 0) return;
|
|
157
|
+
setUploading(true);
|
|
158
|
+
try {
|
|
159
|
+
for (const file of Array.from(files)) {
|
|
160
|
+
const formData = new FormData();
|
|
161
|
+
formData.append("file", file);
|
|
162
|
+
formData.append("path", currentPath);
|
|
163
|
+
const response = await fetch("/api/studio/upload", {
|
|
164
|
+
method: "POST",
|
|
165
|
+
body: formData
|
|
166
|
+
});
|
|
167
|
+
if (!response.ok) {
|
|
168
|
+
const error = await response.json();
|
|
169
|
+
console.error("Upload failed:", error);
|
|
170
|
+
alert(`Failed to upload ${file.name}: ${error.error || "Unknown error"}`);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
triggerRefresh();
|
|
174
|
+
} catch (error) {
|
|
175
|
+
console.error("Upload error:", error);
|
|
176
|
+
alert("Upload failed. Check console for details.");
|
|
177
|
+
} finally {
|
|
178
|
+
setUploading(false);
|
|
179
|
+
if (fileInputRef.current) {
|
|
180
|
+
fileInputRef.current.value = "";
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}, [currentPath, triggerRefresh]);
|
|
149
184
|
const handleReprocess = useCallback(() => {
|
|
150
185
|
console.log("Reprocess clicked", selectedItems);
|
|
151
186
|
}, [selectedItems]);
|
|
@@ -160,8 +195,27 @@ function StudioToolbar() {
|
|
|
160
195
|
}, []);
|
|
161
196
|
const hasSelection = selectedItems.size > 0;
|
|
162
197
|
return /* @__PURE__ */ jsxs("div", { css: styles.toolbar, children: [
|
|
198
|
+
/* @__PURE__ */ jsx(
|
|
199
|
+
"input",
|
|
200
|
+
{
|
|
201
|
+
ref: fileInputRef,
|
|
202
|
+
type: "file",
|
|
203
|
+
multiple: true,
|
|
204
|
+
accept: "image/*",
|
|
205
|
+
onChange: handleFileChange,
|
|
206
|
+
style: { display: "none" }
|
|
207
|
+
}
|
|
208
|
+
),
|
|
163
209
|
/* @__PURE__ */ jsxs("div", { css: styles.left, children: [
|
|
164
|
-
/* @__PURE__ */ jsx(
|
|
210
|
+
/* @__PURE__ */ jsx(
|
|
211
|
+
ToolbarButton,
|
|
212
|
+
{
|
|
213
|
+
onClick: handleUpload,
|
|
214
|
+
icon: "upload",
|
|
215
|
+
label: uploading ? "Uploading..." : "Upload",
|
|
216
|
+
disabled: uploading
|
|
217
|
+
}
|
|
218
|
+
),
|
|
165
219
|
/* @__PURE__ */ jsx(
|
|
166
220
|
ToolbarButton,
|
|
167
221
|
{
|
|
@@ -355,7 +409,7 @@ function StudioBreadcrumb() {
|
|
|
355
409
|
}
|
|
356
410
|
|
|
357
411
|
// src/components/StudioFileGrid.tsx
|
|
358
|
-
import { useEffect, useState } from "react";
|
|
412
|
+
import { useEffect, useState as useState2 } from "react";
|
|
359
413
|
import { css as css3, keyframes } from "@emotion/react";
|
|
360
414
|
import { jsx as jsx3, jsxs as jsxs3 } from "@emotion/react/jsx-runtime";
|
|
361
415
|
var spin = keyframes`
|
|
@@ -478,9 +532,9 @@ var styles3 = {
|
|
|
478
532
|
`
|
|
479
533
|
};
|
|
480
534
|
function StudioFileGrid() {
|
|
481
|
-
const { currentPath, setCurrentPath, selectedItems, toggleSelection } = useStudio();
|
|
482
|
-
const [items, setItems] =
|
|
483
|
-
const [loading, setLoading] =
|
|
535
|
+
const { currentPath, setCurrentPath, selectedItems, toggleSelection, refreshKey } = useStudio();
|
|
536
|
+
const [items, setItems] = useState2([]);
|
|
537
|
+
const [loading, setLoading] = useState2(true);
|
|
484
538
|
useEffect(() => {
|
|
485
539
|
async function loadItems() {
|
|
486
540
|
setLoading(true);
|
|
@@ -496,7 +550,7 @@ function StudioFileGrid() {
|
|
|
496
550
|
setLoading(false);
|
|
497
551
|
}
|
|
498
552
|
loadItems();
|
|
499
|
-
}, [currentPath]);
|
|
553
|
+
}, [currentPath, refreshKey]);
|
|
500
554
|
if (loading) {
|
|
501
555
|
return /* @__PURE__ */ jsx3("div", { css: styles3.loading, children: /* @__PURE__ */ jsx3("div", { css: styles3.spinner }) });
|
|
502
556
|
}
|
|
@@ -563,7 +617,7 @@ function formatFileSize(bytes) {
|
|
|
563
617
|
}
|
|
564
618
|
|
|
565
619
|
// src/components/StudioFileList.tsx
|
|
566
|
-
import { useEffect as useEffect2, useState as
|
|
620
|
+
import { useEffect as useEffect2, useState as useState3 } from "react";
|
|
567
621
|
import { css as css4, keyframes as keyframes2 } from "@emotion/react";
|
|
568
622
|
import { jsx as jsx4, jsxs as jsxs4 } from "@emotion/react/jsx-runtime";
|
|
569
623
|
var spin2 = keyframes2`
|
|
@@ -680,9 +734,9 @@ var styles4 = {
|
|
|
680
734
|
`
|
|
681
735
|
};
|
|
682
736
|
function StudioFileList() {
|
|
683
|
-
const { currentPath, setCurrentPath, selectedItems, toggleSelection } = useStudio();
|
|
684
|
-
const [items, setItems] =
|
|
685
|
-
const [loading, setLoading] =
|
|
737
|
+
const { currentPath, setCurrentPath, selectedItems, toggleSelection, refreshKey } = useStudio();
|
|
738
|
+
const [items, setItems] = useState3([]);
|
|
739
|
+
const [loading, setLoading] = useState3(true);
|
|
686
740
|
useEffect2(() => {
|
|
687
741
|
async function loadItems() {
|
|
688
742
|
setLoading(true);
|
|
@@ -698,7 +752,7 @@ function StudioFileList() {
|
|
|
698
752
|
setLoading(false);
|
|
699
753
|
}
|
|
700
754
|
loadItems();
|
|
701
|
-
}, [currentPath]);
|
|
755
|
+
}, [currentPath, refreshKey]);
|
|
702
756
|
if (loading) {
|
|
703
757
|
return /* @__PURE__ */ jsx4("div", { css: styles4.loading, children: /* @__PURE__ */ jsx4("div", { css: styles4.spinner }) });
|
|
704
758
|
}
|
|
@@ -974,7 +1028,7 @@ function formatFileSize3(bytes) {
|
|
|
974
1028
|
}
|
|
975
1029
|
|
|
976
1030
|
// src/components/StudioSettings.tsx
|
|
977
|
-
import { useState as
|
|
1031
|
+
import { useState as useState4 } from "react";
|
|
978
1032
|
import { css as css6 } from "@emotion/react";
|
|
979
1033
|
import { Fragment as Fragment2, jsx as jsx6, jsxs as jsxs6 } from "@emotion/react/jsx-runtime";
|
|
980
1034
|
var styles6 = {
|
|
@@ -1133,7 +1187,7 @@ var styles6 = {
|
|
|
1133
1187
|
`
|
|
1134
1188
|
};
|
|
1135
1189
|
function StudioSettings() {
|
|
1136
|
-
const [isOpen, setIsOpen] =
|
|
1190
|
+
const [isOpen, setIsOpen] = useState4(false);
|
|
1137
1191
|
return /* @__PURE__ */ jsxs6(Fragment2, { children: [
|
|
1138
1192
|
/* @__PURE__ */ jsx6("button", { css: styles6.btn, onClick: () => setIsOpen(true), "aria-label": "Settings", children: /* @__PURE__ */ jsxs6(
|
|
1139
1193
|
"svg",
|
|
@@ -1262,11 +1316,15 @@ var styles7 = {
|
|
|
1262
1316
|
`
|
|
1263
1317
|
};
|
|
1264
1318
|
function StudioUI({ onClose }) {
|
|
1265
|
-
const [currentPath, setCurrentPathInternal] =
|
|
1266
|
-
const [selectedItems, setSelectedItems] =
|
|
1267
|
-
const [viewMode, setViewMode] =
|
|
1268
|
-
const [meta, setMeta] =
|
|
1269
|
-
const [isLoading, setIsLoading] =
|
|
1319
|
+
const [currentPath, setCurrentPathInternal] = useState5("public");
|
|
1320
|
+
const [selectedItems, setSelectedItems] = useState5(/* @__PURE__ */ new Set());
|
|
1321
|
+
const [viewMode, setViewMode] = useState5("grid");
|
|
1322
|
+
const [meta, setMeta] = useState5(null);
|
|
1323
|
+
const [isLoading, setIsLoading] = useState5(false);
|
|
1324
|
+
const [refreshKey, setRefreshKey] = useState5(0);
|
|
1325
|
+
const triggerRefresh = useCallback2(() => {
|
|
1326
|
+
setRefreshKey((k) => k + 1);
|
|
1327
|
+
}, []);
|
|
1270
1328
|
const navigateUp = useCallback2(() => {
|
|
1271
1329
|
if (currentPath === "public") return;
|
|
1272
1330
|
const parts = currentPath.split("/");
|
|
@@ -1329,7 +1387,9 @@ function StudioUI({ onClose }) {
|
|
|
1329
1387
|
meta,
|
|
1330
1388
|
setMeta,
|
|
1331
1389
|
isLoading,
|
|
1332
|
-
setIsLoading
|
|
1390
|
+
setIsLoading,
|
|
1391
|
+
refreshKey,
|
|
1392
|
+
triggerRefresh
|
|
1333
1393
|
};
|
|
1334
1394
|
return /* @__PURE__ */ jsx7(StudioContext.Provider, { value: contextValue, children: /* @__PURE__ */ jsxs7("div", { css: styles7.container, children: [
|
|
1335
1395
|
/* @__PURE__ */ jsxs7("div", { css: styles7.header, children: [
|
|
@@ -1379,4 +1439,4 @@ export {
|
|
|
1379
1439
|
StudioUI,
|
|
1380
1440
|
StudioUI_default as default
|
|
1381
1441
|
};
|
|
1382
|
-
//# sourceMappingURL=StudioUI-
|
|
1442
|
+
//# sourceMappingURL=StudioUI-DSMTGRJ3.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/components/StudioUI.tsx","../src/components/StudioContext.tsx","../src/components/StudioToolbar.tsx","../src/components/StudioBreadcrumb.tsx","../src/components/StudioFileGrid.tsx","../src/components/StudioFileList.tsx","../src/components/StudioPreview.tsx","../src/components/StudioSettings.tsx"],"sourcesContent":["/** @jsxImportSource @emotion/react */\n'use client'\n\nimport { useEffect, useCallback, useState } from 'react'\nimport { css } from '@emotion/react'\nimport { StudioContext } from './StudioContext'\nimport { StudioToolbar } from './StudioToolbar'\nimport { StudioBreadcrumb } from './StudioBreadcrumb'\nimport { StudioFileGrid } from './StudioFileGrid'\nimport { StudioFileList } from './StudioFileList'\nimport { StudioPreview } from './StudioPreview'\nimport { StudioSettings } from './StudioSettings'\nimport type { FileItem, StudioMeta } from '../types'\n\ninterface StudioUIProps {\n onClose: () => void\n}\n\nconst styles = {\n container: css`\n display: flex;\n flex-direction: column;\n height: 100%;\n font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;\n `,\n header: css`\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 16px 24px;\n border-bottom: 1px solid #e5e7eb;\n `,\n title: css`\n font-size: 20px;\n font-weight: 600;\n color: #111827;\n margin: 0;\n `,\n headerActions: css`\n display: flex;\n align-items: center;\n gap: 8px;\n `,\n closeBtn: css`\n padding: 8px;\n background: none;\n border: none;\n border-radius: 8px;\n cursor: pointer;\n transition: background-color 0.15s;\n \n &:hover {\n background-color: #f3f4f6;\n }\n `,\n closeIcon: css`\n width: 20px;\n height: 20px;\n color: #6b7280;\n `,\n content: css`\n flex: 1;\n display: flex;\n overflow: hidden;\n `,\n fileBrowser: css`\n flex: 1;\n overflow: auto;\n padding: 16px;\n `,\n}\n\n/**\n * Main Studio UI - contains all panels and manages internal state\n * Rendered inside the modal via lazy loading\n */\nexport function StudioUI({ onClose }: StudioUIProps) {\n const [currentPath, setCurrentPathInternal] = useState('public')\n const [selectedItems, setSelectedItems] = useState<Set<string>>(new Set())\n const [viewMode, setViewMode] = useState<'grid' | 'list'>('grid')\n const [meta, setMeta] = useState<StudioMeta | null>(null)\n const [isLoading, setIsLoading] = useState(false)\n const [refreshKey, setRefreshKey] = useState(0)\n\n const triggerRefresh = useCallback(() => {\n setRefreshKey((k) => k + 1)\n }, [])\n\n const navigateUp = useCallback(() => {\n if (currentPath === 'public') return\n const parts = currentPath.split('/')\n parts.pop()\n setCurrentPathInternal(parts.join('/') || 'public')\n setSelectedItems(new Set())\n }, [currentPath])\n\n const setCurrentPath = useCallback((path: string) => {\n setCurrentPathInternal(path)\n setSelectedItems(new Set())\n }, [])\n\n const toggleSelection = useCallback((path: string) => {\n setSelectedItems((prev) => {\n const next = new Set(prev)\n if (next.has(path)) {\n next.delete(path)\n } else {\n next.add(path)\n }\n return next\n })\n }, [])\n\n const selectAll = useCallback((items: FileItem[]) => {\n setSelectedItems(new Set(items.map((item) => item.path)))\n }, [])\n\n const clearSelection = useCallback(() => {\n setSelectedItems(new Set())\n }, [])\n\n const handleKeyDown = useCallback(\n (e: KeyboardEvent) => {\n if (e.key === 'Escape') {\n onClose()\n }\n },\n [onClose]\n )\n\n useEffect(() => {\n document.addEventListener('keydown', handleKeyDown)\n document.body.style.overflow = 'hidden'\n return () => {\n document.removeEventListener('keydown', handleKeyDown)\n document.body.style.overflow = ''\n }\n }, [handleKeyDown])\n\n const contextValue = {\n isOpen: true,\n openStudio: () => {},\n closeStudio: onClose,\n toggleStudio: onClose,\n currentPath,\n setCurrentPath,\n navigateUp,\n selectedItems,\n toggleSelection,\n selectAll,\n clearSelection,\n viewMode,\n setViewMode,\n meta,\n setMeta,\n isLoading,\n setIsLoading,\n refreshKey,\n triggerRefresh,\n }\n\n return (\n <StudioContext.Provider value={contextValue}>\n <div css={styles.container}>\n <div css={styles.header}>\n <h1 css={styles.title}>Studio</h1>\n <div css={styles.headerActions}>\n <StudioSettings />\n <button\n css={styles.closeBtn}\n onClick={onClose}\n aria-label=\"Close Studio\"\n >\n <CloseIcon />\n </button>\n </div>\n </div>\n\n <StudioToolbar />\n <StudioBreadcrumb />\n\n <div css={styles.content}>\n <div css={styles.fileBrowser}>\n {viewMode === 'grid' ? <StudioFileGrid /> : <StudioFileList />}\n </div>\n <StudioPreview />\n </div>\n </div>\n </StudioContext.Provider>\n )\n}\n\nfunction CloseIcon() {\n return (\n <svg\n css={styles.closeIcon}\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth={2}\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n >\n <line x1=\"18\" y1=\"6\" x2=\"6\" y2=\"18\" />\n <line x1=\"6\" y1=\"6\" x2=\"18\" y2=\"18\" />\n </svg>\n )\n}\n\nexport default StudioUI\n","'use client'\n\nimport { createContext, useContext } from 'react'\nimport type { FileItem, StudioMeta } from '../types'\n\n/**\n * Studio state interface\n * State is managed by StudioUI and provided to all child components\n */\nexport interface StudioState {\n isOpen: boolean\n openStudio: () => void\n closeStudio: () => void\n toggleStudio: () => void\n\n // Navigation\n currentPath: string\n setCurrentPath: (path: string) => void\n navigateUp: () => void\n\n // Selection\n selectedItems: Set<string>\n toggleSelection: (path: string) => void\n selectAll: (items: FileItem[]) => void\n clearSelection: () => void\n\n // View\n viewMode: 'grid' | 'list'\n setViewMode: (mode: 'grid' | 'list') => void\n\n // Meta\n meta: StudioMeta | null\n setMeta: (meta: StudioMeta) => void\n\n // Loading\n isLoading: boolean\n setIsLoading: (loading: boolean) => void\n\n // Refresh trigger\n refreshKey: number\n triggerRefresh: () => void\n}\n\nconst defaultState: StudioState = {\n isOpen: false,\n openStudio: () => {},\n closeStudio: () => {},\n toggleStudio: () => {},\n currentPath: 'public',\n setCurrentPath: () => {},\n navigateUp: () => {},\n selectedItems: new Set(),\n toggleSelection: () => {},\n selectAll: () => {},\n clearSelection: () => {},\n viewMode: 'grid',\n setViewMode: () => {},\n meta: null,\n setMeta: () => {},\n isLoading: false,\n setIsLoading: () => {},\n refreshKey: 0,\n triggerRefresh: () => {},\n}\n\nexport const StudioContext = createContext<StudioState>(defaultState)\n\n/**\n * Hook to access Studio state from child components\n */\nexport function useStudio() {\n return useContext(StudioContext)\n}\n","/** @jsxImportSource @emotion/react */\n'use client'\n\nimport { useCallback, useRef, useState } from 'react'\nimport { css } from '@emotion/react'\nimport { useStudio } from './StudioContext'\n\nconst styles = {\n toolbar: css`\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 12px 24px;\n background-color: #f9fafb;\n border-bottom: 1px solid #e5e7eb;\n `,\n left: css`\n display: flex;\n align-items: center;\n gap: 8px;\n `,\n right: css`\n display: flex;\n align-items: center;\n gap: 16px;\n `,\n btn: css`\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 8px 12px;\n border-radius: 8px;\n font-size: 14px;\n font-weight: 500;\n background: none;\n border: none;\n cursor: pointer;\n transition: background-color 0.15s;\n \n &:disabled {\n cursor: not-allowed;\n opacity: 0.5;\n }\n `,\n btnDefault: css`\n color: #374151;\n \n &:hover:not(:disabled) {\n background-color: white;\n }\n `,\n btnDanger: css`\n color: #dc2626;\n \n &:hover:not(:disabled) {\n background-color: #fef2f2;\n }\n `,\n icon: css`\n width: 16px;\n height: 16px;\n `,\n selectionCount: css`\n font-size: 14px;\n color: #4b5563;\n `,\n clearBtn: css`\n margin-left: 8px;\n color: #9333ea;\n background: none;\n border: none;\n cursor: pointer;\n font-size: 14px;\n \n &:hover {\n text-decoration: underline;\n }\n `,\n viewToggle: css`\n display: flex;\n align-items: center;\n background-color: white;\n border: 1px solid #e5e7eb;\n border-radius: 8px;\n overflow: hidden;\n `,\n viewBtn: css`\n padding: 8px;\n background: none;\n border: none;\n cursor: pointer;\n color: #6b7280;\n transition: all 0.15s;\n \n &:hover {\n background-color: #f9fafb;\n }\n `,\n viewBtnActive: css`\n background-color: #f3e8ff;\n color: #7c3aed;\n `,\n}\n\nexport function StudioToolbar() {\n const { selectedItems, viewMode, setViewMode, clearSelection, currentPath, triggerRefresh } = useStudio()\n const fileInputRef = useRef<HTMLInputElement>(null)\n const [uploading, setUploading] = useState(false)\n\n const handleUpload = useCallback(() => {\n fileInputRef.current?.click()\n }, [])\n\n const handleFileChange = useCallback(async (e: React.ChangeEvent<HTMLInputElement>) => {\n const files = e.target.files\n if (!files || files.length === 0) return\n\n setUploading(true)\n try {\n for (const file of Array.from(files)) {\n const formData = new FormData()\n formData.append('file', file)\n formData.append('path', currentPath)\n\n const response = await fetch('/api/studio/upload', {\n method: 'POST',\n body: formData,\n })\n\n if (!response.ok) {\n const error = await response.json()\n console.error('Upload failed:', error)\n alert(`Failed to upload ${file.name}: ${error.error || 'Unknown error'}`)\n }\n }\n triggerRefresh()\n } catch (error) {\n console.error('Upload error:', error)\n alert('Upload failed. Check console for details.')\n } finally {\n setUploading(false)\n // Reset input so same file can be uploaded again\n if (fileInputRef.current) {\n fileInputRef.current.value = ''\n }\n }\n }, [currentPath, triggerRefresh])\n\n const handleReprocess = useCallback(() => {\n console.log('Reprocess clicked', selectedItems)\n }, [selectedItems])\n\n const handleDelete = useCallback(() => {\n console.log('Delete clicked', selectedItems)\n }, [selectedItems])\n\n const handleSyncCdn = useCallback(() => {\n console.log('Sync CDN clicked', selectedItems)\n }, [selectedItems])\n\n const handleScan = useCallback(() => {\n console.log('Scan clicked')\n }, [])\n\n const hasSelection = selectedItems.size > 0\n\n return (\n <div css={styles.toolbar}>\n {/* Hidden file input for upload */}\n <input\n ref={fileInputRef}\n type=\"file\"\n multiple\n accept=\"image/*\"\n onChange={handleFileChange}\n style={{ display: 'none' }}\n />\n \n <div css={styles.left}>\n <ToolbarButton \n onClick={handleUpload} \n icon=\"upload\" \n label={uploading ? 'Uploading...' : 'Upload'} \n disabled={uploading}\n />\n <ToolbarButton\n onClick={handleReprocess}\n icon=\"refresh\"\n label=\"Reprocess\"\n disabled={!hasSelection}\n />\n <ToolbarButton\n onClick={handleDelete}\n icon=\"trash\"\n label=\"Delete\"\n disabled={!hasSelection}\n variant=\"danger\"\n />\n <ToolbarButton\n onClick={handleSyncCdn}\n icon=\"cloud\"\n label=\"Sync CDN\"\n disabled={!hasSelection}\n />\n <ToolbarButton onClick={handleScan} icon=\"scan\" label=\"Scan\" />\n </div>\n\n <div css={styles.right}>\n {hasSelection && (\n <span css={styles.selectionCount}>\n {selectedItems.size} selected\n <button css={styles.clearBtn} onClick={clearSelection}>\n Clear\n </button>\n </span>\n )}\n\n <div css={styles.viewToggle}>\n <button\n css={[styles.viewBtn, viewMode === 'grid' && styles.viewBtnActive]}\n onClick={() => setViewMode('grid')}\n aria-label=\"Grid view\"\n >\n <GridIcon />\n </button>\n <button\n css={[styles.viewBtn, viewMode === 'list' && styles.viewBtnActive]}\n onClick={() => setViewMode('list')}\n aria-label=\"List view\"\n >\n <ListIcon />\n </button>\n </div>\n </div>\n </div>\n )\n}\n\ninterface ToolbarButtonProps {\n onClick: () => void\n icon: 'upload' | 'refresh' | 'trash' | 'cloud' | 'scan'\n label: string\n disabled?: boolean\n variant?: 'default' | 'danger'\n}\n\nfunction ToolbarButton({\n onClick,\n icon,\n label,\n disabled,\n variant = 'default',\n}: ToolbarButtonProps) {\n return (\n <button\n css={[styles.btn, variant === 'danger' ? styles.btnDanger : styles.btnDefault]}\n onClick={onClick}\n disabled={disabled}\n >\n <IconComponent icon={icon} />\n {label}\n </button>\n )\n}\n\nfunction IconComponent({ icon }: { icon: string }) {\n switch (icon) {\n case 'upload':\n return (\n <svg css={styles.icon} fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <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\" />\n </svg>\n )\n case 'refresh':\n return (\n <svg css={styles.icon} fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <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\" />\n </svg>\n )\n case 'trash':\n return (\n <svg css={styles.icon} fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <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\" />\n </svg>\n )\n case 'cloud':\n return (\n <svg css={styles.icon} fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <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\" />\n </svg>\n )\n case 'scan':\n return (\n <svg css={styles.icon} fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z\" />\n </svg>\n )\n default:\n return null\n }\n}\n\nfunction GridIcon() {\n return (\n <svg css={styles.icon} fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <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\" />\n </svg>\n )\n}\n\nfunction ListIcon() {\n return (\n <svg css={styles.icon} fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M4 6h16M4 10h16M4 14h16M4 18h16\" />\n </svg>\n )\n}\n","/** @jsxImportSource @emotion/react */\n'use client'\n\nimport { css } from '@emotion/react'\nimport { useStudio } from './StudioContext'\n\nconst styles = {\n container: css`\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 8px 24px;\n background-color: white;\n border-bottom: 1px solid #f3f4f6;\n `,\n backBtn: css`\n padding: 4px;\n background: none;\n border: none;\n border-radius: 4px;\n cursor: pointer;\n transition: background-color 0.15s;\n \n &:hover {\n background-color: #f3f4f6;\n }\n `,\n backIcon: css`\n width: 16px;\n height: 16px;\n color: #6b7280;\n `,\n nav: css`\n display: flex;\n align-items: center;\n gap: 4px;\n font-size: 14px;\n `,\n item: css`\n display: flex;\n align-items: center;\n gap: 4px;\n `,\n separator: css`\n color: #d1d5db;\n `,\n btn: css`\n padding: 2px 4px;\n background: none;\n border: none;\n border-radius: 4px;\n cursor: pointer;\n transition: all 0.15s;\n \n &:hover {\n background-color: #f3f4f6;\n }\n `,\n btnActive: css`\n color: #111827;\n font-weight: 500;\n `,\n btnInactive: css`\n color: #6b7280;\n \n &:hover {\n color: #374151;\n }\n `,\n}\n\nexport function StudioBreadcrumb() {\n const { currentPath, setCurrentPath, navigateUp } = useStudio()\n\n const parts = currentPath.split('/').filter(Boolean)\n\n const handleClick = (index: number) => {\n const newPath = parts.slice(0, index + 1).join('/')\n setCurrentPath(newPath)\n }\n\n return (\n <div css={styles.container}>\n {currentPath !== 'public' && (\n <button css={styles.backBtn} onClick={navigateUp} aria-label=\"Go back\">\n <svg css={styles.backIcon} fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M15 19l-7-7 7-7\" />\n </svg>\n </button>\n )}\n\n <nav css={styles.nav}>\n {parts.map((part, index) => (\n <span key={index} css={styles.item}>\n {index > 0 && <span css={styles.separator}>/</span>}\n <button\n css={[styles.btn, index === parts.length - 1 ? styles.btnActive : styles.btnInactive]}\n onClick={() => handleClick(index)}\n >\n {part}\n </button>\n </span>\n ))}\n </nav>\n </div>\n )\n}\n","/** @jsxImportSource @emotion/react */\n'use client'\n\nimport { useEffect, useState } from 'react'\nimport { css, keyframes } from '@emotion/react'\nimport { useStudio } from './StudioContext'\nimport type { FileItem } from '../types'\n\nconst spin = keyframes`\n to { transform: rotate(360deg); }\n`\n\nconst styles = {\n loading: css`\n display: flex;\n align-items: center;\n justify-content: center;\n height: 256px;\n `,\n spinner: css`\n width: 32px;\n height: 32px;\n border-radius: 50%;\n border: 2px solid transparent;\n border-bottom-color: #9333ea;\n animation: ${spin} 1s linear infinite;\n `,\n empty: css`\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n height: 256px;\n color: #6b7280;\n `,\n emptyIcon: css`\n width: 48px;\n height: 48px;\n margin-bottom: 16px;\n `,\n emptyText: css`\n font-size: 14px;\n margin: 0;\n `,\n grid: css`\n display: grid;\n grid-template-columns: repeat(2, 1fr);\n gap: 16px;\n \n @media (min-width: 640px) { grid-template-columns: repeat(3, 1fr); }\n @media (min-width: 768px) { grid-template-columns: repeat(4, 1fr); }\n @media (min-width: 1024px) { grid-template-columns: repeat(5, 1fr); }\n @media (min-width: 1280px) { grid-template-columns: repeat(6, 1fr); }\n `,\n item: css`\n position: relative;\n border-radius: 8px;\n border: 2px solid transparent;\n overflow: hidden;\n cursor: pointer;\n transition: all 0.15s;\n background-color: #f9fafb;\n \n &:hover {\n border-color: #e5e7eb;\n }\n `,\n itemSelected: css`\n border-color: #a855f7;\n background-color: #faf5ff;\n `,\n checkbox: css`\n position: absolute;\n top: 8px;\n left: 8px;\n z-index: 10;\n width: 16px;\n height: 16px;\n accent-color: #9333ea;\n `,\n cdnBadge: css`\n position: absolute;\n top: 8px;\n right: 8px;\n z-index: 10;\n background-color: #dcfce7;\n color: #15803d;\n font-size: 12px;\n padding: 2px 6px;\n border-radius: 9999px;\n `,\n content: css`\n aspect-ratio: 1;\n display: flex;\n align-items: center;\n justify-content: center;\n padding: 16px;\n `,\n folderIcon: css`\n width: 64px;\n height: 64px;\n color: #facc15;\n `,\n image: css`\n max-width: 100%;\n max-height: 100%;\n object-fit: contain;\n border-radius: 4px;\n `,\n label: css`\n padding: 6px 8px;\n background-color: white;\n border-top: 1px solid #e5e7eb;\n `,\n name: css`\n font-size: 12px;\n color: #374151;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n margin: 0;\n `,\n size: css`\n font-size: 12px;\n color: #9ca3af;\n margin: 0;\n `,\n}\n\nexport function StudioFileGrid() {\n const { currentPath, setCurrentPath, selectedItems, toggleSelection, refreshKey } = useStudio()\n const [items, setItems] = useState<FileItem[]>([])\n const [loading, setLoading] = useState(true)\n\n useEffect(() => {\n async function loadItems() {\n setLoading(true)\n try {\n const response = await fetch(`/api/studio/list?path=${encodeURIComponent(currentPath)}`)\n if (response.ok) {\n const data = await response.json()\n setItems(data.items || [])\n }\n } catch (error) {\n console.error('Failed to load items:', error)\n }\n setLoading(false)\n }\n loadItems()\n }, [currentPath, refreshKey])\n\n if (loading) {\n return (\n <div css={styles.loading}>\n <div css={styles.spinner} />\n </div>\n )\n }\n\n if (items.length === 0) {\n return (\n <div css={styles.empty}>\n <svg css={styles.emptyIcon} fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <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\" />\n </svg>\n <p css={styles.emptyText}>No files in this folder</p>\n <p css={styles.emptyText}>Upload images to get started</p>\n </div>\n )\n }\n\n const sortedItems = [...items].sort((a, b) => {\n if (a.type === 'folder' && b.type !== 'folder') return -1\n if (a.type !== 'folder' && b.type === 'folder') return 1\n return a.name.localeCompare(b.name)\n })\n\n return (\n <div css={styles.grid}>\n {sortedItems.map((item) => (\n <GridItem\n key={item.path}\n item={item}\n isSelected={selectedItems.has(item.path)}\n onSelect={() => toggleSelection(item.path)}\n onOpen={() => {\n if (item.type === 'folder') {\n setCurrentPath(item.path)\n }\n }}\n />\n ))}\n </div>\n )\n}\n\ninterface GridItemProps {\n item: FileItem\n isSelected: boolean\n onSelect: () => void\n onOpen: () => void\n}\n\nfunction GridItem({ item, isSelected, onSelect, onOpen }: GridItemProps) {\n const isFolder = item.type === 'folder'\n\n return (\n <div css={[styles.item, isSelected && styles.itemSelected]} onDoubleClick={onOpen}>\n <input\n type=\"checkbox\"\n css={styles.checkbox}\n checked={isSelected}\n onChange={onSelect}\n onClick={(e) => e.stopPropagation()}\n />\n\n {item.cdnSynced && <span css={styles.cdnBadge}>CDN</span>}\n\n <div css={styles.content}>\n {isFolder ? (\n <svg css={styles.folderIcon} fill=\"currentColor\" viewBox=\"0 0 24 24\">\n <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\" />\n </svg>\n ) : (\n <img\n css={styles.image}\n src={item.path.replace('public', '')}\n alt={item.name}\n loading=\"lazy\"\n />\n )}\n </div>\n\n <div css={styles.label}>\n <p css={styles.name} title={item.name}>{item.name}</p>\n {item.size && <p css={styles.size}>{formatFileSize(item.size)}</p>}\n </div>\n </div>\n )\n}\n\nfunction formatFileSize(bytes: number): string {\n if (bytes < 1024) return `${bytes} B`\n if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`\n return `${(bytes / (1024 * 1024)).toFixed(1)} MB`\n}\n","/** @jsxImportSource @emotion/react */\n'use client'\n\nimport { useEffect, useState } from 'react'\nimport { css, keyframes } from '@emotion/react'\nimport { useStudio } from './StudioContext'\nimport type { FileItem } from '../types'\n\nconst spin = keyframes`\n to { transform: rotate(360deg); }\n`\n\nconst styles = {\n loading: css`\n display: flex;\n align-items: center;\n justify-content: center;\n height: 256px;\n `,\n spinner: css`\n width: 32px;\n height: 32px;\n border-radius: 50%;\n border: 2px solid transparent;\n border-bottom-color: #9333ea;\n animation: ${spin} 1s linear infinite;\n `,\n empty: css`\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n height: 256px;\n color: #6b7280;\n `,\n table: css`\n width: 100%;\n border-collapse: collapse;\n `,\n th: css`\n text-align: left;\n font-size: 12px;\n color: #6b7280;\n text-transform: uppercase;\n letter-spacing: 0.05em;\n padding-bottom: 8px;\n font-weight: normal;\n `,\n thCheckbox: css`\n width: 32px;\n `,\n thSize: css`\n width: 96px;\n `,\n thDimensions: css`\n width: 128px;\n `,\n thCdn: css`\n width: 96px;\n `,\n tbody: css`\n border-top: 1px solid #f3f4f6;\n `,\n row: css`\n cursor: pointer;\n transition: background-color 0.15s;\n \n &:hover {\n background-color: #f9fafb;\n }\n `,\n rowSelected: css`\n background-color: #faf5ff;\n `,\n td: css`\n padding: 8px 0;\n border-bottom: 1px solid #f3f4f6;\n `,\n checkbox: css`\n width: 16px;\n height: 16px;\n accent-color: #9333ea;\n `,\n nameCell: css`\n display: flex;\n align-items: center;\n gap: 8px;\n `,\n folderIcon: css`\n width: 20px;\n height: 20px;\n color: #facc15;\n `,\n fileIcon: css`\n width: 20px;\n height: 20px;\n color: #9ca3af;\n `,\n name: css`\n font-size: 14px;\n color: #111827;\n `,\n meta: css`\n font-size: 14px;\n color: #6b7280;\n `,\n cdnBadge: css`\n display: inline-flex;\n align-items: center;\n gap: 4px;\n font-size: 12px;\n color: #15803d;\n `,\n cdnIcon: css`\n width: 12px;\n height: 12px;\n `,\n cdnEmpty: css`\n font-size: 12px;\n color: #9ca3af;\n `,\n}\n\nexport function StudioFileList() {\n const { currentPath, setCurrentPath, selectedItems, toggleSelection, refreshKey } = useStudio()\n const [items, setItems] = useState<FileItem[]>([])\n const [loading, setLoading] = useState(true)\n\n useEffect(() => {\n async function loadItems() {\n setLoading(true)\n try {\n const response = await fetch(`/api/studio/list?path=${encodeURIComponent(currentPath)}`)\n if (response.ok) {\n const data = await response.json()\n setItems(data.items || [])\n }\n } catch (error) {\n console.error('Failed to load items:', error)\n }\n setLoading(false)\n }\n loadItems()\n }, [currentPath, refreshKey])\n\n if (loading) {\n return (\n <div css={styles.loading}>\n <div css={styles.spinner} />\n </div>\n )\n }\n\n if (items.length === 0) {\n return (\n <div css={styles.empty}>\n <p>No files in this folder</p>\n </div>\n )\n }\n\n const sortedItems = [...items].sort((a, b) => {\n if (a.type === 'folder' && b.type !== 'folder') return -1\n if (a.type !== 'folder' && b.type === 'folder') return 1\n return a.name.localeCompare(b.name)\n })\n\n return (\n <table css={styles.table}>\n <thead>\n <tr>\n <th css={[styles.th, styles.thCheckbox]}></th>\n <th css={styles.th}>Name</th>\n <th css={[styles.th, styles.thSize]}>Size</th>\n <th css={[styles.th, styles.thDimensions]}>Dimensions</th>\n <th css={[styles.th, styles.thCdn]}>CDN</th>\n </tr>\n </thead>\n <tbody css={styles.tbody}>\n {sortedItems.map((item) => (\n <ListRow\n key={item.path}\n item={item}\n isSelected={selectedItems.has(item.path)}\n onSelect={() => toggleSelection(item.path)}\n onOpen={() => {\n if (item.type === 'folder') {\n setCurrentPath(item.path)\n }\n }}\n />\n ))}\n </tbody>\n </table>\n )\n}\n\ninterface ListRowProps {\n item: FileItem\n isSelected: boolean\n onSelect: () => void\n onOpen: () => void\n}\n\nfunction ListRow({ item, isSelected, onSelect, onOpen }: ListRowProps) {\n const isFolder = item.type === 'folder'\n\n return (\n <tr css={[styles.row, isSelected && styles.rowSelected]} onDoubleClick={onOpen}>\n <td css={styles.td}>\n <input\n type=\"checkbox\"\n css={styles.checkbox}\n checked={isSelected}\n onChange={onSelect}\n onClick={(e) => e.stopPropagation()}\n />\n </td>\n <td css={styles.td}>\n <div css={styles.nameCell}>\n {isFolder ? (\n <svg css={styles.folderIcon} fill=\"currentColor\" viewBox=\"0 0 24 24\">\n <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\" />\n </svg>\n ) : (\n <svg css={styles.fileIcon} fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <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\" />\n </svg>\n )}\n <span css={styles.name}>{item.name}</span>\n </div>\n </td>\n <td css={[styles.td, styles.meta]}>\n {item.size ? formatFileSize(item.size) : '--'}\n </td>\n <td css={[styles.td, styles.meta]}>\n {item.dimensions ? `${item.dimensions.width}x${item.dimensions.height}` : '--'}\n </td>\n <td css={styles.td}>\n {item.cdnSynced ? (\n <span css={styles.cdnBadge}>\n <svg css={styles.cdnIcon} fill=\"currentColor\" viewBox=\"0 0 20 20\">\n <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\" />\n </svg>\n Synced\n </span>\n ) : (\n <span css={styles.cdnEmpty}>--</span>\n )}\n </td>\n </tr>\n )\n}\n\nfunction formatFileSize(bytes: number): string {\n if (bytes < 1024) return `${bytes} B`\n if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`\n return `${(bytes / (1024 * 1024)).toFixed(1)} MB`\n}\n","/** @jsxImportSource @emotion/react */\n'use client'\n\nimport { css } from '@emotion/react'\nimport { useStudio } from './StudioContext'\n\nconst styles = {\n panel: css`\n width: 320px;\n border-left: 1px solid #e5e7eb;\n background-color: #f9fafb;\n padding: 16px;\n overflow: auto;\n `,\n title: css`\n font-size: 14px;\n font-weight: 500;\n color: #111827;\n margin: 0 0 16px 0;\n `,\n imageContainer: css`\n background-color: white;\n border-radius: 8px;\n border: 1px solid #e5e7eb;\n padding: 8px;\n margin-bottom: 16px;\n `,\n image: css`\n width: 100%;\n height: auto;\n border-radius: 4px;\n `,\n info: css`\n display: flex;\n flex-direction: column;\n gap: 12px;\n `,\n row: css`\n display: flex;\n justify-content: space-between;\n font-size: 12px;\n `,\n label: css`\n color: #6b7280;\n `,\n value: css`\n color: #111827;\n `,\n valueTruncate: css`\n max-width: 128px;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n `,\n section: css`\n padding-top: 8px;\n border-top: 1px solid #e5e7eb;\n `,\n sectionTitle: css`\n font-size: 12px;\n font-weight: 500;\n color: #6b7280;\n margin: 0 0 8px 0;\n `,\n cdnStatus: css`\n display: flex;\n align-items: center;\n gap: 8px;\n font-size: 12px;\n color: #16a34a;\n `,\n cdnIcon: css`\n width: 16px;\n height: 16px;\n `,\n copyBtn: css`\n margin-top: 8px;\n font-size: 12px;\n color: #9333ea;\n background: none;\n border: none;\n cursor: pointer;\n padding: 0;\n \n &:hover {\n text-decoration: underline;\n }\n `,\n colorSwatch: css`\n margin-top: 8px;\n height: 32px;\n border-radius: 4px;\n `,\n actions: css`\n margin-top: 16px;\n padding-top: 16px;\n border-top: 1px solid #e5e7eb;\n display: flex;\n flex-direction: column;\n gap: 8px;\n `,\n actionBtn: css`\n width: 100%;\n padding: 8px 12px;\n font-size: 14px;\n background-color: white;\n border: 1px solid #e5e7eb;\n border-radius: 8px;\n cursor: pointer;\n transition: background-color 0.15s;\n color: #374151;\n \n &:hover {\n background-color: #f9fafb;\n }\n `,\n actionBtnDanger: css`\n color: #dc2626;\n \n &:hover {\n background-color: #fef2f2;\n }\n `,\n}\n\nexport function StudioPreview() {\n const { selectedItems, meta } = useStudio()\n\n if (selectedItems.size !== 1) {\n return null\n }\n\n const selectedPath = Array.from(selectedItems)[0]\n const imageKey = selectedPath\n .replace(/^public\\/images\\//, '')\n .replace(/^public\\/originals\\//, '')\n\n const imageData = meta?.images?.[imageKey]\n\n return (\n <div css={styles.panel}>\n <h3 css={styles.title}>Preview</h3>\n\n <div css={styles.imageContainer}>\n <img\n css={styles.image}\n src={selectedPath.replace('public', '')}\n alt=\"Preview\"\n />\n </div>\n\n <div css={styles.info}>\n <InfoRow label=\"Filename\" value={selectedPath.split('/').pop() || ''} />\n\n {imageData && (\n <>\n <InfoRow\n label=\"Original\"\n value={`${imageData.original.width}x${imageData.original.height}`}\n />\n <InfoRow\n label=\"File size\"\n value={formatFileSize(imageData.original.fileSize)}\n />\n\n <div css={styles.section}>\n <p css={styles.sectionTitle}>Generated sizes</p>\n {Object.entries(imageData.sizes).map(([size, data]) => (\n <InfoRow key={size} label={size} value={`${data.width}x${data.height}`} />\n ))}\n </div>\n\n {imageData.cdn?.synced && (\n <div css={styles.section}>\n <p css={styles.sectionTitle}>CDN</p>\n <div css={styles.cdnStatus}>\n <svg css={styles.cdnIcon} fill=\"currentColor\" viewBox=\"0 0 20 20\">\n <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\" />\n </svg>\n Synced to CDN\n </div>\n <button\n css={styles.copyBtn}\n onClick={() => {\n navigator.clipboard.writeText(`${imageData.cdn?.baseUrl}${imageData.sizes.full.path}`)\n }}\n >\n Copy CDN URL\n </button>\n </div>\n )}\n\n {imageData.blurhash && (\n <div css={styles.section}>\n <InfoRow label=\"Blurhash\" value={imageData.blurhash} truncate />\n <div\n css={styles.colorSwatch}\n style={{ backgroundColor: imageData.dominantColor }}\n title={`Dominant color: ${imageData.dominantColor}`}\n />\n </div>\n )}\n </>\n )}\n </div>\n\n <div css={styles.actions}>\n <button css={styles.actionBtn}>Rename</button>\n <button css={[styles.actionBtn, styles.actionBtnDanger]}>Delete</button>\n </div>\n </div>\n )\n}\n\nfunction InfoRow({ label, value, truncate }: { label: string; value: string; truncate?: boolean }) {\n return (\n <div css={styles.row}>\n <span css={styles.label}>{label}</span>\n <span css={[styles.value, truncate && styles.valueTruncate]} title={truncate ? value : undefined}>\n {value}\n </span>\n </div>\n )\n}\n\nfunction formatFileSize(bytes: number): string {\n if (bytes < 1024) return `${bytes} B`\n if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`\n return `${(bytes / (1024 * 1024)).toFixed(1)} MB`\n}\n","/** @jsxImportSource @emotion/react */\n'use client'\n\nimport { useState } from 'react'\nimport { css } from '@emotion/react'\n\nconst styles = {\n btn: css`\n padding: 8px;\n background: none;\n border: none;\n border-radius: 8px;\n cursor: pointer;\n transition: background-color 0.15s;\n \n &:hover {\n background-color: #f3f4f6;\n }\n `,\n icon: css`\n width: 20px;\n height: 20px;\n color: #6b7280;\n `,\n overlay: css`\n position: fixed;\n top: 0;\n right: 0;\n bottom: 0;\n left: 0;\n z-index: 10000;\n display: flex;\n align-items: center;\n justify-content: center;\n `,\n backdrop: css`\n position: absolute;\n top: 0;\n right: 0;\n bottom: 0;\n left: 0;\n background-color: rgba(0, 0, 0, 0.3);\n `,\n panel: css`\n position: relative;\n background-color: white;\n border-radius: 12px;\n box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.25);\n width: 100%;\n max-width: 512px;\n padding: 24px;\n `,\n header: css`\n display: flex;\n align-items: center;\n justify-content: space-between;\n margin-bottom: 24px;\n `,\n title: css`\n font-size: 18px;\n font-weight: 600;\n margin: 0;\n `,\n closeBtn: css`\n padding: 4px;\n background: none;\n border: none;\n border-radius: 8px;\n cursor: pointer;\n \n &:hover {\n background-color: #f3f4f6;\n }\n `,\n sections: css`\n display: flex;\n flex-direction: column;\n gap: 24px;\n `,\n sectionTitle: css`\n font-size: 14px;\n font-weight: 500;\n color: #111827;\n margin: 0 0 12px 0;\n `,\n description: css`\n font-size: 12px;\n color: #6b7280;\n margin: 0 0 12px 0;\n `,\n code: css`\n background-color: #f9fafb;\n border-radius: 8px;\n padding: 12px;\n font-family: monospace;\n font-size: 12px;\n color: #4b5563;\n `,\n codeLine: css`\n margin: 0 0 4px 0;\n \n &:last-child {\n margin: 0;\n }\n `,\n input: css`\n width: 100%;\n padding: 8px 12px;\n border: 1px solid #e5e7eb;\n border-radius: 8px;\n font-size: 14px;\n \n &:focus {\n outline: none;\n box-shadow: 0 0 0 2px #a855f7;\n }\n `,\n grid: css`\n display: grid;\n grid-template-columns: repeat(3, 1fr);\n gap: 12px;\n `,\n label: css`\n font-size: 12px;\n color: #6b7280;\n display: block;\n margin-bottom: 4px;\n `,\n footer: css`\n margin-top: 24px;\n display: flex;\n justify-content: flex-end;\n gap: 12px;\n `,\n cancelBtn: css`\n padding: 8px 16px;\n font-size: 14px;\n color: #4b5563;\n background: none;\n border: none;\n border-radius: 8px;\n cursor: pointer;\n \n &:hover {\n background-color: #f3f4f6;\n }\n `,\n saveBtn: css`\n padding: 8px 16px;\n font-size: 14px;\n color: white;\n background-color: #9333ea;\n border: none;\n border-radius: 8px;\n cursor: pointer;\n \n &:hover {\n background-color: #7c3aed;\n }\n `,\n}\n\nexport function StudioSettings() {\n const [isOpen, setIsOpen] = useState(false)\n\n return (\n <>\n <button css={styles.btn} onClick={() => setIsOpen(true)} aria-label=\"Settings\">\n <svg\n css={styles.icon}\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth={2}\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n >\n <circle cx=\"12\" cy=\"12\" r=\"3\" />\n <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\" />\n </svg>\n </button>\n\n {isOpen && <SettingsPanel onClose={() => setIsOpen(false)} />}\n </>\n )\n}\n\nfunction SettingsPanel({ onClose }: { onClose: () => void }) {\n return (\n <div css={styles.overlay}>\n <div css={styles.backdrop} onClick={onClose} />\n\n <div css={styles.panel}>\n <div css={styles.header}>\n <h2 css={styles.title}>Settings</h2>\n <button css={styles.closeBtn} onClick={onClose}>\n <svg css={styles.icon} fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M6 18L18 6M6 6l12 12\" />\n </svg>\n </button>\n </div>\n\n <div css={styles.sections}>\n <section>\n <h3 css={styles.sectionTitle}>Cloudflare R2</h3>\n <p css={styles.description}>Configure in .env.local file:</p>\n <div css={styles.code}>\n <p css={styles.codeLine}>CLOUDFLARE_R2_ACCOUNT_ID</p>\n <p css={styles.codeLine}>CLOUDFLARE_R2_ACCESS_KEY_ID</p>\n <p css={styles.codeLine}>CLOUDFLARE_R2_SECRET_ACCESS_KEY</p>\n <p css={styles.codeLine}>CLOUDFLARE_R2_BUCKET_NAME</p>\n <p css={styles.codeLine}>CLOUDFLARE_R2_PUBLIC_URL</p>\n </div>\n </section>\n\n <section>\n <h3 css={styles.sectionTitle}>Custom CDN URL</h3>\n <p css={styles.description}>Override the default R2 URL with a custom domain:</p>\n <input css={styles.input} type=\"text\" placeholder=\"https://cdn.yourdomain.com\" />\n </section>\n\n <section>\n <h3 css={styles.sectionTitle}>Thumbnail Sizes</h3>\n <div css={styles.grid}>\n <div>\n <label css={styles.label}>Small</label>\n <input css={styles.input} type=\"number\" defaultValue={300} />\n </div>\n <div>\n <label css={styles.label}>Medium</label>\n <input css={styles.input} type=\"number\" defaultValue={700} />\n </div>\n <div>\n <label css={styles.label}>Large</label>\n <input css={styles.input} type=\"number\" defaultValue={1400} />\n </div>\n </div>\n </section>\n </div>\n\n <div css={styles.footer}>\n <button css={styles.cancelBtn} onClick={onClose}>Cancel</button>\n <button css={styles.saveBtn}>Save Changes</button>\n </div>\n </div>\n </div>\n )\n}\n"],"mappings":"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","names":["useEffect","useCallback","useState","css","css","jsx","jsxs","styles","css","useState","css","jsx","jsxs","styles","css","useState","useEffect","useState","css","keyframes","jsx","jsxs","spin","keyframes","styles","css","useState","useEffect","formatFileSize","css","jsx","jsxs","styles","css","formatFileSize","useState","css","Fragment","jsx","jsxs","styles","jsx","jsxs","styles","css","useState","useCallback","useEffect"]}
|
|
@@ -34,6 +34,9 @@ var defaultState = {
|
|
|
34
34
|
},
|
|
35
35
|
isLoading: false,
|
|
36
36
|
setIsLoading: () => {
|
|
37
|
+
},
|
|
38
|
+
refreshKey: 0,
|
|
39
|
+
triggerRefresh: () => {
|
|
37
40
|
}
|
|
38
41
|
};
|
|
39
42
|
var StudioContext = _react.createContext.call(void 0, defaultState);
|
|
@@ -142,10 +145,42 @@ var styles = {
|
|
|
142
145
|
`
|
|
143
146
|
};
|
|
144
147
|
function StudioToolbar() {
|
|
145
|
-
const { selectedItems, viewMode, setViewMode, clearSelection } = useStudio();
|
|
148
|
+
const { selectedItems, viewMode, setViewMode, clearSelection, currentPath, triggerRefresh } = useStudio();
|
|
149
|
+
const fileInputRef = _react.useRef.call(void 0, null);
|
|
150
|
+
const [uploading, setUploading] = _react.useState.call(void 0, false);
|
|
146
151
|
const handleUpload = _react.useCallback.call(void 0, () => {
|
|
147
|
-
|
|
152
|
+
_optionalChain([fileInputRef, 'access', _ => _.current, 'optionalAccess', _2 => _2.click, 'call', _3 => _3()]);
|
|
148
153
|
}, []);
|
|
154
|
+
const handleFileChange = _react.useCallback.call(void 0, async (e) => {
|
|
155
|
+
const files = e.target.files;
|
|
156
|
+
if (!files || files.length === 0) return;
|
|
157
|
+
setUploading(true);
|
|
158
|
+
try {
|
|
159
|
+
for (const file of Array.from(files)) {
|
|
160
|
+
const formData = new FormData();
|
|
161
|
+
formData.append("file", file);
|
|
162
|
+
formData.append("path", currentPath);
|
|
163
|
+
const response = await fetch("/api/studio/upload", {
|
|
164
|
+
method: "POST",
|
|
165
|
+
body: formData
|
|
166
|
+
});
|
|
167
|
+
if (!response.ok) {
|
|
168
|
+
const error = await response.json();
|
|
169
|
+
console.error("Upload failed:", error);
|
|
170
|
+
alert(`Failed to upload ${file.name}: ${error.error || "Unknown error"}`);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
triggerRefresh();
|
|
174
|
+
} catch (error) {
|
|
175
|
+
console.error("Upload error:", error);
|
|
176
|
+
alert("Upload failed. Check console for details.");
|
|
177
|
+
} finally {
|
|
178
|
+
setUploading(false);
|
|
179
|
+
if (fileInputRef.current) {
|
|
180
|
+
fileInputRef.current.value = "";
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}, [currentPath, triggerRefresh]);
|
|
149
184
|
const handleReprocess = _react.useCallback.call(void 0, () => {
|
|
150
185
|
console.log("Reprocess clicked", selectedItems);
|
|
151
186
|
}, [selectedItems]);
|
|
@@ -160,8 +195,27 @@ function StudioToolbar() {
|
|
|
160
195
|
}, []);
|
|
161
196
|
const hasSelection = selectedItems.size > 0;
|
|
162
197
|
return /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { css: styles.toolbar, children: [
|
|
198
|
+
/* @__PURE__ */ _jsxruntime.jsx.call(void 0,
|
|
199
|
+
"input",
|
|
200
|
+
{
|
|
201
|
+
ref: fileInputRef,
|
|
202
|
+
type: "file",
|
|
203
|
+
multiple: true,
|
|
204
|
+
accept: "image/*",
|
|
205
|
+
onChange: handleFileChange,
|
|
206
|
+
style: { display: "none" }
|
|
207
|
+
}
|
|
208
|
+
),
|
|
163
209
|
/* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { css: styles.left, children: [
|
|
164
|
-
/* @__PURE__ */ _jsxruntime.jsx.call(void 0,
|
|
210
|
+
/* @__PURE__ */ _jsxruntime.jsx.call(void 0,
|
|
211
|
+
ToolbarButton,
|
|
212
|
+
{
|
|
213
|
+
onClick: handleUpload,
|
|
214
|
+
icon: "upload",
|
|
215
|
+
label: uploading ? "Uploading..." : "Upload",
|
|
216
|
+
disabled: uploading
|
|
217
|
+
}
|
|
218
|
+
),
|
|
165
219
|
/* @__PURE__ */ _jsxruntime.jsx.call(void 0,
|
|
166
220
|
ToolbarButton,
|
|
167
221
|
{
|
|
@@ -478,7 +532,7 @@ var styles3 = {
|
|
|
478
532
|
`
|
|
479
533
|
};
|
|
480
534
|
function StudioFileGrid() {
|
|
481
|
-
const { currentPath, setCurrentPath, selectedItems, toggleSelection } = useStudio();
|
|
535
|
+
const { currentPath, setCurrentPath, selectedItems, toggleSelection, refreshKey } = useStudio();
|
|
482
536
|
const [items, setItems] = _react.useState.call(void 0, []);
|
|
483
537
|
const [loading, setLoading] = _react.useState.call(void 0, true);
|
|
484
538
|
_react.useEffect.call(void 0, () => {
|
|
@@ -496,7 +550,7 @@ function StudioFileGrid() {
|
|
|
496
550
|
setLoading(false);
|
|
497
551
|
}
|
|
498
552
|
loadItems();
|
|
499
|
-
}, [currentPath]);
|
|
553
|
+
}, [currentPath, refreshKey]);
|
|
500
554
|
if (loading) {
|
|
501
555
|
return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { css: styles3.loading, children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { css: styles3.spinner }) });
|
|
502
556
|
}
|
|
@@ -680,7 +734,7 @@ var styles4 = {
|
|
|
680
734
|
`
|
|
681
735
|
};
|
|
682
736
|
function StudioFileList() {
|
|
683
|
-
const { currentPath, setCurrentPath, selectedItems, toggleSelection } = useStudio();
|
|
737
|
+
const { currentPath, setCurrentPath, selectedItems, toggleSelection, refreshKey } = useStudio();
|
|
684
738
|
const [items, setItems] = _react.useState.call(void 0, []);
|
|
685
739
|
const [loading, setLoading] = _react.useState.call(void 0, true);
|
|
686
740
|
_react.useEffect.call(void 0, () => {
|
|
@@ -698,7 +752,7 @@ function StudioFileList() {
|
|
|
698
752
|
setLoading(false);
|
|
699
753
|
}
|
|
700
754
|
loadItems();
|
|
701
|
-
}, [currentPath]);
|
|
755
|
+
}, [currentPath, refreshKey]);
|
|
702
756
|
if (loading) {
|
|
703
757
|
return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { css: styles4.loading, children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { css: styles4.spinner }) });
|
|
704
758
|
}
|
|
@@ -893,7 +947,7 @@ function StudioPreview() {
|
|
|
893
947
|
}
|
|
894
948
|
const selectedPath = Array.from(selectedItems)[0];
|
|
895
949
|
const imageKey = selectedPath.replace(/^public\/images\//, "").replace(/^public\/originals\//, "");
|
|
896
|
-
const imageData = _optionalChain([meta, 'optionalAccess',
|
|
950
|
+
const imageData = _optionalChain([meta, 'optionalAccess', _4 => _4.images, 'optionalAccess', _5 => _5[imageKey]]);
|
|
897
951
|
return /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { css: styles5.panel, children: [
|
|
898
952
|
/* @__PURE__ */ _jsxruntime.jsx.call(void 0, "h3", { css: styles5.title, children: "Preview" }),
|
|
899
953
|
/* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { css: styles5.imageContainer, children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
|
|
@@ -925,7 +979,7 @@ function StudioPreview() {
|
|
|
925
979
|
/* @__PURE__ */ _jsxruntime.jsx.call(void 0, "p", { css: styles5.sectionTitle, children: "Generated sizes" }),
|
|
926
980
|
Object.entries(imageData.sizes).map(([size, data]) => /* @__PURE__ */ _jsxruntime.jsx.call(void 0, InfoRow, { label: size, value: `${data.width}x${data.height}` }, size))
|
|
927
981
|
] }),
|
|
928
|
-
_optionalChain([imageData, 'access',
|
|
982
|
+
_optionalChain([imageData, 'access', _6 => _6.cdn, 'optionalAccess', _7 => _7.synced]) && /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { css: styles5.section, children: [
|
|
929
983
|
/* @__PURE__ */ _jsxruntime.jsx.call(void 0, "p", { css: styles5.sectionTitle, children: "CDN" }),
|
|
930
984
|
/* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { css: styles5.cdnStatus, children: [
|
|
931
985
|
/* @__PURE__ */ _jsxruntime.jsx.call(void 0, "svg", { css: styles5.cdnIcon, fill: "currentColor", viewBox: "0 0 20 20", children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "path", { fillRule: "evenodd", d: "M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z", clipRule: "evenodd" }) }),
|
|
@@ -936,7 +990,7 @@ function StudioPreview() {
|
|
|
936
990
|
{
|
|
937
991
|
css: styles5.copyBtn,
|
|
938
992
|
onClick: () => {
|
|
939
|
-
navigator.clipboard.writeText(`${_optionalChain([imageData, 'access',
|
|
993
|
+
navigator.clipboard.writeText(`${_optionalChain([imageData, 'access', _8 => _8.cdn, 'optionalAccess', _9 => _9.baseUrl])}${imageData.sizes.full.path}`);
|
|
940
994
|
},
|
|
941
995
|
children: "Copy CDN URL"
|
|
942
996
|
}
|
|
@@ -1267,6 +1321,10 @@ function StudioUI({ onClose }) {
|
|
|
1267
1321
|
const [viewMode, setViewMode] = _react.useState.call(void 0, "grid");
|
|
1268
1322
|
const [meta, setMeta] = _react.useState.call(void 0, null);
|
|
1269
1323
|
const [isLoading, setIsLoading] = _react.useState.call(void 0, false);
|
|
1324
|
+
const [refreshKey, setRefreshKey] = _react.useState.call(void 0, 0);
|
|
1325
|
+
const triggerRefresh = _react.useCallback.call(void 0, () => {
|
|
1326
|
+
setRefreshKey((k) => k + 1);
|
|
1327
|
+
}, []);
|
|
1270
1328
|
const navigateUp = _react.useCallback.call(void 0, () => {
|
|
1271
1329
|
if (currentPath === "public") return;
|
|
1272
1330
|
const parts = currentPath.split("/");
|
|
@@ -1329,7 +1387,9 @@ function StudioUI({ onClose }) {
|
|
|
1329
1387
|
meta,
|
|
1330
1388
|
setMeta,
|
|
1331
1389
|
isLoading,
|
|
1332
|
-
setIsLoading
|
|
1390
|
+
setIsLoading,
|
|
1391
|
+
refreshKey,
|
|
1392
|
+
triggerRefresh
|
|
1333
1393
|
};
|
|
1334
1394
|
return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, StudioContext.Provider, { value: contextValue, children: /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { css: styles7.container, children: [
|
|
1335
1395
|
/* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { css: styles7.header, children: [
|
|
@@ -1379,4 +1439,4 @@ var StudioUI_default = StudioUI;
|
|
|
1379
1439
|
|
|
1380
1440
|
|
|
1381
1441
|
exports.StudioUI = StudioUI; exports.default = StudioUI_default;
|
|
1382
|
-
//# sourceMappingURL=StudioUI-
|
|
1442
|
+
//# sourceMappingURL=StudioUI-Z3DOQO7P.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["/Users/chrisb/Sites/studio/dist/StudioUI-Z3DOQO7P.js","../src/components/StudioUI.tsx","../src/components/StudioContext.tsx","../src/components/StudioToolbar.tsx","../src/components/StudioBreadcrumb.tsx","../src/components/StudioFileGrid.tsx","../src/components/StudioFileList.tsx","../src/components/StudioPreview.tsx","../src/components/StudioSettings.tsx"],"names":["css","jsx","styles","keyframes","jsxs","useCallback"],"mappings":"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","file":"/Users/chrisb/Sites/studio/dist/StudioUI-Z3DOQO7P.js","sourcesContent":[null,"/** @jsxImportSource @emotion/react */\n'use client'\n\nimport { useEffect, useCallback, useState } from 'react'\nimport { css } from '@emotion/react'\nimport { StudioContext } from './StudioContext'\nimport { StudioToolbar } from './StudioToolbar'\nimport { StudioBreadcrumb } from './StudioBreadcrumb'\nimport { StudioFileGrid } from './StudioFileGrid'\nimport { StudioFileList } from './StudioFileList'\nimport { StudioPreview } from './StudioPreview'\nimport { StudioSettings } from './StudioSettings'\nimport type { FileItem, StudioMeta } from '../types'\n\ninterface StudioUIProps {\n onClose: () => void\n}\n\nconst styles = {\n container: css`\n display: flex;\n flex-direction: column;\n height: 100%;\n font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;\n `,\n header: css`\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 16px 24px;\n border-bottom: 1px solid #e5e7eb;\n `,\n title: css`\n font-size: 20px;\n font-weight: 600;\n color: #111827;\n margin: 0;\n `,\n headerActions: css`\n display: flex;\n align-items: center;\n gap: 8px;\n `,\n closeBtn: css`\n padding: 8px;\n background: none;\n border: none;\n border-radius: 8px;\n cursor: pointer;\n transition: background-color 0.15s;\n \n &:hover {\n background-color: #f3f4f6;\n }\n `,\n closeIcon: css`\n width: 20px;\n height: 20px;\n color: #6b7280;\n `,\n content: css`\n flex: 1;\n display: flex;\n overflow: hidden;\n `,\n fileBrowser: css`\n flex: 1;\n overflow: auto;\n padding: 16px;\n `,\n}\n\n/**\n * Main Studio UI - contains all panels and manages internal state\n * Rendered inside the modal via lazy loading\n */\nexport function StudioUI({ onClose }: StudioUIProps) {\n const [currentPath, setCurrentPathInternal] = useState('public')\n const [selectedItems, setSelectedItems] = useState<Set<string>>(new Set())\n const [viewMode, setViewMode] = useState<'grid' | 'list'>('grid')\n const [meta, setMeta] = useState<StudioMeta | null>(null)\n const [isLoading, setIsLoading] = useState(false)\n const [refreshKey, setRefreshKey] = useState(0)\n\n const triggerRefresh = useCallback(() => {\n setRefreshKey((k) => k + 1)\n }, [])\n\n const navigateUp = useCallback(() => {\n if (currentPath === 'public') return\n const parts = currentPath.split('/')\n parts.pop()\n setCurrentPathInternal(parts.join('/') || 'public')\n setSelectedItems(new Set())\n }, [currentPath])\n\n const setCurrentPath = useCallback((path: string) => {\n setCurrentPathInternal(path)\n setSelectedItems(new Set())\n }, [])\n\n const toggleSelection = useCallback((path: string) => {\n setSelectedItems((prev) => {\n const next = new Set(prev)\n if (next.has(path)) {\n next.delete(path)\n } else {\n next.add(path)\n }\n return next\n })\n }, [])\n\n const selectAll = useCallback((items: FileItem[]) => {\n setSelectedItems(new Set(items.map((item) => item.path)))\n }, [])\n\n const clearSelection = useCallback(() => {\n setSelectedItems(new Set())\n }, [])\n\n const handleKeyDown = useCallback(\n (e: KeyboardEvent) => {\n if (e.key === 'Escape') {\n onClose()\n }\n },\n [onClose]\n )\n\n useEffect(() => {\n document.addEventListener('keydown', handleKeyDown)\n document.body.style.overflow = 'hidden'\n return () => {\n document.removeEventListener('keydown', handleKeyDown)\n document.body.style.overflow = ''\n }\n }, [handleKeyDown])\n\n const contextValue = {\n isOpen: true,\n openStudio: () => {},\n closeStudio: onClose,\n toggleStudio: onClose,\n currentPath,\n setCurrentPath,\n navigateUp,\n selectedItems,\n toggleSelection,\n selectAll,\n clearSelection,\n viewMode,\n setViewMode,\n meta,\n setMeta,\n isLoading,\n setIsLoading,\n refreshKey,\n triggerRefresh,\n }\n\n return (\n <StudioContext.Provider value={contextValue}>\n <div css={styles.container}>\n <div css={styles.header}>\n <h1 css={styles.title}>Studio</h1>\n <div css={styles.headerActions}>\n <StudioSettings />\n <button\n css={styles.closeBtn}\n onClick={onClose}\n aria-label=\"Close Studio\"\n >\n <CloseIcon />\n </button>\n </div>\n </div>\n\n <StudioToolbar />\n <StudioBreadcrumb />\n\n <div css={styles.content}>\n <div css={styles.fileBrowser}>\n {viewMode === 'grid' ? <StudioFileGrid /> : <StudioFileList />}\n </div>\n <StudioPreview />\n </div>\n </div>\n </StudioContext.Provider>\n )\n}\n\nfunction CloseIcon() {\n return (\n <svg\n css={styles.closeIcon}\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth={2}\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n >\n <line x1=\"18\" y1=\"6\" x2=\"6\" y2=\"18\" />\n <line x1=\"6\" y1=\"6\" x2=\"18\" y2=\"18\" />\n </svg>\n )\n}\n\nexport default StudioUI\n","'use client'\n\nimport { createContext, useContext } from 'react'\nimport type { FileItem, StudioMeta } from '../types'\n\n/**\n * Studio state interface\n * State is managed by StudioUI and provided to all child components\n */\nexport interface StudioState {\n isOpen: boolean\n openStudio: () => void\n closeStudio: () => void\n toggleStudio: () => void\n\n // Navigation\n currentPath: string\n setCurrentPath: (path: string) => void\n navigateUp: () => void\n\n // Selection\n selectedItems: Set<string>\n toggleSelection: (path: string) => void\n selectAll: (items: FileItem[]) => void\n clearSelection: () => void\n\n // View\n viewMode: 'grid' | 'list'\n setViewMode: (mode: 'grid' | 'list') => void\n\n // Meta\n meta: StudioMeta | null\n setMeta: (meta: StudioMeta) => void\n\n // Loading\n isLoading: boolean\n setIsLoading: (loading: boolean) => void\n\n // Refresh trigger\n refreshKey: number\n triggerRefresh: () => void\n}\n\nconst defaultState: StudioState = {\n isOpen: false,\n openStudio: () => {},\n closeStudio: () => {},\n toggleStudio: () => {},\n currentPath: 'public',\n setCurrentPath: () => {},\n navigateUp: () => {},\n selectedItems: new Set(),\n toggleSelection: () => {},\n selectAll: () => {},\n clearSelection: () => {},\n viewMode: 'grid',\n setViewMode: () => {},\n meta: null,\n setMeta: () => {},\n isLoading: false,\n setIsLoading: () => {},\n refreshKey: 0,\n triggerRefresh: () => {},\n}\n\nexport const StudioContext = createContext<StudioState>(defaultState)\n\n/**\n * Hook to access Studio state from child components\n */\nexport function useStudio() {\n return useContext(StudioContext)\n}\n","/** @jsxImportSource @emotion/react */\n'use client'\n\nimport { useCallback, useRef, useState } from 'react'\nimport { css } from '@emotion/react'\nimport { useStudio } from './StudioContext'\n\nconst styles = {\n toolbar: css`\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 12px 24px;\n background-color: #f9fafb;\n border-bottom: 1px solid #e5e7eb;\n `,\n left: css`\n display: flex;\n align-items: center;\n gap: 8px;\n `,\n right: css`\n display: flex;\n align-items: center;\n gap: 16px;\n `,\n btn: css`\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 8px 12px;\n border-radius: 8px;\n font-size: 14px;\n font-weight: 500;\n background: none;\n border: none;\n cursor: pointer;\n transition: background-color 0.15s;\n \n &:disabled {\n cursor: not-allowed;\n opacity: 0.5;\n }\n `,\n btnDefault: css`\n color: #374151;\n \n &:hover:not(:disabled) {\n background-color: white;\n }\n `,\n btnDanger: css`\n color: #dc2626;\n \n &:hover:not(:disabled) {\n background-color: #fef2f2;\n }\n `,\n icon: css`\n width: 16px;\n height: 16px;\n `,\n selectionCount: css`\n font-size: 14px;\n color: #4b5563;\n `,\n clearBtn: css`\n margin-left: 8px;\n color: #9333ea;\n background: none;\n border: none;\n cursor: pointer;\n font-size: 14px;\n \n &:hover {\n text-decoration: underline;\n }\n `,\n viewToggle: css`\n display: flex;\n align-items: center;\n background-color: white;\n border: 1px solid #e5e7eb;\n border-radius: 8px;\n overflow: hidden;\n `,\n viewBtn: css`\n padding: 8px;\n background: none;\n border: none;\n cursor: pointer;\n color: #6b7280;\n transition: all 0.15s;\n \n &:hover {\n background-color: #f9fafb;\n }\n `,\n viewBtnActive: css`\n background-color: #f3e8ff;\n color: #7c3aed;\n `,\n}\n\nexport function StudioToolbar() {\n const { selectedItems, viewMode, setViewMode, clearSelection, currentPath, triggerRefresh } = useStudio()\n const fileInputRef = useRef<HTMLInputElement>(null)\n const [uploading, setUploading] = useState(false)\n\n const handleUpload = useCallback(() => {\n fileInputRef.current?.click()\n }, [])\n\n const handleFileChange = useCallback(async (e: React.ChangeEvent<HTMLInputElement>) => {\n const files = e.target.files\n if (!files || files.length === 0) return\n\n setUploading(true)\n try {\n for (const file of Array.from(files)) {\n const formData = new FormData()\n formData.append('file', file)\n formData.append('path', currentPath)\n\n const response = await fetch('/api/studio/upload', {\n method: 'POST',\n body: formData,\n })\n\n if (!response.ok) {\n const error = await response.json()\n console.error('Upload failed:', error)\n alert(`Failed to upload ${file.name}: ${error.error || 'Unknown error'}`)\n }\n }\n triggerRefresh()\n } catch (error) {\n console.error('Upload error:', error)\n alert('Upload failed. Check console for details.')\n } finally {\n setUploading(false)\n // Reset input so same file can be uploaded again\n if (fileInputRef.current) {\n fileInputRef.current.value = ''\n }\n }\n }, [currentPath, triggerRefresh])\n\n const handleReprocess = useCallback(() => {\n console.log('Reprocess clicked', selectedItems)\n }, [selectedItems])\n\n const handleDelete = useCallback(() => {\n console.log('Delete clicked', selectedItems)\n }, [selectedItems])\n\n const handleSyncCdn = useCallback(() => {\n console.log('Sync CDN clicked', selectedItems)\n }, [selectedItems])\n\n const handleScan = useCallback(() => {\n console.log('Scan clicked')\n }, [])\n\n const hasSelection = selectedItems.size > 0\n\n return (\n <div css={styles.toolbar}>\n {/* Hidden file input for upload */}\n <input\n ref={fileInputRef}\n type=\"file\"\n multiple\n accept=\"image/*\"\n onChange={handleFileChange}\n style={{ display: 'none' }}\n />\n \n <div css={styles.left}>\n <ToolbarButton \n onClick={handleUpload} \n icon=\"upload\" \n label={uploading ? 'Uploading...' : 'Upload'} \n disabled={uploading}\n />\n <ToolbarButton\n onClick={handleReprocess}\n icon=\"refresh\"\n label=\"Reprocess\"\n disabled={!hasSelection}\n />\n <ToolbarButton\n onClick={handleDelete}\n icon=\"trash\"\n label=\"Delete\"\n disabled={!hasSelection}\n variant=\"danger\"\n />\n <ToolbarButton\n onClick={handleSyncCdn}\n icon=\"cloud\"\n label=\"Sync CDN\"\n disabled={!hasSelection}\n />\n <ToolbarButton onClick={handleScan} icon=\"scan\" label=\"Scan\" />\n </div>\n\n <div css={styles.right}>\n {hasSelection && (\n <span css={styles.selectionCount}>\n {selectedItems.size} selected\n <button css={styles.clearBtn} onClick={clearSelection}>\n Clear\n </button>\n </span>\n )}\n\n <div css={styles.viewToggle}>\n <button\n css={[styles.viewBtn, viewMode === 'grid' && styles.viewBtnActive]}\n onClick={() => setViewMode('grid')}\n aria-label=\"Grid view\"\n >\n <GridIcon />\n </button>\n <button\n css={[styles.viewBtn, viewMode === 'list' && styles.viewBtnActive]}\n onClick={() => setViewMode('list')}\n aria-label=\"List view\"\n >\n <ListIcon />\n </button>\n </div>\n </div>\n </div>\n )\n}\n\ninterface ToolbarButtonProps {\n onClick: () => void\n icon: 'upload' | 'refresh' | 'trash' | 'cloud' | 'scan'\n label: string\n disabled?: boolean\n variant?: 'default' | 'danger'\n}\n\nfunction ToolbarButton({\n onClick,\n icon,\n label,\n disabled,\n variant = 'default',\n}: ToolbarButtonProps) {\n return (\n <button\n css={[styles.btn, variant === 'danger' ? styles.btnDanger : styles.btnDefault]}\n onClick={onClick}\n disabled={disabled}\n >\n <IconComponent icon={icon} />\n {label}\n </button>\n )\n}\n\nfunction IconComponent({ icon }: { icon: string }) {\n switch (icon) {\n case 'upload':\n return (\n <svg css={styles.icon} fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <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\" />\n </svg>\n )\n case 'refresh':\n return (\n <svg css={styles.icon} fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <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\" />\n </svg>\n )\n case 'trash':\n return (\n <svg css={styles.icon} fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <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\" />\n </svg>\n )\n case 'cloud':\n return (\n <svg css={styles.icon} fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <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\" />\n </svg>\n )\n case 'scan':\n return (\n <svg css={styles.icon} fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z\" />\n </svg>\n )\n default:\n return null\n }\n}\n\nfunction GridIcon() {\n return (\n <svg css={styles.icon} fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <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\" />\n </svg>\n )\n}\n\nfunction ListIcon() {\n return (\n <svg css={styles.icon} fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M4 6h16M4 10h16M4 14h16M4 18h16\" />\n </svg>\n )\n}\n","/** @jsxImportSource @emotion/react */\n'use client'\n\nimport { css } from '@emotion/react'\nimport { useStudio } from './StudioContext'\n\nconst styles = {\n container: css`\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 8px 24px;\n background-color: white;\n border-bottom: 1px solid #f3f4f6;\n `,\n backBtn: css`\n padding: 4px;\n background: none;\n border: none;\n border-radius: 4px;\n cursor: pointer;\n transition: background-color 0.15s;\n \n &:hover {\n background-color: #f3f4f6;\n }\n `,\n backIcon: css`\n width: 16px;\n height: 16px;\n color: #6b7280;\n `,\n nav: css`\n display: flex;\n align-items: center;\n gap: 4px;\n font-size: 14px;\n `,\n item: css`\n display: flex;\n align-items: center;\n gap: 4px;\n `,\n separator: css`\n color: #d1d5db;\n `,\n btn: css`\n padding: 2px 4px;\n background: none;\n border: none;\n border-radius: 4px;\n cursor: pointer;\n transition: all 0.15s;\n \n &:hover {\n background-color: #f3f4f6;\n }\n `,\n btnActive: css`\n color: #111827;\n font-weight: 500;\n `,\n btnInactive: css`\n color: #6b7280;\n \n &:hover {\n color: #374151;\n }\n `,\n}\n\nexport function StudioBreadcrumb() {\n const { currentPath, setCurrentPath, navigateUp } = useStudio()\n\n const parts = currentPath.split('/').filter(Boolean)\n\n const handleClick = (index: number) => {\n const newPath = parts.slice(0, index + 1).join('/')\n setCurrentPath(newPath)\n }\n\n return (\n <div css={styles.container}>\n {currentPath !== 'public' && (\n <button css={styles.backBtn} onClick={navigateUp} aria-label=\"Go back\">\n <svg css={styles.backIcon} fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M15 19l-7-7 7-7\" />\n </svg>\n </button>\n )}\n\n <nav css={styles.nav}>\n {parts.map((part, index) => (\n <span key={index} css={styles.item}>\n {index > 0 && <span css={styles.separator}>/</span>}\n <button\n css={[styles.btn, index === parts.length - 1 ? styles.btnActive : styles.btnInactive]}\n onClick={() => handleClick(index)}\n >\n {part}\n </button>\n </span>\n ))}\n </nav>\n </div>\n )\n}\n","/** @jsxImportSource @emotion/react */\n'use client'\n\nimport { useEffect, useState } from 'react'\nimport { css, keyframes } from '@emotion/react'\nimport { useStudio } from './StudioContext'\nimport type { FileItem } from '../types'\n\nconst spin = keyframes`\n to { transform: rotate(360deg); }\n`\n\nconst styles = {\n loading: css`\n display: flex;\n align-items: center;\n justify-content: center;\n height: 256px;\n `,\n spinner: css`\n width: 32px;\n height: 32px;\n border-radius: 50%;\n border: 2px solid transparent;\n border-bottom-color: #9333ea;\n animation: ${spin} 1s linear infinite;\n `,\n empty: css`\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n height: 256px;\n color: #6b7280;\n `,\n emptyIcon: css`\n width: 48px;\n height: 48px;\n margin-bottom: 16px;\n `,\n emptyText: css`\n font-size: 14px;\n margin: 0;\n `,\n grid: css`\n display: grid;\n grid-template-columns: repeat(2, 1fr);\n gap: 16px;\n \n @media (min-width: 640px) { grid-template-columns: repeat(3, 1fr); }\n @media (min-width: 768px) { grid-template-columns: repeat(4, 1fr); }\n @media (min-width: 1024px) { grid-template-columns: repeat(5, 1fr); }\n @media (min-width: 1280px) { grid-template-columns: repeat(6, 1fr); }\n `,\n item: css`\n position: relative;\n border-radius: 8px;\n border: 2px solid transparent;\n overflow: hidden;\n cursor: pointer;\n transition: all 0.15s;\n background-color: #f9fafb;\n \n &:hover {\n border-color: #e5e7eb;\n }\n `,\n itemSelected: css`\n border-color: #a855f7;\n background-color: #faf5ff;\n `,\n checkbox: css`\n position: absolute;\n top: 8px;\n left: 8px;\n z-index: 10;\n width: 16px;\n height: 16px;\n accent-color: #9333ea;\n `,\n cdnBadge: css`\n position: absolute;\n top: 8px;\n right: 8px;\n z-index: 10;\n background-color: #dcfce7;\n color: #15803d;\n font-size: 12px;\n padding: 2px 6px;\n border-radius: 9999px;\n `,\n content: css`\n aspect-ratio: 1;\n display: flex;\n align-items: center;\n justify-content: center;\n padding: 16px;\n `,\n folderIcon: css`\n width: 64px;\n height: 64px;\n color: #facc15;\n `,\n image: css`\n max-width: 100%;\n max-height: 100%;\n object-fit: contain;\n border-radius: 4px;\n `,\n label: css`\n padding: 6px 8px;\n background-color: white;\n border-top: 1px solid #e5e7eb;\n `,\n name: css`\n font-size: 12px;\n color: #374151;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n margin: 0;\n `,\n size: css`\n font-size: 12px;\n color: #9ca3af;\n margin: 0;\n `,\n}\n\nexport function StudioFileGrid() {\n const { currentPath, setCurrentPath, selectedItems, toggleSelection, refreshKey } = useStudio()\n const [items, setItems] = useState<FileItem[]>([])\n const [loading, setLoading] = useState(true)\n\n useEffect(() => {\n async function loadItems() {\n setLoading(true)\n try {\n const response = await fetch(`/api/studio/list?path=${encodeURIComponent(currentPath)}`)\n if (response.ok) {\n const data = await response.json()\n setItems(data.items || [])\n }\n } catch (error) {\n console.error('Failed to load items:', error)\n }\n setLoading(false)\n }\n loadItems()\n }, [currentPath, refreshKey])\n\n if (loading) {\n return (\n <div css={styles.loading}>\n <div css={styles.spinner} />\n </div>\n )\n }\n\n if (items.length === 0) {\n return (\n <div css={styles.empty}>\n <svg css={styles.emptyIcon} fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <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\" />\n </svg>\n <p css={styles.emptyText}>No files in this folder</p>\n <p css={styles.emptyText}>Upload images to get started</p>\n </div>\n )\n }\n\n const sortedItems = [...items].sort((a, b) => {\n if (a.type === 'folder' && b.type !== 'folder') return -1\n if (a.type !== 'folder' && b.type === 'folder') return 1\n return a.name.localeCompare(b.name)\n })\n\n return (\n <div css={styles.grid}>\n {sortedItems.map((item) => (\n <GridItem\n key={item.path}\n item={item}\n isSelected={selectedItems.has(item.path)}\n onSelect={() => toggleSelection(item.path)}\n onOpen={() => {\n if (item.type === 'folder') {\n setCurrentPath(item.path)\n }\n }}\n />\n ))}\n </div>\n )\n}\n\ninterface GridItemProps {\n item: FileItem\n isSelected: boolean\n onSelect: () => void\n onOpen: () => void\n}\n\nfunction GridItem({ item, isSelected, onSelect, onOpen }: GridItemProps) {\n const isFolder = item.type === 'folder'\n\n return (\n <div css={[styles.item, isSelected && styles.itemSelected]} onDoubleClick={onOpen}>\n <input\n type=\"checkbox\"\n css={styles.checkbox}\n checked={isSelected}\n onChange={onSelect}\n onClick={(e) => e.stopPropagation()}\n />\n\n {item.cdnSynced && <span css={styles.cdnBadge}>CDN</span>}\n\n <div css={styles.content}>\n {isFolder ? (\n <svg css={styles.folderIcon} fill=\"currentColor\" viewBox=\"0 0 24 24\">\n <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\" />\n </svg>\n ) : (\n <img\n css={styles.image}\n src={item.path.replace('public', '')}\n alt={item.name}\n loading=\"lazy\"\n />\n )}\n </div>\n\n <div css={styles.label}>\n <p css={styles.name} title={item.name}>{item.name}</p>\n {item.size && <p css={styles.size}>{formatFileSize(item.size)}</p>}\n </div>\n </div>\n )\n}\n\nfunction formatFileSize(bytes: number): string {\n if (bytes < 1024) return `${bytes} B`\n if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`\n return `${(bytes / (1024 * 1024)).toFixed(1)} MB`\n}\n","/** @jsxImportSource @emotion/react */\n'use client'\n\nimport { useEffect, useState } from 'react'\nimport { css, keyframes } from '@emotion/react'\nimport { useStudio } from './StudioContext'\nimport type { FileItem } from '../types'\n\nconst spin = keyframes`\n to { transform: rotate(360deg); }\n`\n\nconst styles = {\n loading: css`\n display: flex;\n align-items: center;\n justify-content: center;\n height: 256px;\n `,\n spinner: css`\n width: 32px;\n height: 32px;\n border-radius: 50%;\n border: 2px solid transparent;\n border-bottom-color: #9333ea;\n animation: ${spin} 1s linear infinite;\n `,\n empty: css`\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n height: 256px;\n color: #6b7280;\n `,\n table: css`\n width: 100%;\n border-collapse: collapse;\n `,\n th: css`\n text-align: left;\n font-size: 12px;\n color: #6b7280;\n text-transform: uppercase;\n letter-spacing: 0.05em;\n padding-bottom: 8px;\n font-weight: normal;\n `,\n thCheckbox: css`\n width: 32px;\n `,\n thSize: css`\n width: 96px;\n `,\n thDimensions: css`\n width: 128px;\n `,\n thCdn: css`\n width: 96px;\n `,\n tbody: css`\n border-top: 1px solid #f3f4f6;\n `,\n row: css`\n cursor: pointer;\n transition: background-color 0.15s;\n \n &:hover {\n background-color: #f9fafb;\n }\n `,\n rowSelected: css`\n background-color: #faf5ff;\n `,\n td: css`\n padding: 8px 0;\n border-bottom: 1px solid #f3f4f6;\n `,\n checkbox: css`\n width: 16px;\n height: 16px;\n accent-color: #9333ea;\n `,\n nameCell: css`\n display: flex;\n align-items: center;\n gap: 8px;\n `,\n folderIcon: css`\n width: 20px;\n height: 20px;\n color: #facc15;\n `,\n fileIcon: css`\n width: 20px;\n height: 20px;\n color: #9ca3af;\n `,\n name: css`\n font-size: 14px;\n color: #111827;\n `,\n meta: css`\n font-size: 14px;\n color: #6b7280;\n `,\n cdnBadge: css`\n display: inline-flex;\n align-items: center;\n gap: 4px;\n font-size: 12px;\n color: #15803d;\n `,\n cdnIcon: css`\n width: 12px;\n height: 12px;\n `,\n cdnEmpty: css`\n font-size: 12px;\n color: #9ca3af;\n `,\n}\n\nexport function StudioFileList() {\n const { currentPath, setCurrentPath, selectedItems, toggleSelection, refreshKey } = useStudio()\n const [items, setItems] = useState<FileItem[]>([])\n const [loading, setLoading] = useState(true)\n\n useEffect(() => {\n async function loadItems() {\n setLoading(true)\n try {\n const response = await fetch(`/api/studio/list?path=${encodeURIComponent(currentPath)}`)\n if (response.ok) {\n const data = await response.json()\n setItems(data.items || [])\n }\n } catch (error) {\n console.error('Failed to load items:', error)\n }\n setLoading(false)\n }\n loadItems()\n }, [currentPath, refreshKey])\n\n if (loading) {\n return (\n <div css={styles.loading}>\n <div css={styles.spinner} />\n </div>\n )\n }\n\n if (items.length === 0) {\n return (\n <div css={styles.empty}>\n <p>No files in this folder</p>\n </div>\n )\n }\n\n const sortedItems = [...items].sort((a, b) => {\n if (a.type === 'folder' && b.type !== 'folder') return -1\n if (a.type !== 'folder' && b.type === 'folder') return 1\n return a.name.localeCompare(b.name)\n })\n\n return (\n <table css={styles.table}>\n <thead>\n <tr>\n <th css={[styles.th, styles.thCheckbox]}></th>\n <th css={styles.th}>Name</th>\n <th css={[styles.th, styles.thSize]}>Size</th>\n <th css={[styles.th, styles.thDimensions]}>Dimensions</th>\n <th css={[styles.th, styles.thCdn]}>CDN</th>\n </tr>\n </thead>\n <tbody css={styles.tbody}>\n {sortedItems.map((item) => (\n <ListRow\n key={item.path}\n item={item}\n isSelected={selectedItems.has(item.path)}\n onSelect={() => toggleSelection(item.path)}\n onOpen={() => {\n if (item.type === 'folder') {\n setCurrentPath(item.path)\n }\n }}\n />\n ))}\n </tbody>\n </table>\n )\n}\n\ninterface ListRowProps {\n item: FileItem\n isSelected: boolean\n onSelect: () => void\n onOpen: () => void\n}\n\nfunction ListRow({ item, isSelected, onSelect, onOpen }: ListRowProps) {\n const isFolder = item.type === 'folder'\n\n return (\n <tr css={[styles.row, isSelected && styles.rowSelected]} onDoubleClick={onOpen}>\n <td css={styles.td}>\n <input\n type=\"checkbox\"\n css={styles.checkbox}\n checked={isSelected}\n onChange={onSelect}\n onClick={(e) => e.stopPropagation()}\n />\n </td>\n <td css={styles.td}>\n <div css={styles.nameCell}>\n {isFolder ? (\n <svg css={styles.folderIcon} fill=\"currentColor\" viewBox=\"0 0 24 24\">\n <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\" />\n </svg>\n ) : (\n <svg css={styles.fileIcon} fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <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\" />\n </svg>\n )}\n <span css={styles.name}>{item.name}</span>\n </div>\n </td>\n <td css={[styles.td, styles.meta]}>\n {item.size ? formatFileSize(item.size) : '--'}\n </td>\n <td css={[styles.td, styles.meta]}>\n {item.dimensions ? `${item.dimensions.width}x${item.dimensions.height}` : '--'}\n </td>\n <td css={styles.td}>\n {item.cdnSynced ? (\n <span css={styles.cdnBadge}>\n <svg css={styles.cdnIcon} fill=\"currentColor\" viewBox=\"0 0 20 20\">\n <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\" />\n </svg>\n Synced\n </span>\n ) : (\n <span css={styles.cdnEmpty}>--</span>\n )}\n </td>\n </tr>\n )\n}\n\nfunction formatFileSize(bytes: number): string {\n if (bytes < 1024) return `${bytes} B`\n if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`\n return `${(bytes / (1024 * 1024)).toFixed(1)} MB`\n}\n","/** @jsxImportSource @emotion/react */\n'use client'\n\nimport { css } from '@emotion/react'\nimport { useStudio } from './StudioContext'\n\nconst styles = {\n panel: css`\n width: 320px;\n border-left: 1px solid #e5e7eb;\n background-color: #f9fafb;\n padding: 16px;\n overflow: auto;\n `,\n title: css`\n font-size: 14px;\n font-weight: 500;\n color: #111827;\n margin: 0 0 16px 0;\n `,\n imageContainer: css`\n background-color: white;\n border-radius: 8px;\n border: 1px solid #e5e7eb;\n padding: 8px;\n margin-bottom: 16px;\n `,\n image: css`\n width: 100%;\n height: auto;\n border-radius: 4px;\n `,\n info: css`\n display: flex;\n flex-direction: column;\n gap: 12px;\n `,\n row: css`\n display: flex;\n justify-content: space-between;\n font-size: 12px;\n `,\n label: css`\n color: #6b7280;\n `,\n value: css`\n color: #111827;\n `,\n valueTruncate: css`\n max-width: 128px;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n `,\n section: css`\n padding-top: 8px;\n border-top: 1px solid #e5e7eb;\n `,\n sectionTitle: css`\n font-size: 12px;\n font-weight: 500;\n color: #6b7280;\n margin: 0 0 8px 0;\n `,\n cdnStatus: css`\n display: flex;\n align-items: center;\n gap: 8px;\n font-size: 12px;\n color: #16a34a;\n `,\n cdnIcon: css`\n width: 16px;\n height: 16px;\n `,\n copyBtn: css`\n margin-top: 8px;\n font-size: 12px;\n color: #9333ea;\n background: none;\n border: none;\n cursor: pointer;\n padding: 0;\n \n &:hover {\n text-decoration: underline;\n }\n `,\n colorSwatch: css`\n margin-top: 8px;\n height: 32px;\n border-radius: 4px;\n `,\n actions: css`\n margin-top: 16px;\n padding-top: 16px;\n border-top: 1px solid #e5e7eb;\n display: flex;\n flex-direction: column;\n gap: 8px;\n `,\n actionBtn: css`\n width: 100%;\n padding: 8px 12px;\n font-size: 14px;\n background-color: white;\n border: 1px solid #e5e7eb;\n border-radius: 8px;\n cursor: pointer;\n transition: background-color 0.15s;\n color: #374151;\n \n &:hover {\n background-color: #f9fafb;\n }\n `,\n actionBtnDanger: css`\n color: #dc2626;\n \n &:hover {\n background-color: #fef2f2;\n }\n `,\n}\n\nexport function StudioPreview() {\n const { selectedItems, meta } = useStudio()\n\n if (selectedItems.size !== 1) {\n return null\n }\n\n const selectedPath = Array.from(selectedItems)[0]\n const imageKey = selectedPath\n .replace(/^public\\/images\\//, '')\n .replace(/^public\\/originals\\//, '')\n\n const imageData = meta?.images?.[imageKey]\n\n return (\n <div css={styles.panel}>\n <h3 css={styles.title}>Preview</h3>\n\n <div css={styles.imageContainer}>\n <img\n css={styles.image}\n src={selectedPath.replace('public', '')}\n alt=\"Preview\"\n />\n </div>\n\n <div css={styles.info}>\n <InfoRow label=\"Filename\" value={selectedPath.split('/').pop() || ''} />\n\n {imageData && (\n <>\n <InfoRow\n label=\"Original\"\n value={`${imageData.original.width}x${imageData.original.height}`}\n />\n <InfoRow\n label=\"File size\"\n value={formatFileSize(imageData.original.fileSize)}\n />\n\n <div css={styles.section}>\n <p css={styles.sectionTitle}>Generated sizes</p>\n {Object.entries(imageData.sizes).map(([size, data]) => (\n <InfoRow key={size} label={size} value={`${data.width}x${data.height}`} />\n ))}\n </div>\n\n {imageData.cdn?.synced && (\n <div css={styles.section}>\n <p css={styles.sectionTitle}>CDN</p>\n <div css={styles.cdnStatus}>\n <svg css={styles.cdnIcon} fill=\"currentColor\" viewBox=\"0 0 20 20\">\n <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\" />\n </svg>\n Synced to CDN\n </div>\n <button\n css={styles.copyBtn}\n onClick={() => {\n navigator.clipboard.writeText(`${imageData.cdn?.baseUrl}${imageData.sizes.full.path}`)\n }}\n >\n Copy CDN URL\n </button>\n </div>\n )}\n\n {imageData.blurhash && (\n <div css={styles.section}>\n <InfoRow label=\"Blurhash\" value={imageData.blurhash} truncate />\n <div\n css={styles.colorSwatch}\n style={{ backgroundColor: imageData.dominantColor }}\n title={`Dominant color: ${imageData.dominantColor}`}\n />\n </div>\n )}\n </>\n )}\n </div>\n\n <div css={styles.actions}>\n <button css={styles.actionBtn}>Rename</button>\n <button css={[styles.actionBtn, styles.actionBtnDanger]}>Delete</button>\n </div>\n </div>\n )\n}\n\nfunction InfoRow({ label, value, truncate }: { label: string; value: string; truncate?: boolean }) {\n return (\n <div css={styles.row}>\n <span css={styles.label}>{label}</span>\n <span css={[styles.value, truncate && styles.valueTruncate]} title={truncate ? value : undefined}>\n {value}\n </span>\n </div>\n )\n}\n\nfunction formatFileSize(bytes: number): string {\n if (bytes < 1024) return `${bytes} B`\n if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`\n return `${(bytes / (1024 * 1024)).toFixed(1)} MB`\n}\n","/** @jsxImportSource @emotion/react */\n'use client'\n\nimport { useState } from 'react'\nimport { css } from '@emotion/react'\n\nconst styles = {\n btn: css`\n padding: 8px;\n background: none;\n border: none;\n border-radius: 8px;\n cursor: pointer;\n transition: background-color 0.15s;\n \n &:hover {\n background-color: #f3f4f6;\n }\n `,\n icon: css`\n width: 20px;\n height: 20px;\n color: #6b7280;\n `,\n overlay: css`\n position: fixed;\n top: 0;\n right: 0;\n bottom: 0;\n left: 0;\n z-index: 10000;\n display: flex;\n align-items: center;\n justify-content: center;\n `,\n backdrop: css`\n position: absolute;\n top: 0;\n right: 0;\n bottom: 0;\n left: 0;\n background-color: rgba(0, 0, 0, 0.3);\n `,\n panel: css`\n position: relative;\n background-color: white;\n border-radius: 12px;\n box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.25);\n width: 100%;\n max-width: 512px;\n padding: 24px;\n `,\n header: css`\n display: flex;\n align-items: center;\n justify-content: space-between;\n margin-bottom: 24px;\n `,\n title: css`\n font-size: 18px;\n font-weight: 600;\n margin: 0;\n `,\n closeBtn: css`\n padding: 4px;\n background: none;\n border: none;\n border-radius: 8px;\n cursor: pointer;\n \n &:hover {\n background-color: #f3f4f6;\n }\n `,\n sections: css`\n display: flex;\n flex-direction: column;\n gap: 24px;\n `,\n sectionTitle: css`\n font-size: 14px;\n font-weight: 500;\n color: #111827;\n margin: 0 0 12px 0;\n `,\n description: css`\n font-size: 12px;\n color: #6b7280;\n margin: 0 0 12px 0;\n `,\n code: css`\n background-color: #f9fafb;\n border-radius: 8px;\n padding: 12px;\n font-family: monospace;\n font-size: 12px;\n color: #4b5563;\n `,\n codeLine: css`\n margin: 0 0 4px 0;\n \n &:last-child {\n margin: 0;\n }\n `,\n input: css`\n width: 100%;\n padding: 8px 12px;\n border: 1px solid #e5e7eb;\n border-radius: 8px;\n font-size: 14px;\n \n &:focus {\n outline: none;\n box-shadow: 0 0 0 2px #a855f7;\n }\n `,\n grid: css`\n display: grid;\n grid-template-columns: repeat(3, 1fr);\n gap: 12px;\n `,\n label: css`\n font-size: 12px;\n color: #6b7280;\n display: block;\n margin-bottom: 4px;\n `,\n footer: css`\n margin-top: 24px;\n display: flex;\n justify-content: flex-end;\n gap: 12px;\n `,\n cancelBtn: css`\n padding: 8px 16px;\n font-size: 14px;\n color: #4b5563;\n background: none;\n border: none;\n border-radius: 8px;\n cursor: pointer;\n \n &:hover {\n background-color: #f3f4f6;\n }\n `,\n saveBtn: css`\n padding: 8px 16px;\n font-size: 14px;\n color: white;\n background-color: #9333ea;\n border: none;\n border-radius: 8px;\n cursor: pointer;\n \n &:hover {\n background-color: #7c3aed;\n }\n `,\n}\n\nexport function StudioSettings() {\n const [isOpen, setIsOpen] = useState(false)\n\n return (\n <>\n <button css={styles.btn} onClick={() => setIsOpen(true)} aria-label=\"Settings\">\n <svg\n css={styles.icon}\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth={2}\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n >\n <circle cx=\"12\" cy=\"12\" r=\"3\" />\n <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\" />\n </svg>\n </button>\n\n {isOpen && <SettingsPanel onClose={() => setIsOpen(false)} />}\n </>\n )\n}\n\nfunction SettingsPanel({ onClose }: { onClose: () => void }) {\n return (\n <div css={styles.overlay}>\n <div css={styles.backdrop} onClick={onClose} />\n\n <div css={styles.panel}>\n <div css={styles.header}>\n <h2 css={styles.title}>Settings</h2>\n <button css={styles.closeBtn} onClick={onClose}>\n <svg css={styles.icon} fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M6 18L18 6M6 6l12 12\" />\n </svg>\n </button>\n </div>\n\n <div css={styles.sections}>\n <section>\n <h3 css={styles.sectionTitle}>Cloudflare R2</h3>\n <p css={styles.description}>Configure in .env.local file:</p>\n <div css={styles.code}>\n <p css={styles.codeLine}>CLOUDFLARE_R2_ACCOUNT_ID</p>\n <p css={styles.codeLine}>CLOUDFLARE_R2_ACCESS_KEY_ID</p>\n <p css={styles.codeLine}>CLOUDFLARE_R2_SECRET_ACCESS_KEY</p>\n <p css={styles.codeLine}>CLOUDFLARE_R2_BUCKET_NAME</p>\n <p css={styles.codeLine}>CLOUDFLARE_R2_PUBLIC_URL</p>\n </div>\n </section>\n\n <section>\n <h3 css={styles.sectionTitle}>Custom CDN URL</h3>\n <p css={styles.description}>Override the default R2 URL with a custom domain:</p>\n <input css={styles.input} type=\"text\" placeholder=\"https://cdn.yourdomain.com\" />\n </section>\n\n <section>\n <h3 css={styles.sectionTitle}>Thumbnail Sizes</h3>\n <div css={styles.grid}>\n <div>\n <label css={styles.label}>Small</label>\n <input css={styles.input} type=\"number\" defaultValue={300} />\n </div>\n <div>\n <label css={styles.label}>Medium</label>\n <input css={styles.input} type=\"number\" defaultValue={700} />\n </div>\n <div>\n <label css={styles.label}>Large</label>\n <input css={styles.input} type=\"number\" defaultValue={1400} />\n </div>\n </div>\n </section>\n </div>\n\n <div css={styles.footer}>\n <button css={styles.cancelBtn} onClick={onClose}>Cancel</button>\n <button css={styles.saveBtn}>Save Changes</button>\n </div>\n </div>\n </div>\n )\n}\n"]}
|
package/dist/index.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
var _react = require('react');
|
|
5
5
|
var _react3 = require('@emotion/react');
|
|
6
6
|
var _jsxruntime = require('@emotion/react/jsx-runtime');
|
|
7
|
-
var StudioUI = _react.lazy.call(void 0, () => Promise.resolve().then(() => _interopRequireWildcard(require("./StudioUI-
|
|
7
|
+
var StudioUI = _react.lazy.call(void 0, () => Promise.resolve().then(() => _interopRequireWildcard(require("./StudioUI-Z3DOQO7P.js"))));
|
|
8
8
|
var spin = _react3.keyframes`
|
|
9
9
|
to {
|
|
10
10
|
transform: rotate(360deg);
|
package/dist/index.mjs
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
import { useState, useEffect, lazy, Suspense } from "react";
|
|
5
5
|
import { css, keyframes } from "@emotion/react";
|
|
6
6
|
import { Fragment, jsx, jsxs } from "@emotion/react/jsx-runtime";
|
|
7
|
-
var StudioUI = lazy(() => import("./StudioUI-
|
|
7
|
+
var StudioUI = lazy(() => import("./StudioUI-DSMTGRJ3.mjs"));
|
|
8
8
|
var spin = keyframes`
|
|
9
9
|
to {
|
|
10
10
|
transform: rotate(360deg);
|
package/package.json
CHANGED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["/Users/chrisb/Sites/studio/dist/StudioUI-5I3O5VF7.js","../src/components/StudioUI.tsx","../src/components/StudioContext.tsx","../src/components/StudioToolbar.tsx","../src/components/StudioBreadcrumb.tsx","../src/components/StudioFileGrid.tsx","../src/components/StudioFileList.tsx","../src/components/StudioPreview.tsx","../src/components/StudioSettings.tsx"],"names":["styles","css","jsxs","jsx","keyframes","formatFileSize","useCallback"],"mappings":"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","file":"/Users/chrisb/Sites/studio/dist/StudioUI-5I3O5VF7.js","sourcesContent":[null,"/** @jsxImportSource @emotion/react */\n'use client'\n\nimport { useEffect, useCallback, useState } from 'react'\nimport { css } from '@emotion/react'\nimport { StudioContext } from './StudioContext'\nimport { StudioToolbar } from './StudioToolbar'\nimport { StudioBreadcrumb } from './StudioBreadcrumb'\nimport { StudioFileGrid } from './StudioFileGrid'\nimport { StudioFileList } from './StudioFileList'\nimport { StudioPreview } from './StudioPreview'\nimport { StudioSettings } from './StudioSettings'\nimport type { FileItem, StudioMeta } from '../types'\n\ninterface StudioUIProps {\n onClose: () => void\n}\n\nconst styles = {\n container: css`\n display: flex;\n flex-direction: column;\n height: 100%;\n font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;\n `,\n header: css`\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 16px 24px;\n border-bottom: 1px solid #e5e7eb;\n `,\n title: css`\n font-size: 20px;\n font-weight: 600;\n color: #111827;\n margin: 0;\n `,\n headerActions: css`\n display: flex;\n align-items: center;\n gap: 8px;\n `,\n closeBtn: css`\n padding: 8px;\n background: none;\n border: none;\n border-radius: 8px;\n cursor: pointer;\n transition: background-color 0.15s;\n \n &:hover {\n background-color: #f3f4f6;\n }\n `,\n closeIcon: css`\n width: 20px;\n height: 20px;\n color: #6b7280;\n `,\n content: css`\n flex: 1;\n display: flex;\n overflow: hidden;\n `,\n fileBrowser: css`\n flex: 1;\n overflow: auto;\n padding: 16px;\n `,\n}\n\n/**\n * Main Studio UI - contains all panels and manages internal state\n * Rendered inside the modal via lazy loading\n */\nexport function StudioUI({ onClose }: StudioUIProps) {\n const [currentPath, setCurrentPathInternal] = useState('public')\n const [selectedItems, setSelectedItems] = useState<Set<string>>(new Set())\n const [viewMode, setViewMode] = useState<'grid' | 'list'>('grid')\n const [meta, setMeta] = useState<StudioMeta | null>(null)\n const [isLoading, setIsLoading] = useState(false)\n\n const navigateUp = useCallback(() => {\n if (currentPath === 'public') return\n const parts = currentPath.split('/')\n parts.pop()\n setCurrentPathInternal(parts.join('/') || 'public')\n setSelectedItems(new Set())\n }, [currentPath])\n\n const setCurrentPath = useCallback((path: string) => {\n setCurrentPathInternal(path)\n setSelectedItems(new Set())\n }, [])\n\n const toggleSelection = useCallback((path: string) => {\n setSelectedItems((prev) => {\n const next = new Set(prev)\n if (next.has(path)) {\n next.delete(path)\n } else {\n next.add(path)\n }\n return next\n })\n }, [])\n\n const selectAll = useCallback((items: FileItem[]) => {\n setSelectedItems(new Set(items.map((item) => item.path)))\n }, [])\n\n const clearSelection = useCallback(() => {\n setSelectedItems(new Set())\n }, [])\n\n const handleKeyDown = useCallback(\n (e: KeyboardEvent) => {\n if (e.key === 'Escape') {\n onClose()\n }\n },\n [onClose]\n )\n\n useEffect(() => {\n document.addEventListener('keydown', handleKeyDown)\n document.body.style.overflow = 'hidden'\n return () => {\n document.removeEventListener('keydown', handleKeyDown)\n document.body.style.overflow = ''\n }\n }, [handleKeyDown])\n\n const contextValue = {\n isOpen: true,\n openStudio: () => {},\n closeStudio: onClose,\n toggleStudio: onClose,\n currentPath,\n setCurrentPath,\n navigateUp,\n selectedItems,\n toggleSelection,\n selectAll,\n clearSelection,\n viewMode,\n setViewMode,\n meta,\n setMeta,\n isLoading,\n setIsLoading,\n }\n\n return (\n <StudioContext.Provider value={contextValue}>\n <div css={styles.container}>\n <div css={styles.header}>\n <h1 css={styles.title}>Studio</h1>\n <div css={styles.headerActions}>\n <StudioSettings />\n <button\n css={styles.closeBtn}\n onClick={onClose}\n aria-label=\"Close Studio\"\n >\n <CloseIcon />\n </button>\n </div>\n </div>\n\n <StudioToolbar />\n <StudioBreadcrumb />\n\n <div css={styles.content}>\n <div css={styles.fileBrowser}>\n {viewMode === 'grid' ? <StudioFileGrid /> : <StudioFileList />}\n </div>\n <StudioPreview />\n </div>\n </div>\n </StudioContext.Provider>\n )\n}\n\nfunction CloseIcon() {\n return (\n <svg\n css={styles.closeIcon}\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth={2}\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n >\n <line x1=\"18\" y1=\"6\" x2=\"6\" y2=\"18\" />\n <line x1=\"6\" y1=\"6\" x2=\"18\" y2=\"18\" />\n </svg>\n )\n}\n\nexport default StudioUI\n","'use client'\n\nimport { createContext, useContext } from 'react'\nimport type { FileItem, StudioMeta } from '../types'\n\n/**\n * Studio state interface\n * State is managed by StudioUI and provided to all child components\n */\nexport interface StudioState {\n isOpen: boolean\n openStudio: () => void\n closeStudio: () => void\n toggleStudio: () => void\n\n // Navigation\n currentPath: string\n setCurrentPath: (path: string) => void\n navigateUp: () => void\n\n // Selection\n selectedItems: Set<string>\n toggleSelection: (path: string) => void\n selectAll: (items: FileItem[]) => void\n clearSelection: () => void\n\n // View\n viewMode: 'grid' | 'list'\n setViewMode: (mode: 'grid' | 'list') => void\n\n // Meta\n meta: StudioMeta | null\n setMeta: (meta: StudioMeta) => void\n\n // Loading\n isLoading: boolean\n setIsLoading: (loading: boolean) => void\n}\n\nconst defaultState: StudioState = {\n isOpen: false,\n openStudio: () => {},\n closeStudio: () => {},\n toggleStudio: () => {},\n currentPath: 'public',\n setCurrentPath: () => {},\n navigateUp: () => {},\n selectedItems: new Set(),\n toggleSelection: () => {},\n selectAll: () => {},\n clearSelection: () => {},\n viewMode: 'grid',\n setViewMode: () => {},\n meta: null,\n setMeta: () => {},\n isLoading: false,\n setIsLoading: () => {},\n}\n\nexport const StudioContext = createContext<StudioState>(defaultState)\n\n/**\n * Hook to access Studio state from child components\n */\nexport function useStudio() {\n return useContext(StudioContext)\n}\n","/** @jsxImportSource @emotion/react */\n'use client'\n\nimport { useCallback } from 'react'\nimport { css } from '@emotion/react'\nimport { useStudio } from './StudioContext'\n\nconst styles = {\n toolbar: css`\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 12px 24px;\n background-color: #f9fafb;\n border-bottom: 1px solid #e5e7eb;\n `,\n left: css`\n display: flex;\n align-items: center;\n gap: 8px;\n `,\n right: css`\n display: flex;\n align-items: center;\n gap: 16px;\n `,\n btn: css`\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 8px 12px;\n border-radius: 8px;\n font-size: 14px;\n font-weight: 500;\n background: none;\n border: none;\n cursor: pointer;\n transition: background-color 0.15s;\n \n &:disabled {\n cursor: not-allowed;\n opacity: 0.5;\n }\n `,\n btnDefault: css`\n color: #374151;\n \n &:hover:not(:disabled) {\n background-color: white;\n }\n `,\n btnDanger: css`\n color: #dc2626;\n \n &:hover:not(:disabled) {\n background-color: #fef2f2;\n }\n `,\n icon: css`\n width: 16px;\n height: 16px;\n `,\n selectionCount: css`\n font-size: 14px;\n color: #4b5563;\n `,\n clearBtn: css`\n margin-left: 8px;\n color: #9333ea;\n background: none;\n border: none;\n cursor: pointer;\n font-size: 14px;\n \n &:hover {\n text-decoration: underline;\n }\n `,\n viewToggle: css`\n display: flex;\n align-items: center;\n background-color: white;\n border: 1px solid #e5e7eb;\n border-radius: 8px;\n overflow: hidden;\n `,\n viewBtn: css`\n padding: 8px;\n background: none;\n border: none;\n cursor: pointer;\n color: #6b7280;\n transition: all 0.15s;\n \n &:hover {\n background-color: #f9fafb;\n }\n `,\n viewBtnActive: css`\n background-color: #f3e8ff;\n color: #7c3aed;\n `,\n}\n\nexport function StudioToolbar() {\n const { selectedItems, viewMode, setViewMode, clearSelection } = useStudio()\n\n const handleUpload = useCallback(() => {\n console.log('Upload clicked')\n }, [])\n\n const handleReprocess = useCallback(() => {\n console.log('Reprocess clicked', selectedItems)\n }, [selectedItems])\n\n const handleDelete = useCallback(() => {\n console.log('Delete clicked', selectedItems)\n }, [selectedItems])\n\n const handleSyncCdn = useCallback(() => {\n console.log('Sync CDN clicked', selectedItems)\n }, [selectedItems])\n\n const handleScan = useCallback(() => {\n console.log('Scan clicked')\n }, [])\n\n const hasSelection = selectedItems.size > 0\n\n return (\n <div css={styles.toolbar}>\n <div css={styles.left}>\n <ToolbarButton onClick={handleUpload} icon=\"upload\" label=\"Upload\" />\n <ToolbarButton\n onClick={handleReprocess}\n icon=\"refresh\"\n label=\"Reprocess\"\n disabled={!hasSelection}\n />\n <ToolbarButton\n onClick={handleDelete}\n icon=\"trash\"\n label=\"Delete\"\n disabled={!hasSelection}\n variant=\"danger\"\n />\n <ToolbarButton\n onClick={handleSyncCdn}\n icon=\"cloud\"\n label=\"Sync CDN\"\n disabled={!hasSelection}\n />\n <ToolbarButton onClick={handleScan} icon=\"scan\" label=\"Scan\" />\n </div>\n\n <div css={styles.right}>\n {hasSelection && (\n <span css={styles.selectionCount}>\n {selectedItems.size} selected\n <button css={styles.clearBtn} onClick={clearSelection}>\n Clear\n </button>\n </span>\n )}\n\n <div css={styles.viewToggle}>\n <button\n css={[styles.viewBtn, viewMode === 'grid' && styles.viewBtnActive]}\n onClick={() => setViewMode('grid')}\n aria-label=\"Grid view\"\n >\n <GridIcon />\n </button>\n <button\n css={[styles.viewBtn, viewMode === 'list' && styles.viewBtnActive]}\n onClick={() => setViewMode('list')}\n aria-label=\"List view\"\n >\n <ListIcon />\n </button>\n </div>\n </div>\n </div>\n )\n}\n\ninterface ToolbarButtonProps {\n onClick: () => void\n icon: 'upload' | 'refresh' | 'trash' | 'cloud' | 'scan'\n label: string\n disabled?: boolean\n variant?: 'default' | 'danger'\n}\n\nfunction ToolbarButton({\n onClick,\n icon,\n label,\n disabled,\n variant = 'default',\n}: ToolbarButtonProps) {\n return (\n <button\n css={[styles.btn, variant === 'danger' ? styles.btnDanger : styles.btnDefault]}\n onClick={onClick}\n disabled={disabled}\n >\n <IconComponent icon={icon} />\n {label}\n </button>\n )\n}\n\nfunction IconComponent({ icon }: { icon: string }) {\n switch (icon) {\n case 'upload':\n return (\n <svg css={styles.icon} fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <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\" />\n </svg>\n )\n case 'refresh':\n return (\n <svg css={styles.icon} fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <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\" />\n </svg>\n )\n case 'trash':\n return (\n <svg css={styles.icon} fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <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\" />\n </svg>\n )\n case 'cloud':\n return (\n <svg css={styles.icon} fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <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\" />\n </svg>\n )\n case 'scan':\n return (\n <svg css={styles.icon} fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z\" />\n </svg>\n )\n default:\n return null\n }\n}\n\nfunction GridIcon() {\n return (\n <svg css={styles.icon} fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <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\" />\n </svg>\n )\n}\n\nfunction ListIcon() {\n return (\n <svg css={styles.icon} fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M4 6h16M4 10h16M4 14h16M4 18h16\" />\n </svg>\n )\n}\n","/** @jsxImportSource @emotion/react */\n'use client'\n\nimport { css } from '@emotion/react'\nimport { useStudio } from './StudioContext'\n\nconst styles = {\n container: css`\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 8px 24px;\n background-color: white;\n border-bottom: 1px solid #f3f4f6;\n `,\n backBtn: css`\n padding: 4px;\n background: none;\n border: none;\n border-radius: 4px;\n cursor: pointer;\n transition: background-color 0.15s;\n \n &:hover {\n background-color: #f3f4f6;\n }\n `,\n backIcon: css`\n width: 16px;\n height: 16px;\n color: #6b7280;\n `,\n nav: css`\n display: flex;\n align-items: center;\n gap: 4px;\n font-size: 14px;\n `,\n item: css`\n display: flex;\n align-items: center;\n gap: 4px;\n `,\n separator: css`\n color: #d1d5db;\n `,\n btn: css`\n padding: 2px 4px;\n background: none;\n border: none;\n border-radius: 4px;\n cursor: pointer;\n transition: all 0.15s;\n \n &:hover {\n background-color: #f3f4f6;\n }\n `,\n btnActive: css`\n color: #111827;\n font-weight: 500;\n `,\n btnInactive: css`\n color: #6b7280;\n \n &:hover {\n color: #374151;\n }\n `,\n}\n\nexport function StudioBreadcrumb() {\n const { currentPath, setCurrentPath, navigateUp } = useStudio()\n\n const parts = currentPath.split('/').filter(Boolean)\n\n const handleClick = (index: number) => {\n const newPath = parts.slice(0, index + 1).join('/')\n setCurrentPath(newPath)\n }\n\n return (\n <div css={styles.container}>\n {currentPath !== 'public' && (\n <button css={styles.backBtn} onClick={navigateUp} aria-label=\"Go back\">\n <svg css={styles.backIcon} fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M15 19l-7-7 7-7\" />\n </svg>\n </button>\n )}\n\n <nav css={styles.nav}>\n {parts.map((part, index) => (\n <span key={index} css={styles.item}>\n {index > 0 && <span css={styles.separator}>/</span>}\n <button\n css={[styles.btn, index === parts.length - 1 ? styles.btnActive : styles.btnInactive]}\n onClick={() => handleClick(index)}\n >\n {part}\n </button>\n </span>\n ))}\n </nav>\n </div>\n )\n}\n","/** @jsxImportSource @emotion/react */\n'use client'\n\nimport { useEffect, useState } from 'react'\nimport { css, keyframes } from '@emotion/react'\nimport { useStudio } from './StudioContext'\nimport type { FileItem } from '../types'\n\nconst spin = keyframes`\n to { transform: rotate(360deg); }\n`\n\nconst styles = {\n loading: css`\n display: flex;\n align-items: center;\n justify-content: center;\n height: 256px;\n `,\n spinner: css`\n width: 32px;\n height: 32px;\n border-radius: 50%;\n border: 2px solid transparent;\n border-bottom-color: #9333ea;\n animation: ${spin} 1s linear infinite;\n `,\n empty: css`\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n height: 256px;\n color: #6b7280;\n `,\n emptyIcon: css`\n width: 48px;\n height: 48px;\n margin-bottom: 16px;\n `,\n emptyText: css`\n font-size: 14px;\n margin: 0;\n `,\n grid: css`\n display: grid;\n grid-template-columns: repeat(2, 1fr);\n gap: 16px;\n \n @media (min-width: 640px) { grid-template-columns: repeat(3, 1fr); }\n @media (min-width: 768px) { grid-template-columns: repeat(4, 1fr); }\n @media (min-width: 1024px) { grid-template-columns: repeat(5, 1fr); }\n @media (min-width: 1280px) { grid-template-columns: repeat(6, 1fr); }\n `,\n item: css`\n position: relative;\n border-radius: 8px;\n border: 2px solid transparent;\n overflow: hidden;\n cursor: pointer;\n transition: all 0.15s;\n background-color: #f9fafb;\n \n &:hover {\n border-color: #e5e7eb;\n }\n `,\n itemSelected: css`\n border-color: #a855f7;\n background-color: #faf5ff;\n `,\n checkbox: css`\n position: absolute;\n top: 8px;\n left: 8px;\n z-index: 10;\n width: 16px;\n height: 16px;\n accent-color: #9333ea;\n `,\n cdnBadge: css`\n position: absolute;\n top: 8px;\n right: 8px;\n z-index: 10;\n background-color: #dcfce7;\n color: #15803d;\n font-size: 12px;\n padding: 2px 6px;\n border-radius: 9999px;\n `,\n content: css`\n aspect-ratio: 1;\n display: flex;\n align-items: center;\n justify-content: center;\n padding: 16px;\n `,\n folderIcon: css`\n width: 64px;\n height: 64px;\n color: #facc15;\n `,\n image: css`\n max-width: 100%;\n max-height: 100%;\n object-fit: contain;\n border-radius: 4px;\n `,\n label: css`\n padding: 6px 8px;\n background-color: white;\n border-top: 1px solid #e5e7eb;\n `,\n name: css`\n font-size: 12px;\n color: #374151;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n margin: 0;\n `,\n size: css`\n font-size: 12px;\n color: #9ca3af;\n margin: 0;\n `,\n}\n\nexport function StudioFileGrid() {\n const { currentPath, setCurrentPath, selectedItems, toggleSelection } = useStudio()\n const [items, setItems] = useState<FileItem[]>([])\n const [loading, setLoading] = useState(true)\n\n useEffect(() => {\n async function loadItems() {\n setLoading(true)\n try {\n const response = await fetch(`/api/studio/list?path=${encodeURIComponent(currentPath)}`)\n if (response.ok) {\n const data = await response.json()\n setItems(data.items || [])\n }\n } catch (error) {\n console.error('Failed to load items:', error)\n }\n setLoading(false)\n }\n loadItems()\n }, [currentPath])\n\n if (loading) {\n return (\n <div css={styles.loading}>\n <div css={styles.spinner} />\n </div>\n )\n }\n\n if (items.length === 0) {\n return (\n <div css={styles.empty}>\n <svg css={styles.emptyIcon} fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <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\" />\n </svg>\n <p css={styles.emptyText}>No files in this folder</p>\n <p css={styles.emptyText}>Upload images to get started</p>\n </div>\n )\n }\n\n const sortedItems = [...items].sort((a, b) => {\n if (a.type === 'folder' && b.type !== 'folder') return -1\n if (a.type !== 'folder' && b.type === 'folder') return 1\n return a.name.localeCompare(b.name)\n })\n\n return (\n <div css={styles.grid}>\n {sortedItems.map((item) => (\n <GridItem\n key={item.path}\n item={item}\n isSelected={selectedItems.has(item.path)}\n onSelect={() => toggleSelection(item.path)}\n onOpen={() => {\n if (item.type === 'folder') {\n setCurrentPath(item.path)\n }\n }}\n />\n ))}\n </div>\n )\n}\n\ninterface GridItemProps {\n item: FileItem\n isSelected: boolean\n onSelect: () => void\n onOpen: () => void\n}\n\nfunction GridItem({ item, isSelected, onSelect, onOpen }: GridItemProps) {\n const isFolder = item.type === 'folder'\n\n return (\n <div css={[styles.item, isSelected && styles.itemSelected]} onDoubleClick={onOpen}>\n <input\n type=\"checkbox\"\n css={styles.checkbox}\n checked={isSelected}\n onChange={onSelect}\n onClick={(e) => e.stopPropagation()}\n />\n\n {item.cdnSynced && <span css={styles.cdnBadge}>CDN</span>}\n\n <div css={styles.content}>\n {isFolder ? (\n <svg css={styles.folderIcon} fill=\"currentColor\" viewBox=\"0 0 24 24\">\n <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\" />\n </svg>\n ) : (\n <img\n css={styles.image}\n src={item.path.replace('public', '')}\n alt={item.name}\n loading=\"lazy\"\n />\n )}\n </div>\n\n <div css={styles.label}>\n <p css={styles.name} title={item.name}>{item.name}</p>\n {item.size && <p css={styles.size}>{formatFileSize(item.size)}</p>}\n </div>\n </div>\n )\n}\n\nfunction formatFileSize(bytes: number): string {\n if (bytes < 1024) return `${bytes} B`\n if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`\n return `${(bytes / (1024 * 1024)).toFixed(1)} MB`\n}\n","/** @jsxImportSource @emotion/react */\n'use client'\n\nimport { useEffect, useState } from 'react'\nimport { css, keyframes } from '@emotion/react'\nimport { useStudio } from './StudioContext'\nimport type { FileItem } from '../types'\n\nconst spin = keyframes`\n to { transform: rotate(360deg); }\n`\n\nconst styles = {\n loading: css`\n display: flex;\n align-items: center;\n justify-content: center;\n height: 256px;\n `,\n spinner: css`\n width: 32px;\n height: 32px;\n border-radius: 50%;\n border: 2px solid transparent;\n border-bottom-color: #9333ea;\n animation: ${spin} 1s linear infinite;\n `,\n empty: css`\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n height: 256px;\n color: #6b7280;\n `,\n table: css`\n width: 100%;\n border-collapse: collapse;\n `,\n th: css`\n text-align: left;\n font-size: 12px;\n color: #6b7280;\n text-transform: uppercase;\n letter-spacing: 0.05em;\n padding-bottom: 8px;\n font-weight: normal;\n `,\n thCheckbox: css`\n width: 32px;\n `,\n thSize: css`\n width: 96px;\n `,\n thDimensions: css`\n width: 128px;\n `,\n thCdn: css`\n width: 96px;\n `,\n tbody: css`\n border-top: 1px solid #f3f4f6;\n `,\n row: css`\n cursor: pointer;\n transition: background-color 0.15s;\n \n &:hover {\n background-color: #f9fafb;\n }\n `,\n rowSelected: css`\n background-color: #faf5ff;\n `,\n td: css`\n padding: 8px 0;\n border-bottom: 1px solid #f3f4f6;\n `,\n checkbox: css`\n width: 16px;\n height: 16px;\n accent-color: #9333ea;\n `,\n nameCell: css`\n display: flex;\n align-items: center;\n gap: 8px;\n `,\n folderIcon: css`\n width: 20px;\n height: 20px;\n color: #facc15;\n `,\n fileIcon: css`\n width: 20px;\n height: 20px;\n color: #9ca3af;\n `,\n name: css`\n font-size: 14px;\n color: #111827;\n `,\n meta: css`\n font-size: 14px;\n color: #6b7280;\n `,\n cdnBadge: css`\n display: inline-flex;\n align-items: center;\n gap: 4px;\n font-size: 12px;\n color: #15803d;\n `,\n cdnIcon: css`\n width: 12px;\n height: 12px;\n `,\n cdnEmpty: css`\n font-size: 12px;\n color: #9ca3af;\n `,\n}\n\nexport function StudioFileList() {\n const { currentPath, setCurrentPath, selectedItems, toggleSelection } = useStudio()\n const [items, setItems] = useState<FileItem[]>([])\n const [loading, setLoading] = useState(true)\n\n useEffect(() => {\n async function loadItems() {\n setLoading(true)\n try {\n const response = await fetch(`/api/studio/list?path=${encodeURIComponent(currentPath)}`)\n if (response.ok) {\n const data = await response.json()\n setItems(data.items || [])\n }\n } catch (error) {\n console.error('Failed to load items:', error)\n }\n setLoading(false)\n }\n loadItems()\n }, [currentPath])\n\n if (loading) {\n return (\n <div css={styles.loading}>\n <div css={styles.spinner} />\n </div>\n )\n }\n\n if (items.length === 0) {\n return (\n <div css={styles.empty}>\n <p>No files in this folder</p>\n </div>\n )\n }\n\n const sortedItems = [...items].sort((a, b) => {\n if (a.type === 'folder' && b.type !== 'folder') return -1\n if (a.type !== 'folder' && b.type === 'folder') return 1\n return a.name.localeCompare(b.name)\n })\n\n return (\n <table css={styles.table}>\n <thead>\n <tr>\n <th css={[styles.th, styles.thCheckbox]}></th>\n <th css={styles.th}>Name</th>\n <th css={[styles.th, styles.thSize]}>Size</th>\n <th css={[styles.th, styles.thDimensions]}>Dimensions</th>\n <th css={[styles.th, styles.thCdn]}>CDN</th>\n </tr>\n </thead>\n <tbody css={styles.tbody}>\n {sortedItems.map((item) => (\n <ListRow\n key={item.path}\n item={item}\n isSelected={selectedItems.has(item.path)}\n onSelect={() => toggleSelection(item.path)}\n onOpen={() => {\n if (item.type === 'folder') {\n setCurrentPath(item.path)\n }\n }}\n />\n ))}\n </tbody>\n </table>\n )\n}\n\ninterface ListRowProps {\n item: FileItem\n isSelected: boolean\n onSelect: () => void\n onOpen: () => void\n}\n\nfunction ListRow({ item, isSelected, onSelect, onOpen }: ListRowProps) {\n const isFolder = item.type === 'folder'\n\n return (\n <tr css={[styles.row, isSelected && styles.rowSelected]} onDoubleClick={onOpen}>\n <td css={styles.td}>\n <input\n type=\"checkbox\"\n css={styles.checkbox}\n checked={isSelected}\n onChange={onSelect}\n onClick={(e) => e.stopPropagation()}\n />\n </td>\n <td css={styles.td}>\n <div css={styles.nameCell}>\n {isFolder ? (\n <svg css={styles.folderIcon} fill=\"currentColor\" viewBox=\"0 0 24 24\">\n <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\" />\n </svg>\n ) : (\n <svg css={styles.fileIcon} fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <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\" />\n </svg>\n )}\n <span css={styles.name}>{item.name}</span>\n </div>\n </td>\n <td css={[styles.td, styles.meta]}>\n {item.size ? formatFileSize(item.size) : '--'}\n </td>\n <td css={[styles.td, styles.meta]}>\n {item.dimensions ? `${item.dimensions.width}x${item.dimensions.height}` : '--'}\n </td>\n <td css={styles.td}>\n {item.cdnSynced ? (\n <span css={styles.cdnBadge}>\n <svg css={styles.cdnIcon} fill=\"currentColor\" viewBox=\"0 0 20 20\">\n <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\" />\n </svg>\n Synced\n </span>\n ) : (\n <span css={styles.cdnEmpty}>--</span>\n )}\n </td>\n </tr>\n )\n}\n\nfunction formatFileSize(bytes: number): string {\n if (bytes < 1024) return `${bytes} B`\n if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`\n return `${(bytes / (1024 * 1024)).toFixed(1)} MB`\n}\n","/** @jsxImportSource @emotion/react */\n'use client'\n\nimport { css } from '@emotion/react'\nimport { useStudio } from './StudioContext'\n\nconst styles = {\n panel: css`\n width: 320px;\n border-left: 1px solid #e5e7eb;\n background-color: #f9fafb;\n padding: 16px;\n overflow: auto;\n `,\n title: css`\n font-size: 14px;\n font-weight: 500;\n color: #111827;\n margin: 0 0 16px 0;\n `,\n imageContainer: css`\n background-color: white;\n border-radius: 8px;\n border: 1px solid #e5e7eb;\n padding: 8px;\n margin-bottom: 16px;\n `,\n image: css`\n width: 100%;\n height: auto;\n border-radius: 4px;\n `,\n info: css`\n display: flex;\n flex-direction: column;\n gap: 12px;\n `,\n row: css`\n display: flex;\n justify-content: space-between;\n font-size: 12px;\n `,\n label: css`\n color: #6b7280;\n `,\n value: css`\n color: #111827;\n `,\n valueTruncate: css`\n max-width: 128px;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n `,\n section: css`\n padding-top: 8px;\n border-top: 1px solid #e5e7eb;\n `,\n sectionTitle: css`\n font-size: 12px;\n font-weight: 500;\n color: #6b7280;\n margin: 0 0 8px 0;\n `,\n cdnStatus: css`\n display: flex;\n align-items: center;\n gap: 8px;\n font-size: 12px;\n color: #16a34a;\n `,\n cdnIcon: css`\n width: 16px;\n height: 16px;\n `,\n copyBtn: css`\n margin-top: 8px;\n font-size: 12px;\n color: #9333ea;\n background: none;\n border: none;\n cursor: pointer;\n padding: 0;\n \n &:hover {\n text-decoration: underline;\n }\n `,\n colorSwatch: css`\n margin-top: 8px;\n height: 32px;\n border-radius: 4px;\n `,\n actions: css`\n margin-top: 16px;\n padding-top: 16px;\n border-top: 1px solid #e5e7eb;\n display: flex;\n flex-direction: column;\n gap: 8px;\n `,\n actionBtn: css`\n width: 100%;\n padding: 8px 12px;\n font-size: 14px;\n background-color: white;\n border: 1px solid #e5e7eb;\n border-radius: 8px;\n cursor: pointer;\n transition: background-color 0.15s;\n color: #374151;\n \n &:hover {\n background-color: #f9fafb;\n }\n `,\n actionBtnDanger: css`\n color: #dc2626;\n \n &:hover {\n background-color: #fef2f2;\n }\n `,\n}\n\nexport function StudioPreview() {\n const { selectedItems, meta } = useStudio()\n\n if (selectedItems.size !== 1) {\n return null\n }\n\n const selectedPath = Array.from(selectedItems)[0]\n const imageKey = selectedPath\n .replace(/^public\\/images\\//, '')\n .replace(/^public\\/originals\\//, '')\n\n const imageData = meta?.images?.[imageKey]\n\n return (\n <div css={styles.panel}>\n <h3 css={styles.title}>Preview</h3>\n\n <div css={styles.imageContainer}>\n <img\n css={styles.image}\n src={selectedPath.replace('public', '')}\n alt=\"Preview\"\n />\n </div>\n\n <div css={styles.info}>\n <InfoRow label=\"Filename\" value={selectedPath.split('/').pop() || ''} />\n\n {imageData && (\n <>\n <InfoRow\n label=\"Original\"\n value={`${imageData.original.width}x${imageData.original.height}`}\n />\n <InfoRow\n label=\"File size\"\n value={formatFileSize(imageData.original.fileSize)}\n />\n\n <div css={styles.section}>\n <p css={styles.sectionTitle}>Generated sizes</p>\n {Object.entries(imageData.sizes).map(([size, data]) => (\n <InfoRow key={size} label={size} value={`${data.width}x${data.height}`} />\n ))}\n </div>\n\n {imageData.cdn?.synced && (\n <div css={styles.section}>\n <p css={styles.sectionTitle}>CDN</p>\n <div css={styles.cdnStatus}>\n <svg css={styles.cdnIcon} fill=\"currentColor\" viewBox=\"0 0 20 20\">\n <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\" />\n </svg>\n Synced to CDN\n </div>\n <button\n css={styles.copyBtn}\n onClick={() => {\n navigator.clipboard.writeText(`${imageData.cdn?.baseUrl}${imageData.sizes.full.path}`)\n }}\n >\n Copy CDN URL\n </button>\n </div>\n )}\n\n {imageData.blurhash && (\n <div css={styles.section}>\n <InfoRow label=\"Blurhash\" value={imageData.blurhash} truncate />\n <div\n css={styles.colorSwatch}\n style={{ backgroundColor: imageData.dominantColor }}\n title={`Dominant color: ${imageData.dominantColor}`}\n />\n </div>\n )}\n </>\n )}\n </div>\n\n <div css={styles.actions}>\n <button css={styles.actionBtn}>Rename</button>\n <button css={[styles.actionBtn, styles.actionBtnDanger]}>Delete</button>\n </div>\n </div>\n )\n}\n\nfunction InfoRow({ label, value, truncate }: { label: string; value: string; truncate?: boolean }) {\n return (\n <div css={styles.row}>\n <span css={styles.label}>{label}</span>\n <span css={[styles.value, truncate && styles.valueTruncate]} title={truncate ? value : undefined}>\n {value}\n </span>\n </div>\n )\n}\n\nfunction formatFileSize(bytes: number): string {\n if (bytes < 1024) return `${bytes} B`\n if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`\n return `${(bytes / (1024 * 1024)).toFixed(1)} MB`\n}\n","/** @jsxImportSource @emotion/react */\n'use client'\n\nimport { useState } from 'react'\nimport { css } from '@emotion/react'\n\nconst styles = {\n btn: css`\n padding: 8px;\n background: none;\n border: none;\n border-radius: 8px;\n cursor: pointer;\n transition: background-color 0.15s;\n \n &:hover {\n background-color: #f3f4f6;\n }\n `,\n icon: css`\n width: 20px;\n height: 20px;\n color: #6b7280;\n `,\n overlay: css`\n position: fixed;\n top: 0;\n right: 0;\n bottom: 0;\n left: 0;\n z-index: 10000;\n display: flex;\n align-items: center;\n justify-content: center;\n `,\n backdrop: css`\n position: absolute;\n top: 0;\n right: 0;\n bottom: 0;\n left: 0;\n background-color: rgba(0, 0, 0, 0.3);\n `,\n panel: css`\n position: relative;\n background-color: white;\n border-radius: 12px;\n box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.25);\n width: 100%;\n max-width: 512px;\n padding: 24px;\n `,\n header: css`\n display: flex;\n align-items: center;\n justify-content: space-between;\n margin-bottom: 24px;\n `,\n title: css`\n font-size: 18px;\n font-weight: 600;\n margin: 0;\n `,\n closeBtn: css`\n padding: 4px;\n background: none;\n border: none;\n border-radius: 8px;\n cursor: pointer;\n \n &:hover {\n background-color: #f3f4f6;\n }\n `,\n sections: css`\n display: flex;\n flex-direction: column;\n gap: 24px;\n `,\n sectionTitle: css`\n font-size: 14px;\n font-weight: 500;\n color: #111827;\n margin: 0 0 12px 0;\n `,\n description: css`\n font-size: 12px;\n color: #6b7280;\n margin: 0 0 12px 0;\n `,\n code: css`\n background-color: #f9fafb;\n border-radius: 8px;\n padding: 12px;\n font-family: monospace;\n font-size: 12px;\n color: #4b5563;\n `,\n codeLine: css`\n margin: 0 0 4px 0;\n \n &:last-child {\n margin: 0;\n }\n `,\n input: css`\n width: 100%;\n padding: 8px 12px;\n border: 1px solid #e5e7eb;\n border-radius: 8px;\n font-size: 14px;\n \n &:focus {\n outline: none;\n box-shadow: 0 0 0 2px #a855f7;\n }\n `,\n grid: css`\n display: grid;\n grid-template-columns: repeat(3, 1fr);\n gap: 12px;\n `,\n label: css`\n font-size: 12px;\n color: #6b7280;\n display: block;\n margin-bottom: 4px;\n `,\n footer: css`\n margin-top: 24px;\n display: flex;\n justify-content: flex-end;\n gap: 12px;\n `,\n cancelBtn: css`\n padding: 8px 16px;\n font-size: 14px;\n color: #4b5563;\n background: none;\n border: none;\n border-radius: 8px;\n cursor: pointer;\n \n &:hover {\n background-color: #f3f4f6;\n }\n `,\n saveBtn: css`\n padding: 8px 16px;\n font-size: 14px;\n color: white;\n background-color: #9333ea;\n border: none;\n border-radius: 8px;\n cursor: pointer;\n \n &:hover {\n background-color: #7c3aed;\n }\n `,\n}\n\nexport function StudioSettings() {\n const [isOpen, setIsOpen] = useState(false)\n\n return (\n <>\n <button css={styles.btn} onClick={() => setIsOpen(true)} aria-label=\"Settings\">\n <svg\n css={styles.icon}\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth={2}\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n >\n <circle cx=\"12\" cy=\"12\" r=\"3\" />\n <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\" />\n </svg>\n </button>\n\n {isOpen && <SettingsPanel onClose={() => setIsOpen(false)} />}\n </>\n )\n}\n\nfunction SettingsPanel({ onClose }: { onClose: () => void }) {\n return (\n <div css={styles.overlay}>\n <div css={styles.backdrop} onClick={onClose} />\n\n <div css={styles.panel}>\n <div css={styles.header}>\n <h2 css={styles.title}>Settings</h2>\n <button css={styles.closeBtn} onClick={onClose}>\n <svg css={styles.icon} fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M6 18L18 6M6 6l12 12\" />\n </svg>\n </button>\n </div>\n\n <div css={styles.sections}>\n <section>\n <h3 css={styles.sectionTitle}>Cloudflare R2</h3>\n <p css={styles.description}>Configure in .env.local file:</p>\n <div css={styles.code}>\n <p css={styles.codeLine}>CLOUDFLARE_R2_ACCOUNT_ID</p>\n <p css={styles.codeLine}>CLOUDFLARE_R2_ACCESS_KEY_ID</p>\n <p css={styles.codeLine}>CLOUDFLARE_R2_SECRET_ACCESS_KEY</p>\n <p css={styles.codeLine}>CLOUDFLARE_R2_BUCKET_NAME</p>\n <p css={styles.codeLine}>CLOUDFLARE_R2_PUBLIC_URL</p>\n </div>\n </section>\n\n <section>\n <h3 css={styles.sectionTitle}>Custom CDN URL</h3>\n <p css={styles.description}>Override the default R2 URL with a custom domain:</p>\n <input css={styles.input} type=\"text\" placeholder=\"https://cdn.yourdomain.com\" />\n </section>\n\n <section>\n <h3 css={styles.sectionTitle}>Thumbnail Sizes</h3>\n <div css={styles.grid}>\n <div>\n <label css={styles.label}>Small</label>\n <input css={styles.input} type=\"number\" defaultValue={300} />\n </div>\n <div>\n <label css={styles.label}>Medium</label>\n <input css={styles.input} type=\"number\" defaultValue={700} />\n </div>\n <div>\n <label css={styles.label}>Large</label>\n <input css={styles.input} type=\"number\" defaultValue={1400} />\n </div>\n </div>\n </section>\n </div>\n\n <div css={styles.footer}>\n <button css={styles.cancelBtn} onClick={onClose}>Cancel</button>\n <button css={styles.saveBtn}>Save Changes</button>\n </div>\n </div>\n </div>\n )\n}\n"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/components/StudioUI.tsx","../src/components/StudioContext.tsx","../src/components/StudioToolbar.tsx","../src/components/StudioBreadcrumb.tsx","../src/components/StudioFileGrid.tsx","../src/components/StudioFileList.tsx","../src/components/StudioPreview.tsx","../src/components/StudioSettings.tsx"],"sourcesContent":["/** @jsxImportSource @emotion/react */\n'use client'\n\nimport { useEffect, useCallback, useState } from 'react'\nimport { css } from '@emotion/react'\nimport { StudioContext } from './StudioContext'\nimport { StudioToolbar } from './StudioToolbar'\nimport { StudioBreadcrumb } from './StudioBreadcrumb'\nimport { StudioFileGrid } from './StudioFileGrid'\nimport { StudioFileList } from './StudioFileList'\nimport { StudioPreview } from './StudioPreview'\nimport { StudioSettings } from './StudioSettings'\nimport type { FileItem, StudioMeta } from '../types'\n\ninterface StudioUIProps {\n onClose: () => void\n}\n\nconst styles = {\n container: css`\n display: flex;\n flex-direction: column;\n height: 100%;\n font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;\n `,\n header: css`\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 16px 24px;\n border-bottom: 1px solid #e5e7eb;\n `,\n title: css`\n font-size: 20px;\n font-weight: 600;\n color: #111827;\n margin: 0;\n `,\n headerActions: css`\n display: flex;\n align-items: center;\n gap: 8px;\n `,\n closeBtn: css`\n padding: 8px;\n background: none;\n border: none;\n border-radius: 8px;\n cursor: pointer;\n transition: background-color 0.15s;\n \n &:hover {\n background-color: #f3f4f6;\n }\n `,\n closeIcon: css`\n width: 20px;\n height: 20px;\n color: #6b7280;\n `,\n content: css`\n flex: 1;\n display: flex;\n overflow: hidden;\n `,\n fileBrowser: css`\n flex: 1;\n overflow: auto;\n padding: 16px;\n `,\n}\n\n/**\n * Main Studio UI - contains all panels and manages internal state\n * Rendered inside the modal via lazy loading\n */\nexport function StudioUI({ onClose }: StudioUIProps) {\n const [currentPath, setCurrentPathInternal] = useState('public')\n const [selectedItems, setSelectedItems] = useState<Set<string>>(new Set())\n const [viewMode, setViewMode] = useState<'grid' | 'list'>('grid')\n const [meta, setMeta] = useState<StudioMeta | null>(null)\n const [isLoading, setIsLoading] = useState(false)\n\n const navigateUp = useCallback(() => {\n if (currentPath === 'public') return\n const parts = currentPath.split('/')\n parts.pop()\n setCurrentPathInternal(parts.join('/') || 'public')\n setSelectedItems(new Set())\n }, [currentPath])\n\n const setCurrentPath = useCallback((path: string) => {\n setCurrentPathInternal(path)\n setSelectedItems(new Set())\n }, [])\n\n const toggleSelection = useCallback((path: string) => {\n setSelectedItems((prev) => {\n const next = new Set(prev)\n if (next.has(path)) {\n next.delete(path)\n } else {\n next.add(path)\n }\n return next\n })\n }, [])\n\n const selectAll = useCallback((items: FileItem[]) => {\n setSelectedItems(new Set(items.map((item) => item.path)))\n }, [])\n\n const clearSelection = useCallback(() => {\n setSelectedItems(new Set())\n }, [])\n\n const handleKeyDown = useCallback(\n (e: KeyboardEvent) => {\n if (e.key === 'Escape') {\n onClose()\n }\n },\n [onClose]\n )\n\n useEffect(() => {\n document.addEventListener('keydown', handleKeyDown)\n document.body.style.overflow = 'hidden'\n return () => {\n document.removeEventListener('keydown', handleKeyDown)\n document.body.style.overflow = ''\n }\n }, [handleKeyDown])\n\n const contextValue = {\n isOpen: true,\n openStudio: () => {},\n closeStudio: onClose,\n toggleStudio: onClose,\n currentPath,\n setCurrentPath,\n navigateUp,\n selectedItems,\n toggleSelection,\n selectAll,\n clearSelection,\n viewMode,\n setViewMode,\n meta,\n setMeta,\n isLoading,\n setIsLoading,\n }\n\n return (\n <StudioContext.Provider value={contextValue}>\n <div css={styles.container}>\n <div css={styles.header}>\n <h1 css={styles.title}>Studio</h1>\n <div css={styles.headerActions}>\n <StudioSettings />\n <button\n css={styles.closeBtn}\n onClick={onClose}\n aria-label=\"Close Studio\"\n >\n <CloseIcon />\n </button>\n </div>\n </div>\n\n <StudioToolbar />\n <StudioBreadcrumb />\n\n <div css={styles.content}>\n <div css={styles.fileBrowser}>\n {viewMode === 'grid' ? <StudioFileGrid /> : <StudioFileList />}\n </div>\n <StudioPreview />\n </div>\n </div>\n </StudioContext.Provider>\n )\n}\n\nfunction CloseIcon() {\n return (\n <svg\n css={styles.closeIcon}\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth={2}\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n >\n <line x1=\"18\" y1=\"6\" x2=\"6\" y2=\"18\" />\n <line x1=\"6\" y1=\"6\" x2=\"18\" y2=\"18\" />\n </svg>\n )\n}\n\nexport default StudioUI\n","'use client'\n\nimport { createContext, useContext } from 'react'\nimport type { FileItem, StudioMeta } from '../types'\n\n/**\n * Studio state interface\n * State is managed by StudioUI and provided to all child components\n */\nexport interface StudioState {\n isOpen: boolean\n openStudio: () => void\n closeStudio: () => void\n toggleStudio: () => void\n\n // Navigation\n currentPath: string\n setCurrentPath: (path: string) => void\n navigateUp: () => void\n\n // Selection\n selectedItems: Set<string>\n toggleSelection: (path: string) => void\n selectAll: (items: FileItem[]) => void\n clearSelection: () => void\n\n // View\n viewMode: 'grid' | 'list'\n setViewMode: (mode: 'grid' | 'list') => void\n\n // Meta\n meta: StudioMeta | null\n setMeta: (meta: StudioMeta) => void\n\n // Loading\n isLoading: boolean\n setIsLoading: (loading: boolean) => void\n}\n\nconst defaultState: StudioState = {\n isOpen: false,\n openStudio: () => {},\n closeStudio: () => {},\n toggleStudio: () => {},\n currentPath: 'public',\n setCurrentPath: () => {},\n navigateUp: () => {},\n selectedItems: new Set(),\n toggleSelection: () => {},\n selectAll: () => {},\n clearSelection: () => {},\n viewMode: 'grid',\n setViewMode: () => {},\n meta: null,\n setMeta: () => {},\n isLoading: false,\n setIsLoading: () => {},\n}\n\nexport const StudioContext = createContext<StudioState>(defaultState)\n\n/**\n * Hook to access Studio state from child components\n */\nexport function useStudio() {\n return useContext(StudioContext)\n}\n","/** @jsxImportSource @emotion/react */\n'use client'\n\nimport { useCallback } from 'react'\nimport { css } from '@emotion/react'\nimport { useStudio } from './StudioContext'\n\nconst styles = {\n toolbar: css`\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 12px 24px;\n background-color: #f9fafb;\n border-bottom: 1px solid #e5e7eb;\n `,\n left: css`\n display: flex;\n align-items: center;\n gap: 8px;\n `,\n right: css`\n display: flex;\n align-items: center;\n gap: 16px;\n `,\n btn: css`\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 8px 12px;\n border-radius: 8px;\n font-size: 14px;\n font-weight: 500;\n background: none;\n border: none;\n cursor: pointer;\n transition: background-color 0.15s;\n \n &:disabled {\n cursor: not-allowed;\n opacity: 0.5;\n }\n `,\n btnDefault: css`\n color: #374151;\n \n &:hover:not(:disabled) {\n background-color: white;\n }\n `,\n btnDanger: css`\n color: #dc2626;\n \n &:hover:not(:disabled) {\n background-color: #fef2f2;\n }\n `,\n icon: css`\n width: 16px;\n height: 16px;\n `,\n selectionCount: css`\n font-size: 14px;\n color: #4b5563;\n `,\n clearBtn: css`\n margin-left: 8px;\n color: #9333ea;\n background: none;\n border: none;\n cursor: pointer;\n font-size: 14px;\n \n &:hover {\n text-decoration: underline;\n }\n `,\n viewToggle: css`\n display: flex;\n align-items: center;\n background-color: white;\n border: 1px solid #e5e7eb;\n border-radius: 8px;\n overflow: hidden;\n `,\n viewBtn: css`\n padding: 8px;\n background: none;\n border: none;\n cursor: pointer;\n color: #6b7280;\n transition: all 0.15s;\n \n &:hover {\n background-color: #f9fafb;\n }\n `,\n viewBtnActive: css`\n background-color: #f3e8ff;\n color: #7c3aed;\n `,\n}\n\nexport function StudioToolbar() {\n const { selectedItems, viewMode, setViewMode, clearSelection } = useStudio()\n\n const handleUpload = useCallback(() => {\n console.log('Upload clicked')\n }, [])\n\n const handleReprocess = useCallback(() => {\n console.log('Reprocess clicked', selectedItems)\n }, [selectedItems])\n\n const handleDelete = useCallback(() => {\n console.log('Delete clicked', selectedItems)\n }, [selectedItems])\n\n const handleSyncCdn = useCallback(() => {\n console.log('Sync CDN clicked', selectedItems)\n }, [selectedItems])\n\n const handleScan = useCallback(() => {\n console.log('Scan clicked')\n }, [])\n\n const hasSelection = selectedItems.size > 0\n\n return (\n <div css={styles.toolbar}>\n <div css={styles.left}>\n <ToolbarButton onClick={handleUpload} icon=\"upload\" label=\"Upload\" />\n <ToolbarButton\n onClick={handleReprocess}\n icon=\"refresh\"\n label=\"Reprocess\"\n disabled={!hasSelection}\n />\n <ToolbarButton\n onClick={handleDelete}\n icon=\"trash\"\n label=\"Delete\"\n disabled={!hasSelection}\n variant=\"danger\"\n />\n <ToolbarButton\n onClick={handleSyncCdn}\n icon=\"cloud\"\n label=\"Sync CDN\"\n disabled={!hasSelection}\n />\n <ToolbarButton onClick={handleScan} icon=\"scan\" label=\"Scan\" />\n </div>\n\n <div css={styles.right}>\n {hasSelection && (\n <span css={styles.selectionCount}>\n {selectedItems.size} selected\n <button css={styles.clearBtn} onClick={clearSelection}>\n Clear\n </button>\n </span>\n )}\n\n <div css={styles.viewToggle}>\n <button\n css={[styles.viewBtn, viewMode === 'grid' && styles.viewBtnActive]}\n onClick={() => setViewMode('grid')}\n aria-label=\"Grid view\"\n >\n <GridIcon />\n </button>\n <button\n css={[styles.viewBtn, viewMode === 'list' && styles.viewBtnActive]}\n onClick={() => setViewMode('list')}\n aria-label=\"List view\"\n >\n <ListIcon />\n </button>\n </div>\n </div>\n </div>\n )\n}\n\ninterface ToolbarButtonProps {\n onClick: () => void\n icon: 'upload' | 'refresh' | 'trash' | 'cloud' | 'scan'\n label: string\n disabled?: boolean\n variant?: 'default' | 'danger'\n}\n\nfunction ToolbarButton({\n onClick,\n icon,\n label,\n disabled,\n variant = 'default',\n}: ToolbarButtonProps) {\n return (\n <button\n css={[styles.btn, variant === 'danger' ? styles.btnDanger : styles.btnDefault]}\n onClick={onClick}\n disabled={disabled}\n >\n <IconComponent icon={icon} />\n {label}\n </button>\n )\n}\n\nfunction IconComponent({ icon }: { icon: string }) {\n switch (icon) {\n case 'upload':\n return (\n <svg css={styles.icon} fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <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\" />\n </svg>\n )\n case 'refresh':\n return (\n <svg css={styles.icon} fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <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\" />\n </svg>\n )\n case 'trash':\n return (\n <svg css={styles.icon} fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <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\" />\n </svg>\n )\n case 'cloud':\n return (\n <svg css={styles.icon} fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <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\" />\n </svg>\n )\n case 'scan':\n return (\n <svg css={styles.icon} fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z\" />\n </svg>\n )\n default:\n return null\n }\n}\n\nfunction GridIcon() {\n return (\n <svg css={styles.icon} fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <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\" />\n </svg>\n )\n}\n\nfunction ListIcon() {\n return (\n <svg css={styles.icon} fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M4 6h16M4 10h16M4 14h16M4 18h16\" />\n </svg>\n )\n}\n","/** @jsxImportSource @emotion/react */\n'use client'\n\nimport { css } from '@emotion/react'\nimport { useStudio } from './StudioContext'\n\nconst styles = {\n container: css`\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 8px 24px;\n background-color: white;\n border-bottom: 1px solid #f3f4f6;\n `,\n backBtn: css`\n padding: 4px;\n background: none;\n border: none;\n border-radius: 4px;\n cursor: pointer;\n transition: background-color 0.15s;\n \n &:hover {\n background-color: #f3f4f6;\n }\n `,\n backIcon: css`\n width: 16px;\n height: 16px;\n color: #6b7280;\n `,\n nav: css`\n display: flex;\n align-items: center;\n gap: 4px;\n font-size: 14px;\n `,\n item: css`\n display: flex;\n align-items: center;\n gap: 4px;\n `,\n separator: css`\n color: #d1d5db;\n `,\n btn: css`\n padding: 2px 4px;\n background: none;\n border: none;\n border-radius: 4px;\n cursor: pointer;\n transition: all 0.15s;\n \n &:hover {\n background-color: #f3f4f6;\n }\n `,\n btnActive: css`\n color: #111827;\n font-weight: 500;\n `,\n btnInactive: css`\n color: #6b7280;\n \n &:hover {\n color: #374151;\n }\n `,\n}\n\nexport function StudioBreadcrumb() {\n const { currentPath, setCurrentPath, navigateUp } = useStudio()\n\n const parts = currentPath.split('/').filter(Boolean)\n\n const handleClick = (index: number) => {\n const newPath = parts.slice(0, index + 1).join('/')\n setCurrentPath(newPath)\n }\n\n return (\n <div css={styles.container}>\n {currentPath !== 'public' && (\n <button css={styles.backBtn} onClick={navigateUp} aria-label=\"Go back\">\n <svg css={styles.backIcon} fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M15 19l-7-7 7-7\" />\n </svg>\n </button>\n )}\n\n <nav css={styles.nav}>\n {parts.map((part, index) => (\n <span key={index} css={styles.item}>\n {index > 0 && <span css={styles.separator}>/</span>}\n <button\n css={[styles.btn, index === parts.length - 1 ? styles.btnActive : styles.btnInactive]}\n onClick={() => handleClick(index)}\n >\n {part}\n </button>\n </span>\n ))}\n </nav>\n </div>\n )\n}\n","/** @jsxImportSource @emotion/react */\n'use client'\n\nimport { useEffect, useState } from 'react'\nimport { css, keyframes } from '@emotion/react'\nimport { useStudio } from './StudioContext'\nimport type { FileItem } from '../types'\n\nconst spin = keyframes`\n to { transform: rotate(360deg); }\n`\n\nconst styles = {\n loading: css`\n display: flex;\n align-items: center;\n justify-content: center;\n height: 256px;\n `,\n spinner: css`\n width: 32px;\n height: 32px;\n border-radius: 50%;\n border: 2px solid transparent;\n border-bottom-color: #9333ea;\n animation: ${spin} 1s linear infinite;\n `,\n empty: css`\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n height: 256px;\n color: #6b7280;\n `,\n emptyIcon: css`\n width: 48px;\n height: 48px;\n margin-bottom: 16px;\n `,\n emptyText: css`\n font-size: 14px;\n margin: 0;\n `,\n grid: css`\n display: grid;\n grid-template-columns: repeat(2, 1fr);\n gap: 16px;\n \n @media (min-width: 640px) { grid-template-columns: repeat(3, 1fr); }\n @media (min-width: 768px) { grid-template-columns: repeat(4, 1fr); }\n @media (min-width: 1024px) { grid-template-columns: repeat(5, 1fr); }\n @media (min-width: 1280px) { grid-template-columns: repeat(6, 1fr); }\n `,\n item: css`\n position: relative;\n border-radius: 8px;\n border: 2px solid transparent;\n overflow: hidden;\n cursor: pointer;\n transition: all 0.15s;\n background-color: #f9fafb;\n \n &:hover {\n border-color: #e5e7eb;\n }\n `,\n itemSelected: css`\n border-color: #a855f7;\n background-color: #faf5ff;\n `,\n checkbox: css`\n position: absolute;\n top: 8px;\n left: 8px;\n z-index: 10;\n width: 16px;\n height: 16px;\n accent-color: #9333ea;\n `,\n cdnBadge: css`\n position: absolute;\n top: 8px;\n right: 8px;\n z-index: 10;\n background-color: #dcfce7;\n color: #15803d;\n font-size: 12px;\n padding: 2px 6px;\n border-radius: 9999px;\n `,\n content: css`\n aspect-ratio: 1;\n display: flex;\n align-items: center;\n justify-content: center;\n padding: 16px;\n `,\n folderIcon: css`\n width: 64px;\n height: 64px;\n color: #facc15;\n `,\n image: css`\n max-width: 100%;\n max-height: 100%;\n object-fit: contain;\n border-radius: 4px;\n `,\n label: css`\n padding: 6px 8px;\n background-color: white;\n border-top: 1px solid #e5e7eb;\n `,\n name: css`\n font-size: 12px;\n color: #374151;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n margin: 0;\n `,\n size: css`\n font-size: 12px;\n color: #9ca3af;\n margin: 0;\n `,\n}\n\nexport function StudioFileGrid() {\n const { currentPath, setCurrentPath, selectedItems, toggleSelection } = useStudio()\n const [items, setItems] = useState<FileItem[]>([])\n const [loading, setLoading] = useState(true)\n\n useEffect(() => {\n async function loadItems() {\n setLoading(true)\n try {\n const response = await fetch(`/api/studio/list?path=${encodeURIComponent(currentPath)}`)\n if (response.ok) {\n const data = await response.json()\n setItems(data.items || [])\n }\n } catch (error) {\n console.error('Failed to load items:', error)\n }\n setLoading(false)\n }\n loadItems()\n }, [currentPath])\n\n if (loading) {\n return (\n <div css={styles.loading}>\n <div css={styles.spinner} />\n </div>\n )\n }\n\n if (items.length === 0) {\n return (\n <div css={styles.empty}>\n <svg css={styles.emptyIcon} fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <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\" />\n </svg>\n <p css={styles.emptyText}>No files in this folder</p>\n <p css={styles.emptyText}>Upload images to get started</p>\n </div>\n )\n }\n\n const sortedItems = [...items].sort((a, b) => {\n if (a.type === 'folder' && b.type !== 'folder') return -1\n if (a.type !== 'folder' && b.type === 'folder') return 1\n return a.name.localeCompare(b.name)\n })\n\n return (\n <div css={styles.grid}>\n {sortedItems.map((item) => (\n <GridItem\n key={item.path}\n item={item}\n isSelected={selectedItems.has(item.path)}\n onSelect={() => toggleSelection(item.path)}\n onOpen={() => {\n if (item.type === 'folder') {\n setCurrentPath(item.path)\n }\n }}\n />\n ))}\n </div>\n )\n}\n\ninterface GridItemProps {\n item: FileItem\n isSelected: boolean\n onSelect: () => void\n onOpen: () => void\n}\n\nfunction GridItem({ item, isSelected, onSelect, onOpen }: GridItemProps) {\n const isFolder = item.type === 'folder'\n\n return (\n <div css={[styles.item, isSelected && styles.itemSelected]} onDoubleClick={onOpen}>\n <input\n type=\"checkbox\"\n css={styles.checkbox}\n checked={isSelected}\n onChange={onSelect}\n onClick={(e) => e.stopPropagation()}\n />\n\n {item.cdnSynced && <span css={styles.cdnBadge}>CDN</span>}\n\n <div css={styles.content}>\n {isFolder ? (\n <svg css={styles.folderIcon} fill=\"currentColor\" viewBox=\"0 0 24 24\">\n <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\" />\n </svg>\n ) : (\n <img\n css={styles.image}\n src={item.path.replace('public', '')}\n alt={item.name}\n loading=\"lazy\"\n />\n )}\n </div>\n\n <div css={styles.label}>\n <p css={styles.name} title={item.name}>{item.name}</p>\n {item.size && <p css={styles.size}>{formatFileSize(item.size)}</p>}\n </div>\n </div>\n )\n}\n\nfunction formatFileSize(bytes: number): string {\n if (bytes < 1024) return `${bytes} B`\n if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`\n return `${(bytes / (1024 * 1024)).toFixed(1)} MB`\n}\n","/** @jsxImportSource @emotion/react */\n'use client'\n\nimport { useEffect, useState } from 'react'\nimport { css, keyframes } from '@emotion/react'\nimport { useStudio } from './StudioContext'\nimport type { FileItem } from '../types'\n\nconst spin = keyframes`\n to { transform: rotate(360deg); }\n`\n\nconst styles = {\n loading: css`\n display: flex;\n align-items: center;\n justify-content: center;\n height: 256px;\n `,\n spinner: css`\n width: 32px;\n height: 32px;\n border-radius: 50%;\n border: 2px solid transparent;\n border-bottom-color: #9333ea;\n animation: ${spin} 1s linear infinite;\n `,\n empty: css`\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n height: 256px;\n color: #6b7280;\n `,\n table: css`\n width: 100%;\n border-collapse: collapse;\n `,\n th: css`\n text-align: left;\n font-size: 12px;\n color: #6b7280;\n text-transform: uppercase;\n letter-spacing: 0.05em;\n padding-bottom: 8px;\n font-weight: normal;\n `,\n thCheckbox: css`\n width: 32px;\n `,\n thSize: css`\n width: 96px;\n `,\n thDimensions: css`\n width: 128px;\n `,\n thCdn: css`\n width: 96px;\n `,\n tbody: css`\n border-top: 1px solid #f3f4f6;\n `,\n row: css`\n cursor: pointer;\n transition: background-color 0.15s;\n \n &:hover {\n background-color: #f9fafb;\n }\n `,\n rowSelected: css`\n background-color: #faf5ff;\n `,\n td: css`\n padding: 8px 0;\n border-bottom: 1px solid #f3f4f6;\n `,\n checkbox: css`\n width: 16px;\n height: 16px;\n accent-color: #9333ea;\n `,\n nameCell: css`\n display: flex;\n align-items: center;\n gap: 8px;\n `,\n folderIcon: css`\n width: 20px;\n height: 20px;\n color: #facc15;\n `,\n fileIcon: css`\n width: 20px;\n height: 20px;\n color: #9ca3af;\n `,\n name: css`\n font-size: 14px;\n color: #111827;\n `,\n meta: css`\n font-size: 14px;\n color: #6b7280;\n `,\n cdnBadge: css`\n display: inline-flex;\n align-items: center;\n gap: 4px;\n font-size: 12px;\n color: #15803d;\n `,\n cdnIcon: css`\n width: 12px;\n height: 12px;\n `,\n cdnEmpty: css`\n font-size: 12px;\n color: #9ca3af;\n `,\n}\n\nexport function StudioFileList() {\n const { currentPath, setCurrentPath, selectedItems, toggleSelection } = useStudio()\n const [items, setItems] = useState<FileItem[]>([])\n const [loading, setLoading] = useState(true)\n\n useEffect(() => {\n async function loadItems() {\n setLoading(true)\n try {\n const response = await fetch(`/api/studio/list?path=${encodeURIComponent(currentPath)}`)\n if (response.ok) {\n const data = await response.json()\n setItems(data.items || [])\n }\n } catch (error) {\n console.error('Failed to load items:', error)\n }\n setLoading(false)\n }\n loadItems()\n }, [currentPath])\n\n if (loading) {\n return (\n <div css={styles.loading}>\n <div css={styles.spinner} />\n </div>\n )\n }\n\n if (items.length === 0) {\n return (\n <div css={styles.empty}>\n <p>No files in this folder</p>\n </div>\n )\n }\n\n const sortedItems = [...items].sort((a, b) => {\n if (a.type === 'folder' && b.type !== 'folder') return -1\n if (a.type !== 'folder' && b.type === 'folder') return 1\n return a.name.localeCompare(b.name)\n })\n\n return (\n <table css={styles.table}>\n <thead>\n <tr>\n <th css={[styles.th, styles.thCheckbox]}></th>\n <th css={styles.th}>Name</th>\n <th css={[styles.th, styles.thSize]}>Size</th>\n <th css={[styles.th, styles.thDimensions]}>Dimensions</th>\n <th css={[styles.th, styles.thCdn]}>CDN</th>\n </tr>\n </thead>\n <tbody css={styles.tbody}>\n {sortedItems.map((item) => (\n <ListRow\n key={item.path}\n item={item}\n isSelected={selectedItems.has(item.path)}\n onSelect={() => toggleSelection(item.path)}\n onOpen={() => {\n if (item.type === 'folder') {\n setCurrentPath(item.path)\n }\n }}\n />\n ))}\n </tbody>\n </table>\n )\n}\n\ninterface ListRowProps {\n item: FileItem\n isSelected: boolean\n onSelect: () => void\n onOpen: () => void\n}\n\nfunction ListRow({ item, isSelected, onSelect, onOpen }: ListRowProps) {\n const isFolder = item.type === 'folder'\n\n return (\n <tr css={[styles.row, isSelected && styles.rowSelected]} onDoubleClick={onOpen}>\n <td css={styles.td}>\n <input\n type=\"checkbox\"\n css={styles.checkbox}\n checked={isSelected}\n onChange={onSelect}\n onClick={(e) => e.stopPropagation()}\n />\n </td>\n <td css={styles.td}>\n <div css={styles.nameCell}>\n {isFolder ? (\n <svg css={styles.folderIcon} fill=\"currentColor\" viewBox=\"0 0 24 24\">\n <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\" />\n </svg>\n ) : (\n <svg css={styles.fileIcon} fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <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\" />\n </svg>\n )}\n <span css={styles.name}>{item.name}</span>\n </div>\n </td>\n <td css={[styles.td, styles.meta]}>\n {item.size ? formatFileSize(item.size) : '--'}\n </td>\n <td css={[styles.td, styles.meta]}>\n {item.dimensions ? `${item.dimensions.width}x${item.dimensions.height}` : '--'}\n </td>\n <td css={styles.td}>\n {item.cdnSynced ? (\n <span css={styles.cdnBadge}>\n <svg css={styles.cdnIcon} fill=\"currentColor\" viewBox=\"0 0 20 20\">\n <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\" />\n </svg>\n Synced\n </span>\n ) : (\n <span css={styles.cdnEmpty}>--</span>\n )}\n </td>\n </tr>\n )\n}\n\nfunction formatFileSize(bytes: number): string {\n if (bytes < 1024) return `${bytes} B`\n if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`\n return `${(bytes / (1024 * 1024)).toFixed(1)} MB`\n}\n","/** @jsxImportSource @emotion/react */\n'use client'\n\nimport { css } from '@emotion/react'\nimport { useStudio } from './StudioContext'\n\nconst styles = {\n panel: css`\n width: 320px;\n border-left: 1px solid #e5e7eb;\n background-color: #f9fafb;\n padding: 16px;\n overflow: auto;\n `,\n title: css`\n font-size: 14px;\n font-weight: 500;\n color: #111827;\n margin: 0 0 16px 0;\n `,\n imageContainer: css`\n background-color: white;\n border-radius: 8px;\n border: 1px solid #e5e7eb;\n padding: 8px;\n margin-bottom: 16px;\n `,\n image: css`\n width: 100%;\n height: auto;\n border-radius: 4px;\n `,\n info: css`\n display: flex;\n flex-direction: column;\n gap: 12px;\n `,\n row: css`\n display: flex;\n justify-content: space-between;\n font-size: 12px;\n `,\n label: css`\n color: #6b7280;\n `,\n value: css`\n color: #111827;\n `,\n valueTruncate: css`\n max-width: 128px;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n `,\n section: css`\n padding-top: 8px;\n border-top: 1px solid #e5e7eb;\n `,\n sectionTitle: css`\n font-size: 12px;\n font-weight: 500;\n color: #6b7280;\n margin: 0 0 8px 0;\n `,\n cdnStatus: css`\n display: flex;\n align-items: center;\n gap: 8px;\n font-size: 12px;\n color: #16a34a;\n `,\n cdnIcon: css`\n width: 16px;\n height: 16px;\n `,\n copyBtn: css`\n margin-top: 8px;\n font-size: 12px;\n color: #9333ea;\n background: none;\n border: none;\n cursor: pointer;\n padding: 0;\n \n &:hover {\n text-decoration: underline;\n }\n `,\n colorSwatch: css`\n margin-top: 8px;\n height: 32px;\n border-radius: 4px;\n `,\n actions: css`\n margin-top: 16px;\n padding-top: 16px;\n border-top: 1px solid #e5e7eb;\n display: flex;\n flex-direction: column;\n gap: 8px;\n `,\n actionBtn: css`\n width: 100%;\n padding: 8px 12px;\n font-size: 14px;\n background-color: white;\n border: 1px solid #e5e7eb;\n border-radius: 8px;\n cursor: pointer;\n transition: background-color 0.15s;\n color: #374151;\n \n &:hover {\n background-color: #f9fafb;\n }\n `,\n actionBtnDanger: css`\n color: #dc2626;\n \n &:hover {\n background-color: #fef2f2;\n }\n `,\n}\n\nexport function StudioPreview() {\n const { selectedItems, meta } = useStudio()\n\n if (selectedItems.size !== 1) {\n return null\n }\n\n const selectedPath = Array.from(selectedItems)[0]\n const imageKey = selectedPath\n .replace(/^public\\/images\\//, '')\n .replace(/^public\\/originals\\//, '')\n\n const imageData = meta?.images?.[imageKey]\n\n return (\n <div css={styles.panel}>\n <h3 css={styles.title}>Preview</h3>\n\n <div css={styles.imageContainer}>\n <img\n css={styles.image}\n src={selectedPath.replace('public', '')}\n alt=\"Preview\"\n />\n </div>\n\n <div css={styles.info}>\n <InfoRow label=\"Filename\" value={selectedPath.split('/').pop() || ''} />\n\n {imageData && (\n <>\n <InfoRow\n label=\"Original\"\n value={`${imageData.original.width}x${imageData.original.height}`}\n />\n <InfoRow\n label=\"File size\"\n value={formatFileSize(imageData.original.fileSize)}\n />\n\n <div css={styles.section}>\n <p css={styles.sectionTitle}>Generated sizes</p>\n {Object.entries(imageData.sizes).map(([size, data]) => (\n <InfoRow key={size} label={size} value={`${data.width}x${data.height}`} />\n ))}\n </div>\n\n {imageData.cdn?.synced && (\n <div css={styles.section}>\n <p css={styles.sectionTitle}>CDN</p>\n <div css={styles.cdnStatus}>\n <svg css={styles.cdnIcon} fill=\"currentColor\" viewBox=\"0 0 20 20\">\n <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\" />\n </svg>\n Synced to CDN\n </div>\n <button\n css={styles.copyBtn}\n onClick={() => {\n navigator.clipboard.writeText(`${imageData.cdn?.baseUrl}${imageData.sizes.full.path}`)\n }}\n >\n Copy CDN URL\n </button>\n </div>\n )}\n\n {imageData.blurhash && (\n <div css={styles.section}>\n <InfoRow label=\"Blurhash\" value={imageData.blurhash} truncate />\n <div\n css={styles.colorSwatch}\n style={{ backgroundColor: imageData.dominantColor }}\n title={`Dominant color: ${imageData.dominantColor}`}\n />\n </div>\n )}\n </>\n )}\n </div>\n\n <div css={styles.actions}>\n <button css={styles.actionBtn}>Rename</button>\n <button css={[styles.actionBtn, styles.actionBtnDanger]}>Delete</button>\n </div>\n </div>\n )\n}\n\nfunction InfoRow({ label, value, truncate }: { label: string; value: string; truncate?: boolean }) {\n return (\n <div css={styles.row}>\n <span css={styles.label}>{label}</span>\n <span css={[styles.value, truncate && styles.valueTruncate]} title={truncate ? value : undefined}>\n {value}\n </span>\n </div>\n )\n}\n\nfunction formatFileSize(bytes: number): string {\n if (bytes < 1024) return `${bytes} B`\n if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`\n return `${(bytes / (1024 * 1024)).toFixed(1)} MB`\n}\n","/** @jsxImportSource @emotion/react */\n'use client'\n\nimport { useState } from 'react'\nimport { css } from '@emotion/react'\n\nconst styles = {\n btn: css`\n padding: 8px;\n background: none;\n border: none;\n border-radius: 8px;\n cursor: pointer;\n transition: background-color 0.15s;\n \n &:hover {\n background-color: #f3f4f6;\n }\n `,\n icon: css`\n width: 20px;\n height: 20px;\n color: #6b7280;\n `,\n overlay: css`\n position: fixed;\n top: 0;\n right: 0;\n bottom: 0;\n left: 0;\n z-index: 10000;\n display: flex;\n align-items: center;\n justify-content: center;\n `,\n backdrop: css`\n position: absolute;\n top: 0;\n right: 0;\n bottom: 0;\n left: 0;\n background-color: rgba(0, 0, 0, 0.3);\n `,\n panel: css`\n position: relative;\n background-color: white;\n border-radius: 12px;\n box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.25);\n width: 100%;\n max-width: 512px;\n padding: 24px;\n `,\n header: css`\n display: flex;\n align-items: center;\n justify-content: space-between;\n margin-bottom: 24px;\n `,\n title: css`\n font-size: 18px;\n font-weight: 600;\n margin: 0;\n `,\n closeBtn: css`\n padding: 4px;\n background: none;\n border: none;\n border-radius: 8px;\n cursor: pointer;\n \n &:hover {\n background-color: #f3f4f6;\n }\n `,\n sections: css`\n display: flex;\n flex-direction: column;\n gap: 24px;\n `,\n sectionTitle: css`\n font-size: 14px;\n font-weight: 500;\n color: #111827;\n margin: 0 0 12px 0;\n `,\n description: css`\n font-size: 12px;\n color: #6b7280;\n margin: 0 0 12px 0;\n `,\n code: css`\n background-color: #f9fafb;\n border-radius: 8px;\n padding: 12px;\n font-family: monospace;\n font-size: 12px;\n color: #4b5563;\n `,\n codeLine: css`\n margin: 0 0 4px 0;\n \n &:last-child {\n margin: 0;\n }\n `,\n input: css`\n width: 100%;\n padding: 8px 12px;\n border: 1px solid #e5e7eb;\n border-radius: 8px;\n font-size: 14px;\n \n &:focus {\n outline: none;\n box-shadow: 0 0 0 2px #a855f7;\n }\n `,\n grid: css`\n display: grid;\n grid-template-columns: repeat(3, 1fr);\n gap: 12px;\n `,\n label: css`\n font-size: 12px;\n color: #6b7280;\n display: block;\n margin-bottom: 4px;\n `,\n footer: css`\n margin-top: 24px;\n display: flex;\n justify-content: flex-end;\n gap: 12px;\n `,\n cancelBtn: css`\n padding: 8px 16px;\n font-size: 14px;\n color: #4b5563;\n background: none;\n border: none;\n border-radius: 8px;\n cursor: pointer;\n \n &:hover {\n background-color: #f3f4f6;\n }\n `,\n saveBtn: css`\n padding: 8px 16px;\n font-size: 14px;\n color: white;\n background-color: #9333ea;\n border: none;\n border-radius: 8px;\n cursor: pointer;\n \n &:hover {\n background-color: #7c3aed;\n }\n `,\n}\n\nexport function StudioSettings() {\n const [isOpen, setIsOpen] = useState(false)\n\n return (\n <>\n <button css={styles.btn} onClick={() => setIsOpen(true)} aria-label=\"Settings\">\n <svg\n css={styles.icon}\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth={2}\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n >\n <circle cx=\"12\" cy=\"12\" r=\"3\" />\n <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\" />\n </svg>\n </button>\n\n {isOpen && <SettingsPanel onClose={() => setIsOpen(false)} />}\n </>\n )\n}\n\nfunction SettingsPanel({ onClose }: { onClose: () => void }) {\n return (\n <div css={styles.overlay}>\n <div css={styles.backdrop} onClick={onClose} />\n\n <div css={styles.panel}>\n <div css={styles.header}>\n <h2 css={styles.title}>Settings</h2>\n <button css={styles.closeBtn} onClick={onClose}>\n <svg css={styles.icon} fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M6 18L18 6M6 6l12 12\" />\n </svg>\n </button>\n </div>\n\n <div css={styles.sections}>\n <section>\n <h3 css={styles.sectionTitle}>Cloudflare R2</h3>\n <p css={styles.description}>Configure in .env.local file:</p>\n <div css={styles.code}>\n <p css={styles.codeLine}>CLOUDFLARE_R2_ACCOUNT_ID</p>\n <p css={styles.codeLine}>CLOUDFLARE_R2_ACCESS_KEY_ID</p>\n <p css={styles.codeLine}>CLOUDFLARE_R2_SECRET_ACCESS_KEY</p>\n <p css={styles.codeLine}>CLOUDFLARE_R2_BUCKET_NAME</p>\n <p css={styles.codeLine}>CLOUDFLARE_R2_PUBLIC_URL</p>\n </div>\n </section>\n\n <section>\n <h3 css={styles.sectionTitle}>Custom CDN URL</h3>\n <p css={styles.description}>Override the default R2 URL with a custom domain:</p>\n <input css={styles.input} type=\"text\" placeholder=\"https://cdn.yourdomain.com\" />\n </section>\n\n <section>\n <h3 css={styles.sectionTitle}>Thumbnail Sizes</h3>\n <div css={styles.grid}>\n <div>\n <label css={styles.label}>Small</label>\n <input css={styles.input} type=\"number\" defaultValue={300} />\n </div>\n <div>\n <label css={styles.label}>Medium</label>\n <input css={styles.input} type=\"number\" defaultValue={700} />\n </div>\n <div>\n <label css={styles.label}>Large</label>\n <input css={styles.input} type=\"number\" defaultValue={1400} />\n </div>\n </div>\n </section>\n </div>\n\n <div css={styles.footer}>\n <button css={styles.cancelBtn} onClick={onClose}>Cancel</button>\n <button css={styles.saveBtn}>Save Changes</button>\n </div>\n </div>\n </div>\n )\n}\n"],"mappings":"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","names":["useEffect","useCallback","useState","css","css","jsx","jsxs","styles","css","css","jsx","jsxs","styles","css","useEffect","useState","css","keyframes","jsx","jsxs","spin","keyframes","styles","css","useState","useEffect","formatFileSize","css","jsx","jsxs","styles","css","formatFileSize","useState","css","Fragment","jsx","jsxs","styles","jsx","jsxs","styles","css","useState","useCallback","useEffect"]}
|