4 Commits

Author SHA1 Message Date
d3d5270889 closes #6 2026-03-03 17:47:45 -05:00
017a2ae5a4 multiline clip name 2026-03-01 17:47:59 -05:00
791abef1ef better auto focus for delete 2026-03-01 17:35:24 -05:00
31cc3079a8 kinda bad, but functional delete on enter 2026-03-01 17:31:14 -05:00
9 changed files with 65 additions and 32 deletions

View File

@ -1,8 +1,8 @@
import { ipcMain } from 'electron'; import { dialog, ipcMain } from 'electron';
import fs from 'fs'; import fs from 'fs';
import AudioChannels from './channels'; import AudioChannels from './channels';
import { LoadAudioBufferArgs, LoadAudioBufferResult } from './types'; import { LoadAudioBufferArgs, LoadAudioBufferResult } from './types';
import PythonSubprocessManager from '../../main/service'; import PythonSubprocessManager from '../main/service';
export default function registerAudioIpcHandlers() { export default function registerAudioIpcHandlers() {
ipcMain.handle( ipcMain.handle(

View File

@ -1,8 +0,0 @@
const SettingsChannels = {
GET_DEFAULTS: 'settings:get-defaults',
GET_SETTINGS: 'settings:get-settings',
SET_SETTINGS: 'settings:set-settings',
GET_INPUT_DEVICES: 'settings:get-input-devices',
} as const;
export default SettingsChannels;

View File

@ -10,12 +10,20 @@
*/ */
import fs from 'fs'; import fs from 'fs';
import path from 'path'; import path from 'path';
import { app, BrowserWindow, shell, ipcMain, Tray, Menu } from 'electron'; import {
app,
BrowserWindow,
shell,
ipcMain,
Tray,
Menu,
dialog,
} from 'electron';
import { autoUpdater } from 'electron-updater'; import { autoUpdater } from 'electron-updater';
import log from 'electron-log'; import log from 'electron-log';
import MenuBuilder from './menu'; import MenuBuilder from './menu';
import { resolveHtmlPath } from './util'; import { resolveHtmlPath } from './util';
import registerFileIpcHandlers from '../ipc/audio/main'; import registerFileIpcHandlers from '../ipc/main';
import PythonSubprocessManager from './service'; import PythonSubprocessManager from './service';
const pythonManager = new PythonSubprocessManager('src/main.py'); const pythonManager = new PythonSubprocessManager('src/main.py');
@ -110,6 +118,21 @@ const createWindow = async () => {
menuBuilder.buildMenu(); menuBuilder.buildMenu();
registerFileIpcHandlers(); registerFileIpcHandlers();
ipcMain.handle('select-directory', async () => {
try {
const result = await dialog.showOpenDialog(mainWindow, {
properties: ['openDirectory'], // Key property to select a folder
});
if (!result.canceled && result.filePaths.length > 0) {
// Send the selected directory path back to the renderer process
return result.filePaths[0];
}
return null;
} catch (err: any) {
return { error: err.message };
}
});
// Open urls in the user's browser // Open urls in the user's browser
mainWindow.webContents.setWindowOpenHandler((edata) => { mainWindow.webContents.setWindowOpenHandler((edata) => {
shell.openExternal(edata.url); shell.openExternal(edata.url);
@ -164,7 +187,7 @@ app
.whenReady() .whenReady()
.then(() => { .then(() => {
// if (app.isPackaged) { // if (app.isPackaged) {
// pythonManager.start(); pythonManager.start();
// } // }
createWindow(); createWindow();
app.on('activate', () => { app.on('activate', () => {

View File

@ -1,8 +1,8 @@
// Disable no-unused-vars, broken for spread args // Disable no-unused-vars, broken for spread args
/* eslint no-unused-vars: off */ /* eslint no-unused-vars: off */
import { contextBridge, ipcRenderer, IpcRendererEvent } from 'electron'; import { contextBridge, ipcRenderer, IpcRendererEvent } from 'electron';
import { LoadAudioBufferArgs } from '../ipc/audio/types'; import { LoadAudioBufferArgs } from '../ipc/types';
import AudioChannels from '../ipc/audio/channels'; import AudioChannels from '../ipc/channels';
// import '../ipc/file/preload'; // Import file API preload to ensure it runs and exposes the API // import '../ipc/file/preload'; // Import file API preload to ensure it runs and exposes the API
export type Channels = 'ipc-example'; export type Channels = 'ipc-example';

View File

@ -1,10 +1,13 @@
import React, { useEffect, useState } from 'react'; import React, { useEffect, useState } from 'react';
// import { ipcRenderer } from 'electron';
import { useNavigate } from 'react-router-dom'; import { useNavigate } from 'react-router-dom';
import './App.css'; import './App.css';
import TextField from '@mui/material/TextField'; import TextField from '@mui/material/TextField';
import Select from '@mui/material/Select'; import Select from '@mui/material/Select';
import MenuItem from '@mui/material/MenuItem'; import MenuItem from '@mui/material/MenuItem';
import { IconButton } from '@mui/material';
import MoreHorizIcon from '@mui/icons-material/MoreHoriz';
import { apiFetch } from './api'; import { apiFetch } from './api';
type AudioDevice = { type AudioDevice = {
@ -123,18 +126,22 @@ export default function SettingsPage() {
}; };
const handleFolderChange = async () => { const handleFolderChange = async () => {
// Replace with actual folder picker await window.electron.ipcRenderer
// Example: const folder = await window.api.selectFolder(); .invoke('select-directory')
// const folder = window.prompt( .then((result) => {
// 'Enter output folder path:', if (result) {
// settings.outputFolder, setSettings((prev) => ({
// ); ...prev,
// if (folder !== null) { save_path: result,
// setSettings((prev) => ({ }));
// ...prev, sendSettingsToBackend({
// outputFolder: folder, ...settings,
// })); save_path: result,
// } });
}
return null;
});
return null;
}; };
return ( return (
@ -259,13 +266,22 @@ export default function SettingsPage() {
value={settings.save_path} value={settings.save_path}
className="ml-2 w-[300px]" className="ml-2 w-[300px]"
/> />
<button <IconButton
component="label"
size="small"
tabIndex={-1}
onClick={handleFolderChange}
>
<MoreHorizIcon />
</IconButton>
{/* <button
type="button" type="button"
onClick={handleFolderChange} onClick={handleFolderChange}
className="ml-2 px-3 py-1 rounded bg-plumDark text-offwhite hover:bg-plum" className="ml-2 px-3 py-1 rounded bg-plumDark text-offwhite hover:bg-plum"
> >
<VisuallyHiddenInput type="file" />
... ...
</button> </button> */}
</div> </div>
</div> </div>
</div> </div>

View File

@ -35,6 +35,7 @@ export default function DeleteClipDialog({
<button <button
type="button" type="button"
onClick={onDelete} onClick={onDelete}
autoFocus
className="bg-plum hover:bg-plumDark text-white font-bold h-10 px-4 rounded-md" className="bg-plum hover:bg-plumDark text-white font-bold h-10 px-4 rounded-md"
> >
Delete Delete

View File

@ -4,6 +4,7 @@ import {
DialogTitle, DialogTitle,
DialogContent, DialogContent,
DialogActions, DialogActions,
TextField,
} from '@mui/material'; } from '@mui/material';
export default function NameEditDialog({ export default function NameEditDialog({
@ -35,17 +36,17 @@ export default function NameEditDialog({
> >
<DialogTitle>Edit Clip Name</DialogTitle> <DialogTitle>Edit Clip Name</DialogTitle>
<DialogContent> <DialogContent>
<textarea <TextField
autoFocus autoFocus
multiline
variant="standard"
className="font-bold text-lg bg-transparent outline-none border-b border-plum focus:border-plumDark text-white mb-1 w-full text-center resize-y" className="font-bold text-lg bg-transparent outline-none border-b border-plum focus:border-plumDark text-white mb-1 w-full text-center resize-y"
value={input} value={input}
onChange={(e) => { onChange={(e) => {
setInput(e.target.value); setInput(e.target.value);
}} }}
rows={3}
onFocus={(event) => event.target.select()} onFocus={(event) => event.target.select()}
aria-label="Edit clip name" aria-label="Edit clip name"
style={{ minHeight: '3em' }}
/> />
</DialogContent> </DialogContent>
<DialogActions> <DialogActions>