settings page created

This commit is contained in:
michalcourson
2026-02-22 13:10:22 -05:00
parent b8f26496a0
commit 86e30e6ec3
3 changed files with 236 additions and 7 deletions

View File

@ -27,7 +27,7 @@
"name": "Clip 20260220_200442", "name": "Clip 20260220_200442",
"playbackType": "playOverlap", "playbackType": "playOverlap",
"startTime": 25.120307988450435, "startTime": 25.120307988450435,
"volume": 1 "volume": 0.64
} }
] ]
} }

View File

@ -12,6 +12,8 @@ import './App.css';
import ClipList from './components/ClipList'; import ClipList from './components/ClipList';
import { useAppDispatch, useAppSelector } from './hooks'; import { useAppDispatch, useAppSelector } from './hooks';
import { store } from '../redux/main'; import { store } from '../redux/main';
import { useNavigate } from 'react-router-dom';
import SettingsPage from './Settings';
function MainPage() { function MainPage() {
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
@ -23,6 +25,7 @@ function MainPage() {
); );
const [newCollectionOpen, setNewCollectionOpen] = useState(false); const [newCollectionOpen, setNewCollectionOpen] = useState(false);
const [newCollectionName, setNewCollectionName] = useState<string>(''); const [newCollectionName, setNewCollectionName] = useState<string>('');
const navigate = useNavigate();
useEffect(() => { useEffect(() => {
const fetchMetadata = async () => { const fetchMetadata = async () => {
@ -139,6 +142,22 @@ function MainPage() {
</li> </li>
))} ))}
</ul> </ul>
{/* Settings Button at Bottom Left */}
<div className="mt-auto mb-2">
<button
type="button"
className="w-full rounded px-4 py-2 bg-neutral-800 text-offwhite hover:bg-plumDark text-left"
style={{
position: 'absolute',
bottom: 16,
left: 8,
width: 'calc(100% - 16px)',
}}
onClick={() => navigate('/settings')}
>
Settings
</button>
</div>
</nav> </nav>
{/* Main Content */} {/* Main Content */}
<div <div
@ -153,14 +172,27 @@ function MainPage() {
export default function App() { export default function App() {
const theme = createTheme({ const theme = createTheme({
palette: { colorSchemes: {
primary: { light: false,
main: '#6e44ba', // plum dark: {
}, palette: {
secondary: { primary: {
main: '#4f3186', // plumDark main: '#6e44ba', // plum
dark: '#6e44ba', // plum
contrastText: '#ffffff',
},
secondary: {
main: '#4f3186', // plumDark
dark: '#4f3186', // plumDark
contrastText: '#ffffff',
},
},
}, },
}, },
// colorSchemes: {
// light: false,
// dark: true,
// },
}); });
return ( return (
<Provider store={store}> <Provider store={store}>
@ -168,6 +200,7 @@ export default function App() {
<Router> <Router>
<Routes> <Routes>
<Route path="/" element={<MainPage />} /> <Route path="/" element={<MainPage />} />
<Route path="/settings" element={<SettingsPage />} />
</Routes> </Routes>
</Router> </Router>
</ThemeProvider> </ThemeProvider>

View File

@ -0,0 +1,196 @@
import React, { useEffect, useState } from 'react';
import { useNavigate } from 'react-router-dom';
import './App.css';
import TextField from '@mui/material/TextField';
import Select from '@mui/material/Select';
import MenuItem from '@mui/material/MenuItem';
type AudioDevice = {
id: string;
label: string;
};
type Settings = {
httpPort: string;
inputDevice: AudioDevice;
outputDevice: AudioDevice;
recordingLength: number;
outputFolder: string;
};
const defaultSettings: Settings = {
httpPort: '',
inputDevice: { id: '', label: '' },
outputDevice: { id: '', label: '' },
recordingLength: 0,
outputFolder: '',
};
const fetchAudioDevices = async (): Promise<AudioDevice[]> => {
// Replace with actual backend call
// Example: return window.api.getAudioDevices();
return [
{ id: 'default-in', label: 'Default Input' },
{ id: 'mic-1', label: 'Microphone 1' },
{ id: 'default-out', label: 'Default Output' },
{ id: 'spk-1', label: 'Speakers 1' },
];
};
const sendSettingsToBackend = (settings: Settings) => {
// Replace with actual backend call
// Example: window.api.updateSettings(settings);
console.log('Settings updated:', settings);
};
export default function SettingsPage() {
const [settings, setSettings] = useState<Settings>(defaultSettings);
const [inputDevices, setInputDevices] = useState<AudioDevice[]>([]);
const [outputDevices, setOutputDevices] = useState<AudioDevice[]>([]);
const navigate = useNavigate();
useEffect(() => {
fetchAudioDevices()
.then((devices) => {
// For demo, split devices by id
setInputDevices(devices.filter((d) => d.id.includes('in')));
setOutputDevices(devices.filter((d) => d.id.includes('out')));
return devices;
})
.catch((error) => {
console.error('Error fetching audio devices:', error);
});
}, []);
useEffect(() => {
sendSettingsToBackend(settings);
}, [settings]);
const handleChange = () => {
// const { name, value } = e.target;
// setSettings((prev) => ({
// ...prev,
// [name]: value,
// }));
};
const handleFolderChange = async () => {
// Replace with actual folder picker
// Example: const folder = await window.api.selectFolder();
const folder = window.prompt(
'Enter output folder path:',
settings.outputFolder,
);
if (folder !== null) {
setSettings((prev) => ({
...prev,
outputFolder: folder,
}));
}
};
return (
<div className="min-w-screen min-h-screen bg-midnight text-offwhite flex items-center justify-center relative">
<div className="w-3/4 min-w-[600px] max-w-[800px] self-start flex flex-col font-sans bg-midnight text-offwhite p-6 rounded-lg relative">
{/* X Close Button */}
<button
type="button"
className="absolute top-6 right-6 text-3xl font-bold text-offwhite bg-transparent hover:text-plumDark"
aria-label="Close settings"
onClick={() => navigate('/')}
>
×
</button>
<span className="text-2xl font-bold mb-4">Settings</span>
<div className="mb-4 flex justify-between">
<span>HTTP Port:</span>
<TextField
variant="standard"
type="text"
name="httpPort"
value={settings.httpPort}
onChange={(e) => {
setSettings((prev) => ({ ...prev, httpPort: e.target.value }));
}}
className="ml-2 text-white w-[150px]"
/>
</div>
<div className="mb-4 flex justify-between">
<span>Input Audio Device:</span>
<Select
variant="standard"
name="inputDevice"
value={settings.inputDevice}
onChange={(e) => {
setSettings((prev) => ({ ...prev, inputDevice: e.target.value }));
}}
className="ml-2 w-64"
>
{inputDevices.map((dev) => (
<MenuItem key={dev.id} value={dev.id}>
{dev.label}
</MenuItem>
))}
</Select>
</div>
<div className="mb-4 flex justify-between">
<span>Output Audio Device:</span>
<Select
variant="standard"
name="outputDevice"
value={settings.outputDevice}
onChange={(e) => {
setSettings((prev) => ({
...prev,
outputDevice: e.target.value,
}));
}}
className="ml-2 w-64"
>
{outputDevices.map((dev) => (
<MenuItem key={dev.id} value={dev.id}>
{dev.label}
</MenuItem>
))}
</Select>
</div>
<div className="mb-4 flex justify-between">
<span>Recording Length (seconds):</span>
<TextField
variant="standard"
type="text"
name="recordingLength"
value={settings.recordingLength}
onChange={(e) => {
setSettings((prev) => ({
...prev,
recordingLength: e.target.value,
}));
}}
className="ml-2 w-[150px]"
/>
</div>
<div className="mb-4 flex justify-between">
<span>Clip Output Folder:</span>
<div className="flex justify-end">
<TextField
variant="standard"
type="text"
name="outputFolder"
value={settings.outputFolder}
className="ml-2 w-[300px]"
/>
<button
type="button"
onClick={handleFolderChange}
className="ml-2 px-3 py-1 rounded bg-plumDark text-offwhite hover:bg-plum"
>
...
</button>
</div>
</div>
</div>
</div>
);
}