Skip to content

Commit 8cba11d

Browse files
committed
feat: folder button
1 parent 11b7840 commit 8cba11d

10 files changed

Lines changed: 90 additions & 7 deletions

File tree

electron-app/preload.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@ contextBridge.exposeInMainWorld("electronAPI", {
3636
importConfig: () => ipcRenderer.invoke("import-config"),
3737
// Open folder selection dialog
3838
selectFolder: () => ipcRenderer.invoke("select-folder"),
39+
// Open a folder path in the OS file explorer
40+
openFolder: (path) => ipcRenderer.invoke("open-folder", path),
3941
// Receive log message from backend
4042
onLog: (callback) => {
4143
const listener = (_event, value) => callback(value);

electron-app/src/ipc/handlers.js

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,15 @@
77
* - Folder selection dialogs
88
*/
99

10-
import { dialog, ipcMain } from "electron";
10+
import { dialog, ipcMain, shell } from "electron";
11+
import fs from "fs";
12+
import { isAbsolute, join } from "path";
1113
import {
1214
importConfig,
1315
readConfig,
1416
writeConfig,
1517
} from "../config/configInstance.js";
16-
import { restartBackend } from "../processes/backend.js";
18+
import { getBackendWorkingDir, restartBackend } from "../processes/backend.js";
1719
import { logger } from "../utils/logger.js";
1820
import {
1921
getCurrentView,
@@ -136,6 +138,28 @@ function setupIpcHandlers() {
136138
throw error;
137139
}
138140
});
141+
142+
/**
143+
* @event open-folder
144+
* @async
145+
* @description Opens the specified folder path in the OS file explorer.
146+
* @param {import("electron").IpcMainInvokeEvent} event - The IPC event object.
147+
* @param {string} folderPath - The folder path to open.
148+
* @returns {Promise<void>}
149+
* @throws {Error} If opening the folder fails.
150+
*/
151+
ipcMain.handle("open-folder", async (event, folderPath) => {
152+
try {
153+
const resolvedPath = isAbsolute(folderPath)
154+
? folderPath
155+
: join(getBackendWorkingDir(), folderPath);
156+
const loggerPath = join(resolvedPath, "logger");
157+
await shell.openPath(fs.existsSync(loggerPath) ? loggerPath : resolvedPath);
158+
} catch (error) {
159+
logger.electron.error("Error opening folder:", error);
160+
throw error;
161+
}
162+
});
139163
}
140164

141165
export { setupIpcHandlers };

electron-app/src/processes/backend.js

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -212,4 +212,10 @@ async function restartBackend() {
212212
}
213213
}
214214

215-
export { restartBackend, startBackend, stopBackend };
215+
function getBackendWorkingDir() {
216+
return !app.isPackaged
217+
? path.join(appPath, "..", "backend", "cmd")
218+
: path.dirname(getUserConfigPath());
219+
}
220+
221+
export { getBackendWorkingDir, restartBackend, startBackend, stopBackend };
Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,5 @@
1-
export { Folder, Trash2 } from "lucide-react";
1+
export {
2+
Folder,
3+
FolderOpen,
4+
Trash2,
5+
} from "lucide-react";

frontend/testing-view/src/components/settings/PathField.tsx

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,18 @@
11
import { Button, Input, Label } from "@workspace/ui";
2+
import { FolderOpen } from "@workspace/ui/icons";
3+
import { logger } from "@workspace/core";
4+
import { useOpenFolder } from "../../hooks/useOpenFolder";
25
import type { FieldProps } from "../../types/common/settings";
36

47
export const PathField = ({ field, value, onChange }: FieldProps<string>) => {
8+
const { openFolder } = useOpenFolder();
9+
510
const handleBrowse = async () => {
6-
// Accessing the Electron API exposed via preload script
7-
const path = await window.electronAPI?.selectFolder();
11+
if (!window.electronAPI) {
12+
logger.testingView.warn("electronAPI is not available");
13+
return;
14+
}
15+
const path = await window.electronAPI.selectFolder();
816
if (path) {
917
onChange(path);
1018
}
@@ -20,6 +28,9 @@ export const PathField = ({ field, value, onChange }: FieldProps<string>) => {
2028
placeholder={field.placeholder || "No path selected"}
2129
className="bg-muted/50"
2230
/>
31+
<Button variant="outline" size="icon" type="button" onClick={() => openFolder(value?.toString())} title="Open folder">
32+
<FolderOpen size={16} />
33+
</Button>
2334
<Button variant="outline" type="button" onClick={handleBrowse}>
2435
Browse
2536
</Button>

frontend/testing-view/src/components/settings/SettingsDialog.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ export const SettingsDialog = () => {
1313
const isSettingsOpen = useStore((s) => s.isSettingsOpen);
1414
const setSettingsOpen = useStore((s) => s.setSettingsOpen);
1515
const setRestarting = useStore((s) => s.setRestarting);
16+
const setConfig = useStore((s) => s.setConfig);
1617
const [localConfig, setLocalConfig] = useState<ConfigData | null>(null);
1718
const [isSynced, setIsSynced] = useState(false);
1819
const [isSaving, startSaving] = useTransition();
@@ -24,6 +25,7 @@ export const SettingsDialog = () => {
2425
try {
2526
const config = await window.electronAPI.getConfig();
2627
setLocalConfig(config);
28+
setConfig(config);
2729
setIsSynced(true);
2830
} catch (error) {
2931
console.error("Error loading config:", error);

frontend/testing-view/src/features/workspace/components/LoggerControl.tsx

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
import { Button, Separator } from "@workspace/ui";
2-
import { Settings2 } from "@workspace/ui/icons";
2+
import { FolderOpen, Settings2 } from "@workspace/ui/icons";
33
import { cn } from "@workspace/ui/lib";
44
import { LOGGER_CONTROL_CONFIG } from "../../../constants/loggerControlConfig";
55
import { useLogger } from "../../../hooks/useLogger";
6+
import { useOpenFolder } from "../../../hooks/useOpenFolder";
67
import { useStore } from "../../../store/store";
78

89
interface LoggerControlProps {
@@ -11,8 +12,10 @@ interface LoggerControlProps {
1112

1213
export const LoggerControl = ({ disabled }: LoggerControlProps) => {
1314
const { status, startLogging, stopLogging } = useLogger();
15+
const { openFolder } = useOpenFolder();
1416
const openFilterDialog = useStore((s) => s.openFilterDialog);
1517
const filteredCount = useStore((state) => state.getFilteredCount("logs"));
18+
const loggingPath = useStore((s) => s.config?.logging?.logging_path as string | undefined);
1619

1720
const handleToggle = () => {
1821
if (status === "loading") return;
@@ -66,6 +69,17 @@ export const LoggerControl = ({ disabled }: LoggerControlProps) => {
6669
{config.icon}
6770
</Button>
6871

72+
<Button
73+
variant="ghost"
74+
size="icon"
75+
className="text-muted-foreground hover:text-foreground h-8 w-8"
76+
onClick={() => openFolder(loggingPath)}
77+
title="Open logs folder"
78+
disabled={disabled}
79+
>
80+
<FolderOpen size={14} />
81+
</Button>
82+
6983
<Button
7084
variant="ghost"
7185
size="icon"

frontend/testing-view/src/hooks/useAppConfigs.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,16 @@
11
import { useFetchConfig } from "@workspace/ui/hooks";
22
import { useEffect } from "react";
3+
import { useStore } from "../store/store";
34
import type { OrdersData, PacketsData } from "../types/data/board";
45

56
const useAppConfigs = (isConnected: boolean) => {
67
const backendUrl =
78
import.meta.env.VITE_BACKEND_URL ?? "http://127.0.0.1:4000/backend";
9+
const setConfig = useStore((s) => s.setConfig);
10+
11+
useEffect(() => {
12+
window.electronAPI?.getConfig().then(setConfig);
13+
}, [setConfig]);
814

915
const {
1016
data: packets,
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import { logger } from "@workspace/core";
2+
3+
export const useOpenFolder = () => {
4+
const openFolder = (path?: string) => {
5+
if (!window.electronAPI) {
6+
logger.testingView.warn("electronAPI is not available");
7+
return;
8+
}
9+
window.electronAPI.openFolder(path ?? ".");
10+
};
11+
12+
return { openFolder };
13+
};

frontend/testing-view/src/vite-end.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ interface ElectronAPI {
77
getConfig: () => Promise<ConfigData>;
88
importConfig: () => Promise<void>;
99
selectFolder: () => Promise<string>;
10+
openFolder: (path: string) => Promise<void>;
1011
}
1112

1213
declare global {

0 commit comments

Comments
 (0)