Skip to content

Commit 48cb0e1

Browse files
committed
feat: polish mounted file workflows
1 parent 3610c04 commit 48cb0e1

3 files changed

Lines changed: 188 additions & 9 deletions

File tree

client/main.js

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ const {
77
dialog,
88
Menu,
99
screen,
10+
shell,
1011
} = require("electron");
1112

1213
let mainWindow;
@@ -157,6 +158,16 @@ ipcMain.handle("open-local-file", async (_event, options = {}) => {
157158
}
158159
});
159160

161+
ipcMain.handle("reveal-in-finder", async (_event, targetPath) => {
162+
if (!targetPath) return null;
163+
try {
164+
await shell.showItemInFolder(targetPath);
165+
} catch (err) {
166+
return null;
167+
}
168+
return true;
169+
});
170+
160171
app.on("ready", createWindow);
161172

162173
app.on("window-all-closed", () => {

client/src/components/FilePickerModal.js

Lines changed: 45 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,11 @@ import {
88
} from "@ant-design/icons";
99
import { apiClient } from "../api";
1010

11-
const HIDDEN_SYSTEM_FILES = new Set(["workflow_preference.json"]);
11+
const HIDDEN_SYSTEM_FILES = new Set([
12+
"workflow_preference.json",
13+
".ds_store",
14+
"thumbs.db",
15+
]);
1216
const IMAGE_EXTENSIONS = new Set([
1317
".png",
1418
".jpg",
@@ -31,6 +35,7 @@ const FilePickerModal = ({
3135
const [items, setItems] = useState([]);
3236
const [loading, setLoading] = useState(false);
3337
const [previewStatus, setPreviewStatus] = useState({});
38+
const [onlyImages, setOnlyImages] = useState(false);
3439
const previewBaseUrl =
3540
apiClient.defaults.baseURL || "http://localhost:4242";
3641

@@ -40,6 +45,7 @@ const FilePickerModal = ({
4045
useEffect(() => {
4146
if (visible) {
4247
setCurrentPath("root");
48+
setOnlyImages(false);
4349
loadAllData();
4450
}
4551
}, [visible]);
@@ -59,18 +65,23 @@ const FilePickerModal = ({
5965
// Derive items for current view
6066
useEffect(() => {
6167
const filtered = allData.filter((f) => {
62-
if (!f.is_folder && HIDDEN_SYSTEM_FILES.has(f.name)) return false;
68+
const nameLower = String(f.name || "").toLowerCase();
69+
if (!f.is_folder && HIDDEN_SYSTEM_FILES.has(nameLower)) return false;
6370
if (currentPath === "root") return f.path === "root" || !f.path;
6471
return String(f.path) === currentPath;
6572
});
6673

67-
filtered.sort((a, b) => {
74+
const filteredByType = onlyImages
75+
? filtered.filter((f) => f.is_folder || isImageFile(f))
76+
: filtered;
77+
78+
filteredByType.sort((a, b) => {
6879
if (a.is_folder === b.is_folder) return a.name.localeCompare(b.name);
6980
return a.is_folder ? -1 : 1;
7081
});
7182

72-
setItems(filtered);
73-
}, [currentPath, allData]);
83+
setItems(filteredByType);
84+
}, [currentPath, allData, onlyImages]);
7485

7586
const getParentPath = () => {
7687
if (currentPath === "root") return null;
@@ -238,6 +249,13 @@ const FilePickerModal = ({
238249
>
239250
Upload from Local
240251
</Button>
252+
<Button
253+
type={onlyImages ? "primary" : "default"}
254+
onClick={() => setOnlyImages((prev) => !prev)}
255+
style={{ marginLeft: 8 }}
256+
>
257+
Images Only
258+
</Button>
241259
</div>
242260

243261
<div style={{ height: "400px", overflow: "auto" }}>
@@ -349,7 +367,28 @@ const FilePickerModal = ({
349367
<FileOutlined style={{ fontSize: "20px" }} />
350368
)
351369
}
352-
title={item.name}
370+
title={
371+
<span>
372+
{item.name}
373+
{item.is_folder &&
374+
(item.path === "root" || !item.path) &&
375+
item.physical_path && (
376+
<span
377+
style={{
378+
marginLeft: 8,
379+
padding: "2px 6px",
380+
fontSize: 10,
381+
borderRadius: 10,
382+
background: "#f0f5ff",
383+
color: "#2f54eb",
384+
border: "1px solid #adc6ff",
385+
}}
386+
>
387+
Mounted
388+
</span>
389+
)}
390+
</span>
391+
}
353392
description={item.size ? item.size : null}
354393
/>
355394
</List.Item>

client/src/views/FilesManager.js

Lines changed: 132 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,11 @@ import {
2525
import { apiClient } from "../api";
2626
import FileTreeSidebar from "../components/FileTreeSidebar";
2727

28-
const HIDDEN_SYSTEM_FILES = new Set(["workflow_preference.json"]);
28+
const HIDDEN_SYSTEM_FILES = new Set([
29+
"workflow_preference.json",
30+
".ds_store",
31+
"thumbs.db",
32+
]);
2933
const IMAGE_EXTENSIONS = new Set([
3034
".png",
3135
".jpg",
@@ -42,7 +46,8 @@ const transformFiles = (fileList) => {
4246
const folders = [];
4347
const files = {};
4448
fileList.forEach((f) => {
45-
if (!f.is_folder && HIDDEN_SYSTEM_FILES.has(f.name)) {
49+
const nameLower = String(f.name || "").toLowerCase();
50+
if (!f.is_folder && HIDDEN_SYSTEM_FILES.has(nameLower)) {
4651
return;
4752
}
4853
if (f.is_folder) {
@@ -62,6 +67,7 @@ const transformFiles = (fileList) => {
6267
size: f.size,
6368
type: f.type,
6469
is_folder: false,
70+
physical_path: f.physical_path || null,
6571
});
6672
}
6773
});
@@ -502,14 +508,54 @@ function FilesManager() {
502508
const item = folder || file;
503509

504510
if (item) {
511+
const isMountedFolder =
512+
!!folder && folder.parent === "root" && folder.physical_path;
513+
let descendantFileCount = 0;
514+
let descendantFolderCount = 0;
515+
let descendantSizeKb = 0;
516+
517+
if (isMountedFolder) {
518+
const queue = [folder.key];
519+
while (queue.length) {
520+
const parentId = queue.shift();
521+
const childFolders = folders.filter((f) => f.parent === parentId);
522+
descendantFolderCount += childFolders.length;
523+
childFolders.forEach((f) => queue.push(f.key));
524+
525+
const childFiles = files[parentId] || [];
526+
descendantFileCount += childFiles.length;
527+
childFiles.forEach((f) => {
528+
const sizeStr = String(f.size || "");
529+
const match = sizeStr.match(/([0-9.]+)\s*(KB|MB|GB|B)/i);
530+
if (match) {
531+
const value = parseFloat(match[1]);
532+
const unit = match[2].toUpperCase();
533+
if (unit === "B") descendantSizeKb += value / 1024;
534+
if (unit === "KB") descendantSizeKb += value;
535+
if (unit === "MB") descendantSizeKb += value * 1024;
536+
if (unit === "GB") descendantSizeKb += value * 1024 * 1024;
537+
}
538+
});
539+
}
540+
}
541+
542+
const totalSizeStr = isMountedFolder
543+
? descendantSizeKb > 1024
544+
? `${(descendantSizeKb / 1024).toFixed(2)} MB`
545+
: `${descendantSizeKb.toFixed(2)} KB`
546+
: item.size || "N/A";
547+
505548
setPropertiesData({
506549
type: "single",
507550
name: item.title || item.name,
508551
isFolder: !!folder,
509-
size: item.size || "N/A",
552+
size: totalSizeStr,
510553
fileType: item.type || "Folder",
511554
created: new Date().toLocaleString(), // Backend should provide this
512555
modified: new Date().toLocaleString(), // Backend should provide this
556+
fileCount: isMountedFolder ? descendantFileCount : undefined,
557+
folderCount: isMountedFolder ? descendantFolderCount : undefined,
558+
isMountedFolder,
513559
});
514560
}
515561
} else {
@@ -921,6 +967,24 @@ function FilesManager() {
921967
) : (
922968
<div style={{ wordBreak: "break-word", fontSize: 12 }}>
923969
{item.title || item.name}
970+
{type === "folder" &&
971+
item.parent === "root" &&
972+
item.physical_path && (
973+
<span
974+
style={{
975+
display: "inline-block",
976+
marginLeft: 6,
977+
padding: "2px 6px",
978+
fontSize: 10,
979+
borderRadius: 10,
980+
background: "#f0f5ff",
981+
color: "#2f54eb",
982+
border: "1px solid #adc6ff",
983+
}}
984+
>
985+
Mounted
986+
</span>
987+
)}
924988
</div>
925989
)}
926990
</div>
@@ -1019,6 +1083,26 @@ function FilesManager() {
10191083
icon: <FolderOpenOutlined />,
10201084
});
10211085
}
1086+
if (selectedItems.length === 1) {
1087+
const selectedFile = Object.values(files)
1088+
.flat()
1089+
.find((f) => f.key === selectedKey);
1090+
const selectedFolder = folders.find((f) => f.key === selectedKey);
1091+
const hasPhysicalPath =
1092+
(selectedFile &&
1093+
selectedFile.physical_path &&
1094+
selectedFile.physical_path.startsWith("/")) ||
1095+
(selectedFolder &&
1096+
selectedFolder.physical_path &&
1097+
selectedFolder.physical_path.startsWith("/"));
1098+
if (hasPhysicalPath) {
1099+
items.push({
1100+
key: "reveal_in_finder",
1101+
label: "Open in Finder",
1102+
icon: <FolderOpenOutlined />,
1103+
});
1104+
}
1105+
}
10221106
items.push(
10231107
{
10241108
key: "rename",
@@ -1138,6 +1222,25 @@ function FilesManager() {
11381222
});
11391223
};
11401224

1225+
const handleRevealInFinder = (key) => {
1226+
const item =
1227+
folders.find((f) => f.key === key) ||
1228+
Object.values(files)
1229+
.flat()
1230+
.find((f) => f.key === key);
1231+
if (!item || !item.physical_path || !item.physical_path.startsWith("/")) {
1232+
message.warning("This item is not linked to a local file.");
1233+
return;
1234+
}
1235+
try {
1236+
const { ipcRenderer } = window.require("electron");
1237+
ipcRenderer.invoke("reveal-in-finder", item.physical_path);
1238+
} catch (err) {
1239+
console.error("Reveal in Finder error", err);
1240+
message.error("Failed to open in Finder");
1241+
}
1242+
};
1243+
11411244
return (
11421245
<div
11431246
style={{
@@ -1425,6 +1528,8 @@ function FilesManager() {
14251528
if (key === "mount_project") handleMountProjectDirectory();
14261529
if (key === "unmount_project")
14271530
handleUnmountProject(contextMenu.key);
1531+
if (key === "reveal_in_finder")
1532+
handleRevealInFinder(contextMenu.key);
14281533
if (key === "rename") {
14291534
const item =
14301535
folders.find((f) => f.key === contextMenu.key) ||
@@ -1568,6 +1673,30 @@ function FilesManager() {
15681673
<span style={{ fontWeight: 500 }}>Size:</span>
15691674
<span>{propertiesData.size}</span>
15701675
</div>
1676+
{propertiesData.isMountedFolder && (
1677+
<>
1678+
<div
1679+
style={{
1680+
marginBottom: 12,
1681+
display: "flex",
1682+
justifyContent: "space-between",
1683+
}}
1684+
>
1685+
<span style={{ fontWeight: 500 }}>Folders:</span>
1686+
<span>{propertiesData.folderCount}</span>
1687+
</div>
1688+
<div
1689+
style={{
1690+
marginBottom: 12,
1691+
display: "flex",
1692+
justifyContent: "space-between",
1693+
}}
1694+
>
1695+
<span style={{ fontWeight: 500 }}>Files:</span>
1696+
<span>{propertiesData.fileCount}</span>
1697+
</div>
1698+
</>
1699+
)}
15711700
<div
15721701
style={{
15731702
marginBottom: 12,

0 commit comments

Comments
 (0)