@gallop.software/studio 0.1.6 → 0.1.7
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-PW3ZGLXL.mjs → StudioUI-2CBIV4Q5.mjs} +140 -43
- package/dist/StudioUI-2CBIV4Q5.mjs.map +1 -0
- package/dist/{StudioUI-P5VY2DPS.js → StudioUI-ZAD65UPD.js} +140 -43
- package/dist/StudioUI-ZAD65UPD.js.map +1 -0
- package/dist/index.js +1 -1
- package/dist/index.mjs +1 -1
- package/package.json +1 -1
- package/dist/StudioUI-P5VY2DPS.js.map +0 -1
- package/dist/StudioUI-PW3ZGLXL.mjs.map +0 -1
|
@@ -22,10 +22,13 @@ var defaultState = {
|
|
|
22
22
|
selectedItems: /* @__PURE__ */ new Set(),
|
|
23
23
|
toggleSelection: () => {
|
|
24
24
|
},
|
|
25
|
+
selectRange: () => {
|
|
26
|
+
},
|
|
25
27
|
selectAll: () => {
|
|
26
28
|
},
|
|
27
29
|
clearSelection: () => {
|
|
28
30
|
},
|
|
31
|
+
lastSelectedPath: null,
|
|
29
32
|
viewMode: "grid",
|
|
30
33
|
setViewMode: () => {
|
|
31
34
|
},
|
|
@@ -547,10 +550,34 @@ var styles3 = {
|
|
|
547
550
|
font-size: 12px;
|
|
548
551
|
color: #9ca3af;
|
|
549
552
|
margin: 0;
|
|
553
|
+
`,
|
|
554
|
+
selectAllRow: _react3.css`
|
|
555
|
+
display: flex;
|
|
556
|
+
align-items: center;
|
|
557
|
+
margin-bottom: 12px;
|
|
558
|
+
padding-bottom: 12px;
|
|
559
|
+
border-bottom: 1px solid #e5e7eb;
|
|
560
|
+
`,
|
|
561
|
+
selectAllLabel: _react3.css`
|
|
562
|
+
display: flex;
|
|
563
|
+
align-items: center;
|
|
564
|
+
gap: 8px;
|
|
565
|
+
font-size: 14px;
|
|
566
|
+
color: #6b7280;
|
|
567
|
+
cursor: pointer;
|
|
568
|
+
|
|
569
|
+
&:hover {
|
|
570
|
+
color: #374151;
|
|
571
|
+
}
|
|
572
|
+
`,
|
|
573
|
+
selectAllCheckbox: _react3.css`
|
|
574
|
+
width: 16px;
|
|
575
|
+
height: 16px;
|
|
576
|
+
accent-color: #9333ea;
|
|
550
577
|
`
|
|
551
578
|
};
|
|
552
579
|
function StudioFileGrid() {
|
|
553
|
-
const { currentPath, setCurrentPath, selectedItems, toggleSelection, refreshKey } = useStudio();
|
|
580
|
+
const { currentPath, setCurrentPath, selectedItems, toggleSelection, selectRange, lastSelectedPath, selectAll, clearSelection, refreshKey } = useStudio();
|
|
554
581
|
const [items, setItems] = _react.useState.call(void 0, []);
|
|
555
582
|
const [loading, setLoading] = _react.useState.call(void 0, true);
|
|
556
583
|
_react.useEffect.call(void 0, () => {
|
|
@@ -584,38 +611,67 @@ function StudioFileGrid() {
|
|
|
584
611
|
if (a.type !== "folder" && b.type === "folder") return 1;
|
|
585
612
|
return a.name.localeCompare(b.name);
|
|
586
613
|
});
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
}
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
const handleClick = () => {
|
|
605
|
-
if (isFolder) {
|
|
606
|
-
onOpen();
|
|
614
|
+
const files = sortedItems.filter((item) => item.type !== "folder");
|
|
615
|
+
const allFilesSelected = files.length > 0 && files.every((item) => selectedItems.has(item.path));
|
|
616
|
+
const someFilesSelected = files.some((item) => selectedItems.has(item.path));
|
|
617
|
+
const handleSelectAll = () => {
|
|
618
|
+
if (allFilesSelected) {
|
|
619
|
+
clearSelection();
|
|
620
|
+
} else {
|
|
621
|
+
selectAll(files);
|
|
622
|
+
}
|
|
623
|
+
};
|
|
624
|
+
const handleItemClick = (item, e) => {
|
|
625
|
+
if (item.type === "folder") {
|
|
626
|
+
setCurrentPath(item.path);
|
|
627
|
+
return;
|
|
628
|
+
}
|
|
629
|
+
if (e.shiftKey && lastSelectedPath) {
|
|
630
|
+
selectRange(lastSelectedPath, item.path, sortedItems);
|
|
607
631
|
} else {
|
|
608
|
-
|
|
632
|
+
toggleSelection(item.path);
|
|
609
633
|
}
|
|
610
634
|
};
|
|
611
|
-
return /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", {
|
|
635
|
+
return /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { children: [
|
|
636
|
+
files.length > 0 && /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { css: styles3.selectAllRow, children: /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "label", { css: styles3.selectAllLabel, children: [
|
|
637
|
+
/* @__PURE__ */ _jsxruntime.jsx.call(void 0,
|
|
638
|
+
"input",
|
|
639
|
+
{
|
|
640
|
+
type: "checkbox",
|
|
641
|
+
css: styles3.selectAllCheckbox,
|
|
642
|
+
checked: allFilesSelected,
|
|
643
|
+
ref: (el) => {
|
|
644
|
+
if (el) el.indeterminate = someFilesSelected && !allFilesSelected;
|
|
645
|
+
},
|
|
646
|
+
onChange: handleSelectAll
|
|
647
|
+
}
|
|
648
|
+
),
|
|
649
|
+
"Select all (",
|
|
650
|
+
files.length,
|
|
651
|
+
")"
|
|
652
|
+
] }) }),
|
|
653
|
+
/* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { css: styles3.grid, children: sortedItems.map((item) => /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
|
|
654
|
+
GridItem,
|
|
655
|
+
{
|
|
656
|
+
item,
|
|
657
|
+
isSelected: selectedItems.has(item.path),
|
|
658
|
+
onClick: (e) => handleItemClick(item, e)
|
|
659
|
+
},
|
|
660
|
+
item.path
|
|
661
|
+
)) })
|
|
662
|
+
] });
|
|
663
|
+
}
|
|
664
|
+
function GridItem({ item, isSelected, onClick }) {
|
|
665
|
+
const isFolder = item.type === "folder";
|
|
666
|
+
return /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { css: [styles3.item, isSelected && styles3.itemSelected], onClick, children: [
|
|
612
667
|
!isFolder && /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
|
|
613
668
|
"input",
|
|
614
669
|
{
|
|
615
670
|
type: "checkbox",
|
|
616
671
|
css: styles3.checkbox,
|
|
617
672
|
checked: isSelected,
|
|
618
|
-
onChange:
|
|
673
|
+
onChange: () => {
|
|
674
|
+
},
|
|
619
675
|
onClick: (e) => e.stopPropagation()
|
|
620
676
|
}
|
|
621
677
|
),
|
|
@@ -759,7 +815,7 @@ var styles4 = {
|
|
|
759
815
|
`
|
|
760
816
|
};
|
|
761
817
|
function StudioFileList() {
|
|
762
|
-
const { currentPath, setCurrentPath, selectedItems, toggleSelection, refreshKey } = useStudio();
|
|
818
|
+
const { currentPath, setCurrentPath, selectedItems, toggleSelection, selectRange, lastSelectedPath, selectAll, clearSelection, refreshKey } = useStudio();
|
|
763
819
|
const [items, setItems] = _react.useState.call(void 0, []);
|
|
764
820
|
const [loading, setLoading] = _react.useState.call(void 0, true);
|
|
765
821
|
_react.useEffect.call(void 0, () => {
|
|
@@ -789,9 +845,41 @@ function StudioFileList() {
|
|
|
789
845
|
if (a.type !== "folder" && b.type === "folder") return 1;
|
|
790
846
|
return a.name.localeCompare(b.name);
|
|
791
847
|
});
|
|
848
|
+
const files = sortedItems.filter((item) => item.type !== "folder");
|
|
849
|
+
const allFilesSelected = files.length > 0 && files.every((item) => selectedItems.has(item.path));
|
|
850
|
+
const someFilesSelected = files.some((item) => selectedItems.has(item.path));
|
|
851
|
+
const handleSelectAll = () => {
|
|
852
|
+
if (allFilesSelected) {
|
|
853
|
+
clearSelection();
|
|
854
|
+
} else {
|
|
855
|
+
selectAll(files);
|
|
856
|
+
}
|
|
857
|
+
};
|
|
858
|
+
const handleItemClick = (item, e) => {
|
|
859
|
+
if (item.type === "folder") {
|
|
860
|
+
setCurrentPath(item.path);
|
|
861
|
+
return;
|
|
862
|
+
}
|
|
863
|
+
if (e.shiftKey && lastSelectedPath) {
|
|
864
|
+
selectRange(lastSelectedPath, item.path, sortedItems);
|
|
865
|
+
} else {
|
|
866
|
+
toggleSelection(item.path);
|
|
867
|
+
}
|
|
868
|
+
};
|
|
792
869
|
return /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "table", { css: styles4.table, children: [
|
|
793
870
|
/* @__PURE__ */ _jsxruntime.jsx.call(void 0, "thead", { children: /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "tr", { children: [
|
|
794
|
-
/* @__PURE__ */ _jsxruntime.jsx.call(void 0, "th", { css: [styles4.th, styles4.thCheckbox]
|
|
871
|
+
/* @__PURE__ */ _jsxruntime.jsx.call(void 0, "th", { css: [styles4.th, styles4.thCheckbox], children: files.length > 0 && /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
|
|
872
|
+
"input",
|
|
873
|
+
{
|
|
874
|
+
type: "checkbox",
|
|
875
|
+
css: styles4.checkbox,
|
|
876
|
+
checked: allFilesSelected,
|
|
877
|
+
ref: (el) => {
|
|
878
|
+
if (el) el.indeterminate = someFilesSelected && !allFilesSelected;
|
|
879
|
+
},
|
|
880
|
+
onChange: handleSelectAll
|
|
881
|
+
}
|
|
882
|
+
) }),
|
|
795
883
|
/* @__PURE__ */ _jsxruntime.jsx.call(void 0, "th", { css: styles4.th, children: "Name" }),
|
|
796
884
|
/* @__PURE__ */ _jsxruntime.jsx.call(void 0, "th", { css: [styles4.th, styles4.thSize], children: "Size" }),
|
|
797
885
|
/* @__PURE__ */ _jsxruntime.jsx.call(void 0, "th", { css: [styles4.th, styles4.thDimensions], children: "Dimensions" }),
|
|
@@ -802,34 +890,23 @@ function StudioFileList() {
|
|
|
802
890
|
{
|
|
803
891
|
item,
|
|
804
892
|
isSelected: selectedItems.has(item.path),
|
|
805
|
-
|
|
806
|
-
onOpen: () => {
|
|
807
|
-
if (item.type === "folder") {
|
|
808
|
-
setCurrentPath(item.path);
|
|
809
|
-
}
|
|
810
|
-
}
|
|
893
|
+
onClick: (e) => handleItemClick(item, e)
|
|
811
894
|
},
|
|
812
895
|
item.path
|
|
813
896
|
)) })
|
|
814
897
|
] });
|
|
815
898
|
}
|
|
816
|
-
function ListRow({ item, isSelected,
|
|
899
|
+
function ListRow({ item, isSelected, onClick }) {
|
|
817
900
|
const isFolder = item.type === "folder";
|
|
818
|
-
|
|
819
|
-
if (isFolder) {
|
|
820
|
-
onOpen();
|
|
821
|
-
} else {
|
|
822
|
-
onSelect();
|
|
823
|
-
}
|
|
824
|
-
};
|
|
825
|
-
return /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "tr", { css: [styles4.row, isSelected && styles4.rowSelected], onClick: handleClick, children: [
|
|
901
|
+
return /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "tr", { css: [styles4.row, isSelected && styles4.rowSelected], onClick, children: [
|
|
826
902
|
/* @__PURE__ */ _jsxruntime.jsx.call(void 0, "td", { css: styles4.td, children: !isFolder && /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
|
|
827
903
|
"input",
|
|
828
904
|
{
|
|
829
905
|
type: "checkbox",
|
|
830
906
|
css: styles4.checkbox,
|
|
831
907
|
checked: isSelected,
|
|
832
|
-
onChange:
|
|
908
|
+
onChange: () => {
|
|
909
|
+
},
|
|
833
910
|
onClick: (e) => e.stopPropagation()
|
|
834
911
|
}
|
|
835
912
|
) }),
|
|
@@ -1399,6 +1476,7 @@ var styles7 = {
|
|
|
1399
1476
|
function StudioUI({ onClose }) {
|
|
1400
1477
|
const [currentPath, setCurrentPathInternal] = _react.useState.call(void 0, "public");
|
|
1401
1478
|
const [selectedItems, setSelectedItems] = _react.useState.call(void 0, /* @__PURE__ */ new Set());
|
|
1479
|
+
const [lastSelectedPath, setLastSelectedPath] = _react.useState.call(void 0, null);
|
|
1402
1480
|
const [viewMode, setViewMode] = _react.useState.call(void 0, "grid");
|
|
1403
1481
|
const [meta, setMeta] = _react.useState.call(void 0, null);
|
|
1404
1482
|
const [isLoading, setIsLoading] = _react.useState.call(void 0, false);
|
|
@@ -1427,6 +1505,23 @@ function StudioUI({ onClose }) {
|
|
|
1427
1505
|
}
|
|
1428
1506
|
return next;
|
|
1429
1507
|
});
|
|
1508
|
+
setLastSelectedPath(path);
|
|
1509
|
+
}, []);
|
|
1510
|
+
const selectRange = _react.useCallback.call(void 0, (fromPath, toPath, allItems) => {
|
|
1511
|
+
const files = allItems.filter((item) => item.type !== "folder");
|
|
1512
|
+
const fromIndex = files.findIndex((item) => item.path === fromPath);
|
|
1513
|
+
const toIndex = files.findIndex((item) => item.path === toPath);
|
|
1514
|
+
if (fromIndex === -1 || toIndex === -1) return;
|
|
1515
|
+
const start = Math.min(fromIndex, toIndex);
|
|
1516
|
+
const end = Math.max(fromIndex, toIndex);
|
|
1517
|
+
setSelectedItems((prev) => {
|
|
1518
|
+
const next = new Set(prev);
|
|
1519
|
+
for (let i = start; i <= end; i++) {
|
|
1520
|
+
next.add(files[i].path);
|
|
1521
|
+
}
|
|
1522
|
+
return next;
|
|
1523
|
+
});
|
|
1524
|
+
setLastSelectedPath(toPath);
|
|
1430
1525
|
}, []);
|
|
1431
1526
|
const selectAll = _react.useCallback.call(void 0, (items) => {
|
|
1432
1527
|
setSelectedItems(new Set(items.map((item) => item.path)));
|
|
@@ -1461,8 +1556,10 @@ function StudioUI({ onClose }) {
|
|
|
1461
1556
|
navigateUp,
|
|
1462
1557
|
selectedItems,
|
|
1463
1558
|
toggleSelection,
|
|
1559
|
+
selectRange,
|
|
1464
1560
|
selectAll,
|
|
1465
1561
|
clearSelection,
|
|
1562
|
+
lastSelectedPath,
|
|
1466
1563
|
viewMode,
|
|
1467
1564
|
setViewMode,
|
|
1468
1565
|
meta,
|
|
@@ -1520,4 +1617,4 @@ var StudioUI_default = StudioUI;
|
|
|
1520
1617
|
|
|
1521
1618
|
|
|
1522
1619
|
exports.StudioUI = StudioUI; exports.default = StudioUI_default;
|
|
1523
|
-
//# sourceMappingURL=StudioUI-
|
|
1620
|
+
//# sourceMappingURL=StudioUI-ZAD65UPD.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["/Users/chrisb/Sites/studio/dist/StudioUI-ZAD65UPD.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-ZAD65UPD.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 min-width: 0;\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 [lastSelectedPath, setLastSelectedPath] = useState<string | null>(null)\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 setLastSelectedPath(path)\n }, [])\n\n const selectRange = useCallback((fromPath: string, toPath: string, allItems: FileItem[]) => {\n // Get only files (not folders)\n const files = allItems.filter(item => item.type !== 'folder')\n const fromIndex = files.findIndex(item => item.path === fromPath)\n const toIndex = files.findIndex(item => item.path === toPath)\n \n if (fromIndex === -1 || toIndex === -1) return\n \n const start = Math.min(fromIndex, toIndex)\n const end = Math.max(fromIndex, toIndex)\n \n setSelectedItems((prev) => {\n const next = new Set(prev)\n for (let i = start; i <= end; i++) {\n next.add(files[i].path)\n }\n return next\n })\n setLastSelectedPath(toPath)\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 selectRange,\n selectAll,\n clearSelection,\n lastSelectedPath,\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 selectRange: (fromPath: string, toPath: string, allItems: FileItem[]) => void\n selectAll: (items: FileItem[]) => void\n clearSelection: () => void\n lastSelectedPath: string | null\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 selectRange: () => {},\n selectAll: () => {},\n clearSelection: () => {},\n lastSelectedPath: null,\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(async () => {\n if (selectedItems.size === 0) return\n if (!confirm(`Delete ${selectedItems.size} item(s)?`)) return\n\n try {\n const response = await fetch('/api/studio/delete', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ paths: Array.from(selectedItems) }),\n })\n\n if (response.ok) {\n clearSelection()\n triggerRefresh()\n } else {\n const error = await response.json()\n alert(`Delete failed: ${error.error || 'Unknown error'}`)\n }\n } catch (error) {\n console.error('Delete error:', error)\n alert('Delete failed. Check console for details.')\n }\n }, [selectedItems, clearSelection, triggerRefresh])\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 selectAllRow: css`\n display: flex;\n align-items: center;\n margin-bottom: 12px;\n padding-bottom: 12px;\n border-bottom: 1px solid #e5e7eb;\n `,\n selectAllLabel: css`\n display: flex;\n align-items: center;\n gap: 8px;\n font-size: 14px;\n color: #6b7280;\n cursor: pointer;\n \n &:hover {\n color: #374151;\n }\n `,\n selectAllCheckbox: css`\n width: 16px;\n height: 16px;\n accent-color: #9333ea;\n `,\n}\n\nexport function StudioFileGrid() {\n const { currentPath, setCurrentPath, selectedItems, toggleSelection, selectRange, lastSelectedPath, selectAll, clearSelection, 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 const files = sortedItems.filter(item => item.type !== 'folder')\n const allFilesSelected = files.length > 0 && files.every(item => selectedItems.has(item.path))\n const someFilesSelected = files.some(item => selectedItems.has(item.path))\n\n const handleSelectAll = () => {\n if (allFilesSelected) {\n clearSelection()\n } else {\n selectAll(files)\n }\n }\n\n const handleItemClick = (item: FileItem, e: React.MouseEvent) => {\n if (item.type === 'folder') {\n setCurrentPath(item.path)\n return\n }\n\n if (e.shiftKey && lastSelectedPath) {\n selectRange(lastSelectedPath, item.path, sortedItems)\n } else {\n toggleSelection(item.path)\n }\n }\n\n return (\n <div>\n {files.length > 0 && (\n <div css={styles.selectAllRow}>\n <label css={styles.selectAllLabel}>\n <input\n type=\"checkbox\"\n css={styles.selectAllCheckbox}\n checked={allFilesSelected}\n ref={(el) => {\n if (el) el.indeterminate = someFilesSelected && !allFilesSelected\n }}\n onChange={handleSelectAll}\n />\n Select all ({files.length})\n </label>\n </div>\n )}\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 onClick={(e) => handleItemClick(item, e)}\n />\n ))}\n </div>\n </div>\n )\n}\n\ninterface GridItemProps {\n item: FileItem\n isSelected: boolean\n onClick: (e: React.MouseEvent) => void\n}\n\nfunction GridItem({ item, isSelected, onClick }: GridItemProps) {\n const isFolder = item.type === 'folder'\n\n return (\n <div css={[styles.item, isSelected && styles.itemSelected]} onClick={onClick}>\n {/* Only show checkbox for files, not folders */}\n {!isFolder && (\n <input\n type=\"checkbox\"\n css={styles.checkbox}\n checked={isSelected}\n onChange={() => {}}\n onClick={(e) => e.stopPropagation()}\n />\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, selectRange, lastSelectedPath, selectAll, clearSelection, 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 const files = sortedItems.filter(item => item.type !== 'folder')\n const allFilesSelected = files.length > 0 && files.every(item => selectedItems.has(item.path))\n const someFilesSelected = files.some(item => selectedItems.has(item.path))\n\n const handleSelectAll = () => {\n if (allFilesSelected) {\n clearSelection()\n } else {\n selectAll(files)\n }\n }\n\n const handleItemClick = (item: FileItem, e: React.MouseEvent) => {\n if (item.type === 'folder') {\n setCurrentPath(item.path)\n return\n }\n\n if (e.shiftKey && lastSelectedPath) {\n selectRange(lastSelectedPath, item.path, sortedItems)\n } else {\n toggleSelection(item.path)\n }\n }\n\n return (\n <table css={styles.table}>\n <thead>\n <tr>\n <th css={[styles.th, styles.thCheckbox]}>\n {files.length > 0 && (\n <input\n type=\"checkbox\"\n css={styles.checkbox}\n checked={allFilesSelected}\n ref={(el) => {\n if (el) el.indeterminate = someFilesSelected && !allFilesSelected\n }}\n onChange={handleSelectAll}\n />\n )}\n </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 onClick={(e) => handleItemClick(item, e)}\n />\n ))}\n </tbody>\n </table>\n )\n}\n\ninterface ListRowProps {\n item: FileItem\n isSelected: boolean\n onClick: (e: React.MouseEvent) => void\n}\n\nfunction ListRow({ item, isSelected, onClick }: ListRowProps) {\n const isFolder = item.type === 'folder'\n\n return (\n <tr css={[styles.row, isSelected && styles.rowSelected]} onClick={onClick}>\n <td css={styles.td}>\n {/* Only show checkbox for files, not folders */}\n {!isFolder && (\n <input\n type=\"checkbox\"\n css={styles.checkbox}\n checked={isSelected}\n onChange={() => {}}\n onClick={(e) => e.stopPropagation()}\n />\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 emptyState: css`\n display: flex;\n align-items: center;\n justify-content: center;\n height: 200px;\n `,\n emptyText: css`\n font-size: 14px;\n color: #9ca3af;\n margin: 0;\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, triggerRefresh, clearSelection } = useStudio()\n\n const handleDelete = async () => {\n if (selectedItems.size === 0) return\n if (!confirm(`Delete ${selectedItems.size} item(s)?`)) return\n\n try {\n const response = await fetch('/api/studio/delete', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ paths: Array.from(selectedItems) }),\n })\n\n if (response.ok) {\n clearSelection()\n triggerRefresh()\n } else {\n const error = await response.json()\n alert(`Delete failed: ${error.error || 'Unknown error'}`)\n }\n } catch (error) {\n console.error('Delete error:', error)\n alert('Delete failed. Check console for details.')\n }\n }\n\n // Always show the sidebar\n if (selectedItems.size === 0) {\n return (\n <div css={styles.panel}>\n <h3 css={styles.title}>Preview</h3>\n <div css={styles.emptyState}>\n <p css={styles.emptyText}>Select an image to preview</p>\n </div>\n </div>\n )\n }\n\n if (selectedItems.size > 1) {\n return (\n <div css={styles.panel}>\n <h3 css={styles.title}>{selectedItems.size} items selected</h3>\n <div css={styles.actions}>\n <button css={[styles.actionBtn, styles.actionBtnDanger]} onClick={handleDelete}>\n Delete {selectedItems.size} items\n </button>\n </div>\n </div>\n )\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]} onClick={handleDelete}>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-ZAD65UPD.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-2CBIV4Q5.mjs"));
|
|
8
8
|
var spin = keyframes`
|
|
9
9
|
to {
|
|
10
10
|
transform: rotate(360deg);
|
package/package.json
CHANGED