@gallop.software/studio 0.1.81 → 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);
@@ -1554,6 +1559,194 @@ function ImageStackIcon() {
1554
1559
 
1555
1560
 
1556
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
+
1557
1750
  var spin2 = _react3.keyframes`
1558
1751
  to { transform: rotate(360deg); }
1559
1752
  `;
@@ -1875,86 +2068,30 @@ var styles4 = {
1875
2068
  `
1876
2069
  };
1877
2070
  function StudioFileGrid() {
1878
- const { currentPath, setCurrentPath, navigateUp, selectedItems, toggleSelection, selectRange, lastSelectedPath, selectAll, clearSelection, refreshKey, setFocusedItem, triggerRefresh, searchQuery } = useStudio();
1879
- const [items, setItems] = _react.useState.call(void 0, []);
1880
- const [loading, setLoading] = _react.useState.call(void 0, true);
1881
- const isInitialLoad = _react.useRef.call(void 0, true);
1882
- const lastPath = _react.useRef.call(void 0, currentPath);
1883
- _react.useEffect.call(void 0, () => {
1884
- async function loadItems() {
1885
- const isPathChange = lastPath.current !== currentPath;
1886
- if (isInitialLoad.current || isPathChange) {
1887
- setLoading(true);
1888
- }
1889
- lastPath.current = currentPath;
1890
- try {
1891
- const url = searchQuery && searchQuery.length >= 2 ? `/api/studio/search?q=${encodeURIComponent(searchQuery)}` : `/api/studio/list?path=${encodeURIComponent(currentPath)}`;
1892
- const response = await fetch(url);
1893
- if (response.ok) {
1894
- const data = await response.json();
1895
- setItems(data.items || []);
1896
- }
1897
- } catch (error) {
1898
- console.error("Failed to load items:", error);
1899
- }
1900
- setLoading(false);
1901
- isInitialLoad.current = false;
1902
- }
1903
- loadItems();
1904
- }, [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();
1905
2085
  if (loading) {
1906
2086
  return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { css: styles4.loading, children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { css: styles4.spinner }) });
1907
2087
  }
1908
- const isAtRoot = currentPath === "public";
1909
- if (items.length === 0 && isAtRoot) {
2088
+ if (sortedItems.length === 0 && isAtRoot) {
1910
2089
  return /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { css: styles4.empty, children: [
1911
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" }) }),
1912
2091
  /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "p", { css: styles4.emptyText, children: "No files in this folder" }),
1913
2092
  /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "p", { css: styles4.emptyText, children: "Upload images to get started" })
1914
2093
  ] });
1915
2094
  }
1916
- const isSearching = searchQuery && searchQuery.length >= 2;
1917
- const sortedItems = [...items].sort((a, b) => {
1918
- if (a.type === "folder" && b.type !== "folder") return -1;
1919
- if (a.type !== "folder" && b.type === "folder") return 1;
1920
- return a.name.localeCompare(b.name);
1921
- });
1922
- const handleItemClick = (item, e) => {
1923
- if (e.shiftKey && lastSelectedPath) {
1924
- selectRange(lastSelectedPath, item.path, sortedItems);
1925
- } else {
1926
- toggleSelection(item.path);
1927
- }
1928
- };
1929
- const handleOpen = (item) => {
1930
- if (item.type === "folder") {
1931
- setCurrentPath(item.path);
1932
- } else {
1933
- setFocusedItem(item);
1934
- }
1935
- };
1936
- const handleGenerateThumbnail = async (item) => {
1937
- try {
1938
- const imageKey = item.path.replace(/^public\//, "");
1939
- await fetch("/api/studio/reprocess", {
1940
- method: "POST",
1941
- headers: { "Content-Type": "application/json" },
1942
- body: JSON.stringify({ imageKeys: [imageKey] })
1943
- });
1944
- triggerRefresh();
1945
- } catch (error) {
1946
- console.error("Failed to generate thumbnail:", error);
1947
- }
1948
- };
1949
- const allItemsSelected = sortedItems.length > 0 && sortedItems.every((item) => selectedItems.has(item.path));
1950
- const someItemsSelected = sortedItems.some((item) => selectedItems.has(item.path));
1951
- const handleSelectAll = () => {
1952
- if (allItemsSelected) {
1953
- clearSelection();
1954
- } else {
1955
- selectAll(sortedItems);
1956
- }
1957
- };
1958
2095
  return /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { children: [
1959
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: [
1960
2097
  /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
@@ -2432,82 +2569,26 @@ var styles5 = {
2432
2569
  `
2433
2570
  };
2434
2571
  function StudioFileList() {
2435
- const { currentPath, setCurrentPath, navigateUp, selectedItems, toggleSelection, selectRange, lastSelectedPath, selectAll, clearSelection, refreshKey, setFocusedItem, triggerRefresh, searchQuery } = useStudio();
2436
- const [items, setItems] = _react.useState.call(void 0, []);
2437
- const [loading, setLoading] = _react.useState.call(void 0, true);
2438
- const isInitialLoad = _react.useRef.call(void 0, true);
2439
- const lastPath = _react.useRef.call(void 0, currentPath);
2440
- _react.useEffect.call(void 0, () => {
2441
- async function loadItems() {
2442
- const isPathChange = lastPath.current !== currentPath;
2443
- if (isInitialLoad.current || isPathChange) {
2444
- setLoading(true);
2445
- }
2446
- lastPath.current = currentPath;
2447
- try {
2448
- const url = searchQuery && searchQuery.length >= 2 ? `/api/studio/search?q=${encodeURIComponent(searchQuery)}` : `/api/studio/list?path=${encodeURIComponent(currentPath)}`;
2449
- const response = await fetch(url);
2450
- if (response.ok) {
2451
- const data = await response.json();
2452
- setItems(data.items || []);
2453
- }
2454
- } catch (error) {
2455
- console.error("Failed to load items:", error);
2456
- }
2457
- setLoading(false);
2458
- isInitialLoad.current = false;
2459
- }
2460
- loadItems();
2461
- }, [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();
2462
2586
  if (loading) {
2463
2587
  return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { css: styles5.loading, children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { css: styles5.spinner }) });
2464
2588
  }
2465
- const isAtRoot = currentPath === "public";
2466
- if (items.length === 0 && isAtRoot) {
2589
+ if (sortedItems.length === 0 && isAtRoot) {
2467
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" }) });
2468
2591
  }
2469
- const isSearching = searchQuery && searchQuery.length >= 2;
2470
- const sortedItems = [...items].sort((a, b) => {
2471
- if (a.type === "folder" && b.type !== "folder") return -1;
2472
- if (a.type !== "folder" && b.type === "folder") return 1;
2473
- return a.name.localeCompare(b.name);
2474
- });
2475
- const handleItemClick = (item, e) => {
2476
- if (e.shiftKey && lastSelectedPath) {
2477
- selectRange(lastSelectedPath, item.path, sortedItems);
2478
- } else {
2479
- toggleSelection(item.path);
2480
- }
2481
- };
2482
- const handleOpen = (item) => {
2483
- if (item.type === "folder") {
2484
- setCurrentPath(item.path);
2485
- } else {
2486
- setFocusedItem(item);
2487
- }
2488
- };
2489
- const handleGenerateThumbnail = async (item) => {
2490
- try {
2491
- const imageKey = item.path.replace(/^public\//, "");
2492
- await fetch("/api/studio/reprocess", {
2493
- method: "POST",
2494
- headers: { "Content-Type": "application/json" },
2495
- body: JSON.stringify({ imageKeys: [imageKey] })
2496
- });
2497
- triggerRefresh();
2498
- } catch (error) {
2499
- console.error("Failed to generate thumbnail:", error);
2500
- }
2501
- };
2502
- const allItemsSelected = sortedItems.length > 0 && sortedItems.every((item) => selectedItems.has(item.path));
2503
- const someItemsSelected = sortedItems.some((item) => selectedItems.has(item.path));
2504
- const handleSelectAll = () => {
2505
- if (allItemsSelected) {
2506
- clearSelection();
2507
- } else {
2508
- selectAll(sortedItems);
2509
- }
2510
- };
2511
2592
  return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { css: styles5.tableWrapper, children: /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "table", { css: styles5.table, children: [
2512
2593
  /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "thead", { children: /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "tr", { children: [
2513
2594
  /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "th", { css: [styles5.th, styles5.thCheckbox], children: sortedItems.length > 0 && /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
@@ -3508,10 +3589,85 @@ function SettingsPanel({ onClose }) {
3508
3589
  ] }) });
3509
3590
  }
3510
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
+
3511
3667
  // src/components/StudioUI.tsx
3512
3668
 
3513
3669
  var btnHeight3 = "36px";
3514
- var styles8 = {
3670
+ var styles9 = {
3515
3671
  container: _react3.css`
3516
3672
  ${_chunkUFCWGUAGjs.baseReset}
3517
3673
  display: flex;
@@ -3658,10 +3814,17 @@ function StudioUI({ onClose, isVisible = true }) {
3658
3814
  const [isLoading, setIsLoading] = _react.useState.call(void 0, false);
3659
3815
  const [refreshKey, setRefreshKey] = _react.useState.call(void 0, 0);
3660
3816
  const [searchQuery, setSearchQuery] = _react.useState.call(void 0, "");
3817
+ const [error, setError] = _react.useState.call(void 0, null);
3661
3818
  const [isDragging, setIsDragging] = _react.useState.call(void 0, false);
3662
3819
  const triggerRefresh = _react.useCallback.call(void 0, () => {
3663
3820
  setRefreshKey((k) => k + 1);
3664
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
+ }, []);
3665
3828
  const handleDragOver = _react.useCallback.call(void 0, (e) => {
3666
3829
  e.preventDefault();
3667
3830
  e.stopPropagation();
@@ -3690,8 +3853,8 @@ function StudioUI({ onClose, isVisible = true }) {
3690
3853
  method: "POST",
3691
3854
  body: formData
3692
3855
  });
3693
- } catch (error) {
3694
- console.error("Upload error:", error);
3856
+ } catch (error2) {
3857
+ console.error("Upload error:", error2);
3695
3858
  }
3696
3859
  }
3697
3860
  triggerRefresh();
@@ -3793,18 +3956,21 @@ function StudioUI({ onClose, isVisible = true }) {
3793
3956
  refreshKey,
3794
3957
  triggerRefresh,
3795
3958
  searchQuery,
3796
- setSearchQuery
3959
+ setSearchQuery,
3960
+ error,
3961
+ showError,
3962
+ clearError
3797
3963
  };
3798
- return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, StudioContext.Provider, { value: contextValue, children: /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { css: styles8.container, children: [
3799
- /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { css: styles8.header, children: [
3800
- /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { css: styles8.headerLeft, children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "h1", { css: styles8.title, children: "Studio" }) }),
3801
- /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { css: styles8.headerCenter, children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, Breadcrumbs, { currentPath, onNavigate: setCurrentPath }) }),
3802
- /* @__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: [
3803
3969
  /* @__PURE__ */ _jsxruntime.jsx.call(void 0, StudioSettings, {}),
3804
3970
  /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
3805
3971
  "button",
3806
3972
  {
3807
- css: styles8.headerBtn,
3973
+ css: styles9.headerBtn,
3808
3974
  onClick: onClose,
3809
3975
  "aria-label": "Close Studio",
3810
3976
  children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, CloseIcon, {})
@@ -3816,20 +3982,21 @@ function StudioUI({ onClose, isVisible = true }) {
3816
3982
  /* @__PURE__ */ _jsxruntime.jsxs.call(void 0,
3817
3983
  "div",
3818
3984
  {
3819
- css: styles8.content,
3985
+ css: styles9.content,
3820
3986
  onDragOver: handleDragOver,
3821
3987
  onDragLeave: handleDragLeave,
3822
3988
  onDrop: handleDrop,
3823
3989
  children: [
3824
- isDragging && /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { css: styles8.dropOverlay, children: /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { css: styles8.dropMessage, children: [
3825
- /* @__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" }) }),
3826
3992
  /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "span", { children: "Drop files to upload" })
3827
3993
  ] }) }),
3828
- /* @__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, {}) })
3829
3995
  ]
3830
3996
  }
3831
3997
  ),
3832
- 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, {})
3833
4000
  ] }) });
3834
4001
  }
3835
4002
  function Breadcrumbs({ currentPath, onNavigate }) {
@@ -3838,12 +4005,12 @@ function Breadcrumbs({ currentPath, onNavigate }) {
3838
4005
  name: part,
3839
4006
  path: parts.slice(0, index + 1).join("/")
3840
4007
  }));
3841
- 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: [
3842
- index > 0 && /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "span", { css: styles8.breadcrumbSeparator, children: "/" }),
3843
- 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,
3844
4011
  "span",
3845
4012
  {
3846
- css: styles8.breadcrumbItem,
4013
+ css: styles9.breadcrumbItem,
3847
4014
  onClick: () => onNavigate(crumb.path),
3848
4015
  children: crumb.name
3849
4016
  }
@@ -3854,7 +4021,7 @@ function CloseIcon() {
3854
4021
  return /* @__PURE__ */ _jsxruntime.jsxs.call(void 0,
3855
4022
  "svg",
3856
4023
  {
3857
- css: styles8.headerIcon,
4024
+ css: styles9.headerIcon,
3858
4025
  xmlns: "http://www.w3.org/2000/svg",
3859
4026
  viewBox: "0 0 24 24",
3860
4027
  fill: "none",
@@ -3874,4 +4041,4 @@ var StudioUI_default = StudioUI;
3874
4041
 
3875
4042
 
3876
4043
  exports.StudioUI = StudioUI; exports.default = StudioUI_default;
3877
- //# sourceMappingURL=StudioUI-RH4ZXWKP.js.map
4044
+ //# sourceMappingURL=StudioUI-YFDO5MGG.js.map