@gallop.software/studio 0.1.80 → 0.1.82

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.
@@ -52,6 +52,11 @@ var defaultState = {
52
52
  },
53
53
  searchQuery: "",
54
54
  setSearchQuery: () => {
55
+ },
56
+ error: null,
57
+ showError: () => {
58
+ },
59
+ clearError: () => {
55
60
  }
56
61
  };
57
62
  var StudioContext = _react.createContext.call(void 0, defaultState);
@@ -891,6 +896,7 @@ function StudioToolbar() {
891
896
  const [imagesToProcess, setImagesToProcess] = _react.useState.call(void 0, []);
892
897
  const [alertMessage, setAlertMessage] = _react.useState.call(void 0, null);
893
898
  const [showNewFolderModal, setShowNewFolderModal] = _react.useState.call(void 0, false);
899
+ const [showRenameFolderModal, setShowRenameFolderModal] = _react.useState.call(void 0, false);
894
900
  const [showMoveModal, setShowMoveModal] = _react.useState.call(void 0, false);
895
901
  const isInImagesFolder = currentPath === "public/images" || currentPath.startsWith("public/images/");
896
902
  const handleUpload = _react.useCallback.call(void 0, () => {
@@ -947,13 +953,13 @@ function StudioToolbar() {
947
953
  const handleProcessImages = _react.useCallback.call(void 0, async () => {
948
954
  const hasSelection2 = selectedItems.size > 0;
949
955
  if (hasSelection2) {
950
- const selectedPaths = Array.from(selectedItems);
956
+ const selectedPaths2 = Array.from(selectedItems);
951
957
  const imageExtensions = ["jpg", "jpeg", "png", "gif", "webp", "svg", "ico", "bmp", "tiff", "tif"];
952
- const selectedImagePaths = selectedPaths.filter((p) => {
958
+ const selectedImagePaths = selectedPaths2.filter((p) => {
953
959
  const ext = _optionalChain([p, 'access', _5 => _5.split, 'call', _6 => _6("."), 'access', _7 => _7.pop, 'call', _8 => _8(), 'optionalAccess', _9 => _9.toLowerCase, 'call', _10 => _10()]) || "";
954
960
  return imageExtensions.includes(ext);
955
961
  });
956
- const selectedFolders = selectedPaths.filter((p) => !p.includes(".") || p.endsWith("/"));
962
+ const selectedFolders = selectedPaths2.filter((p) => !p.includes(".") || p.endsWith("/"));
957
963
  if (selectedFolders.length > 0) {
958
964
  try {
959
965
  const response = await fetch(`/api/studio/folder-images?folders=${encodeURIComponent(selectedFolders.join(","))}`);
@@ -1263,6 +1269,27 @@ function StudioToolbar() {
1263
1269
  }
1264
1270
  }, [setSearchQuery]);
1265
1271
  const hasSelection = selectedItems.size > 0;
1272
+ const selectedPaths = Array.from(selectedItems);
1273
+ const singleFolderSelected = selectedPaths.length === 1 && !selectedPaths[0].includes(".");
1274
+ const selectedFolderPath = singleFolderSelected ? selectedPaths[0] : null;
1275
+ const selectedFolderName = selectedFolderPath ? selectedFolderPath.split("/").pop() || "" : "";
1276
+ const handleRenameFolder = _react.useCallback.call(void 0, async (newName) => {
1277
+ if (!selectedFolderPath) return;
1278
+ setShowRenameFolderModal(false);
1279
+ try {
1280
+ const response = await fetch("/api/studio/rename", {
1281
+ method: "POST",
1282
+ headers: { "Content-Type": "application/json" },
1283
+ body: JSON.stringify({ oldPath: selectedFolderPath, newName })
1284
+ });
1285
+ if (response.ok) {
1286
+ clearSelection();
1287
+ triggerRefresh();
1288
+ }
1289
+ } catch (error) {
1290
+ console.error("Failed to rename folder:", error);
1291
+ }
1292
+ }, [selectedFolderPath, clearSelection, triggerRefresh]);
1266
1293
  if (focusedItem) {
1267
1294
  return null;
1268
1295
  }
@@ -1328,6 +1355,18 @@ function StudioToolbar() {
1328
1355
  onCancel: () => setShowMoveModal(false)
1329
1356
  }
1330
1357
  ),
1358
+ showRenameFolderModal && selectedFolderPath && /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
1359
+ InputModal,
1360
+ {
1361
+ title: "Rename Folder",
1362
+ message: "Enter a new name for the folder:",
1363
+ placeholder: selectedFolderName,
1364
+ defaultValue: selectedFolderName,
1365
+ confirmLabel: "Rename",
1366
+ onConfirm: handleRenameFolder,
1367
+ onCancel: () => setShowRenameFolderModal(false)
1368
+ }
1369
+ ),
1331
1370
  alertMessage && /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
1332
1371
  AlertModal,
1333
1372
  {
@@ -1365,12 +1404,12 @@ function StudioToolbar() {
1365
1404
  "button",
1366
1405
  {
1367
1406
  css: styles3.btn,
1368
- onClick: () => setShowNewFolderModal(true),
1369
- disabled: isInImagesFolder,
1370
- title: isInImagesFolder ? "Cannot create folders in protected images folder" : void 0,
1407
+ onClick: () => singleFolderSelected ? setShowRenameFolderModal(true) : setShowNewFolderModal(true),
1408
+ disabled: isInImagesFolder && !singleFolderSelected,
1409
+ title: isInImagesFolder && !singleFolderSelected ? "Cannot create folders in protected images folder" : void 0,
1371
1410
  children: [
1372
- /* @__PURE__ */ _jsxruntime.jsx.call(void 0, FolderPlusIcon, {}),
1373
- "New Folder"
1411
+ singleFolderSelected ? /* @__PURE__ */ _jsxruntime.jsx.call(void 0, RenameIcon, {}) : /* @__PURE__ */ _jsxruntime.jsx.call(void 0, FolderPlusIcon, {}),
1412
+ singleFolderSelected ? "Rename Folder" : "New Folder"
1374
1413
  ]
1375
1414
  }
1376
1415
  ),
@@ -1497,6 +1536,9 @@ function TrashIcon() {
1497
1536
  function FolderPlusIcon() {
1498
1537
  return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "svg", { css: styles3.icon, fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M9 13h6m-3-3v6m-9 1V7a2 2 0 012-2h6l2 2h6a2 2 0 012 2v8a2 2 0 01-2 2H5a2 2 0 01-2-2z" }) });
1499
1538
  }
1539
+ function RenameIcon() {
1540
+ return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "svg", { css: styles3.icon, fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z" }) });
1541
+ }
1500
1542
  function MoveIcon() {
1501
1543
  return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "svg", { css: styles3.icon, fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M8 7h12m0 0l-4-4m4 4l-4 4m0 6H4m0 0l4 4m-4-4l4-4" }) });
1502
1544
  }
@@ -1517,6 +1559,194 @@ function ImageStackIcon() {
1517
1559
 
1518
1560
 
1519
1561
 
1562
+ // src/hooks/useFileList.ts
1563
+
1564
+
1565
+ // src/lib/api.ts
1566
+ var StudioApiClient = class {
1567
+ async get(url) {
1568
+ const response = await fetch(url);
1569
+ if (!response.ok) {
1570
+ const data = await response.json().catch(() => ({}));
1571
+ throw new Error(data.error || `Request failed: ${response.status}`);
1572
+ }
1573
+ return response.json();
1574
+ }
1575
+ async post(url, body) {
1576
+ const response = await fetch(url, {
1577
+ method: "POST",
1578
+ headers: body ? { "Content-Type": "application/json" } : void 0,
1579
+ body: body ? JSON.stringify(body) : void 0
1580
+ });
1581
+ if (!response.ok) {
1582
+ const data = await response.json().catch(() => ({}));
1583
+ throw new Error(data.error || `Request failed: ${response.status}`);
1584
+ }
1585
+ return response.json();
1586
+ }
1587
+ // List handlers
1588
+ async list(path = "public") {
1589
+ return this.get(`/api/studio/list?path=${encodeURIComponent(path)}`);
1590
+ }
1591
+ async search(query) {
1592
+ return this.get(`/api/studio/search?q=${encodeURIComponent(query)}`);
1593
+ }
1594
+ async listFolders() {
1595
+ return this.get("/api/studio/list-folders");
1596
+ }
1597
+ async countImages() {
1598
+ return this.get("/api/studio/count-images");
1599
+ }
1600
+ async folderImages(folders) {
1601
+ return this.get(`/api/studio/folder-images?folders=${encodeURIComponent(folders.join(","))}`);
1602
+ }
1603
+ // File handlers
1604
+ async upload(file, targetPath = "public") {
1605
+ const formData = new FormData();
1606
+ formData.append("file", file);
1607
+ formData.append("path", targetPath);
1608
+ const response = await fetch("/api/studio/upload", {
1609
+ method: "POST",
1610
+ body: formData
1611
+ });
1612
+ if (!response.ok) {
1613
+ const data = await response.json().catch(() => ({}));
1614
+ throw new Error(data.error || `Upload failed: ${response.status}`);
1615
+ }
1616
+ return response.json();
1617
+ }
1618
+ async delete(paths) {
1619
+ return this.post("/api/studio/delete", { paths });
1620
+ }
1621
+ async createFolder(parentPath, name) {
1622
+ return this.post("/api/studio/create-folder", { parentPath, name });
1623
+ }
1624
+ async rename(oldPath, newName) {
1625
+ return this.post("/api/studio/rename", { oldPath, newName });
1626
+ }
1627
+ async move(paths, destination) {
1628
+ return this.post("/api/studio/move", { paths, destination });
1629
+ }
1630
+ // Image handlers
1631
+ async sync(imageKeys) {
1632
+ return this.post("/api/studio/sync", { imageKeys });
1633
+ }
1634
+ async reprocess(imageKeys) {
1635
+ return this.post("/api/studio/reprocess", { imageKeys });
1636
+ }
1637
+ // Process all returns a stream, handle separately
1638
+ processAllStream() {
1639
+ return new EventSource("/api/studio/process-all");
1640
+ }
1641
+ };
1642
+ var studioApi = new StudioApiClient();
1643
+
1644
+ // src/hooks/useFileList.ts
1645
+ function useFileList() {
1646
+ const {
1647
+ currentPath,
1648
+ setCurrentPath,
1649
+ navigateUp,
1650
+ selectedItems,
1651
+ toggleSelection,
1652
+ selectRange,
1653
+ lastSelectedPath,
1654
+ selectAll,
1655
+ clearSelection,
1656
+ refreshKey,
1657
+ setFocusedItem,
1658
+ triggerRefresh,
1659
+ searchQuery,
1660
+ showError
1661
+ } = useStudio();
1662
+ const [items, setItems] = _react.useState.call(void 0, []);
1663
+ const [loading, setLoading] = _react.useState.call(void 0, true);
1664
+ const isInitialLoad = _react.useRef.call(void 0, true);
1665
+ const lastPath = _react.useRef.call(void 0, currentPath);
1666
+ _react.useEffect.call(void 0, () => {
1667
+ async function loadItems() {
1668
+ const isPathChange = lastPath.current !== currentPath;
1669
+ if (isInitialLoad.current || isPathChange) {
1670
+ setLoading(true);
1671
+ }
1672
+ lastPath.current = currentPath;
1673
+ try {
1674
+ const data = searchQuery && searchQuery.length >= 2 ? await studioApi.search(searchQuery) : await studioApi.list(currentPath);
1675
+ setItems(data.items || []);
1676
+ } catch (error) {
1677
+ const message = error instanceof Error ? error.message : "Failed to load items";
1678
+ showError("Load Error", message);
1679
+ setItems([]);
1680
+ }
1681
+ setLoading(false);
1682
+ isInitialLoad.current = false;
1683
+ }
1684
+ loadItems();
1685
+ }, [currentPath, refreshKey, searchQuery, showError]);
1686
+ const isAtRoot = currentPath === "public";
1687
+ const isSearching = searchQuery && searchQuery.length >= 2;
1688
+ const sortedItems = [...items].sort((a, b) => {
1689
+ if (a.type === "folder" && b.type !== "folder") return -1;
1690
+ if (a.type !== "folder" && b.type === "folder") return 1;
1691
+ return a.name.localeCompare(b.name);
1692
+ });
1693
+ const allItemsSelected = sortedItems.length > 0 && sortedItems.every((item) => selectedItems.has(item.path));
1694
+ const someItemsSelected = sortedItems.some((item) => selectedItems.has(item.path));
1695
+ const handleItemClick = _react.useCallback.call(void 0, (item, e) => {
1696
+ if (e.shiftKey && lastSelectedPath) {
1697
+ selectRange(lastSelectedPath, item.path, sortedItems);
1698
+ } else {
1699
+ toggleSelection(item.path);
1700
+ }
1701
+ }, [lastSelectedPath, selectRange, sortedItems, toggleSelection]);
1702
+ const handleOpen = _react.useCallback.call(void 0, (item) => {
1703
+ if (item.type === "folder") {
1704
+ setCurrentPath(item.path);
1705
+ } else {
1706
+ setFocusedItem(item);
1707
+ }
1708
+ }, [setCurrentPath, setFocusedItem]);
1709
+ const handleGenerateThumbnail = _react.useCallback.call(void 0, async (item) => {
1710
+ try {
1711
+ const imageKey = "/" + item.path.replace(/^public\//, "");
1712
+ await studioApi.reprocess([imageKey]);
1713
+ triggerRefresh();
1714
+ } catch (error) {
1715
+ const message = error instanceof Error ? error.message : "Failed to generate thumbnail";
1716
+ showError("Processing Error", message);
1717
+ }
1718
+ }, [triggerRefresh, showError]);
1719
+ const handleSelectAll = _react.useCallback.call(void 0, () => {
1720
+ if (allItemsSelected) {
1721
+ clearSelection();
1722
+ } else {
1723
+ selectAll(sortedItems);
1724
+ }
1725
+ }, [allItemsSelected, clearSelection, selectAll, sortedItems]);
1726
+ return {
1727
+ // State
1728
+ items,
1729
+ loading,
1730
+ sortedItems,
1731
+ // Computed
1732
+ isAtRoot,
1733
+ isSearching,
1734
+ allItemsSelected,
1735
+ someItemsSelected,
1736
+ // Context values
1737
+ currentPath,
1738
+ selectedItems,
1739
+ navigateUp,
1740
+ // Handlers
1741
+ handleItemClick,
1742
+ handleOpen,
1743
+ handleGenerateThumbnail,
1744
+ handleSelectAll
1745
+ };
1746
+ }
1747
+
1748
+ // src/components/StudioFileGrid.tsx
1749
+
1520
1750
  var spin2 = _react3.keyframes`
1521
1751
  to { transform: rotate(360deg); }
1522
1752
  `;
@@ -1582,10 +1812,6 @@ var styles4 = {
1582
1812
  &:hover {
1583
1813
  border-color: #d0d5dd;
1584
1814
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.06);
1585
-
1586
- button[title="Rename"] {
1587
- opacity: 1;
1588
- }
1589
1815
  }
1590
1816
  `,
1591
1817
  itemSelected: _react3.css`
@@ -1745,35 +1971,6 @@ var styles4 = {
1745
1971
  color: ${_chunkUFCWGUAGjs.colors.text};
1746
1972
  }
1747
1973
  `,
1748
- nameRow: _react3.css`
1749
- display: flex;
1750
- align-items: center;
1751
- gap: 4px;
1752
- `,
1753
- renameBtn: _react3.css`
1754
- flex-shrink: 0;
1755
- height: 20px;
1756
- width: 20px;
1757
- color: ${_chunkUFCWGUAGjs.colors.textMuted};
1758
- background: transparent;
1759
- border: none;
1760
- padding: 0;
1761
- cursor: pointer;
1762
- border-radius: 4px;
1763
- transition: all 0.15s ease;
1764
- display: flex;
1765
- align-items: center;
1766
- justify-content: center;
1767
- opacity: 0;
1768
-
1769
- &:hover {
1770
- color: ${_chunkUFCWGUAGjs.colors.text};
1771
- }
1772
- `,
1773
- renameIcon: _react3.css`
1774
- width: 14px;
1775
- height: 14px;
1776
- `,
1777
1974
  copyIcon: _react3.css`
1778
1975
  width: 18px;
1779
1976
  height: 18px;
@@ -1871,116 +2068,31 @@ var styles4 = {
1871
2068
  `
1872
2069
  };
1873
2070
  function StudioFileGrid() {
1874
- const { currentPath, setCurrentPath, navigateUp, selectedItems, toggleSelection, selectRange, lastSelectedPath, selectAll, clearSelection, refreshKey, setFocusedItem, triggerRefresh, searchQuery } = useStudio();
1875
- const [items, setItems] = _react.useState.call(void 0, []);
1876
- const [loading, setLoading] = _react.useState.call(void 0, true);
1877
- const [renameItem, setRenameItem] = _react.useState.call(void 0, null);
1878
- const isInitialLoad = _react.useRef.call(void 0, true);
1879
- const lastPath = _react.useRef.call(void 0, currentPath);
1880
- _react.useEffect.call(void 0, () => {
1881
- async function loadItems() {
1882
- const isPathChange = lastPath.current !== currentPath;
1883
- if (isInitialLoad.current || isPathChange) {
1884
- setLoading(true);
1885
- }
1886
- lastPath.current = currentPath;
1887
- try {
1888
- const url = searchQuery && searchQuery.length >= 2 ? `/api/studio/search?q=${encodeURIComponent(searchQuery)}` : `/api/studio/list?path=${encodeURIComponent(currentPath)}`;
1889
- const response = await fetch(url);
1890
- if (response.ok) {
1891
- const data = await response.json();
1892
- setItems(data.items || []);
1893
- }
1894
- } catch (error) {
1895
- console.error("Failed to load items:", error);
1896
- }
1897
- setLoading(false);
1898
- isInitialLoad.current = false;
1899
- }
1900
- loadItems();
1901
- }, [currentPath, refreshKey, searchQuery]);
2071
+ const {
2072
+ loading,
2073
+ sortedItems,
2074
+ isAtRoot,
2075
+ isSearching,
2076
+ allItemsSelected,
2077
+ someItemsSelected,
2078
+ selectedItems,
2079
+ navigateUp,
2080
+ handleItemClick,
2081
+ handleOpen,
2082
+ handleGenerateThumbnail,
2083
+ handleSelectAll
2084
+ } = useFileList();
1902
2085
  if (loading) {
1903
2086
  return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { css: styles4.loading, children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { css: styles4.spinner }) });
1904
2087
  }
1905
- const isAtRoot = currentPath === "public";
1906
- if (items.length === 0 && isAtRoot) {
2088
+ if (sortedItems.length === 0 && isAtRoot) {
1907
2089
  return /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { css: styles4.empty, children: [
1908
2090
  /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "svg", { css: styles4.emptyIcon, fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 1.5, d: "M4 16l4.586-4.586a2 2 0 012.828 0L16 16m-2-2l1.586-1.586a2 2 0 012.828 0L20 14m-6-6h.01M6 20h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z" }) }),
1909
2091
  /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "p", { css: styles4.emptyText, children: "No files in this folder" }),
1910
2092
  /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "p", { css: styles4.emptyText, children: "Upload images to get started" })
1911
2093
  ] });
1912
2094
  }
1913
- const isSearching = searchQuery && searchQuery.length >= 2;
1914
- const sortedItems = [...items].sort((a, b) => {
1915
- if (a.type === "folder" && b.type !== "folder") return -1;
1916
- if (a.type !== "folder" && b.type === "folder") return 1;
1917
- return a.name.localeCompare(b.name);
1918
- });
1919
- const handleItemClick = (item, e) => {
1920
- if (e.shiftKey && lastSelectedPath) {
1921
- selectRange(lastSelectedPath, item.path, sortedItems);
1922
- } else {
1923
- toggleSelection(item.path);
1924
- }
1925
- };
1926
- const handleOpen = (item) => {
1927
- if (item.type === "folder") {
1928
- setCurrentPath(item.path);
1929
- } else {
1930
- setFocusedItem(item);
1931
- }
1932
- };
1933
- const handleGenerateThumbnail = async (item) => {
1934
- try {
1935
- const imageKey = item.path.replace(/^public\//, "");
1936
- await fetch("/api/studio/reprocess", {
1937
- method: "POST",
1938
- headers: { "Content-Type": "application/json" },
1939
- body: JSON.stringify({ imageKeys: [imageKey] })
1940
- });
1941
- triggerRefresh();
1942
- } catch (error) {
1943
- console.error("Failed to generate thumbnail:", error);
1944
- }
1945
- };
1946
- const handleRename = async (newName) => {
1947
- if (!renameItem) return;
1948
- setRenameItem(null);
1949
- try {
1950
- const response = await fetch("/api/studio/rename", {
1951
- method: "POST",
1952
- headers: { "Content-Type": "application/json" },
1953
- body: JSON.stringify({ oldPath: renameItem.path, newName })
1954
- });
1955
- if (response.ok) {
1956
- triggerRefresh();
1957
- }
1958
- } catch (error) {
1959
- console.error("Failed to rename:", error);
1960
- }
1961
- };
1962
- const allItemsSelected = sortedItems.length > 0 && sortedItems.every((item) => selectedItems.has(item.path));
1963
- const someItemsSelected = sortedItems.some((item) => selectedItems.has(item.path));
1964
- const handleSelectAll = () => {
1965
- if (allItemsSelected) {
1966
- clearSelection();
1967
- } else {
1968
- selectAll(sortedItems);
1969
- }
1970
- };
1971
2095
  return /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { children: [
1972
- renameItem && /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
1973
- InputModal,
1974
- {
1975
- title: renameItem.type === "folder" ? "Rename Folder" : "Rename File",
1976
- message: "Enter a new name:",
1977
- placeholder: renameItem.name,
1978
- defaultValue: renameItem.name,
1979
- confirmLabel: "Rename",
1980
- onConfirm: handleRename,
1981
- onCancel: () => setRenameItem(null)
1982
- }
1983
- ),
1984
2096
  sortedItems.length > 0 && /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { css: styles4.selectAllRow, children: /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "label", { css: styles4.selectAllLabel, children: [
1985
2097
  /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
1986
2098
  "input",
@@ -2020,15 +2132,14 @@ function StudioFileGrid() {
2020
2132
  isSelected: selectedItems.has(item.path),
2021
2133
  onClick: (e) => handleItemClick(item, e),
2022
2134
  onOpen: () => handleOpen(item),
2023
- onGenerateThumbnail: () => handleGenerateThumbnail(item),
2024
- onRename: () => setRenameItem(item)
2135
+ onGenerateThumbnail: () => handleGenerateThumbnail(item)
2025
2136
  },
2026
2137
  item.path
2027
2138
  ))
2028
2139
  ] })
2029
2140
  ] });
2030
2141
  }
2031
- function GridItem({ item, isSelected, onClick, onOpen, onGenerateThumbnail, onRename }) {
2142
+ function GridItem({ item, isSelected, onClick, onOpen, onGenerateThumbnail }) {
2032
2143
  const [showCopied, setShowCopied] = _react.useState.call(void 0, false);
2033
2144
  const isFolder = item.type === "folder";
2034
2145
  const isImage = !isFolder && item.thumbnail !== void 0;
@@ -2115,21 +2226,7 @@ function GridItem({ item, isSelected, onClick, onOpen, onGenerateThumbnail, onRe
2115
2226
  ) : /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "svg", { css: styles4.fileIcon, fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 1.5, d: "M7 21h10a2 2 0 002-2V9.414a1 1 0 00-.293-.707l-5.414-5.414A1 1 0 0012.586 3H7a2 2 0 00-2 2v14a2 2 0 002 2z" }) })
2116
2227
  ] }),
2117
2228
  /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { css: styles4.label, children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { css: styles4.labelRow, children: /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { css: styles4.labelText, children: [
2118
- /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { css: styles4.nameRow, children: [
2119
- /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "p", { css: styles4.name, title: item.name, children: truncateMiddle(item.name) }),
2120
- /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
2121
- "button",
2122
- {
2123
- css: styles4.renameBtn,
2124
- onClick: (e) => {
2125
- e.stopPropagation();
2126
- onRename();
2127
- },
2128
- title: "Rename",
2129
- children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "svg", { css: styles4.renameIcon, fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z" }) })
2130
- }
2131
- )
2132
- ] }),
2229
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "p", { css: styles4.name, title: item.name, children: truncateMiddle(item.name) }),
2133
2230
  isFolder ? /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "p", { css: styles4.size, children: [
2134
2231
  item.fileCount !== void 0 ? `${item.fileCount} files` : "",
2135
2232
  item.fileCount !== void 0 && item.totalSize !== void 0 ? " \xB7 " : "",
@@ -2472,159 +2569,71 @@ var styles5 = {
2472
2569
  `
2473
2570
  };
2474
2571
  function StudioFileList() {
2475
- const { currentPath, setCurrentPath, navigateUp, selectedItems, toggleSelection, selectRange, lastSelectedPath, selectAll, clearSelection, refreshKey, setFocusedItem, triggerRefresh, searchQuery } = useStudio();
2476
- const [items, setItems] = _react.useState.call(void 0, []);
2477
- const [loading, setLoading] = _react.useState.call(void 0, true);
2478
- const [renameItem, setRenameItem] = _react.useState.call(void 0, null);
2479
- const isInitialLoad = _react.useRef.call(void 0, true);
2480
- const lastPath = _react.useRef.call(void 0, currentPath);
2481
- _react.useEffect.call(void 0, () => {
2482
- async function loadItems() {
2483
- const isPathChange = lastPath.current !== currentPath;
2484
- if (isInitialLoad.current || isPathChange) {
2485
- setLoading(true);
2486
- }
2487
- lastPath.current = currentPath;
2488
- try {
2489
- const url = searchQuery && searchQuery.length >= 2 ? `/api/studio/search?q=${encodeURIComponent(searchQuery)}` : `/api/studio/list?path=${encodeURIComponent(currentPath)}`;
2490
- const response = await fetch(url);
2491
- if (response.ok) {
2492
- const data = await response.json();
2493
- setItems(data.items || []);
2494
- }
2495
- } catch (error) {
2496
- console.error("Failed to load items:", error);
2497
- }
2498
- setLoading(false);
2499
- isInitialLoad.current = false;
2500
- }
2501
- loadItems();
2502
- }, [currentPath, refreshKey, searchQuery]);
2572
+ const {
2573
+ loading,
2574
+ sortedItems,
2575
+ isAtRoot,
2576
+ isSearching,
2577
+ allItemsSelected,
2578
+ someItemsSelected,
2579
+ selectedItems,
2580
+ navigateUp,
2581
+ handleItemClick,
2582
+ handleOpen,
2583
+ handleGenerateThumbnail,
2584
+ handleSelectAll
2585
+ } = useFileList();
2503
2586
  if (loading) {
2504
2587
  return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { css: styles5.loading, children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { css: styles5.spinner }) });
2505
2588
  }
2506
- const isAtRoot = currentPath === "public";
2507
- if (items.length === 0 && isAtRoot) {
2589
+ if (sortedItems.length === 0 && isAtRoot) {
2508
2590
  return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { css: styles5.empty, children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "p", { children: "No files in this folder" }) });
2509
2591
  }
2510
- const isSearching = searchQuery && searchQuery.length >= 2;
2511
- const sortedItems = [...items].sort((a, b) => {
2512
- if (a.type === "folder" && b.type !== "folder") return -1;
2513
- if (a.type !== "folder" && b.type === "folder") return 1;
2514
- return a.name.localeCompare(b.name);
2515
- });
2516
- const handleItemClick = (item, e) => {
2517
- if (e.shiftKey && lastSelectedPath) {
2518
- selectRange(lastSelectedPath, item.path, sortedItems);
2519
- } else {
2520
- toggleSelection(item.path);
2521
- }
2522
- };
2523
- const handleOpen = (item) => {
2524
- if (item.type === "folder") {
2525
- setCurrentPath(item.path);
2526
- } else {
2527
- setFocusedItem(item);
2528
- }
2529
- };
2530
- const handleGenerateThumbnail = async (item) => {
2531
- try {
2532
- const imageKey = item.path.replace(/^public\//, "");
2533
- await fetch("/api/studio/reprocess", {
2534
- method: "POST",
2535
- headers: { "Content-Type": "application/json" },
2536
- body: JSON.stringify({ imageKeys: [imageKey] })
2537
- });
2538
- triggerRefresh();
2539
- } catch (error) {
2540
- console.error("Failed to generate thumbnail:", error);
2541
- }
2542
- };
2543
- const handleRename = async (newName) => {
2544
- if (!renameItem) return;
2545
- setRenameItem(null);
2546
- try {
2547
- const response = await fetch("/api/studio/rename", {
2548
- method: "POST",
2549
- headers: { "Content-Type": "application/json" },
2550
- body: JSON.stringify({ oldPath: renameItem.path, newName })
2551
- });
2552
- if (response.ok) {
2553
- triggerRefresh();
2554
- }
2555
- } catch (error) {
2556
- console.error("Failed to rename:", error);
2557
- }
2558
- };
2559
- const allItemsSelected = sortedItems.length > 0 && sortedItems.every((item) => selectedItems.has(item.path));
2560
- const someItemsSelected = sortedItems.some((item) => selectedItems.has(item.path));
2561
- const handleSelectAll = () => {
2562
- if (allItemsSelected) {
2563
- clearSelection();
2564
- } else {
2565
- selectAll(sortedItems);
2566
- }
2567
- };
2568
- return /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { css: styles5.tableWrapper, children: [
2569
- renameItem && /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
2570
- InputModal,
2571
- {
2572
- title: renameItem.type === "folder" ? "Rename Folder" : "Rename File",
2573
- message: "Enter a new name:",
2574
- placeholder: renameItem.name,
2575
- defaultValue: renameItem.name,
2576
- confirmLabel: "Rename",
2577
- onConfirm: handleRename,
2578
- onCancel: () => setRenameItem(null)
2579
- }
2580
- ),
2581
- /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "table", { css: styles5.table, children: [
2582
- /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "thead", { children: /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "tr", { children: [
2583
- /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "th", { css: [styles5.th, styles5.thCheckbox], children: sortedItems.length > 0 && /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
2584
- "input",
2585
- {
2586
- type: "checkbox",
2587
- css: styles5.checkbox,
2588
- checked: allItemsSelected,
2589
- ref: (el) => {
2590
- if (el) el.indeterminate = someItemsSelected && !allItemsSelected;
2591
- },
2592
- onChange: handleSelectAll
2593
- }
2594
- ) }),
2595
- /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "th", { css: styles5.th, children: "Name" }),
2596
- /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "th", { css: [styles5.th, styles5.thSize], children: "Size" }),
2597
- /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "th", { css: [styles5.th, styles5.thDimensions], children: "Dimensions" }),
2598
- /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "th", { css: [styles5.th, styles5.thCdn], children: "CDN" })
2599
- ] }) }),
2600
- /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "tbody", { css: styles5.tbody, children: [
2601
- !isAtRoot && !isSearching && /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "tr", { css: styles5.parentRow, onClick: navigateUp, children: [
2602
- /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "td", { css: styles5.td }),
2603
- /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "td", { css: styles5.td, children: /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { css: styles5.nameCell, children: [
2604
- /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "svg", { css: styles5.parentIcon, fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 1.5, d: "M3 10h10a8 8 0 018 8v2M3 10l6 6m-6-6l6-6" }) }),
2605
- /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "span", { css: styles5.name, children: ".." })
2606
- ] }) }),
2607
- /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "td", { css: [styles5.td, styles5.meta], children: "--" }),
2608
- /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "td", { css: [styles5.td, styles5.meta], children: "Parent folder" }),
2609
- /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "td", { css: styles5.td, children: "--" })
2610
- ] }),
2611
- sortedItems.map((item) => /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
2612
- ListRow,
2613
- {
2614
- item,
2615
- isSelected: selectedItems.has(item.path),
2616
- onClick: (e) => handleItemClick(item, e),
2617
- onOpen: () => handleOpen(item),
2618
- onGenerateThumbnail: () => handleGenerateThumbnail(item),
2619
- onRename: () => setRenameItem(item)
2592
+ return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { css: styles5.tableWrapper, children: /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "table", { css: styles5.table, children: [
2593
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "thead", { children: /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "tr", { children: [
2594
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "th", { css: [styles5.th, styles5.thCheckbox], children: sortedItems.length > 0 && /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
2595
+ "input",
2596
+ {
2597
+ type: "checkbox",
2598
+ css: styles5.checkbox,
2599
+ checked: allItemsSelected,
2600
+ ref: (el) => {
2601
+ if (el) el.indeterminate = someItemsSelected && !allItemsSelected;
2620
2602
  },
2621
- item.path
2622
- ))
2623
- ] })
2603
+ onChange: handleSelectAll
2604
+ }
2605
+ ) }),
2606
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "th", { css: styles5.th, children: "Name" }),
2607
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "th", { css: [styles5.th, styles5.thSize], children: "Size" }),
2608
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "th", { css: [styles5.th, styles5.thDimensions], children: "Dimensions" }),
2609
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "th", { css: [styles5.th, styles5.thCdn], children: "CDN" })
2610
+ ] }) }),
2611
+ /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "tbody", { css: styles5.tbody, children: [
2612
+ !isAtRoot && !isSearching && /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "tr", { css: styles5.parentRow, onClick: navigateUp, children: [
2613
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "td", { css: styles5.td }),
2614
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "td", { css: styles5.td, children: /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { css: styles5.nameCell, children: [
2615
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "svg", { css: styles5.parentIcon, fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 1.5, d: "M3 10h10a8 8 0 018 8v2M3 10l6 6m-6-6l6-6" }) }),
2616
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "span", { css: styles5.name, children: ".." })
2617
+ ] }) }),
2618
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "td", { css: [styles5.td, styles5.meta], children: "--" }),
2619
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "td", { css: [styles5.td, styles5.meta], children: "Parent folder" }),
2620
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "td", { css: styles5.td, children: "--" })
2621
+ ] }),
2622
+ sortedItems.map((item) => /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
2623
+ ListRow,
2624
+ {
2625
+ item,
2626
+ isSelected: selectedItems.has(item.path),
2627
+ onClick: (e) => handleItemClick(item, e),
2628
+ onOpen: () => handleOpen(item),
2629
+ onGenerateThumbnail: () => handleGenerateThumbnail(item)
2630
+ },
2631
+ item.path
2632
+ ))
2624
2633
  ] })
2625
- ] });
2634
+ ] }) });
2626
2635
  }
2627
- function ListRow({ item, isSelected, onClick, onOpen, onGenerateThumbnail, onRename }) {
2636
+ function ListRow({ item, isSelected, onClick, onOpen, onGenerateThumbnail }) {
2628
2637
  const [showCopied, setShowCopied] = _react.useState.call(void 0, false);
2629
2638
  const isFolder = item.type === "folder";
2630
2639
  const isImage = !isFolder && item.thumbnail !== void 0;
@@ -2688,18 +2697,6 @@ function ListRow({ item, isSelected, onClick, onOpen, onGenerateThumbnail, onRen
2688
2697
  ]
2689
2698
  }
2690
2699
  ),
2691
- /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
2692
- "button",
2693
- {
2694
- css: styles5.copyBtn,
2695
- onClick: (e) => {
2696
- e.stopPropagation();
2697
- onRename();
2698
- },
2699
- title: "Rename",
2700
- children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "svg", { css: styles5.copyIcon, fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z" }) })
2701
- }
2702
- ),
2703
2700
  /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
2704
2701
  "button",
2705
2702
  {
@@ -3592,10 +3589,85 @@ function SettingsPanel({ onClose }) {
3592
3589
  ] }) });
3593
3590
  }
3594
3591
 
3592
+ // src/components/ErrorModal.tsx
3593
+
3594
+
3595
+ var styles8 = {
3596
+ overlay: _react3.css`
3597
+ position: fixed;
3598
+ inset: 0;
3599
+ background: rgba(0, 0, 0, 0.5);
3600
+ display: flex;
3601
+ align-items: center;
3602
+ justify-content: center;
3603
+ z-index: 1100;
3604
+ `,
3605
+ modal: _react3.css`
3606
+ background: ${_chunkUFCWGUAGjs.colors.surface};
3607
+ border-radius: 12px;
3608
+ padding: 24px;
3609
+ max-width: 400px;
3610
+ width: 90%;
3611
+ box-shadow: 0 20px 40px rgba(0, 0, 0, 0.2);
3612
+ `,
3613
+ header: _react3.css`
3614
+ display: flex;
3615
+ align-items: center;
3616
+ gap: 12px;
3617
+ margin-bottom: 12px;
3618
+ `,
3619
+ icon: _react3.css`
3620
+ width: 24px;
3621
+ height: 24px;
3622
+ color: ${_chunkUFCWGUAGjs.colors.danger};
3623
+ flex-shrink: 0;
3624
+ `,
3625
+ title: _react3.css`
3626
+ font-size: ${_chunkUFCWGUAGjs.fontSize.lg};
3627
+ font-weight: 600;
3628
+ color: ${_chunkUFCWGUAGjs.colors.text};
3629
+ margin: 0;
3630
+ `,
3631
+ message: _react3.css`
3632
+ font-size: ${_chunkUFCWGUAGjs.fontSize.base};
3633
+ color: ${_chunkUFCWGUAGjs.colors.textSecondary};
3634
+ margin: 0 0 20px 0;
3635
+ line-height: 1.5;
3636
+ `,
3637
+ button: _react3.css`
3638
+ width: 100%;
3639
+ padding: 10px 16px;
3640
+ border-radius: 6px;
3641
+ font-size: ${_chunkUFCWGUAGjs.fontSize.base};
3642
+ font-weight: 500;
3643
+ border: none;
3644
+ background: ${_chunkUFCWGUAGjs.colors.primary};
3645
+ color: white;
3646
+ cursor: pointer;
3647
+ transition: background 0.15s ease;
3648
+
3649
+ &:hover {
3650
+ background: ${_chunkUFCWGUAGjs.colors.primaryHover};
3651
+ }
3652
+ `
3653
+ };
3654
+ function ErrorModal() {
3655
+ const { error, clearError } = useStudio();
3656
+ if (!error) return null;
3657
+ return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { css: styles8.overlay, onClick: clearError, children: /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { css: styles8.modal, onClick: (e) => e.stopPropagation(), children: [
3658
+ /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { css: styles8.header, children: [
3659
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "svg", { css: styles8.icon, fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" }) }),
3660
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "h3", { css: styles8.title, children: error.title })
3661
+ ] }),
3662
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "p", { css: styles8.message, children: error.message }),
3663
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "button", { css: styles8.button, onClick: clearError, children: "OK" })
3664
+ ] }) });
3665
+ }
3666
+
3595
3667
  // src/components/StudioUI.tsx
3596
3668
 
3597
3669
  var btnHeight3 = "36px";
3598
- var styles8 = {
3670
+ var styles9 = {
3599
3671
  container: _react3.css`
3600
3672
  ${_chunkUFCWGUAGjs.baseReset}
3601
3673
  display: flex;
@@ -3742,10 +3814,17 @@ function StudioUI({ onClose, isVisible = true }) {
3742
3814
  const [isLoading, setIsLoading] = _react.useState.call(void 0, false);
3743
3815
  const [refreshKey, setRefreshKey] = _react.useState.call(void 0, 0);
3744
3816
  const [searchQuery, setSearchQuery] = _react.useState.call(void 0, "");
3817
+ const [error, setError] = _react.useState.call(void 0, null);
3745
3818
  const [isDragging, setIsDragging] = _react.useState.call(void 0, false);
3746
3819
  const triggerRefresh = _react.useCallback.call(void 0, () => {
3747
3820
  setRefreshKey((k) => k + 1);
3748
3821
  }, []);
3822
+ const showError = _react.useCallback.call(void 0, (title, message) => {
3823
+ setError({ title, message });
3824
+ }, []);
3825
+ const clearError = _react.useCallback.call(void 0, () => {
3826
+ setError(null);
3827
+ }, []);
3749
3828
  const handleDragOver = _react.useCallback.call(void 0, (e) => {
3750
3829
  e.preventDefault();
3751
3830
  e.stopPropagation();
@@ -3774,8 +3853,8 @@ function StudioUI({ onClose, isVisible = true }) {
3774
3853
  method: "POST",
3775
3854
  body: formData
3776
3855
  });
3777
- } catch (error) {
3778
- console.error("Upload error:", error);
3856
+ } catch (error2) {
3857
+ console.error("Upload error:", error2);
3779
3858
  }
3780
3859
  }
3781
3860
  triggerRefresh();
@@ -3877,18 +3956,21 @@ function StudioUI({ onClose, isVisible = true }) {
3877
3956
  refreshKey,
3878
3957
  triggerRefresh,
3879
3958
  searchQuery,
3880
- setSearchQuery
3959
+ setSearchQuery,
3960
+ error,
3961
+ showError,
3962
+ clearError
3881
3963
  };
3882
- return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, StudioContext.Provider, { value: contextValue, children: /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { css: styles8.container, children: [
3883
- /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { css: styles8.header, children: [
3884
- /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { css: styles8.headerLeft, children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "h1", { css: styles8.title, children: "Studio" }) }),
3885
- /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { css: styles8.headerCenter, children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, Breadcrumbs, { currentPath, onNavigate: setCurrentPath }) }),
3886
- /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { css: styles8.headerActions, children: [
3964
+ return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, StudioContext.Provider, { value: contextValue, children: /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { css: styles9.container, children: [
3965
+ /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { css: styles9.header, children: [
3966
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { css: styles9.headerLeft, children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "h1", { css: styles9.title, children: "Studio" }) }),
3967
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { css: styles9.headerCenter, children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, Breadcrumbs, { currentPath, onNavigate: setCurrentPath }) }),
3968
+ /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { css: styles9.headerActions, children: [
3887
3969
  /* @__PURE__ */ _jsxruntime.jsx.call(void 0, StudioSettings, {}),
3888
3970
  /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
3889
3971
  "button",
3890
3972
  {
3891
- css: styles8.headerBtn,
3973
+ css: styles9.headerBtn,
3892
3974
  onClick: onClose,
3893
3975
  "aria-label": "Close Studio",
3894
3976
  children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, CloseIcon, {})
@@ -3900,20 +3982,21 @@ function StudioUI({ onClose, isVisible = true }) {
3900
3982
  /* @__PURE__ */ _jsxruntime.jsxs.call(void 0,
3901
3983
  "div",
3902
3984
  {
3903
- css: styles8.content,
3985
+ css: styles9.content,
3904
3986
  onDragOver: handleDragOver,
3905
3987
  onDragLeave: handleDragLeave,
3906
3988
  onDrop: handleDrop,
3907
3989
  children: [
3908
- isDragging && /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { css: styles8.dropOverlay, children: /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { css: styles8.dropMessage, children: [
3909
- /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "svg", { css: styles8.dropIcon, fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-8l-4-4m0 0L8 8m4-4v12" }) }),
3990
+ isDragging && /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { css: styles9.dropOverlay, children: /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { css: styles9.dropMessage, children: [
3991
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "svg", { css: styles9.dropIcon, fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-8l-4-4m0 0L8 8m4-4v12" }) }),
3910
3992
  /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "span", { children: "Drop files to upload" })
3911
3993
  ] }) }),
3912
- /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { css: styles8.fileBrowser, children: viewMode === "grid" ? /* @__PURE__ */ _jsxruntime.jsx.call(void 0, StudioFileGrid, {}) : /* @__PURE__ */ _jsxruntime.jsx.call(void 0, StudioFileList, {}) })
3994
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { css: styles9.fileBrowser, children: viewMode === "grid" ? /* @__PURE__ */ _jsxruntime.jsx.call(void 0, StudioFileGrid, {}) : /* @__PURE__ */ _jsxruntime.jsx.call(void 0, StudioFileList, {}) })
3913
3995
  ]
3914
3996
  }
3915
3997
  ),
3916
- focusedItem && /* @__PURE__ */ _jsxruntime.jsx.call(void 0, StudioDetailView, {})
3998
+ focusedItem && /* @__PURE__ */ _jsxruntime.jsx.call(void 0, StudioDetailView, {}),
3999
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, ErrorModal, {})
3917
4000
  ] }) });
3918
4001
  }
3919
4002
  function Breadcrumbs({ currentPath, onNavigate }) {
@@ -3922,12 +4005,12 @@ function Breadcrumbs({ currentPath, onNavigate }) {
3922
4005
  name: part,
3923
4006
  path: parts.slice(0, index + 1).join("/")
3924
4007
  }));
3925
- return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { css: styles8.breadcrumbs, children: breadcrumbs.map((crumb, index) => /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "span", { style: { display: "flex", alignItems: "center", gap: 6 }, children: [
3926
- index > 0 && /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "span", { css: styles8.breadcrumbSeparator, children: "/" }),
3927
- index === breadcrumbs.length - 1 ? /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "span", { css: styles8.breadcrumbCurrent, children: crumb.name }) : /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
4008
+ return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { css: styles9.breadcrumbs, children: breadcrumbs.map((crumb, index) => /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "span", { style: { display: "flex", alignItems: "center", gap: 6 }, children: [
4009
+ index > 0 && /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "span", { css: styles9.breadcrumbSeparator, children: "/" }),
4010
+ index === breadcrumbs.length - 1 ? /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "span", { css: styles9.breadcrumbCurrent, children: crumb.name }) : /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
3928
4011
  "span",
3929
4012
  {
3930
- css: styles8.breadcrumbItem,
4013
+ css: styles9.breadcrumbItem,
3931
4014
  onClick: () => onNavigate(crumb.path),
3932
4015
  children: crumb.name
3933
4016
  }
@@ -3938,7 +4021,7 @@ function CloseIcon() {
3938
4021
  return /* @__PURE__ */ _jsxruntime.jsxs.call(void 0,
3939
4022
  "svg",
3940
4023
  {
3941
- css: styles8.headerIcon,
4024
+ css: styles9.headerIcon,
3942
4025
  xmlns: "http://www.w3.org/2000/svg",
3943
4026
  viewBox: "0 0 24 24",
3944
4027
  fill: "none",
@@ -3958,4 +4041,4 @@ var StudioUI_default = StudioUI;
3958
4041
 
3959
4042
 
3960
4043
  exports.StudioUI = StudioUI; exports.default = StudioUI_default;
3961
- //# sourceMappingURL=StudioUI-OAZ7CTB4.js.map
4044
+ //# sourceMappingURL=StudioUI-YFDO5MGG.js.map