@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.
- package/dist/{StudioUI-VPNL5NMI.mjs → StudioUI-VJVOSOPD.mjs} +485 -402
- package/dist/StudioUI-VJVOSOPD.mjs.map +1 -0
- package/dist/{StudioUI-OAZ7CTB4.js → StudioUI-YFDO5MGG.js} +420 -337
- package/dist/StudioUI-YFDO5MGG.js.map +1 -0
- package/dist/{handlers.d.ts → handlers/index.d.mts} +1 -1
- package/dist/{handlers.d.mts → handlers/index.d.ts} +1 -1
- package/dist/{handlers.js → handlers/index.js} +611 -579
- package/dist/handlers/index.js.map +1 -0
- package/dist/{handlers.mjs → handlers/index.mjs} +620 -588
- package/dist/handlers/index.mjs.map +1 -0
- package/dist/index.js +1 -1
- package/dist/index.mjs +1 -1
- package/package.json +4 -4
- package/dist/StudioUI-OAZ7CTB4.js.map +0 -1
- package/dist/StudioUI-VPNL5NMI.mjs.map +0 -1
- package/dist/handlers.js.map +0 -1
- package/dist/handlers.mjs.map +0 -1
|
@@ -7,8 +7,8 @@ import {
|
|
|
7
7
|
} from "./chunk-HXE6XCG2.mjs";
|
|
8
8
|
|
|
9
9
|
// src/components/StudioUI.tsx
|
|
10
|
-
import { useEffect as
|
|
11
|
-
import { css as
|
|
10
|
+
import { useEffect as useEffect3, useCallback as useCallback3, useState as useState8 } from "react";
|
|
11
|
+
import { css as css9 } from "@emotion/react";
|
|
12
12
|
|
|
13
13
|
// src/components/StudioContext.tsx
|
|
14
14
|
import { createContext, useContext } from "react";
|
|
@@ -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 = createContext(defaultState);
|
|
@@ -891,6 +896,7 @@ function StudioToolbar() {
|
|
|
891
896
|
const [imagesToProcess, setImagesToProcess] = useState2([]);
|
|
892
897
|
const [alertMessage, setAlertMessage] = useState2(null);
|
|
893
898
|
const [showNewFolderModal, setShowNewFolderModal] = useState2(false);
|
|
899
|
+
const [showRenameFolderModal, setShowRenameFolderModal] = useState2(false);
|
|
894
900
|
const [showMoveModal, setShowMoveModal] = useState2(false);
|
|
895
901
|
const isInImagesFolder = currentPath === "public/images" || currentPath.startsWith("public/images/");
|
|
896
902
|
const handleUpload = useCallback(() => {
|
|
@@ -947,13 +953,13 @@ function StudioToolbar() {
|
|
|
947
953
|
const handleProcessImages = useCallback(async () => {
|
|
948
954
|
const hasSelection2 = selectedItems.size > 0;
|
|
949
955
|
if (hasSelection2) {
|
|
950
|
-
const
|
|
956
|
+
const selectedPaths2 = Array.from(selectedItems);
|
|
951
957
|
const imageExtensions = ["jpg", "jpeg", "png", "gif", "webp", "svg", "ico", "bmp", "tiff", "tif"];
|
|
952
|
-
const selectedImagePaths =
|
|
958
|
+
const selectedImagePaths = selectedPaths2.filter((p) => {
|
|
953
959
|
const ext = p.split(".").pop()?.toLowerCase() || "";
|
|
954
960
|
return imageExtensions.includes(ext);
|
|
955
961
|
});
|
|
956
|
-
const selectedFolders =
|
|
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 = useCallback(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__ */ jsx3(
|
|
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__ */ jsx3(
|
|
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__ */ jsx3(FolderPlusIcon, {}),
|
|
1373
|
-
"New Folder"
|
|
1411
|
+
singleFolderSelected ? /* @__PURE__ */ jsx3(RenameIcon, {}) : /* @__PURE__ */ jsx3(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__ */ jsx3("svg", { css: styles3.icon, fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ jsx3("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__ */ jsx3("svg", { css: styles3.icon, fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ jsx3("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__ */ jsx3("svg", { css: styles3.icon, fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ jsx3("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M8 7h12m0 0l-4-4m4 4l-4 4m0 6H4m0 0l4 4m-4-4l4-4" }) });
|
|
1502
1544
|
}
|
|
@@ -1514,8 +1556,196 @@ function ImageStackIcon() {
|
|
|
1514
1556
|
}
|
|
1515
1557
|
|
|
1516
1558
|
// src/components/StudioFileGrid.tsx
|
|
1517
|
-
import {
|
|
1559
|
+
import { useState as useState4 } from "react";
|
|
1518
1560
|
import { css as css4, keyframes as keyframes4 } from "@emotion/react";
|
|
1561
|
+
|
|
1562
|
+
// src/hooks/useFileList.ts
|
|
1563
|
+
import { useEffect as useEffect2, useState as useState3, useRef as useRef2, useCallback as useCallback2 } from "react";
|
|
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] = useState3([]);
|
|
1663
|
+
const [loading, setLoading] = useState3(true);
|
|
1664
|
+
const isInitialLoad = useRef2(true);
|
|
1665
|
+
const lastPath = useRef2(currentPath);
|
|
1666
|
+
useEffect2(() => {
|
|
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 = useCallback2((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 = useCallback2((item) => {
|
|
1703
|
+
if (item.type === "folder") {
|
|
1704
|
+
setCurrentPath(item.path);
|
|
1705
|
+
} else {
|
|
1706
|
+
setFocusedItem(item);
|
|
1707
|
+
}
|
|
1708
|
+
}, [setCurrentPath, setFocusedItem]);
|
|
1709
|
+
const handleGenerateThumbnail = useCallback2(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 = useCallback2(() => {
|
|
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
|
|
1519
1749
|
import { jsx as jsx4, jsxs as jsxs4 } from "@emotion/react/jsx-runtime";
|
|
1520
1750
|
var spin2 = keyframes4`
|
|
1521
1751
|
to { transform: rotate(360deg); }
|
|
@@ -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: css4`
|
|
@@ -1745,35 +1971,6 @@ var styles4 = {
|
|
|
1745
1971
|
color: ${colors.text};
|
|
1746
1972
|
}
|
|
1747
1973
|
`,
|
|
1748
|
-
nameRow: css4`
|
|
1749
|
-
display: flex;
|
|
1750
|
-
align-items: center;
|
|
1751
|
-
gap: 4px;
|
|
1752
|
-
`,
|
|
1753
|
-
renameBtn: css4`
|
|
1754
|
-
flex-shrink: 0;
|
|
1755
|
-
height: 20px;
|
|
1756
|
-
width: 20px;
|
|
1757
|
-
color: ${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: ${colors.text};
|
|
1771
|
-
}
|
|
1772
|
-
`,
|
|
1773
|
-
renameIcon: css4`
|
|
1774
|
-
width: 14px;
|
|
1775
|
-
height: 14px;
|
|
1776
|
-
`,
|
|
1777
1974
|
copyIcon: css4`
|
|
1778
1975
|
width: 18px;
|
|
1779
1976
|
height: 18px;
|
|
@@ -1871,116 +2068,31 @@ var styles4 = {
|
|
|
1871
2068
|
`
|
|
1872
2069
|
};
|
|
1873
2070
|
function StudioFileGrid() {
|
|
1874
|
-
const {
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
|
|
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__ */ jsx4("div", { css: styles4.loading, children: /* @__PURE__ */ jsx4("div", { css: styles4.spinner }) });
|
|
1904
2087
|
}
|
|
1905
|
-
|
|
1906
|
-
if (items.length === 0 && isAtRoot) {
|
|
2088
|
+
if (sortedItems.length === 0 && isAtRoot) {
|
|
1907
2089
|
return /* @__PURE__ */ jsxs4("div", { css: styles4.empty, children: [
|
|
1908
2090
|
/* @__PURE__ */ jsx4("svg", { css: styles4.emptyIcon, fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ jsx4("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 1.5, d: "M4 16l4.586-4.586a2 2 0 012.828 0L16 16m-2-2l1.586-1.586a2 2 0 012.828 0L20 14m-6-6h.01M6 20h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z" }) }),
|
|
1909
2091
|
/* @__PURE__ */ jsx4("p", { css: styles4.emptyText, children: "No files in this folder" }),
|
|
1910
2092
|
/* @__PURE__ */ jsx4("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__ */ jsxs4("div", { children: [
|
|
1972
|
-
renameItem && /* @__PURE__ */ jsx4(
|
|
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__ */ jsx4("div", { css: styles4.selectAllRow, children: /* @__PURE__ */ jsxs4("label", { css: styles4.selectAllLabel, children: [
|
|
1985
2097
|
/* @__PURE__ */ jsx4(
|
|
1986
2098
|
"input",
|
|
@@ -2020,16 +2132,15 @@ 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
|
|
2032
|
-
const [showCopied, setShowCopied] =
|
|
2142
|
+
function GridItem({ item, isSelected, onClick, onOpen, onGenerateThumbnail }) {
|
|
2143
|
+
const [showCopied, setShowCopied] = useState4(false);
|
|
2033
2144
|
const isFolder = item.type === "folder";
|
|
2034
2145
|
const isImage = !isFolder && item.thumbnail !== void 0;
|
|
2035
2146
|
const isImagesFolder = isFolder && (item.name === "images" || item.path.includes("/images/"));
|
|
@@ -2115,21 +2226,7 @@ function GridItem({ item, isSelected, onClick, onOpen, onGenerateThumbnail, onRe
|
|
|
2115
2226
|
) : /* @__PURE__ */ jsx4("svg", { css: styles4.fileIcon, fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ jsx4("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 1.5, d: "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__ */ jsx4("div", { css: styles4.label, children: /* @__PURE__ */ jsx4("div", { css: styles4.labelRow, children: /* @__PURE__ */ jsxs4("div", { css: styles4.labelText, children: [
|
|
2118
|
-
/* @__PURE__ */
|
|
2119
|
-
/* @__PURE__ */ jsx4("p", { css: styles4.name, title: item.name, children: truncateMiddle(item.name) }),
|
|
2120
|
-
/* @__PURE__ */ jsx4(
|
|
2121
|
-
"button",
|
|
2122
|
-
{
|
|
2123
|
-
css: styles4.renameBtn,
|
|
2124
|
-
onClick: (e) => {
|
|
2125
|
-
e.stopPropagation();
|
|
2126
|
-
onRename();
|
|
2127
|
-
},
|
|
2128
|
-
title: "Rename",
|
|
2129
|
-
children: /* @__PURE__ */ jsx4("svg", { css: styles4.renameIcon, fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ jsx4("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__ */ jsx4("p", { css: styles4.name, title: item.name, children: truncateMiddle(item.name) }),
|
|
2133
2230
|
isFolder ? /* @__PURE__ */ jsxs4("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 " : "",
|
|
@@ -2160,7 +2257,7 @@ function truncateMiddle(str, maxLength = 24) {
|
|
|
2160
2257
|
}
|
|
2161
2258
|
|
|
2162
2259
|
// src/components/StudioFileList.tsx
|
|
2163
|
-
import {
|
|
2260
|
+
import { useState as useState5 } from "react";
|
|
2164
2261
|
import { css as css5, keyframes as keyframes5 } from "@emotion/react";
|
|
2165
2262
|
import { jsx as jsx5, jsxs as jsxs5 } from "@emotion/react/jsx-runtime";
|
|
2166
2263
|
var spin3 = keyframes5`
|
|
@@ -2472,160 +2569,72 @@ var styles5 = {
|
|
|
2472
2569
|
`
|
|
2473
2570
|
};
|
|
2474
2571
|
function StudioFileList() {
|
|
2475
|
-
const {
|
|
2476
|
-
|
|
2477
|
-
|
|
2478
|
-
|
|
2479
|
-
|
|
2480
|
-
|
|
2481
|
-
|
|
2482
|
-
|
|
2483
|
-
|
|
2484
|
-
|
|
2485
|
-
|
|
2486
|
-
|
|
2487
|
-
|
|
2488
|
-
|
|
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__ */ jsx5("div", { css: styles5.loading, children: /* @__PURE__ */ jsx5("div", { css: styles5.spinner }) });
|
|
2505
2588
|
}
|
|
2506
|
-
|
|
2507
|
-
if (items.length === 0 && isAtRoot) {
|
|
2589
|
+
if (sortedItems.length === 0 && isAtRoot) {
|
|
2508
2590
|
return /* @__PURE__ */ jsx5("div", { css: styles5.empty, children: /* @__PURE__ */ jsx5("p", { children: "No files in this folder" }) });
|
|
2509
2591
|
}
|
|
2510
|
-
|
|
2511
|
-
|
|
2512
|
-
|
|
2513
|
-
|
|
2514
|
-
|
|
2515
|
-
|
|
2516
|
-
|
|
2517
|
-
|
|
2518
|
-
|
|
2519
|
-
|
|
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__ */ jsxs5("div", { css: styles5.tableWrapper, children: [
|
|
2569
|
-
renameItem && /* @__PURE__ */ jsx5(
|
|
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__ */ jsxs5("table", { css: styles5.table, children: [
|
|
2582
|
-
/* @__PURE__ */ jsx5("thead", { children: /* @__PURE__ */ jsxs5("tr", { children: [
|
|
2583
|
-
/* @__PURE__ */ jsx5("th", { css: [styles5.th, styles5.thCheckbox], children: sortedItems.length > 0 && /* @__PURE__ */ jsx5(
|
|
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__ */ jsx5("th", { css: styles5.th, children: "Name" }),
|
|
2596
|
-
/* @__PURE__ */ jsx5("th", { css: [styles5.th, styles5.thSize], children: "Size" }),
|
|
2597
|
-
/* @__PURE__ */ jsx5("th", { css: [styles5.th, styles5.thDimensions], children: "Dimensions" }),
|
|
2598
|
-
/* @__PURE__ */ jsx5("th", { css: [styles5.th, styles5.thCdn], children: "CDN" })
|
|
2599
|
-
] }) }),
|
|
2600
|
-
/* @__PURE__ */ jsxs5("tbody", { css: styles5.tbody, children: [
|
|
2601
|
-
!isAtRoot && !isSearching && /* @__PURE__ */ jsxs5("tr", { css: styles5.parentRow, onClick: navigateUp, children: [
|
|
2602
|
-
/* @__PURE__ */ jsx5("td", { css: styles5.td }),
|
|
2603
|
-
/* @__PURE__ */ jsx5("td", { css: styles5.td, children: /* @__PURE__ */ jsxs5("div", { css: styles5.nameCell, children: [
|
|
2604
|
-
/* @__PURE__ */ jsx5("svg", { css: styles5.parentIcon, fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ jsx5("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 1.5, d: "M3 10h10a8 8 0 018 8v2M3 10l6 6m-6-6l6-6" }) }),
|
|
2605
|
-
/* @__PURE__ */ jsx5("span", { css: styles5.name, children: ".." })
|
|
2606
|
-
] }) }),
|
|
2607
|
-
/* @__PURE__ */ jsx5("td", { css: [styles5.td, styles5.meta], children: "--" }),
|
|
2608
|
-
/* @__PURE__ */ jsx5("td", { css: [styles5.td, styles5.meta], children: "Parent folder" }),
|
|
2609
|
-
/* @__PURE__ */ jsx5("td", { css: styles5.td, children: "--" })
|
|
2610
|
-
] }),
|
|
2611
|
-
sortedItems.map((item) => /* @__PURE__ */ jsx5(
|
|
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__ */ jsx5("div", { css: styles5.tableWrapper, children: /* @__PURE__ */ jsxs5("table", { css: styles5.table, children: [
|
|
2593
|
+
/* @__PURE__ */ jsx5("thead", { children: /* @__PURE__ */ jsxs5("tr", { children: [
|
|
2594
|
+
/* @__PURE__ */ jsx5("th", { css: [styles5.th, styles5.thCheckbox], children: sortedItems.length > 0 && /* @__PURE__ */ jsx5(
|
|
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
|
-
|
|
2622
|
-
|
|
2623
|
-
|
|
2603
|
+
onChange: handleSelectAll
|
|
2604
|
+
}
|
|
2605
|
+
) }),
|
|
2606
|
+
/* @__PURE__ */ jsx5("th", { css: styles5.th, children: "Name" }),
|
|
2607
|
+
/* @__PURE__ */ jsx5("th", { css: [styles5.th, styles5.thSize], children: "Size" }),
|
|
2608
|
+
/* @__PURE__ */ jsx5("th", { css: [styles5.th, styles5.thDimensions], children: "Dimensions" }),
|
|
2609
|
+
/* @__PURE__ */ jsx5("th", { css: [styles5.th, styles5.thCdn], children: "CDN" })
|
|
2610
|
+
] }) }),
|
|
2611
|
+
/* @__PURE__ */ jsxs5("tbody", { css: styles5.tbody, children: [
|
|
2612
|
+
!isAtRoot && !isSearching && /* @__PURE__ */ jsxs5("tr", { css: styles5.parentRow, onClick: navigateUp, children: [
|
|
2613
|
+
/* @__PURE__ */ jsx5("td", { css: styles5.td }),
|
|
2614
|
+
/* @__PURE__ */ jsx5("td", { css: styles5.td, children: /* @__PURE__ */ jsxs5("div", { css: styles5.nameCell, children: [
|
|
2615
|
+
/* @__PURE__ */ jsx5("svg", { css: styles5.parentIcon, fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ jsx5("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 1.5, d: "M3 10h10a8 8 0 018 8v2M3 10l6 6m-6-6l6-6" }) }),
|
|
2616
|
+
/* @__PURE__ */ jsx5("span", { css: styles5.name, children: ".." })
|
|
2617
|
+
] }) }),
|
|
2618
|
+
/* @__PURE__ */ jsx5("td", { css: [styles5.td, styles5.meta], children: "--" }),
|
|
2619
|
+
/* @__PURE__ */ jsx5("td", { css: [styles5.td, styles5.meta], children: "Parent folder" }),
|
|
2620
|
+
/* @__PURE__ */ jsx5("td", { css: styles5.td, children: "--" })
|
|
2621
|
+
] }),
|
|
2622
|
+
sortedItems.map((item) => /* @__PURE__ */ jsx5(
|
|
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
|
|
2628
|
-
const [showCopied, setShowCopied] =
|
|
2636
|
+
function ListRow({ item, isSelected, onClick, onOpen, onGenerateThumbnail }) {
|
|
2637
|
+
const [showCopied, setShowCopied] = useState5(false);
|
|
2629
2638
|
const isFolder = item.type === "folder";
|
|
2630
2639
|
const isImage = !isFolder && item.thumbnail !== void 0;
|
|
2631
2640
|
const isImagesFolder = isFolder && (item.name === "images" || item.path.includes("/images/"));
|
|
@@ -2688,18 +2697,6 @@ function ListRow({ item, isSelected, onClick, onOpen, onGenerateThumbnail, onRen
|
|
|
2688
2697
|
]
|
|
2689
2698
|
}
|
|
2690
2699
|
),
|
|
2691
|
-
/* @__PURE__ */ jsx5(
|
|
2692
|
-
"button",
|
|
2693
|
-
{
|
|
2694
|
-
css: styles5.copyBtn,
|
|
2695
|
-
onClick: (e) => {
|
|
2696
|
-
e.stopPropagation();
|
|
2697
|
-
onRename();
|
|
2698
|
-
},
|
|
2699
|
-
title: "Rename",
|
|
2700
|
-
children: /* @__PURE__ */ jsx5("svg", { css: styles5.copyIcon, fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ jsx5("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__ */ jsx5(
|
|
2704
2701
|
"button",
|
|
2705
2702
|
{
|
|
@@ -2743,7 +2740,7 @@ function truncateMiddle2(str, maxLength = 32) {
|
|
|
2743
2740
|
}
|
|
2744
2741
|
|
|
2745
2742
|
// src/components/StudioDetailView.tsx
|
|
2746
|
-
import { useState as
|
|
2743
|
+
import { useState as useState6 } from "react";
|
|
2747
2744
|
import { css as css6 } from "@emotion/react";
|
|
2748
2745
|
import { Fragment as Fragment3, jsx as jsx6, jsxs as jsxs6 } from "@emotion/react/jsx-runtime";
|
|
2749
2746
|
var IMAGE_EXTENSIONS = [".jpg", ".jpeg", ".png", ".gif", ".webp", ".svg", ".ico", ".bmp", ".tiff", ".tif"];
|
|
@@ -3004,12 +3001,12 @@ var styles6 = {
|
|
|
3004
3001
|
};
|
|
3005
3002
|
function StudioDetailView() {
|
|
3006
3003
|
const { focusedItem, setFocusedItem, triggerRefresh, clearSelection } = useStudio();
|
|
3007
|
-
const [showDeleteConfirm, setShowDeleteConfirm] =
|
|
3008
|
-
const [showRenameModal, setShowRenameModal] =
|
|
3009
|
-
const [showProcessConfirm, setShowProcessConfirm] =
|
|
3010
|
-
const [processProgress, setProcessProgress] =
|
|
3011
|
-
const [alertMessage, setAlertMessage] =
|
|
3012
|
-
const [showCopied, setShowCopied] =
|
|
3004
|
+
const [showDeleteConfirm, setShowDeleteConfirm] = useState6(false);
|
|
3005
|
+
const [showRenameModal, setShowRenameModal] = useState6(false);
|
|
3006
|
+
const [showProcessConfirm, setShowProcessConfirm] = useState6(false);
|
|
3007
|
+
const [processProgress, setProcessProgress] = useState6(null);
|
|
3008
|
+
const [alertMessage, setAlertMessage] = useState6(null);
|
|
3009
|
+
const [showCopied, setShowCopied] = useState6(false);
|
|
3013
3010
|
if (!focusedItem) return null;
|
|
3014
3011
|
const isImage = isImageFile(focusedItem.name);
|
|
3015
3012
|
const isVideo = isVideoFile(focusedItem.name);
|
|
@@ -3270,7 +3267,7 @@ function formatFileSize3(bytes) {
|
|
|
3270
3267
|
}
|
|
3271
3268
|
|
|
3272
3269
|
// src/components/StudioSettings.tsx
|
|
3273
|
-
import { useState as
|
|
3270
|
+
import { useState as useState7 } from "react";
|
|
3274
3271
|
import { css as css7 } from "@emotion/react";
|
|
3275
3272
|
import { Fragment as Fragment4, jsx as jsx7, jsxs as jsxs7 } from "@emotion/react/jsx-runtime";
|
|
3276
3273
|
var btnHeight2 = "36px";
|
|
@@ -3510,7 +3507,7 @@ var styles7 = {
|
|
|
3510
3507
|
`
|
|
3511
3508
|
};
|
|
3512
3509
|
function StudioSettings() {
|
|
3513
|
-
const [isOpen, setIsOpen] =
|
|
3510
|
+
const [isOpen, setIsOpen] = useState7(false);
|
|
3514
3511
|
return /* @__PURE__ */ jsxs7(Fragment4, { children: [
|
|
3515
3512
|
/* @__PURE__ */ jsx7("button", { css: styles7.btn, onClick: () => setIsOpen(true), "aria-label": "Settings", children: /* @__PURE__ */ jsxs7(
|
|
3516
3513
|
"svg",
|
|
@@ -3538,7 +3535,7 @@ CLOUDFLARE_R2_SECRET_ACCESS_KEY=your_secret_access_key_here
|
|
|
3538
3535
|
CLOUDFLARE_R2_BUCKET_NAME=my-images-bucket
|
|
3539
3536
|
CLOUDFLARE_R2_PUBLIC_URL=https://cdn.yourdomain.com`;
|
|
3540
3537
|
function SettingsPanel({ onClose }) {
|
|
3541
|
-
const [copied, setCopied] =
|
|
3538
|
+
const [copied, setCopied] = useState7(false);
|
|
3542
3539
|
const handleCopy = () => {
|
|
3543
3540
|
navigator.clipboard.writeText(envTemplate);
|
|
3544
3541
|
setCopied(true);
|
|
@@ -3592,18 +3589,93 @@ function SettingsPanel({ onClose }) {
|
|
|
3592
3589
|
] }) });
|
|
3593
3590
|
}
|
|
3594
3591
|
|
|
3595
|
-
// src/components/
|
|
3592
|
+
// src/components/ErrorModal.tsx
|
|
3593
|
+
import { css as css8 } from "@emotion/react";
|
|
3596
3594
|
import { jsx as jsx8, jsxs as jsxs8 } from "@emotion/react/jsx-runtime";
|
|
3597
|
-
var btnHeight3 = "36px";
|
|
3598
3595
|
var styles8 = {
|
|
3599
|
-
|
|
3596
|
+
overlay: css8`
|
|
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: css8`
|
|
3606
|
+
background: ${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: css8`
|
|
3614
|
+
display: flex;
|
|
3615
|
+
align-items: center;
|
|
3616
|
+
gap: 12px;
|
|
3617
|
+
margin-bottom: 12px;
|
|
3618
|
+
`,
|
|
3619
|
+
icon: css8`
|
|
3620
|
+
width: 24px;
|
|
3621
|
+
height: 24px;
|
|
3622
|
+
color: ${colors.danger};
|
|
3623
|
+
flex-shrink: 0;
|
|
3624
|
+
`,
|
|
3625
|
+
title: css8`
|
|
3626
|
+
font-size: ${fontSize.lg};
|
|
3627
|
+
font-weight: 600;
|
|
3628
|
+
color: ${colors.text};
|
|
3629
|
+
margin: 0;
|
|
3630
|
+
`,
|
|
3631
|
+
message: css8`
|
|
3632
|
+
font-size: ${fontSize.base};
|
|
3633
|
+
color: ${colors.textSecondary};
|
|
3634
|
+
margin: 0 0 20px 0;
|
|
3635
|
+
line-height: 1.5;
|
|
3636
|
+
`,
|
|
3637
|
+
button: css8`
|
|
3638
|
+
width: 100%;
|
|
3639
|
+
padding: 10px 16px;
|
|
3640
|
+
border-radius: 6px;
|
|
3641
|
+
font-size: ${fontSize.base};
|
|
3642
|
+
font-weight: 500;
|
|
3643
|
+
border: none;
|
|
3644
|
+
background: ${colors.primary};
|
|
3645
|
+
color: white;
|
|
3646
|
+
cursor: pointer;
|
|
3647
|
+
transition: background 0.15s ease;
|
|
3648
|
+
|
|
3649
|
+
&:hover {
|
|
3650
|
+
background: ${colors.primaryHover};
|
|
3651
|
+
}
|
|
3652
|
+
`
|
|
3653
|
+
};
|
|
3654
|
+
function ErrorModal() {
|
|
3655
|
+
const { error, clearError } = useStudio();
|
|
3656
|
+
if (!error) return null;
|
|
3657
|
+
return /* @__PURE__ */ jsx8("div", { css: styles8.overlay, onClick: clearError, children: /* @__PURE__ */ jsxs8("div", { css: styles8.modal, onClick: (e) => e.stopPropagation(), children: [
|
|
3658
|
+
/* @__PURE__ */ jsxs8("div", { css: styles8.header, children: [
|
|
3659
|
+
/* @__PURE__ */ jsx8("svg", { css: styles8.icon, fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ jsx8("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__ */ jsx8("h3", { css: styles8.title, children: error.title })
|
|
3661
|
+
] }),
|
|
3662
|
+
/* @__PURE__ */ jsx8("p", { css: styles8.message, children: error.message }),
|
|
3663
|
+
/* @__PURE__ */ jsx8("button", { css: styles8.button, onClick: clearError, children: "OK" })
|
|
3664
|
+
] }) });
|
|
3665
|
+
}
|
|
3666
|
+
|
|
3667
|
+
// src/components/StudioUI.tsx
|
|
3668
|
+
import { jsx as jsx9, jsxs as jsxs9 } from "@emotion/react/jsx-runtime";
|
|
3669
|
+
var btnHeight3 = "36px";
|
|
3670
|
+
var styles9 = {
|
|
3671
|
+
container: css9`
|
|
3600
3672
|
${baseReset}
|
|
3601
3673
|
display: flex;
|
|
3602
3674
|
flex-direction: column;
|
|
3603
3675
|
height: 100%;
|
|
3604
3676
|
background: ${colors.background};
|
|
3605
3677
|
`,
|
|
3606
|
-
header:
|
|
3678
|
+
header: css9`
|
|
3607
3679
|
display: flex;
|
|
3608
3680
|
align-items: center;
|
|
3609
3681
|
justify-content: space-between;
|
|
@@ -3612,7 +3684,7 @@ var styles8 = {
|
|
|
3612
3684
|
border-bottom: 1px solid ${colors.border};
|
|
3613
3685
|
position: relative;
|
|
3614
3686
|
`,
|
|
3615
|
-
title:
|
|
3687
|
+
title: css9`
|
|
3616
3688
|
font-size: ${fontSize.lg};
|
|
3617
3689
|
font-weight: 600;
|
|
3618
3690
|
color: ${colors.text};
|
|
@@ -3620,14 +3692,14 @@ var styles8 = {
|
|
|
3620
3692
|
letter-spacing: -0.02em;
|
|
3621
3693
|
flex-shrink: 0;
|
|
3622
3694
|
`,
|
|
3623
|
-
headerLeft:
|
|
3695
|
+
headerLeft: css9`
|
|
3624
3696
|
display: flex;
|
|
3625
3697
|
align-items: center;
|
|
3626
3698
|
gap: 12px;
|
|
3627
3699
|
flex: 1;
|
|
3628
3700
|
min-width: 0;
|
|
3629
3701
|
`,
|
|
3630
|
-
headerCenter:
|
|
3702
|
+
headerCenter: css9`
|
|
3631
3703
|
position: absolute;
|
|
3632
3704
|
left: 50%;
|
|
3633
3705
|
transform: translateX(-50%);
|
|
@@ -3635,7 +3707,7 @@ var styles8 = {
|
|
|
3635
3707
|
align-items: center;
|
|
3636
3708
|
max-width: 50%;
|
|
3637
3709
|
`,
|
|
3638
|
-
breadcrumbs:
|
|
3710
|
+
breadcrumbs: css9`
|
|
3639
3711
|
display: flex;
|
|
3640
3712
|
align-items: center;
|
|
3641
3713
|
gap: 6px;
|
|
@@ -3643,11 +3715,11 @@ var styles8 = {
|
|
|
3643
3715
|
color: ${colors.textSecondary};
|
|
3644
3716
|
overflow: hidden;
|
|
3645
3717
|
`,
|
|
3646
|
-
breadcrumbSeparator:
|
|
3718
|
+
breadcrumbSeparator: css9`
|
|
3647
3719
|
color: ${colors.border};
|
|
3648
3720
|
flex-shrink: 0;
|
|
3649
3721
|
`,
|
|
3650
|
-
breadcrumbItem:
|
|
3722
|
+
breadcrumbItem: css9`
|
|
3651
3723
|
color: ${colors.textSecondary};
|
|
3652
3724
|
text-decoration: none;
|
|
3653
3725
|
cursor: pointer;
|
|
@@ -3658,19 +3730,19 @@ var styles8 = {
|
|
|
3658
3730
|
color: ${colors.primary};
|
|
3659
3731
|
}
|
|
3660
3732
|
`,
|
|
3661
|
-
breadcrumbCurrent:
|
|
3733
|
+
breadcrumbCurrent: css9`
|
|
3662
3734
|
color: ${colors.text};
|
|
3663
3735
|
font-weight: 500;
|
|
3664
3736
|
white-space: nowrap;
|
|
3665
3737
|
overflow: hidden;
|
|
3666
3738
|
text-overflow: ellipsis;
|
|
3667
3739
|
`,
|
|
3668
|
-
headerActions:
|
|
3740
|
+
headerActions: css9`
|
|
3669
3741
|
display: flex;
|
|
3670
3742
|
align-items: center;
|
|
3671
3743
|
gap: 8px;
|
|
3672
3744
|
`,
|
|
3673
|
-
headerBtn:
|
|
3745
|
+
headerBtn: css9`
|
|
3674
3746
|
height: ${btnHeight3};
|
|
3675
3747
|
padding: 0 12px;
|
|
3676
3748
|
background: ${colors.surface};
|
|
@@ -3687,23 +3759,23 @@ var styles8 = {
|
|
|
3687
3759
|
border-color: ${colors.borderHover};
|
|
3688
3760
|
}
|
|
3689
3761
|
`,
|
|
3690
|
-
headerIcon:
|
|
3762
|
+
headerIcon: css9`
|
|
3691
3763
|
width: 16px;
|
|
3692
3764
|
height: 16px;
|
|
3693
3765
|
color: ${colors.textSecondary};
|
|
3694
3766
|
`,
|
|
3695
|
-
content:
|
|
3767
|
+
content: css9`
|
|
3696
3768
|
flex: 1;
|
|
3697
3769
|
display: flex;
|
|
3698
3770
|
overflow: hidden;
|
|
3699
3771
|
`,
|
|
3700
|
-
fileBrowser:
|
|
3772
|
+
fileBrowser: css9`
|
|
3701
3773
|
flex: 1;
|
|
3702
3774
|
min-width: 0;
|
|
3703
3775
|
overflow: auto;
|
|
3704
3776
|
padding: 20px 24px;
|
|
3705
3777
|
`,
|
|
3706
|
-
dropOverlay:
|
|
3778
|
+
dropOverlay: css9`
|
|
3707
3779
|
position: absolute;
|
|
3708
3780
|
top: 0;
|
|
3709
3781
|
left: 0;
|
|
@@ -3718,7 +3790,7 @@ var styles8 = {
|
|
|
3718
3790
|
z-index: 50;
|
|
3719
3791
|
pointer-events: none;
|
|
3720
3792
|
`,
|
|
3721
|
-
dropMessage:
|
|
3793
|
+
dropMessage: css9`
|
|
3722
3794
|
display: flex;
|
|
3723
3795
|
flex-direction: column;
|
|
3724
3796
|
align-items: center;
|
|
@@ -3727,36 +3799,43 @@ var styles8 = {
|
|
|
3727
3799
|
font-size: ${fontSize.lg};
|
|
3728
3800
|
font-weight: 600;
|
|
3729
3801
|
`,
|
|
3730
|
-
dropIcon:
|
|
3802
|
+
dropIcon: css9`
|
|
3731
3803
|
width: 48px;
|
|
3732
3804
|
height: 48px;
|
|
3733
3805
|
`
|
|
3734
3806
|
};
|
|
3735
3807
|
function StudioUI({ onClose, isVisible = true }) {
|
|
3736
|
-
const [currentPath, setCurrentPathInternal] =
|
|
3737
|
-
const [selectedItems, setSelectedItems] =
|
|
3738
|
-
const [lastSelectedPath, setLastSelectedPath] =
|
|
3739
|
-
const [viewMode, setViewMode] =
|
|
3740
|
-
const [focusedItem, setFocusedItem] =
|
|
3741
|
-
const [meta, setMeta] =
|
|
3742
|
-
const [isLoading, setIsLoading] =
|
|
3743
|
-
const [refreshKey, setRefreshKey] =
|
|
3744
|
-
const [searchQuery, setSearchQuery] =
|
|
3745
|
-
const [
|
|
3746
|
-
const
|
|
3808
|
+
const [currentPath, setCurrentPathInternal] = useState8("public");
|
|
3809
|
+
const [selectedItems, setSelectedItems] = useState8(/* @__PURE__ */ new Set());
|
|
3810
|
+
const [lastSelectedPath, setLastSelectedPath] = useState8(null);
|
|
3811
|
+
const [viewMode, setViewMode] = useState8("grid");
|
|
3812
|
+
const [focusedItem, setFocusedItem] = useState8(null);
|
|
3813
|
+
const [meta, setMeta] = useState8(null);
|
|
3814
|
+
const [isLoading, setIsLoading] = useState8(false);
|
|
3815
|
+
const [refreshKey, setRefreshKey] = useState8(0);
|
|
3816
|
+
const [searchQuery, setSearchQuery] = useState8("");
|
|
3817
|
+
const [error, setError] = useState8(null);
|
|
3818
|
+
const [isDragging, setIsDragging] = useState8(false);
|
|
3819
|
+
const triggerRefresh = useCallback3(() => {
|
|
3747
3820
|
setRefreshKey((k) => k + 1);
|
|
3748
3821
|
}, []);
|
|
3749
|
-
const
|
|
3822
|
+
const showError = useCallback3((title, message) => {
|
|
3823
|
+
setError({ title, message });
|
|
3824
|
+
}, []);
|
|
3825
|
+
const clearError = useCallback3(() => {
|
|
3826
|
+
setError(null);
|
|
3827
|
+
}, []);
|
|
3828
|
+
const handleDragOver = useCallback3((e) => {
|
|
3750
3829
|
e.preventDefault();
|
|
3751
3830
|
e.stopPropagation();
|
|
3752
3831
|
setIsDragging(true);
|
|
3753
3832
|
}, []);
|
|
3754
|
-
const handleDragLeave =
|
|
3833
|
+
const handleDragLeave = useCallback3((e) => {
|
|
3755
3834
|
e.preventDefault();
|
|
3756
3835
|
e.stopPropagation();
|
|
3757
3836
|
setIsDragging(false);
|
|
3758
3837
|
}, []);
|
|
3759
|
-
const handleDrop =
|
|
3838
|
+
const handleDrop = useCallback3(async (e) => {
|
|
3760
3839
|
e.preventDefault();
|
|
3761
3840
|
e.stopPropagation();
|
|
3762
3841
|
setIsDragging(false);
|
|
@@ -3774,25 +3853,25 @@ function StudioUI({ onClose, isVisible = true }) {
|
|
|
3774
3853
|
method: "POST",
|
|
3775
3854
|
body: formData
|
|
3776
3855
|
});
|
|
3777
|
-
} catch (
|
|
3778
|
-
console.error("Upload error:",
|
|
3856
|
+
} catch (error2) {
|
|
3857
|
+
console.error("Upload error:", error2);
|
|
3779
3858
|
}
|
|
3780
3859
|
}
|
|
3781
3860
|
triggerRefresh();
|
|
3782
3861
|
}, [currentPath, triggerRefresh]);
|
|
3783
|
-
const navigateUp =
|
|
3862
|
+
const navigateUp = useCallback3(() => {
|
|
3784
3863
|
if (currentPath === "public") return;
|
|
3785
3864
|
const parts = currentPath.split("/");
|
|
3786
3865
|
parts.pop();
|
|
3787
3866
|
setCurrentPathInternal(parts.join("/") || "public");
|
|
3788
3867
|
setSelectedItems(/* @__PURE__ */ new Set());
|
|
3789
3868
|
}, [currentPath]);
|
|
3790
|
-
const setCurrentPath =
|
|
3869
|
+
const setCurrentPath = useCallback3((path) => {
|
|
3791
3870
|
setCurrentPathInternal(path);
|
|
3792
3871
|
setSelectedItems(/* @__PURE__ */ new Set());
|
|
3793
3872
|
setFocusedItem(null);
|
|
3794
3873
|
}, []);
|
|
3795
|
-
const toggleSelection =
|
|
3874
|
+
const toggleSelection = useCallback3((path) => {
|
|
3796
3875
|
setSelectedItems((prev) => {
|
|
3797
3876
|
const next = new Set(prev);
|
|
3798
3877
|
if (next.has(path)) {
|
|
@@ -3804,7 +3883,7 @@ function StudioUI({ onClose, isVisible = true }) {
|
|
|
3804
3883
|
});
|
|
3805
3884
|
setLastSelectedPath(path);
|
|
3806
3885
|
}, []);
|
|
3807
|
-
const selectRange =
|
|
3886
|
+
const selectRange = useCallback3((fromPath, toPath, allItems) => {
|
|
3808
3887
|
const fromIndex = allItems.findIndex((item) => item.path === fromPath);
|
|
3809
3888
|
const toIndex = allItems.findIndex((item) => item.path === toPath);
|
|
3810
3889
|
if (fromIndex === -1 || toIndex === -1) return;
|
|
@@ -3819,13 +3898,13 @@ function StudioUI({ onClose, isVisible = true }) {
|
|
|
3819
3898
|
});
|
|
3820
3899
|
setLastSelectedPath(toPath);
|
|
3821
3900
|
}, []);
|
|
3822
|
-
const selectAll =
|
|
3901
|
+
const selectAll = useCallback3((items) => {
|
|
3823
3902
|
setSelectedItems(new Set(items.map((item) => item.path)));
|
|
3824
3903
|
}, []);
|
|
3825
|
-
const clearSelection =
|
|
3904
|
+
const clearSelection = useCallback3(() => {
|
|
3826
3905
|
setSelectedItems(/* @__PURE__ */ new Set());
|
|
3827
3906
|
}, []);
|
|
3828
|
-
const handleKeyDown =
|
|
3907
|
+
const handleKeyDown = useCallback3(
|
|
3829
3908
|
(e) => {
|
|
3830
3909
|
if (e.key === "Escape") {
|
|
3831
3910
|
const target = e.target;
|
|
@@ -3841,7 +3920,7 @@ function StudioUI({ onClose, isVisible = true }) {
|
|
|
3841
3920
|
},
|
|
3842
3921
|
[onClose, focusedItem]
|
|
3843
3922
|
);
|
|
3844
|
-
|
|
3923
|
+
useEffect3(() => {
|
|
3845
3924
|
if (isVisible) {
|
|
3846
3925
|
document.addEventListener("keydown", handleKeyDown);
|
|
3847
3926
|
document.body.style.overflow = "hidden";
|
|
@@ -3877,43 +3956,47 @@ 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__ */
|
|
3883
|
-
/* @__PURE__ */
|
|
3884
|
-
/* @__PURE__ */
|
|
3885
|
-
/* @__PURE__ */
|
|
3886
|
-
/* @__PURE__ */
|
|
3887
|
-
/* @__PURE__ */
|
|
3888
|
-
/* @__PURE__ */
|
|
3964
|
+
return /* @__PURE__ */ jsx9(StudioContext.Provider, { value: contextValue, children: /* @__PURE__ */ jsxs9("div", { css: styles9.container, children: [
|
|
3965
|
+
/* @__PURE__ */ jsxs9("div", { css: styles9.header, children: [
|
|
3966
|
+
/* @__PURE__ */ jsx9("div", { css: styles9.headerLeft, children: /* @__PURE__ */ jsx9("h1", { css: styles9.title, children: "Studio" }) }),
|
|
3967
|
+
/* @__PURE__ */ jsx9("div", { css: styles9.headerCenter, children: /* @__PURE__ */ jsx9(Breadcrumbs, { currentPath, onNavigate: setCurrentPath }) }),
|
|
3968
|
+
/* @__PURE__ */ jsxs9("div", { css: styles9.headerActions, children: [
|
|
3969
|
+
/* @__PURE__ */ jsx9(StudioSettings, {}),
|
|
3970
|
+
/* @__PURE__ */ jsx9(
|
|
3889
3971
|
"button",
|
|
3890
3972
|
{
|
|
3891
|
-
css:
|
|
3973
|
+
css: styles9.headerBtn,
|
|
3892
3974
|
onClick: onClose,
|
|
3893
3975
|
"aria-label": "Close Studio",
|
|
3894
|
-
children: /* @__PURE__ */
|
|
3976
|
+
children: /* @__PURE__ */ jsx9(CloseIcon, {})
|
|
3895
3977
|
}
|
|
3896
3978
|
)
|
|
3897
3979
|
] })
|
|
3898
3980
|
] }),
|
|
3899
|
-
/* @__PURE__ */
|
|
3900
|
-
/* @__PURE__ */
|
|
3981
|
+
/* @__PURE__ */ jsx9(StudioToolbar, {}),
|
|
3982
|
+
/* @__PURE__ */ jsxs9(
|
|
3901
3983
|
"div",
|
|
3902
3984
|
{
|
|
3903
|
-
css:
|
|
3985
|
+
css: styles9.content,
|
|
3904
3986
|
onDragOver: handleDragOver,
|
|
3905
3987
|
onDragLeave: handleDragLeave,
|
|
3906
3988
|
onDrop: handleDrop,
|
|
3907
3989
|
children: [
|
|
3908
|
-
isDragging && /* @__PURE__ */
|
|
3909
|
-
/* @__PURE__ */
|
|
3910
|
-
/* @__PURE__ */
|
|
3990
|
+
isDragging && /* @__PURE__ */ jsx9("div", { css: styles9.dropOverlay, children: /* @__PURE__ */ jsxs9("div", { css: styles9.dropMessage, children: [
|
|
3991
|
+
/* @__PURE__ */ jsx9("svg", { css: styles9.dropIcon, fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ jsx9("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" }) }),
|
|
3992
|
+
/* @__PURE__ */ jsx9("span", { children: "Drop files to upload" })
|
|
3911
3993
|
] }) }),
|
|
3912
|
-
/* @__PURE__ */
|
|
3994
|
+
/* @__PURE__ */ jsx9("div", { css: styles9.fileBrowser, children: viewMode === "grid" ? /* @__PURE__ */ jsx9(StudioFileGrid, {}) : /* @__PURE__ */ jsx9(StudioFileList, {}) })
|
|
3913
3995
|
]
|
|
3914
3996
|
}
|
|
3915
3997
|
),
|
|
3916
|
-
focusedItem && /* @__PURE__ */
|
|
3998
|
+
focusedItem && /* @__PURE__ */ jsx9(StudioDetailView, {}),
|
|
3999
|
+
/* @__PURE__ */ jsx9(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__ */
|
|
3926
|
-
index > 0 && /* @__PURE__ */
|
|
3927
|
-
index === breadcrumbs.length - 1 ? /* @__PURE__ */
|
|
4008
|
+
return /* @__PURE__ */ jsx9("div", { css: styles9.breadcrumbs, children: breadcrumbs.map((crumb, index) => /* @__PURE__ */ jsxs9("span", { style: { display: "flex", alignItems: "center", gap: 6 }, children: [
|
|
4009
|
+
index > 0 && /* @__PURE__ */ jsx9("span", { css: styles9.breadcrumbSeparator, children: "/" }),
|
|
4010
|
+
index === breadcrumbs.length - 1 ? /* @__PURE__ */ jsx9("span", { css: styles9.breadcrumbCurrent, children: crumb.name }) : /* @__PURE__ */ jsx9(
|
|
3928
4011
|
"span",
|
|
3929
4012
|
{
|
|
3930
|
-
css:
|
|
4013
|
+
css: styles9.breadcrumbItem,
|
|
3931
4014
|
onClick: () => onNavigate(crumb.path),
|
|
3932
4015
|
children: crumb.name
|
|
3933
4016
|
}
|
|
@@ -3935,10 +4018,10 @@ function Breadcrumbs({ currentPath, onNavigate }) {
|
|
|
3935
4018
|
] }, crumb.path)) });
|
|
3936
4019
|
}
|
|
3937
4020
|
function CloseIcon() {
|
|
3938
|
-
return /* @__PURE__ */
|
|
4021
|
+
return /* @__PURE__ */ jsxs9(
|
|
3939
4022
|
"svg",
|
|
3940
4023
|
{
|
|
3941
|
-
css:
|
|
4024
|
+
css: styles9.headerIcon,
|
|
3942
4025
|
xmlns: "http://www.w3.org/2000/svg",
|
|
3943
4026
|
viewBox: "0 0 24 24",
|
|
3944
4027
|
fill: "none",
|
|
@@ -3947,8 +4030,8 @@ function CloseIcon() {
|
|
|
3947
4030
|
strokeLinecap: "round",
|
|
3948
4031
|
strokeLinejoin: "round",
|
|
3949
4032
|
children: [
|
|
3950
|
-
/* @__PURE__ */
|
|
3951
|
-
/* @__PURE__ */
|
|
4033
|
+
/* @__PURE__ */ jsx9("line", { x1: "18", y1: "6", x2: "6", y2: "18" }),
|
|
4034
|
+
/* @__PURE__ */ jsx9("line", { x1: "6", y1: "6", x2: "18", y2: "18" })
|
|
3952
4035
|
]
|
|
3953
4036
|
}
|
|
3954
4037
|
);
|
|
@@ -3958,4 +4041,4 @@ export {
|
|
|
3958
4041
|
StudioUI,
|
|
3959
4042
|
StudioUI_default as default
|
|
3960
4043
|
};
|
|
3961
|
-
//# sourceMappingURL=StudioUI-
|
|
4044
|
+
//# sourceMappingURL=StudioUI-VJVOSOPD.mjs.map
|