feat (ui): File picker for scheduling recipes default to recipe dir (#3611)
This commit is contained in:
@@ -6,6 +6,7 @@ import { Select } from '../ui/Select';
|
||||
import cronstrue from 'cronstrue';
|
||||
import * as yaml from 'yaml';
|
||||
import { Recipe, decodeRecipe } from '../../recipe';
|
||||
import { getStorageDirectory } from '../../recipe/recipeStorage';
|
||||
import ClockIcon from '../../assets/clock-icon.svg';
|
||||
|
||||
type FrequencyValue = 'once' | 'every' | 'daily' | 'weekly' | 'monthly';
|
||||
@@ -361,7 +362,9 @@ export const CreateScheduleModal: React.FC<CreateScheduleModalProps> = ({
|
||||
};
|
||||
|
||||
const handleBrowseFile = async () => {
|
||||
const filePath = await window.electron.selectFileOrDirectory();
|
||||
// Default to global recipes directory, but fallback to local if needed
|
||||
const defaultPath = getStorageDirectory(true);
|
||||
const filePath = await window.electron.selectFileOrDirectory(defaultPath);
|
||||
if (filePath) {
|
||||
if (filePath.endsWith('.yaml') || filePath.endsWith('.yml')) {
|
||||
setRecipeSourcePath(filePath);
|
||||
|
||||
@@ -3,7 +3,7 @@ import { createServer } from 'net';
|
||||
import os from 'node:os';
|
||||
import path from 'node:path';
|
||||
import fs from 'node:fs';
|
||||
import { getBinaryPath } from './utils/binaryPath';
|
||||
import { getBinaryPath } from './utils/pathUtils';
|
||||
import log from './utils/logger';
|
||||
import { App } from 'electron';
|
||||
import { Buffer } from 'node:buffer';
|
||||
|
||||
+32
-17
@@ -1,4 +1,4 @@
|
||||
import type { OpenDialogReturnValue } from 'electron';
|
||||
import type { OpenDialogReturnValue, OpenDialogOptions } from 'electron';
|
||||
import {
|
||||
app,
|
||||
App,
|
||||
@@ -20,10 +20,11 @@ import fs from 'node:fs/promises';
|
||||
import fsSync from 'node:fs';
|
||||
import started from 'electron-squirrel-startup';
|
||||
import path from 'node:path';
|
||||
import os from 'node:os';
|
||||
import { spawn } from 'child_process';
|
||||
import 'dotenv/config';
|
||||
import { startGoosed } from './goosed';
|
||||
import { getBinaryPath } from './utils/binaryPath';
|
||||
import { getBinaryPath, expandTilde } from './utils/pathUtils';
|
||||
import { loadShellEnv } from './utils/loadEnv';
|
||||
import log from './utils/logger';
|
||||
import { ensureWinShims } from './utils/winShims';
|
||||
@@ -1176,10 +1177,32 @@ ipcMain.handle('get-wakelock-state', () => {
|
||||
});
|
||||
|
||||
// Add file/directory selection handler
|
||||
ipcMain.handle('select-file-or-directory', async () => {
|
||||
const result = (await dialog.showOpenDialog({
|
||||
ipcMain.handle('select-file-or-directory', async (_event, defaultPath?: string) => {
|
||||
const dialogOptions: OpenDialogOptions = {
|
||||
properties: process.platform === 'darwin' ? ['openFile', 'openDirectory'] : ['openFile'],
|
||||
})) as unknown as OpenDialogReturnValue;
|
||||
};
|
||||
|
||||
// Set default path if provided
|
||||
if (defaultPath) {
|
||||
// Expand tilde to home directory
|
||||
const expandedPath = expandTilde(defaultPath);
|
||||
|
||||
// Check if the path exists
|
||||
try {
|
||||
const stats = await fs.stat(expandedPath);
|
||||
if (stats.isDirectory()) {
|
||||
dialogOptions.defaultPath = expandedPath;
|
||||
} else {
|
||||
dialogOptions.defaultPath = path.dirname(expandedPath);
|
||||
}
|
||||
} catch (error) {
|
||||
// If path doesn't exist, fall back to home directory and log error
|
||||
console.error(`Default path does not exist: ${expandedPath}, falling back to home directory`);
|
||||
dialogOptions.defaultPath = os.homedir();
|
||||
}
|
||||
}
|
||||
|
||||
const result = (await dialog.showOpenDialog(dialogOptions)) as unknown as OpenDialogReturnValue;
|
||||
|
||||
if (!result.canceled && result.filePaths.length > 0) {
|
||||
return result.filePaths[0];
|
||||
@@ -1449,9 +1472,7 @@ ipcMain.handle('get-binary-path', (_event, binaryName) => {
|
||||
ipcMain.handle('read-file', (_event, filePath) => {
|
||||
return new Promise((resolve) => {
|
||||
// Expand tilde to home directory
|
||||
const expandedPath = filePath.startsWith('~')
|
||||
? path.join(app.getPath('home'), filePath.slice(1))
|
||||
: filePath;
|
||||
const expandedPath = expandTilde(filePath);
|
||||
|
||||
const cat = spawn('cat', [expandedPath]);
|
||||
let output = '';
|
||||
@@ -1484,9 +1505,7 @@ ipcMain.handle('read-file', (_event, filePath) => {
|
||||
ipcMain.handle('write-file', (_event, filePath, content) => {
|
||||
return new Promise((resolve) => {
|
||||
// Expand tilde to home directory
|
||||
const expandedPath = filePath.startsWith('~')
|
||||
? path.join(app.getPath('home'), filePath.slice(1))
|
||||
: filePath;
|
||||
const expandedPath = expandTilde(filePath);
|
||||
|
||||
// Create a write stream to the file
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
@@ -1505,9 +1524,7 @@ ipcMain.handle('write-file', (_event, filePath, content) => {
|
||||
ipcMain.handle('ensure-directory', async (_event, dirPath) => {
|
||||
try {
|
||||
// Expand tilde to home directory
|
||||
const expandedPath = dirPath.startsWith('~')
|
||||
? path.join(app.getPath('home'), dirPath.slice(1))
|
||||
: dirPath;
|
||||
const expandedPath = expandTilde(dirPath);
|
||||
|
||||
await fs.mkdir(expandedPath, { recursive: true });
|
||||
return true;
|
||||
@@ -1520,9 +1537,7 @@ ipcMain.handle('ensure-directory', async (_event, dirPath) => {
|
||||
ipcMain.handle('list-files', async (_event, dirPath, extension) => {
|
||||
try {
|
||||
// Expand tilde to home directory
|
||||
const expandedPath = dirPath.startsWith('~')
|
||||
? path.join(app.getPath('home'), dirPath.slice(1))
|
||||
: dirPath;
|
||||
const expandedPath = expandTilde(dirPath);
|
||||
|
||||
const files = await fs.readdir(expandedPath);
|
||||
if (extension) {
|
||||
|
||||
@@ -62,7 +62,7 @@ type ElectronAPI = {
|
||||
fetchMetadata: (url: string) => Promise<string>;
|
||||
reloadApp: () => void;
|
||||
checkForOllama: () => Promise<boolean>;
|
||||
selectFileOrDirectory: () => Promise<string | null>;
|
||||
selectFileOrDirectory: (defaultPath?: string) => Promise<string | null>;
|
||||
startPowerSaveBlocker: () => Promise<number>;
|
||||
stopPowerSaveBlocker: () => Promise<void>;
|
||||
getBinaryPath: (binaryName: string) => Promise<string>;
|
||||
@@ -151,7 +151,8 @@ const electronAPI: ElectronAPI = {
|
||||
fetchMetadata: (url: string) => ipcRenderer.invoke('fetch-metadata', url),
|
||||
reloadApp: () => ipcRenderer.send('reload-app'),
|
||||
checkForOllama: () => ipcRenderer.invoke('check-ollama'),
|
||||
selectFileOrDirectory: () => ipcRenderer.invoke('select-file-or-directory'),
|
||||
selectFileOrDirectory: (defaultPath?: string) =>
|
||||
ipcRenderer.invoke('select-file-or-directory', defaultPath),
|
||||
startPowerSaveBlocker: () => ipcRenderer.invoke('start-power-save-blocker'),
|
||||
stopPowerSaveBlocker: () => ipcRenderer.invoke('stop-power-save-blocker'),
|
||||
getBinaryPath: (binaryName: string) => ipcRenderer.invoke('get-binary-path', binaryName),
|
||||
|
||||
@@ -31,7 +31,7 @@ function parseLastModified(val: string | Date): Date {
|
||||
/**
|
||||
* Get the storage directory path for recipes
|
||||
*/
|
||||
function getStorageDirectory(isGlobal: boolean): string {
|
||||
export function getStorageDirectory(isGlobal: boolean): string {
|
||||
return isGlobal ? '~/.config/goose/recipes' : '.goose/recipes';
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import path from 'node:path';
|
||||
import fs from 'node:fs';
|
||||
import os from 'node:os';
|
||||
import Electron from 'electron';
|
||||
import log from './logger';
|
||||
|
||||
@@ -111,3 +112,15 @@ const addPaths = (
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Expands tilde (~) to the user's home directory
|
||||
* @param filePath - The file path that may contain tilde
|
||||
* @returns The expanded path with tilde replaced by home directory
|
||||
*/
|
||||
export function expandTilde(filePath: string): string {
|
||||
if (filePath.startsWith('~')) {
|
||||
return path.join(os.homedir(), filePath.slice(1));
|
||||
}
|
||||
return filePath;
|
||||
}
|
||||
Reference in New Issue
Block a user